1、概述
BN层来源于2015年的论文《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》。在训练深度网络的时候,随着训练进行,每一层输入的分布会发生变化,这种现象称为internal covariate shift,这使得网络训练需要更小的学习率,更仔细的初始化,并且使得饱和非线性激活函数(saturating nonlinearities)更加难以使用。
使用了BN之后,可以使用较大的学习率,并且不需要过多的关注参数初始化,BN层也是一种正则化,某些情况下可以替代dropout(dropout也是一种正则化方法,可以缓解过拟合,提高模型泛化性能,但是dropout会减慢收敛速度,而BN层却能大幅提高训练速度)。论文里报告的结果达到了4.9% top-5 validation error (and 4.8% test error),超过了人类水平。
2、算法说明
算法流程如下所示
其中是为了数值稳定引入的常数,tensorflow中
默认值为0.001。加入参数
和
是为了使得BN层具有identity transform的能力(这一点和ResNet中的思想类似),如果训练过程中使得
并且
,那么就相当于
。在有BN层的时候,mini-batch的size要大于1,否则1个样本的情况下减去均值就等于0。
在加入BN层之后,网络整体的训练流程如下所示
上面这个整体的训练过程中重点在于第10行和第11行,上图中的做法是每一个mini-batch计算单独的均值和方差,然后进行BN转换,下一个mini-batch再重新计算均值和方差,最后模型训练结束后把所有的mini-batch均值和方差再求个均值作为最终的和
,参数
和
在训练中按照梯度下降进行更新。模型推断的时候,BN转换按照上图第11行的公式计算,其实这个公式就是把
和
带入本文第一张图中Algorithm1中的公式得到的。
需要注意的是,在tensorflow中,最终的均值和方差不是按照论文中这种方法计算的,而是采用了滑动平均(moving averages,实际论文中也提到了这种方法),tf.layers.batch_normalization中的计算方法是得到mini-batch之后,计算其均值和方差,然后和上一个mini-batch的均值方差进行加权求和。tf.layers.batch_normalization中有一个参数momentum用来控制加权。
比如当前mini-batch的均值和方差为1和0.1,上一个mini-batch平滑后的均值和方差为0.7和0.2,当momentum=0.9的时候,当前mini-batch平滑后的均值为0.9*0.7+(1-0.9)*1=0.73,平滑后的方差为0.9*0.2+(1-0.9)*0.1=0.19。
另外还需注意的是,BN层的均值和方差不会自动完成平滑操作,因此需要用下面这种写法。
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
with tf.control_dependencies(update_ops):
train_op = optimizer.apply_gradients(grads_and_vars=gradients)
通过tf.get_collection(tf.GraphKeys.UPDATE_OPS)可以获得更新均值和方差的操作,在进行训练之前首先进行更新操作,否则均值和方差一直保持初始化的值不变。
3、总结
- 加入BN层可以缓解Internal Covariate Shift,提升模型性能。
- 加入BN层之后允许较大的学习率,缓解了传统深度网络在较大学习率时出现的梯度消失和梯度爆炸问题。
- BN层具有正则化的效果。
- 在卷积网络中对于一个特征图学习一组参数
和
,这样可以减少参数数量。