批正则化(Batch Normalize,BN)是2015年由Sergey Ioffe提出的方法,用于消除神经网络上一层不同分布的输入导致本层参数更新困难。由于各个层的卷积核参数不同,根据反向传播法则我们知道,深度学习 多层 是如何做正则化的 批正则化层_批正则化及结果深度学习 多层 是如何做正则化的 批正则化层_Batch Normalization_02越大对梯度的影响也就越大,而同一学习率在不同层造成的影响不一样。这时,就需要BN层来消除这种差异。


原理简析

原文 BN的实现其实非常简单,主要通对过特征scale和shift来消除特征间的差异带来的参数更新的影响。具体的介绍可以参照这个视频(youtube),里面有非常详细的介绍。以下是原论文中的BN伪代码:

深度学习 多层 是如何做正则化的 批正则化层_深度学习_03

在训练中使用移动平均(moving average)法更新均值和方差,在预测中,使用训练的均值深度学习 多层 是如何做正则化的 批正则化层_深度学习_04和方差深度学习 多层 是如何做正则化的 批正则化层_深度学习 多层 是如何做正则化的_05作为参数,配合深度学习 多层 是如何做正则化的 批正则化层_深度学习 多层 是如何做正则化的_06深度学习 多层 是如何做正则化的 批正则化层_批正则化_07进行正则化。

这篇文章用各个实验对比了BN层的在不同网络中的作用差异,有兴趣的同学可以参考一下。

已知问题

1.如果使用tensorflow,不要忘记在optimizer前增加BN参数的更新操作。 2.不要将BN用在少量数据中。一旦数据总量变小,那么整体均值标准差与单个数据的均值和方法偏差过大(下面会举例子说明),导致预测和训练数据相差过大。

Tensorflow中的BN层

我们首先分析tf.layers.batch_normalization中重要的参数。

  • axis:在此维度上做归一化(Normalize)。一般选择channel维度,NHWC时为-1,NCHW时为1。
  • momentum:滑动平均时的动量。假设平均值为深度学习 多层 是如何做正则化的 批正则化层_深度学习 多层 是如何做正则化的_08,当前值为深度学习 多层 是如何做正则化的 批正则化层_标准差_09,公式如下:源码参见此处深度学习 多层 是如何做正则化的 批正则化层_批正则化_10
  • epsilon: 防止分母为0的数
  • center/scale: 当为True时,深度学习 多层 是如何做正则化的 批正则化层_Batch Normalization_11$\gamma$会生效(详见上图代码第11行)
  • training: 一定要在训练时设置为True,在预测时设置为False。这个参数决定了bn层的归一是使用一个batch内的均值和标准差,还是使用训练的均值和标准差(下面有详解)
  • fused: 能加速bn过程的参数,建议设置为True

BN在训练时

训练时的BN层会直接使用batch内的mean和var进行归一化,公式如下:深度学习 多层 是如何做正则化的 批正则化层_深度学习_12 我们用以下代码段验证一下:

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之前,原始数据如下:

深度学习 多层 是如何做正则化的 批正则化层_深度学习_13

在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的批正则后,数值如下:

深度学习 多层 是如何做正则化的 批正则化层_标准差_14

和我们之前推算的差不多。这说明在训练时,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参数),我们看一下之前的例子。

更新前的均值和标准差:

深度学习 多层 是如何做正则化的 批正则化层_深度学习 多层 是如何做正则化的_15

深度学习 多层 是如何做正则化的 批正则化层_批正则化_16

更新后的均值和标准差(使用momentum=0):

深度学习 多层 是如何做正则化的 批正则化层_Batch Normalization_17

深度学习 多层 是如何做正则化的 批正则化层_批正则化_18

和之前我们计算的结果一致,说明mean和variance就是用每个mini-batch的数据的均值和方差进行更新的。

总结

使用BN层可以归一化层的输入和输出,使不同分布的输入差异的影响最小,让学习率调整得更加便捷,减少过拟合风险,加快训练速度。但使用BN后,会造成训练和预测的输出差异,这种差异在小数据量时尤为明显。

最后,祝您身体健康再见!