之前写了一篇讲解keras实现BatchNormalization的文章Keras防止过拟合(四) Batch Normalization代码实现,以为自己已经将keras实现BatchNormalization的细节完全明白了,突然看到了一篇文章keras BatchNormalization 之坑,其中写道了一个坑:训练时和测试时的均值mean和方差var差异太大,影响结果。而其文中提出,training参数设置为0或者False可以解决。但通过我自己分析和浏览一些资料后,发现这个说法是错误的。要解决这个问题,不能改变training参数
首先先解释一下,为啥直接使用keras.layers.BatchNormalization时会出现训练时和测试时的mean和var差异太大,原因在于:
keras.layers.BatchNormalization(axis=-1, momentum=0.99, epsilon=0.001, center=True, scale=True, beta_initializer='zeros', gamma_initializer='ones', moving_mean_initializer='zeros', moving_variance_initializer='ones', beta_regularizer=None, gamma_regularizer=None, beta_constraint=None, gamma_constraint=None)
训练时是一个一个批次训练的,mean和var使用的是各个批次的mean和var,归一化也是针对各个批次。而测试时使用的并不是测试集的所有数据的mean和var,而是在训练时利用权重滑动平均法更新而得(权重滑动平均法定义可以参考这篇吴恩达深度学习公开课,这篇文章有讲解指数加权移动平均法(EWMA)),公式如下:
self.moving_mean = momentum * self.moving_mean + (1-momentum) * mean
其中mean是训练时每个批次的均值,momentum为衰减率,self.moving_mean是测试使用的mean.
使用权重滑动平均法时,若将衰减率momentum设置为0.99,最后moving_mean的值越约等于最后100个mean的加权,若我们设置的batch数量远远小于100时,momentum还使用默认值0.99的话,几乎不会通过每个批次更新,也就导致了训练集使用的mean是每个批次的均值,而测试集使用的mean几乎等于初值,产生很大差异!(方差也一样),训练时和测试时的mean和var差异太大这个坑就是因为这个原因。 所以,在这种情况下,减小momentum的值就可以解决此问题。
当然,你也可以增加batch数量,即减少batch_size,但使用BN时,batch_size不能小,如果你的样本数量很少(假设训练集只有几百个),最好还是减小momentum的值。
momentum的值设置为多少合适:
根据你的batch数决定,若你的batch只有10组,设置为0.9;若为50,设置为0.98;若大于100,甚至可以设置的再大一点(但建议就使用默认值)。
引用自指数加权移动平均法(EWMA),图中最后的
0.
9
50
0.9^{50}
0.950应该是
0.9
8
50
0.98^{50}
0.9850.
当
M
B
=
1
e
M^{B} = \frac{1}{e}
MB=e1时,
ln
M
B
=
ln
1
e
\ln M^{B} =\ln\frac{1}{e}
lnMB=lne1,
B
ln
M
=
−
1
B\ln M =-1
BlnM=−1,
ln
M
=
−
1
B
\ln M = -\frac{1} {B}
lnM=−B1,
则
M
=
e
−
1
B
M = e^{-\frac{1} {B}}
M=e−B1,其中M是momentum值,B是batch数量。
至于该文章中说是将training参数一定要设置为0或者False。其实我一开始并没有注意到training这个参数,因为它都不是在init中定义的,而是在call函数中定义:
def call(self, inputs, training=None):#BN层实现函数call中有一个参数training,默认值为None
if training in {0, False}:
return normalize_inference()
如果设置为0或者False,则在训练时直接返回inference时的操作。keras BatchNormalization 之坑这篇文章中写道:
翻看keras BN 的源码, 原来keras 的BN层的call函数里面有个默认参数traing, 默认是None。此参数意义如下:
training=False/0, 训练时通过每个batch的移动平均的均值、方差去做批归一化,测试时拿整个训练集的均值、方差做归一化
training=True/1/None,训练时通过当前batch的均值、方差去做批归一化,测试时拿整个训练集的均值、方差做归一化
文中还建议说是最后将training定义为0或False:
当training=None时,训练和测试的批归一化方式不一致,导致validation的输出指标翻车。
当training=True时,拿训练完的模型预测一个样本和预测一个batch的样本的差异非常大,也就是预测的结果根据batch的大小会不同!导致模型结果无法准确评估!也是个坑!
用keras的BN时切记要设置training=False!!!
其文章意思是,training设置为0或False时,训练通过每个batch的移动平均的均值、方差去做批归一化,测试时拿整个训练集的均值、方差做归一化。这样两者的差异较小。
但根据我个人看完源码的理解,加上一些资料,发现这是错误的!
源码中,如果我们将training设置为0或False,则normalize_inference()中使用的均值self.moving_mean,方差self.moving_variance将只会使用其初始化的值(默认初始化方法:均值moving_mean为’zero’,即设置为全0,方差moving_variance为’ones’,即设置为全1),而不会更新。这意味着,训练时和测试时使用的mean和var都是初值,不是通过各个批次计算而得,在这种情况下,使用BN没有意义。
关于这点,不管是keras中文文档还是官方文档都没有提到,源码中也没有相应的注释予以描述,我搜了一些文章,也没有对这点进行讲解。但tensorflow中有一个类似的layer,tf.layers.batch_normalization()方法,其中有training的描述。
文章Batch Normalization的正确打开方式中有tf.layers.batch_normalization()方法的讲解:
tf.layers.batch_normalization(
inputs,
axis=-1,
momentum=0.99,
epsilon=0.001,
center=True,
scale=True,
beta_initializer=tf.zeros_initializer(),
gamma_initializer=tf.ones_initializer(),
moving_mean_initializer=tf.zeros_initializer(),
moving_variance_initializer=tf.ones_initializer(),
beta_regularizer=None,
gamma_regularizer=None,
beta_constraint=None,
gamma_constraint=None,
training=False,
trainable=True,
name=None,
reuse=None,
renorm=False,
renorm_clipping=None,
renorm_momentum=0.99,
fused=None,
virtual_batch_size=None,
adjustment=None
)
对于keras的Batch Normalization来说也是这样,如果在训练时将raining设置为0或False时,不会滑动更新,都是初值!
这确实会使训练和测试的mean和var完全一致(因为都是初值啊),以至于该作者认为这样设置会更好,但其实这就相当于未使用BN。