批正则化(Batch Normalize,BN)是2015年由Sergey Ioffe提出的方法,用于消除神经网络上一层不同分布的输入导致本层参数更新困难。由于各个层的卷积核参数不同,根据反向传播法则我们知道,及结果越大对梯度的影响也就越大,而同一学习率在不同层造成的影响不一样。这时,就需要BN层来消除这种差异。
原理简析
原文 BN的实现其实非常简单,主要通对过特征scale和shift来消除特征间的差异带来的参数更新的影响。具体的介绍可以参照这个视频(youtube),里面有非常详细的介绍。以下是原论文中的BN伪代码:
在训练中使用移动平均(moving average)法更新均值和方差,在预测中,使用训练的均值和方差作为参数,配合和进行正则化。
这篇文章用各个实验对比了BN层的在不同网络中的作用差异,有兴趣的同学可以参考一下。
已知问题
1.如果使用tensorflow,不要忘记在optimizer前增加BN参数的更新操作。 2.不要将BN用在少量数据中。一旦数据总量变小,那么整体均值标准差与单个数据的均值和方法偏差过大(下面会举例子说明),导致预测和训练数据相差过大。
Tensorflow中的BN层
我们首先分析tf.layers.batch_normalization中重要的参数。
- axis:在此维度上做归一化(Normalize)。一般选择channel维度,NHWC时为-1,NCHW时为1。
- momentum:滑动平均时的动量。假设平均值为,当前值为,公式如下:源码参见此处
- epsilon: 防止分母为0的数
- center/scale: 当为True时,$\gamma$会生效(详见上图代码第11行)
- training: 一定要在训练时设置为True,在预测时设置为False。这个参数决定了bn层的归一是使用一个batch内的均值和标准差,还是使用训练的均值和标准差(下面有详解)
- fused: 能加速bn过程的参数,建议设置为True
BN在训练时
训练时的BN层会直接使用batch内的mean和var进行归一化,公式如下: 我们用以下代码段验证一下:
a = tf.Variable(tf.random_uniform((3, 4),seed=1))
bn = tf.layers.batch_normalization(a, -1, 1, 1e-6, False, False,training=True)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
while True:
temp1, temp2 = sess.run([a, bn])
在BN之前,原始数据如下:
在0维度下
均值为
[0.6144663 , 0.5615021 , 0.30789164, 0.42147708]
方差为
[0.07121453, 0.14869653, 0.11407541, 0.00624432]
我们用公式算一下,BN后的值应该是:
[[-1.4068358 , 0.93072194, -0.76203936, 0.9398434 ], [ 0.82835776, -1.3874875 , -0.6507127 , 0.44523987], [ 0.578478 , 0.45676556, 1.4127523 , -1.3850833 ]]
而在使用tensorflow的批正则后,数值如下:
和我们之前推算的差不多。这说明在训练时,BN层会直接使用输入数据的均值与标准差,来作正则化处理。
训练时更新参数
在预测时,BN层需要直接用训练时统计的均值与标准差,所以在训练时需要更新moving_average和moving_variance(两者在使用BN后就会自动创建)。 具体方法如下: 1.使用tf.control_dependencies:
...
optimizer = tf.train.AdamOptimizer(learning_rate) #可以是任意的optimizer
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(update_ops):
train_op = optimizer.minimize(loss)
...
这里tf.control_dependencies是表示在做操作前(这里是反向传播),先进行括号里的操作(这里是更新moving_average和moving_variance)。如果直接使用optimizer的话,均值和标准差不更新,预测结果会全错。
2.使用slim的方式:
optimizer = tf.train.AdamOptimizer(learning_rate)
train_op = slim.learning.create_train_op(total_loss, optimizer)
在create_train_op时,会自动调用上面的tf.control_dependencies,源码位置见此处。
BN在预测时
在预测时,BN层中会使用训练时无偏统计的均值和标准差(注意,不使用输入数据的均值和标准差),这会导致训练时和预测时的结果有"偏差",数据量越大,这种“偏差”越不明显。所以我们说,在数据量小的时候,不要使用batch normalize。
更新均值和标准差的方式在上文提过(momentum参数),我们看一下之前的例子。
更新前的均值和标准差:
更新后的均值和标准差(使用momentum=0):
和之前我们计算的结果一致,说明mean和variance就是用每个mini-batch的数据的均值和方差进行更新的。
总结
使用BN层可以归一化层的输入和输出,使不同分布的输入差异的影响最小,让学习率调整得更加便捷,减少过拟合风险,加快训练速度。但使用BN后,会造成训练和预测的输出差异,这种差异在小数据量时尤为明显。
最后,祝您身体健康再见!