一、使用顺序:

C o n v = > B N = > R e L U = > d r o p o u t = > C o n v Conv=>BN=>ReLU=>dropout=>Conv Conv=>BN=>ReLU=>dropout=>Conv
BN本质上是解决传播过程中的梯度消失问题

二、训练

下图面试常考!

训练时前向传导过程公式:

理解:

  1. 这个可学习重构参数 γ、β,让我们的网络可以学习恢复出原始网络所要学习的特征分布。(因为如果没有 γ、β ,那相当于我这一层网络所学习到的特征分布被你搞坏了)
  2. 这个 scale 和 shift ,它们的主要作用就是找到一个线性和非线性的平衡点,既能享受非线性较强的表达能力,有可以避免非线性饱和导致网络收敛变慢问题。

细节:

  1. BN层是对于每个神经元做归一化处理,甚至只需要对某一个神经元进行归一化,而不是对一整层网络的神经元进行归一化。既然BN是对单个神经元的运算,那么在CNN中卷积层上要怎么搞?假如某一层卷积层有6个特征图,每个特征图的大小是100100,这样就相当于这一层网络有6100100个神经元,如果采用BN,就会有6100*100个参数γ、β,这样岂不是太恐怖了。因此卷积层上的BN使用,其实也是使用了类似权值共享的策略,把一整张特征图当做一个神经元进行处理。

  2. 卷积神经网络经过卷积后得到的是一系列的特征图,如果min-batch sizes为m,那么网络某一层输入数据可以表示为四维矩阵 (m,f,w,h),m为min-batch sizes,f为特征图个数,w、h分别为特征图的宽高。在CNN中我们可以把每个特征图看成是一个特征处理(一个神经元),因此在使用Batch Normalization,mini-batch size 的大小就是: m ∗ w ∗ h m * w * h mwh,于是对于每个特征图都只有一对可学习参数:γ、β。说白了吧,这就是相当于求取所有样本所对应的一个特征图的所有神经元的平均值、方差,然后对这个特征图神经元做归一化。

  3. 在使用BN前,减小学习率、小心的权重初始化的目的是:使其输出的数据分布不要发生太大的变化。

  4. 因为偏置参数 b 经过 BN 层后其实是没有用的,最后也会被均值归一化,当然BN层后面还有个 β 参数作为偏置项,所以 b 这个参数就可以不用了。因此最后把 BN 层 + 激活函数 层就变成了:

    z = g ( B N ( W ) ) z=g(BN(W)) z=g(BN(W))

三、测试

对于BN,当一个模型训练完成之后,它的所有参数都确定了,包括均值和方差,gamma和bata。

测试的时候还是类似的:

测试的时候,是一个一个样本进行测试的,所以没办法求 均值和 方差,所以可以用训练数据的。因为每次做 Mini-Batch 训练时,都会有那个 Mini-Batch 里 m 个训练实例获得的均值和方差,现在要全局统计量,只要把每个Mini-Batch的均值和方差统计量记住,然后 均值采用训练集所有 batch 均值的期望,方差采用训练集所有 batch 的方差的无偏估计即可得出全局统计量,即:


(在测试时,所使用的均值和方差是整个训练集的均值和方差。整个训练集的均值和方差的值通常是在训练的同时用 移动平均法 来计算的。)
关于移动平均参考:移动平均

所以测试时总的公式入下:

============================================================

细节:
上述这个总的测试公式和训练的时候下式:

形式上是保持一致的,但是似乎做了某种变换。有可能是为了减少计算量。
因为对于每个隐层节点来说,

上式红框中两项都是固定值,这样这两个值可以事先算好存起来,在预测的时候直接用就行了,这样比原始的公式每一步骤都现算少了除法的运算过程,乍一看也没少多少计算量,但是如果隐层节点个数多的话节省的计算量就比较多了。

这里其实是和训练保持一致,因为都要先减去训练集的均值再除以方差。这里你把训练时候的 x ( k ) x^{(k)} x(k) 带进去算下就能理解测试时候的了。

四、BN的作用

  1. 改善流经网络的梯度

  2. 允许更大的学习率,大幅提高训练速度:
    你可以选择比较大的初始学习率,让你的训练速度飙涨。以前还需要慢慢调整学习率,甚至在网络训练到一半的时候,还需要想着学习率进一步调小的比例选择多少比较合适,现在我们可以采用初始很大的学习率,然后学习率的衰减速度也很大,因为这个算法收敛很快。当然这个算法即使你选择了较小的学习率,也比以前的收敛速度快,因为它具有快速训练收敛的特性;

  3. 减少对初始化的强烈依赖

  4. 改善正则化策略:作为正则化的一种形式,轻微减少了对dropout的需求
    你再也不用去理会过拟合中dropout、L2正则项参数的选择问题,采用BN算法后,你可以移除这两项了参数,或者可以选择更小的L2正则约束参数了,因为BN具有提高网络泛化能力的特性;

  5. 再也不需要使用使用局部响应归一化层了(局部响应归一化是Alexnet网络用到的方法,搞视觉的估计比较熟悉),因为BN本身就是一个归一化网络层;

  6. 可以把训练数据彻底打乱(防止每批训练的时候,某一个样本都经常被挑选到,文献说这个可以提高1%的精度)。

五、BN的不足

  1. batch size较小时(比如2、4这样),该batch数据的均值和方差的代表性较差,因此对最后的结果影响也较大。随着batch size越来越小,BN层所计算的统计信息的可靠性越来越差,这样就容易导致最后错误率的上升;而在batch size较大时则没有明显的差别。虽然在分类算法中一般的GPU显存都能cover住较大的batch设置,但是在目标检测、分割以及视频相关的算法中,由于输入图像较大、维度多样以及算法本身原因等,batch size一般都设置比较小,所以GN对于这种类型算法的改进应该比较明显。

    如上图,可见 BN 随着 batchsize 减小,性能下降比较明显。
    由于BN训练的时候是基于一个 mini-batch 来计算均值和方差的,这相当于在梯度计算时引入噪声,如果 batchsize 很小的话, BN 就有很多不足。不适用于在线学习(batchsize = 1)

  2. 无法在RNN等网络中使用。

  3. 在mask rcnn的代码中,网络结构中有个重写batch normalization的类:

class BatchNorm(KL.BatchNormalization):
    """Extends the Keras BatchNormalization class to allow a central place to make changes if needed. Batch normalization has a negative effect on training if batches are small so this layer is often frozen (via setting in Config class) and functions as linear layer. """

当数据是大图片,并且一个batch一般都只有1-2张图片,不建议使用 BN

  1. batchsize 小的时候可以用 group normalization 代替

BatchNorm:batch 方向做归一化,算 N ∗ H ∗ W N*H*W NHW 的均值

LayerNorm:channel 方向做归一化,算 C ∗ H ∗ W C*H*W CHW 的均值

InstanceNorm:一个 channel 内做归一化,算 H ∗ W H*W HW 的均值

GroupNorm:将 channel 方向分 group ,然后每个 group 内做归一化,算 ( C / / G ) ∗ H ∗ W (C//G)*H*W (C//G)HW的均值

针对每一个 γ γ γ β β β ,GN 是对每一个 channel 进行学习的

其中两维 C 和 N 分别表示 channel 和 batch size,第三维表示 H,W,可以理解为该维度大小是 H ∗ W H*W HW,也就是拉长成一维,这样总体就可以用三维图形来表示。可以看出 BN 的计算和 batch size 相关(蓝***域为计算均值和方差的单元),而 LN、BN 和 GN 的计算和 batch size 无关。同时 LN 和 IN 都可以看作是 GN 的特殊情况(LN 是 group=1 时候的 GN,IN 是 group=C 时候的GN)。

Tensorflow代码:

def GroupNorm(x,G=16,eps=1e-5):
    N,H,W,C=x.shape    	
    x=tf.reshape(x,[tf.cast(N,tf.int32),tf.cast(H,tf.int32),tf.cast(W,tf.int32),tf.cast(G,tf.int32),tf.cast(C//G,tf.int32)])
    mean,var=tf.nn.moments(x,[1,2,4],keep_dims=True)
    x=(x-mean)/tf.sqrt(var+eps)
    x=tf.reshape(x,[tf.cast(N,tf.int32),tf.cast(H,tf.int32),tf.cast(W,tf.int32),tf.cast(C,tf.int32)])
    gamma = tf.Variable(tf.ones(shape=[1,1,1,tf.cast(C,tf.int32)]), name="gamma")
    beta = tf.Variable(tf.zeros(shape=[1,1,1,tf.cast(C,tf.int32)]), name="beta")
    return x*gamma+beta

参考:

  1. cs231n学习笔记-激活函数-BN-参数优化
  2. BN和Dropout在训练和测试时的差别
  3. 深入理解 BatchNormalization
  4. Group Normalization