批量归一化(Batch Normalization, BN)方法的初衷:从数据分布入手,有效减弱了模型中的复杂参数对网络训练产生的影响,在加速训练收敛的同时也提升了网络的泛化能力。
神经网络的本质是学习数据分布,训练数据和测试数据分布的不同会大大降低网络的泛化能力,所以我们经常会在训练开始前对所有输入数据进行归一化处理。然后这是不够的。
随着网络的训练,每一层的参数会让这一层的输出结果的数据的分布与输入不同,即喂给后一层的输入数据分布发生变化,另外,由于每一批(Batch)的数据分布也会存在不同,
致使网络在每次迭代面对新的一批数据时都需要拟合(适应)不同的数据分布,这间接增大了训练的复杂度以及过拟合的风险。
即从大的方向上看,神经网络则需要在这多个分布中找到平衡点,从小的方向上看,由于每层网络输入数据分布在不断变化,这也会导致每层网络在找平衡点,显然,神经网络就很难收敛了。
Batch Normalization方法是针对每一批数据,在网络的每一层输入之前增加归一化处理(均值为0,标准差为1),将所有批数据强制在统一的数据分布下(正态分布)
只对输入进行标准化之后,必然导致上层学习到的数据分布发生变化,以sigmoid激活函数为例,如果BN之后数据经过一个sigmoid激活函数,由于标准化之后的数据在0-1,处于sigmoid函数的非饱和区域,相当于只经过只经过线性变换了,连sigmoid激活非线性变换的作用都没了。这样就破坏了BN之前学到的该有的分布,所以,需要恢复BN数据之前的分布,具体实现中引入了变换重构以及可学习参数γ,β,γ,β变成了该层的学习参数,与之前网络层的参数无关,从而更加有利于优化的过程。(γ,β变成了该层的学习参数,与之前网络层的参数无关,这句话是核心,说明了虽然我们要恢复到前一层学习到的特征分布,但怎么恢复,只与当前该层有关,与之前的各层已都没关系了)
由于ICS问题的存在,输入数据x的分布可能相差很大,要解决独立同分布问题,“理论正确”的方法就是对每一层的数据都进行白话操作,然而标准的白化操作代价高昂,特别是我们还希望白化是可微的,保证白化操作可以通过反向传播来更新梯度。
因此,以BN为代表的Normalization 方法退而求其次,进行来简单的白话操作,基本思想是:在将x送给神经元之前,先对其做平移和伸缩变换,将x的分布规范化在固定区间范围内的标准分布。
通用框架就是:
h = f ( g ∗ x − u σ + b ) h=f(g*\dfrac{x-u}{\sigma}+b) h=f(g∗σx−u+b)
我们再来看看公式中的各参数,
奇不奇怪?奇不奇怪?
说好的处理ICS,在第一步得到了标准分布,为什么第二步又给变走了?
答案——为了保证模型的表达能力不因为规范化而下降
我们可以看到,第一步的变换将输入数据限制到了一个全局统一的确定范围(均值为0,方差为1)。下层(底层)神经元再努力的学习,但不论其如何变化,其输出的结果在交给上层神经元进行处理之前,将被粗暴地调整到这一固定范围内。
沮不沮丧?沮不沮丧?
难道我们底层的神经元人民就在做无用功吗?
所以为了尊重底层神经元网络的学习结果,我们将规范化后的数据进行再平移和再放缩,使得神经元对应的输入范围是针对该层量身定制的一个确定范围(均值为b,方差为 g 2 g^{2} g2)。这里再缩放参数 g g g(re-shift parameter),再平移参数 b b b(re-scale parameter)都是再学习的,并且与底层解耦了。这就使得Normalization层可以自己学习如何去尊重底层学习到的结果。
如何充分利用底层学习的结果,加入再缩放参数 g g g(re-shift parameter),再平移参数 b b b(re-scale parameter)参数还有另一方面的考虑,就是要保证能有获得非线性的表达能力,sigmoid等激活函数在神经网络中地位举足轻重,通过区分饱和区和非饱和区,使得神经网络的数据变换具有非线性计算能力,而第一步的规范化将数据映射到了0-1 ,这就将数据映射到了sigmoid激活函数的非饱和区(线性区),仅利用到了线性变换的能力,从而降低了神经网络的表达能力,所以,加入再缩放参数 g g g(re-shift parameter),再平移参数 b b b(re-scale parameter)参数,则可以让数据从线性区变换到非线性区,恢复模型的表达能力。
那么,另一个问题来了,
经过这么的变回来再变过去,会不会和没变一样呢?
答案是——不会的,因为再变欢引入的两个新参数,再缩放参数 g g g(re-shift parameter),再平移参数 b b b(re-scale parameter)参数,可以表示表示旧参数作为输入的同一组函数,但是新参数有不同的学习动态,在旧参数中,x的均值取决为底层神经网络的复杂关联,而利用了新参数, y = g ∗ x ^ + b y=g*\hat{x}+b y=g∗x^+b,均值将由b来确定,去除来和下层计算的密切耦合,新参数很容易通过梯度下降来学习,简化来神经网络的训练。
类似的有(BN、LN、IN、GN和SN)
这里需要重点提一下LN
论文:https://arxiv.org/pdf/1607.06450v1.pdf
之前看了很久,没那么懂,应该是是被方法的名字给弄混淆了。
讲道理,BN中的batch normalization好理解,因为求平均,求方差时,除以的就是batchsize,对应算法的B
那么,对LN中的layer normalization而言,求平均,求方差时,除以的就是layer-size了,这就不好理解了。
看了论文中,LN中的求平均,求方差的方法是,
u l = 1 H ∑ i = 1 H a i l u^{l}=\dfrac{1}{H} \sum_{i=1}^{H} a_{i}^{l} ul=H1∑i=1Hail
σ l = 1 H ∑ i = 1 H ( a i l − u l ) 2 \sigma^{l}=\sqrt{\dfrac{1}{H} \sum_{i=1}^{H} (a_{i}^{l}-u^{l})^{2}} σl=H1∑i=1H(ail−ul)2
论文中说到的是,H代表一层中隐藏元的个数(the number of hidden units),这就懵逼了,明明除以的不是layer-size,而是 hidden units,为什么搞个layer normalization的名字,很是无语。
而且论文中,讲到,different training cases have different have different normalization terms.这更让人懵逼了,应该是中BN的毒太深,并且也不太懂LN方法的初衷。
知道看到这篇博文的图(Batch Normalization和Layer Normalization的原理分析对比,及Tensorflow代码实现),才算大概摸明白了LN的计算方式,
显然对于这个batch的中间数据。
[[1,2,0,4,5,1],
[3,2,1,6,2,0],
[6,2,5,1,3,1]
]
BN符合常理,就像常规的数据挖掘一样,对每个维度求均值方差来,针对各个维度做出归一化。
而LN就似乎不太符合常规的数据挖掘中归一化的思路,是让每一条的特征放在一起看是被归一化。
所以BN是样本有多少个,均值求和时加多少次。
而LN是一条数据特征有多少维,均值求和时加多少次,相当于把特征值全相加,求和求均值。
另外,从图中能看出,LN处理单个样本,LN的处理对象是单个样本的所有维度特征做归一化。
好吧,会不会觉得奇怪,LN这样对所有维度特征做归一化靠谱吗?
(其实也是,LN更适合特征的物理含义都是一类的情况,如图片,特征都是像素值,nlp,特征都是词向量的某种表示。这个不是重点),
总结一下:
Batch Normalization 针对多个样本的同一维度的输出值;
Layer Normalization 针对一个样本各个维度的输出值;
所以,BN看着像横向,LN看着像纵向,当然你也可以说,BN看着像纵向,LN看着像横向,无非换下图的方向。
注意的是,LN中一个样本的各维度,就是经过 w x h w_{xh} wxh, w h h w_{hh} whh产生的,这样就好理解为什么除以的是the number of hidden units了。
最后,工程中,LN对RNN效果好,CNN效果差。
LN可以针对单个训练进行,不依赖其他数据,因为可以避免bn中受mini-batch数据分布影响的问题,可以适用于小mini-batch场景、动态网络场景和rnn,特别是自然语言处理领域。此外ln不需要保存mini-batch的场景和方差,节省了格外的存储空间,这算理由吗?深度学习还在乎这么点存储空间嘛,呵呵。
但是,bn的转换是针对单个神经元可训练的——不同神经元的输入经过再平移和再缩放后分布在不同的区间,而LN对于一层的神经元得到同一个转换——所有的输入都在同一个区间范围内,如果不同输入特征不属于相同的类别(比如颜色和大小),那么LN的做法反而会降低模型的表达能力。
所以在ctr预估这样的多类别拼接的处理过程中,应该还是避免使用LN。
但在自然语言处理,这种各维都在表示词向量的场景下,LN显得很适用。
我们知道,加入激活函数就是为了先神经网络引入非线性,如果每层都归一化把值拉回来线性区再经过激活函数,那么激活函数的非线性区就没作用了呀,那要激活函数来干啥。所以机智的Batch Normalization的作者把这个“烂摊子”又丢回给神经网络,Batch Normalization为了保证非线性的获得,对变换后的满足均值为0方差为1的各个输出值x又进行了scale加上shift的操作(y=scale*x+shift),归一化层加入scale和shift这两个参数,这两个参数是通过训练学习到的,意思是通过shift把这个值从标准正态分布左移或者右移一点,通过scale让正太分布长胖一点或者变瘦一点,每个神经元的scale和shift不一样,从而使一个样本在某些维度特征上利用线性区,在某些维度特征上利用非线性区。这相当于给了神经网络自己判断当前样本该用多少非线性的功能,利用多少,利不利用,你自己看着办吧,拜~(BN作者真机智)。
而Layer Normalization本身是对各个维度的输出做归一化,也就是说输出值本身有些就在线性区,本身有些就在非线性区,所以不用再做其他操作了。
BN:
1,高度依赖于mini-batch的大小,实际使用中会对mini-batch的大小进行约束,不适合在线学习(mini-batch=1)的情况。
2,不适合rnn网络中normalize操作,BN实际使用中需要计算并保存某一层的神经网络mini-batch数据的均值和方差等统计信息,对于一个固定深度的前向神经网络(dnn,cnn)使用bn,很方便,但对于rnn来说,sequence的长度是不一致的,也就是我们常说的rnn深度不固定,这意味着不同的time-step是需要保存不同的统计信息的,可能存在一个特殊的sequence的长度比其他的sequence的长度长很多,这样,training时计算很麻烦。
LN:
LN不依赖mini-batch的大小和输入sequence的长度,因此可以用于mini-batch=1的情况,和rnn中对边长的输入sequence的normalize操作,但在大批量的训练样本时,效果没有BN好。
最后,实践证明,工程中,LN对RNN进行normalize时,效果比BN好,但用于CNN时,效果不如BN明显。
最后,不管是BN还是LN在Tensorflow或者pytorch或者keras中实现都是一句话函数调用而已:
BN:
fc = tf.layers.dense(input, output, activation = None)
fc = tf.layers.batch_normalization(fc, training=training)
fc = tf.nn.relu(fc)
#...
with tf.control_dependencies(tf.get_collection(tf.GraphKeys.UPDATE_OPS)):#用于更新moving_mean和moving_variance
train_op = tf.train.AdamOptimizer(learning_rate).minimize(loss)
LN:
fc = tf.layers.dense(input, output, activation = None)
fc = tf.contrib.layers.layer_norm(fc)
fc = tf.nn.relu(fc)
#...
train_op = tf.train.AdamOptimizer(learning_rate).minimize(loss)