目录
概要:
为什么引入BN
为什么如果gamma和beta默认1和0,最终输出等于原样不变?
关于为什么直接用mean和var做normalize就解决分布问题,还要经过scale和shift进行转换的问题
关于这个BN如何被训练到
根据说明文档可知,无论用哪种实现,training阶段的选择都要有:
重中之重:无论接口白盒黑盒,BN操作需要使用依赖关系来完成滑动平均的更新
BN更新和EMA的关系
工程实际效果对比:
歪用:
工程
测试代码:
矛盾体:既要归一化又要偏离中心,trade-off。
有两三种用法。
同时包含可训练参数和不可训练参数。
因为BN是带变量的,要注意变量的保存。
注意阶段的切换。
TF提供了一些自动化操作和防呆设计,如果简单跑例子或者抄别人代码的套路,可能遇不到问题,但是最好理解机制并明白TF为你省去了什么操作。
机器学习就是基于IID的,你现在SGD的分布乱变,不符合这种假设,网络模型学不到规律。BN去除了这个烦恼,全是同分布(虽然他也用gamma和beta搞偏移了。但是这组变量是共用的,所以分布应该还是相同的)
总之,把激活的输入分布固定住,避免Internal Covariate Shift。
另外一方面就是导致梯度消失和收敛速度之类的,就不赘述了。
“这里t层某个神经元的x(k)不是指原始输入,就是说不是t-1层每个神经元的输出,而是t层这个神经元的线性激活x=WU+B,这里的U才是t-1层神经元的输出。”
这句话,当前层t的“特征”x(k),不是前一层t-1的神经元的输出
疑问:x=WU+B能叫线性激活吗?不是线性变换,还没激活吗?
总之,BN要变的东西,是当前这一层乘以W并且加B以后,激活之前的数。所以更没局限在0附近了,测试数据不超纲,为什么得不到想要的结果?
normalize变换之后,形成均值0,方差1的正态分布。(虽然目前在TF实现上还有点疑问)
先不说TF得不到想要结果,假设能得到,变换后,某个神经元的激活x(对应前边那句话的x)是均值0,方差1的正态分布。从训练和收敛的角度,到这就够了。
但是会导致网络表达能力下降,所以加了scale和shift,这两个参数也是通过训练学到的!但是这具体是怎么学的?怎么促进参数往这个方向上靠?因为normalize之后表达能力差,所以预测表现差,所以为了让预测表现更好,就自然缩放和偏移了,就训练了scale和shift参数!
关于表达能力下降的解释:
BN会让激活值落在非线性函数的线性區内,具体的说,sigmoid的斜率为1的那个位置,非线性。
深度网络的本质是非线性变换和拟合复杂曲线,BN相当于遏止了向这一目的发展。
所以才引入了scale和shift,也相信这个对应的gamma和beta不会太大,相对于原本偏的离谱的分布,最后应该整体还是比较偏向于中心的分布,但是又不极端靠近中心——也就是sigmoid斜率为1的0点。
先normalize,又scale和shift,好像确实这一矛盾操作也有些争议。但是完全抵消也是一个理论上的状态,实际还是有些效果的,姑且算是一个tradeoff吧。实际效果好,并且有很多其他优点,这是一个实践先于理论的行业。。。。
BN的替代方案有GN和Weight Normalization。
BN额外产生噪声,对于噪声敏感的RL和GAN、VAE等不利。可以用Weight Normalization代替。
BN也不适合动态网络结构和RNN等。
GN和WN细节略
四组参数,optimizer会自然训练到其中两组,另外两组是滑动平均的。
更多细节在笔记中。
无论手动初始化变量,还是用接口隐藏内部的变量,本质都一样:
训练阶段和测试阶段不同,一定要设定
一个是当前batch的mean、variance,一个是滑动平均的mean、variance。
如果不切换阶段,等于什么都没做,还是拿当前batch做平均。
如果阶段选错:
如果测试时选了training=True,可能输出却“很不如意”,因为如果用batch自己的mean和variance,输出总是规范的在一个区间,他总是将自身先缩放到标准范围,然后缩放偏移,但是BN的目的就是消除batch自己的偏差,使用统一的滑动mean和variance!
self.mean, self.variance = tf.nn.moments(x, axes=reduce_dims)#默认tf.nn.moments=False
update_move_mean = moving_averages.assign_moving_average(self.moving_mean,
self.mean, decay=decay)
update_move_variance = moving_averages.assign_moving_average(self.moving_variance,
self.variance, decay=decay)
tf.add_to_collection(tf.GraphKeys.UPDATE_OPS, update_move_mean)
tf.add_to_collection(tf.GraphKeys.UPDATE_OPS, update_move_variance)
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(update_ops):
train_op = optimizer.minimize(loss)
What's the differences between
tf.GraphKeys.TRAINABLE_VARIABLES and
tf.GraphKeys.UPDATE_OPS in tensorflow?
tf.GraphKeys中,TRAINABLE_VARIABLES就是被optimizer训练的variable子集。
黑盒的BN层,参数就被自动加到了GraphKeys.UPDATE_OPS
用tf.get_collection()能找到想要的tensor
When use tensorflow.contrib.layers.batch_norm(), the parameter updates_collections default value is GraphKeys.UPDATE_OPS.
How can we understand those collections, and difference in them.
Besides, we can find more in ops.py.
TRAINABLE_VARIABLES是variables的集合!
是minimizing loss时候训练的,如果不指定trainable=False,一般variable都被自动加进去了。
不可训练的使用场景两步训练,fine-tune
UPDATE_OPS是ops的集合!不是variables
维护了每个训练步骤之前的操作列表
怎么加进来的?
根据定义,更新操作发生在损失最小化的常规培训流之外,因此通常只有在特殊情况下才会将操作添加到此集合中。例如,在执行批处理规范化时,您希望在每个培训步骤之前重新计算批平均值和差异,这就是它的实现方式。本文更详细地描述了使用tf.contrib.layers.batch_norm的批处理规范化机制。
http://ruishu.io/2016/12/27/batchnorm/
ema可以选tensor列表,比如所有trainable变量,这个是可以包括BN的变量的,不过都是和Adam相关的变量。
BN有独自维护的变量:moving_mean、moving_variance,这个是ema包括不到的,也不应该包括。
update_ops是必选操作,不选,moving_xx不会变。
ema是可选操作,取决于是否有变量decay的需求。
无论是哪种情况,最好自己提前打印调试好,以免遗漏!
ema = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step,name='ema')
print('ema:',ema)
ema_op = ema.apply(tf.trainable_variables())
print('ema_op:',ema_op)
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)#BN需要依赖操作
print(tf.global_variables())
print('update_ops:',update_ops)
with tf.control_dependencies([train_step, ema_op]):
train_op = tf.no_op(name='train')
EMA中包含BN的beta和gamma,根本的原因是beta和gamma本来就要被训练到,EMA要对他们进行操作,还有恢复模型时的替换工作。但是并没有包含moving_mean和moving_variance,其实EMA是不负责更新BN的滑动平均的,需要自己手动来。
如果实际训练和测试中,BN层设置training=False之后,预测效果反倒不好!这个就是没有使用update_ops的原因。通过tensorboard跟踪可知是否有更新滑动平均值。
如下,没使用update_ops时的滑动平均和使用update_ops时的滑动平均:
https://blog.csdn.net/huqinweI987/article/details/87884341
实验对比(无LRN且无BN对比有BN):
使用BN前,可以看到三层卷积层的激活输出relu1、2、3,一个比一个萎缩,两层FC的输出也不太好。使用BN后(图二),conv层激活RELU3的输出分布明显好转,仔细看FC层y1、y2的数值分布,深色区域训练初期1.5+,训练后期1.5-,明显好于图一,而图一大多数深色区域小于1。
然后是变量的区别,使用BN前,训练过程中,变量weights范围变化大,biases有大范围的偏移,说明参数要迁就数据,使用BN后(图二),weights范围相对平稳了许多(biases为固定值0.1,因为在BN前取消掉了biases,闲置了,fc_b3对应最终分类输出,没有做BN)
如果是利用BN接口去强行拟合一个目标,比如让1,2,3,去拟合2,4,6,那么输入其他比如1,3,5,预测也会是2,4,6,因为BN就是干这个的,让输入经过BN层的转换,集中在某一个区间,而不是学习规律。
https://github.com/huqinwei/tensorflow_demo/batch_normalization_use_TF.ipynb
https://blog.csdn.net/huqinweI987/article/details/87884341
手动、自动、保存、恢复的代码实现,相关参数的变化:
https://github.com/huqinwei/tensorflow_demo/batch_normalization_use_TF.ipynb
这里边有不同接口、不同暴露程度的BN层实现。
其实TensorFlow提供了两三种接口,透明程度和使用方法稍有不同,但是本质都一样,如果想学习,最好是先看纯python版本
https://blog.csdn.net/huqinweI987/article/details/103224054