前言:
Batch Normalization || 1. 原理介绍
Batch Normalization || 2. 在tensorflow 中的API
Batch Normalization || 3. 在tensorflow 中bn的坑 —— Is_training、momentum的设置
1 问题概述
在神经网络中使用 Batch Normalization,已经是一个基本必用的正则手段。
现象:
当训练好神经网络,信心满满的进行预测,却发现结果一塌糊涂。
分析:
训练和测试时,bn中的均值和方差的计算方法。要明确:训练时使用batch内数据的mean和var。测试时会使用更新后的滑动均值和滑动方差)
问题:
那么可能出现bug的点就显而易见了:
- 训练时,bn 的 momentum、Is_training 参数设置的是否合理
- 训练时,moving_mean 和 moving_var 在每次迭代中是否得到有效的更新
- 模型保存时,moving_mean 和 moving_var 是否被保存在图结构中
1.1 momentum 的设置
在tensorflow 中,bn的常用的api:
tf.layers.batch_normalization
tf.layers.batch_normalization( inputs, axis=-1, momentum=0.99, training=False )
首先我们要理解滑动均值的意义:
滑动平均(exponential moving average),或者叫做指数加权平均(exponentially weighted moving average),可以用来估计变量的局部均值,使得变量的更新与一段时间内的历史取值有关。【Andrew Ng】在【Course 2 Improving Deep Neural Networks】中讲到, t t t 时刻变量 m o v i n g _ m e a n moving\_mean moving_mean 的滑动平均值大致等于过去的 1 / ( 1 − m o m e n t u m ) 1/(1−momentum) 1/(1−momentum) 个时刻 m e a n mean mean 值的平均。
- 好处:占内存少,不需要保存过去的10个或者100个历史值的 θ 值,就能估计其均值。
- 劣处:滑动平均不如将历史值全部保存下来计算均值准确,但后者占用更多的内存和计算成本更高。
- 公式: m o m e n t u m ∈ [ 0 , 1 ) momentum \in[0,1) momentum∈[0,1)。当 m o m e n t u m = 0 momentum=0 momentum=0 相当于没有使用滑动平均。 m o v i n g _ m e a n = m o m e n t u m ∗ m o v i n g _ m e a n + ( 1 − m o m e n t u m ) ∗ m e a n moving\_mean = momentum * moving\_mean + (1-momentum) * mean moving_mean=momentum∗moving_mean+(1−momentum)∗mean
- 例子:
momentum = 0.9 ,mean 的 t 滑动平均值为 前10个时刻的mean的平均;
momentum = 0.98 ,mean 的 t 滑动平均值为 前50个时刻的mean的平均;
momentum = 0.99 ,mean 的 t 滑动平均值为 前100个时刻的mean的平均;
在神经网络bn中的 momentum 值的设置:
对于滑动平均,我们需要深刻理解,它就是在估计 某一时间段的均值。那对于神经网络中的bn中的滑动均值和方差,我们取过去的时间段的均值,那么这个时间段应该取多长?个人经验:
- 假设有10w数据量,batch为10,一轮的训练有1w次迭代,此时设置 momentum = 0.99,记录过去的100次迭代的平均值,这样是可以的;
- 当batch=1000(不考虑显存的限制),一轮的训练有100次迭代,这时,我们依然记录过去的100此迭代的平均值,就显得不合理了。
在前期和中期时,一轮次中,神经网络更新的梯度变化相对较大,batch=1000,将这变化划分了100等分,前第100次迭代 和 前第一次迭代的 变量变化已经相距较大,这样我们应该 缩短统计平均值的区间。此时选择 momentum = 0.95,记录过去的20次迭代的平均值,会更加合理些。
所以 momentum的设置,需要结合 数据集的大小、batch的大小 进行设置。个人习惯设置的区间 [0.9,0.99],momentum 设置数值少量的偏差不会有决定性的影响,但不考虑应用情况,一律默认是完全不可取的。
- 有人说了,以这个思路,反正训练到后期,网络变量的梯度变化已经不大,统计过去的100次迭代,都会得到较好的结果,所以不管什么情况都设置 momentum=0.99。但这样肯定是不可行的。我们要考虑到,如果为了得到bn的更好的滑动平均,而做更多的迭代,网络可训练的变量就会发生过拟合,从而导致网络性能变得更差
个人实际应用:
- 项目中有数据1600个,
当momentum=0.99时,验证集运行时设置 Is_training=False,在前10轮的验证loss以及accuracy非常差;当momentum=0.9 时,验证集运行时设置 Is_training=False,在训练开始时,验证集的loss和accuracy就会有较好的收敛。在训练15轮左右网络已经可以得到相对较好的结果。
1.2 Is_training 的设置
当 api 中的参数
training = True
时,表示bn是处于训练阶段,均值方差的是 batch 内计算而来
当 api 中的参数training = False
时,表示 bn 是处于测试阶段,均值和方法 使用的是训练阶段统计的滑动均值和滑动方差。 所以一定要保证训练阶段的滑动均值方差,正确的得到更新和保存。
1.3 bn 的滑动mean、滑动var的更新
bn的前向传播公式如下图,其中
mean、var 不是训练参数。训练时是batch内计算而来;测试时是统计而来的(计算滑动平均)
scale、shift 是可训练参数。在神经网络训练时训练而来。
x_norm = tf.layers.batch_normalization(x, training=Is_training) # ... train_op = optimizer.minimize(loss)
上面的例子,我们使用sess.run(train_op)时,网络会自动更新所有可训练的参数,但是滑动平均这种统计的参数,是无法直接得到更新的。所以需要额外添加限制,使得网络在反向传播之前,先进行bn中统计参数的更新。如下:
x_norm = tf.layers.batch_normalization(x, training=Is_training) # ... update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) with tf.control_dependencies(update_ops): train_op = optimizer.minimize(loss)
batch_normalization中更新mean和variance的操作,在tensorflow的内部实现中自动被加入tf.GraphKeys.UPDATE_OPS这个集合的,with tf.control_dependencies(update_ops) 表示我们执行 train_op时候,代码会自动先执行 update_ops,然后再执行train_op。
1.4 bn 的滑动mean、滑动var的保存
直接说最方便也是最建议的方式,将所有的参数都进行保存,包括可训练、不可训练的参数。这样也方便神经网络被中断后的继续训练。
var_list = tf.global_variables() saver = tf.train.Saver(var_list=var_list, max_to_keep=5)
注意:千万不能设置成var_list = tf.trainable_variables()
。