目录
1 提出背景
1.1 ICS是什么
1.2 ICS带来的问题
1.2.1 训练速度降低
1.2.2 梯度消失问题
1.3 ICS能如何被解决
1.3.1 Whitening
1.3.2 Batch Normalization的提出
2 BN算法思想
2.1 大体思路
2.2 具体算法
2.2.1 Training过程中的BN
2.2.2 Inference过程中的BN
2.3 CNN中的BN
3 BN的好处
3.1 加速模型训练速度
3.2 对初始值没那么敏感
3.3 具有正则化作用
3.4 允许使用饱和激活函数
4 实验结果
5 总结
在Batch Normalization提出者的原论文中,给出了一下关于Internal Covariate Shift(ICS)的定义——We define Internal Covariate Shift as the change in the distribution of network activations due to the change in network parameters during training. 即,在训练过程中,由于参数更新的原因所导致的网络内部节点数据分布的改变。
我们该如何理解这句话呢?首先来理解一下Covariate Shift。Covariate Shift是Data Shift的一种类型,描述的是学习阶段和泛化阶段之间(即训练数据和测试数据)输入变量X的分布变化。而在深度神经网络中,除输出层之外的所有层均可被看作是其下一层的输入,即所有隐藏层在某种角度上也可以被视为输入层。我们知道,随着训练的进行,深度神经网络中每一层的参数是不断被更新的,而每一层参数的更新会导致其下一层的输入数据分布发生变化,并且这种变化会随着神经网络深度的加深而放大,因为每一层隐藏层的输入数据都受到该层之前所有层的参数的影响,所以,深度神经网络中的每个隐藏层都会面临Covariate Shift问题。
让我们来看一个例子以便更形象的理解。下图是一个具有四层隐藏层的全连接神经网络模型。我们把目光聚焦在第三层隐藏层上,那么第二层隐藏层节点值与w_2_3、b_2_3的线性变换再套上一个非线性激活函数g(·)的结果,即为第三层隐藏层的输入值。而这组输入值,又会受到前面的参数w_0_1、b_0_1、w_1_2、b_1_2的影响,并且这四个参数会随着模型训练的过程不断地更新,这也就会导致第三层隐藏层的输入值分布不断地发生变化,也就是第三层隐藏层面临着Covariate Shift问题。推广到每一个隐藏层,均是如此。这种发生在神经网络内部层的输入变量分布变化,就称为Internal Covariate Shift,而不是像Covariate shift问题只发生在最开始的输入层。
我们在上面提到了,深度神经网络中每一层隐藏层的输入值分布会发生变化。这就使得后层的网络需要不停地调整去适应输入数据分布的变化,导致整个网络的学习速度降低。通俗的来说,比如模型经过一次迭代训练后,已经学到了该组输入数据分布所蕴含的若干特征、并且将模型参数调整得还算不错,按理来说接下来应该不断地学习这种输入数据分布所蕴含的其他特征。但由于存在ICS问题,此时进入下一次迭代训练时,输入数据分布发生了改变,模型又需要调整参数去适应新的分布,从而影响了模型应有的学习进程。同时,深度神经网络通常涉及到很多层的叠加,即便是微小的变化也会由于线性变换与非线性激活映射的层层叠加而放大其影响,使得我们的模型训练变得困难。
当我们在神经网络中使用饱和激活函数(saturated activation function)时,由于前一层的输入值变化可能趋向于变小或者变大,这很容易使得模型训练陷入非线性饱和区,此时梯度会变得很小甚至接近于0,即出现梯度消失问题,模型参数的更新速度也会变慢。常见的饱和激活函数有sigmoid函数和tanh函数。举个例子,对于sigmoid函数来说,由于前一层输入值分布变化的不确定性(趋向于变小或变大),可能导致下一层的输入落入sigmoid函数靠左或靠右的区域,而这些区域即为sigmoid的非线性饱和区。从下图可以看到,sigmoid函数左右两端的梯度几乎趋于0了。
对于梯度消失问题,有两种解决思路。第一种思路是更换为非饱和激活函数(unsaturated activation function),如ReLU或Leaky ReLU函数,这可以在一定程度上解决训练时进入非线性饱和区的问题。第二种思路是,我们可以让激活函数的输入分布保持在一个稳定状态,尽可能避免它们陷入非线性饱和区,这也就是Normalization的思路。
鉴于上述ICS所带来的问题,我们可以给出解决ICS问题的思路,即在不影响数据性质的前提下,尽可能使得每次参数更新前后,保证:一,每层每个神经元的输入依然满足独立同分布;二,尽可能避免该分布下的数据,落入激活函数的非线性饱和区。
在讲whitening之前,先看一个引例。我们知道,在训练线性模型(如线性回归模型、logistic模型)之前,对自变量数据进行标准化操作是很重要的,它可以使得模型损失函数的等高线呈圆形。在这种情况下,模型参数对初始值的选择更不敏感,也更容易找到合适的学习步长,且更容易收敛到损失函数最小值点。那么,很自然的会联想到,若在深度神经网络模型中对每一层输入值进行标准化操作,是否也能给模型的训练带来好处?
答案是肯定的。要缓解ICS的问题,就要明白它产生的原因。简单回顾一下,ICS产生的原因是由于参数更新带来的网络内部每一层输入值分布的改变,并且随着网络层数的加深而变得更加严重,因此我们可以通过固定深度神经网络模型每一层的输入值分布来减缓ICS问题。LeCun等人以及Wiesler&Ney提出,通过白化(Whitening)操作——即线性变换为零均值和单位方差,并且去相关(decorrelated)——可以减缓ICS的问题,从而加速模型训练的收敛。由于白化操作是线性变换,故不会影响数据性质,满足了1.3节开头提出的前提,同时也满足了其后两个保证:一,每层输入分布的均值均为0、方差均为1,满足独立同分布;二,由于输入值集中在0附近,即便是饱和激活函数如sigmoid、tanh函数,都不会出现梯度消失的问题。
然而,白化有几个缺点:
一,白化过程计算成本太高。当我们白化每一层输入时,不仅每次都要重新计算全部数据的均值与方差,还要将白化过程考虑在反向传播算法中,求解参数梯度时计算量很大。于此同时,为了去相关,还需要计算协方差矩阵及其倒数平方根,用以计算白化后的节点值
,以及反向传播算法中要用到的这些变换的导数。
二,白化过程会改变模型原本的表达能力。例如,对于sigmoid函数,由于白化过程会把输入数据集中在0附近,这会使得数据集中在sigmoid的线性饱和区域,从而学不到什么非线性特征。
三,白化并不是处处可微的,所以在深度学习中,其实很少用到白化。这就激发我们另寻一种更合适的normalization方式。
鉴于白化以上的缺点,我们的解决思路就很清晰了。一方面,由于去相关的计算复杂,我们一般不进行去相关操作,只简化进行z-score处理,即独立地标准化每个数量特征,使其均值为0、方差为1。LeCun等人在文章中表示,这种简化的白化处理加快了模型的收敛,即使没有对特征进行去相关操作。另一方面,为了避免标准化后的值落入激活函数的线性饱和区域,我们对标准化后的值再添加一个shift和scale操作,使得数据分布从激活函数的正中心向周围稍微挪动。于是基于简化后的白化并稍作改进的方法——Batch Normalization,简称BN,便应运而生。在下一节中我们会讲到,Batch Normalization同时也是处处可微的。
就像全连接层、卷积层、池化层、激活层一样,Batch Normalization(BN)也可以理解成网络的一层。BN提出者在原论文中建议把BN层放在激活层之前,因为这样更可能产生具有稳定分布的输入节点值。但关于BN层与激活层的顺序,不论是BN-Act,还是Act-BN,本质上都是可以起到消除ICS问题的作用,实际操作时还需要具体问题具体分析。比如,若某深度神经网络模型靠后面的层所使用的激活函数具有截断作用,此时若选择BN-Act,BN在前会让一开始收敛明显,但是遇到模型后面的有截断作用的激活函数时,后面层的输入数据分布还是会改变;而若选择Act-BN的话,BN在后就能够使得后续层的输入值分布不受激活函数的截断影响而固定下来。所以,使用顺序还需要视具体情况而定。
BN的思想其实相当直观,其真正作用就是固定模型中各层输入数据的均值与方差。主要包括两个重要操作:
一,normalization,即零均值化、单位方差;
二,scale和shift,即引入参数γ和β,它们不仅能够保证BN是恒等变换,还能够控制输入数据分布的均值和方差。
首先,对于具有d维的输入层,我们独立地标准化每个特征,使其均值为零、方差为1,即对于
,计算
,其中期望
和方差
是在整个训练数据集上计算的。
但请注意,简单地标准化每个输入层可能会改变该层的表达能力。通过简单标准化的手段,我们能够把逐渐跑偏的输入值分布强行拉回到均值为0、方差为1的情况,虽然这确实能够使得激活函数输入值落在梯度较大的区域,从而避免了梯度消失的问题,加快了训练的速度,但是均值为0、方差为1这个限定,对目前大部分的激活函数不太友好,因为这会使得激活函数输入值落入非线性激活函数的线性区内,从而导致模型学不到什么非线性特征,网络的表达能力下降。这样一来,经过简单标准化后,模型中的非线性激活函数就相当于被替换成线性函数的效果了,而我们知道多层线性函数的结果与一层线性函数是等价的,如此一来,深度神经网络的“深度”就失去了意义。
为了恢复模型的表达能力,我们不希望输入数据分布一定就为零均值单位方差的情形,如果能稍微改变一下输入数据分布的均值与方差,使其稍微远离非线性激活函数的线性区,问题便迎刃而解。为此,可以对输入层的每个节点值引入一对参数
,它们的作用是缩放(scale)和移动(shift)简单标准化后的结果
,即
。输入层有几个维度,就有几个
-
对,并且这些
-
对不是超参数,而是需要与原始模型中的参数一起被学习更新的。Scale和shift的操作能够控制输入数据的均值与方差,使得输入数据分布从激活函数的线性区往非线性区稍微挪动,并希望通过模型训练找到一个线性和非线性较好的平衡点,即能享受到线性区的较大梯度、又能享受非线性的较强表达能力,从而恢复深度神经网络的表达能力。
以上所述的过程都是基于整个训练数据集的,而在训练模型时,上述标准化计算是不切实际的,因为对于每一次迭代,我们都要重新计算基于整个训练数据集的每层每个节点的均值、方差,计算量太大了。而Batch Normalization的“Batch”,指的就是在标准化的过程中使用了mini-batch。在标准化输入层的每个特征时,我们不再计算该特征基于全部数据的均值与方差,转而计算该特征基于mini-batch的均值与方差,用它们来当作总体均值与方差的估计,从而降低了计算量。值得注意的是,batch size不应取得太小。如果太小的话,基于mini-batch所计算出来的均值与方差,就不会是基于整个训练数据集所算出来的均值与方差的良好估计。
假设我们将整个训练数据集分为若干个batch size为m的mini-batch,考虑其中一个mini-batch B。对于模型中具有d维的输入层而言,为了方便阐述,让我们先关注第k个特征值
。首先我们计算一个mini-batch
中第k个特征值的均值与方差:
接着我们利用与
来标准化第k个特征值:
然后再引入和
,scale和shift标准化后的值
,得到
:
为了简便起见,省略公式中的所有k,例如把简写为
,上面的公式可以简化写为:
这个过程就叫做Batch Normalization Transform,BN变换。总结一下,BN变换可以写成如下算法1。其中,为了避免方差为0的情况,在第三步标准化时给mini-batch的方差加了一个常数ε。
在进行反向传播算法时,我们需要计算包含BN层的新模型的损失函数l,以及计算损失函数l关于各个参数的梯度。采用链式求导法则,化简前结果如下:
故BN变换是一种可微变换,能够使反向传播算法更新参数时将BN变换考虑进来。由此可见,BN不仅减缓了ICS问题(也就是稳定了每层输入数据的分布),而且还克服了whitening的三个缺点(计算复杂、模型表达能力下降、不处处可微),同时极大地加速了模型的训练速度,是一个很不错的方法。
在推断(inference)阶段时,由于输入只有一个实例,我们没有办法利用mini-batch计算均值与方差,那么此时应该怎么对输入数据进行BN变换呢?原文中提出,可以使用训练过程中所有mini-batch算出来的均值与方差来无偏估计这个两个总体参数(、
)以作为每个测试样本进行BN变换时的均值和方差,具体计算公式如下:
得到每个特征的均值与方差的无偏估计后,由于每个特征也已经有对应训练好的scale和shift的参数,就可以在推断阶段进行BN变换了。此时的BN变换,其实就可以看作一个简单的线性变换,因为所有参数都固定下来了,具体计算公式如下:
总结一下,将BN变换的训练阶段与推断阶段结合起来,可总结为如下算法2:
其中,与
其实是等价的。前面这种写法可以减少计算量,因为
与
在推断过程中其实都是固定的值,只用算一遍,存起来就可以了;而如果写成后面这种形式的话,为了得到
,对每一个节点
都要现算多一个减法和除法
。乍一看没有多多少计算量,但当节点个数多时,节省的计算量也是蛮多的了。另外,除了采用整体样本的无偏估计外,吴恩达老师的Deep Learning课程中指出可以对训练阶段每个mini-batch计算的均值与方差采用指数加权平均来得到测试阶段均值与方差的估计。
在卷积神经网络(CNN)中,鉴于卷积层的特殊性,我们希望BN变换仍然能够保持卷积的特性。那么在卷积层中应该如何进行BN呢?
在理解了全连接层的BN算法之后,只要把卷积层中的每个feature map,看作全连接层中的每个节点(也即该层输入的每个维度),事情就很好理解了。在全连接层的BN中,算法2中的K指的是该层输入数据的维度,而在卷积层的BN中,指的则是该层卷积核的个数。在全连接层的BN中,算法2对于每个维度进行同样的BN变换,而在卷积层的BN中,则是对每个feature map进行同样的BN变换。在全连接层的BN中,该层输入数据有多少个维度,就有多少个γ-β对,而在卷积层的BN中,该层有多少个卷积核,就有多少个γ-β对。
但卷积层中的batch有点特别,它指的是在同一个feature map上分割成的batch。举个例子,现在有一个有3个卷积核的卷积层,那么该卷积层的输入数据通过这3个卷积核后,会形成3个feature map(假设这3个feature map的长度与宽度均分别为p、q),这三个feature map就是下一层的输入。我们现在要对这3个feature map进行BN变换,取batch size=m,那么一个batch指的就是同一个feature map上的m张数据图,即一个batch有m×p×q个节点值。
下面接着上面举的例子来解释卷积层的BN变换的具体过程。
首先,搞清楚我们要对什么对象进行BN。在卷积层中,输入一张数据图,会出来许多feature maps,此时每个feature maps的深度均为1,因为只输入了一个数据。
于是乎,若输入训练数据集的所有数据图(个数设为n),经过3个卷积核后,得到的结果就是3个feature maps,每个feature maps深度就是输入数据图的个数,长度宽度仍为p、q。现在我们就是要对这三个feature maps长方形(维度为n×p×q),进行BN。注意到算法2里,是有个for K循环的。在卷积层中,这个K指的就是卷积核的个数。比如我们刚刚举例子说有3个卷积核,那么这个K就取3。也就是说,卷积层的BN,是要对每一个卷积核所map出来的feature maps,都进行BN。
那么,对一个卷积核所得到的一个feature map进行BN,具体要怎么操作呢?刚才说过,batch指的是在同一个feature map里面划分batch。假设batch size仍为m,那么对于一个维度为n×p×q的feature map,就可以划分为若干个维度为m×p×q的feature map,每一个维度为m×p×q的feature map称为一个batch。对这个batch进行标准化,需要用到均值与方差。而此时计算均值与方差的方法,是计算一个batch中所有节点的均值与方差,也就是计算m×p×q个数据的均值与方差,然后再对这个batch中的所有节点进行标准化,即减去刚算出来的均值、除以刚刚算出来的方差开根号加ε,然后再用γ scale、用β shift。别忘了,BN过程中,参数γ和β是要训练的,这和全连接神经网络里的BN是一样。同时注意,对同一个feature map中的节点所进行的BN操作都是一样的。
以上就是对一个feature map进行BN的操作了。要记得,我们是要分别对每个feature map进行BN,所以对每一个卷积核map出来的feature maps,都重复上面的BN操作,直到所有feature maps都被BN过了,算法就结束。
传统神经网络中,大的学习率会放大参数的scale,也就会在反向传播中放大梯度,导致梯度爆炸问题。并且,如果每层的scale不一致,实际上每层需要的学习率是不一样的,同一层不同维度的scale往往也需要不同大小的学习率,通常需要使用最小的那个学习率才能保证损失函数有效下降。而BN将每层、每维的scale保持一致,那么我们就可以直接使用较高的学习率进行优化,提高了模型的训练速度。
同时,BN通过标准化与线性变换使得模型每一层输入数据的均值与方差都在一定范围内,使得后一层网络不必不断去适应来自前一层网络输入的变化,从而实现了网络中层与层之间的解耦,相当于削弱了层与层之间参数作用的联系。这使得网络中每层都可以稍稍独立于其他层来自己学习,提高了模型的学习速率。此外,学习率的衰减速度也很大,因为这个算法收敛很快。所以BN不仅极大提升了模型训练速度,也使得收敛过程大大加快。
在神经网络中,我们经常会谨慎地采用一些权重初始化方法来保证网络稳定训练。但是,使用BN的网络将不会受到参数数值大小的影响。例如,我们对参数 W 进行缩放得到 aW 。对于缩放前的值 Wu(u为当前层的输入) ,我们设其均值为,方差为
;对于缩放值aWu ,设其均值为
,方差为
,则有:
忽略ε,则有:
我们可以看到,经过BN操作以后,权重的缩放值 a 会被“抹去”,因此保证了输入数据分布稳定在一定范围内。另外,权重的缩放并不会影响到对W的梯度计算,并且当权重越大时,即 a 越大,越小,意味着权重 W 的梯度反而越小,这样BN就保证了梯度不会依赖于参数的scale,使得参数的更新处在更加稳定的状态。
因此,在使用Batch Normalization之后,抑制了参数微小变化随着网络层数加深被放大的问题,使得网络对参数大小的适应能力更强,此时我们可以设置较大的学习率而不用过于担心模型发散的风险。
由于BN中的均值与方差是根据mini-batch来计算,而不是根据整个数据集计算的,所以均值与方差有一些小噪音,故标准化输入值的过程中也有一些小噪音,因为它是根据有噪音的均值与方差计算出来的。所以,BN的过程与dropout类似,它往每个隐藏层的节点值增加了噪音,这迫使后面的节点不过分依赖于任何一个隐藏层节点,因此有稍微正则化的效果。但因为添加的噪音很微小,所以并不是巨大的正则化效果,实际中可以将BN于dropout一起使用,以获得更强大的正则化效果。
在不使用BN层的时候,由于深度神经网络的深度与复杂性,很容易导致模型训练进入到激活函数的非线性饱和区,即出现梯度消失问题。经过BN变换后,可以让激活函数的输入数据落在梯度非饱和区,缓解梯度消失的问题,从而使得使用饱和激活函数(如sigmoid、tanh等)训练模型称为可能。
在BN提出者的原文中,给出了相应的实验结果证明BN的有效性。
首先看一个在简单例子上的效果。作者考虑了一个具有3层全连接层的神经网络模型,并将之应用于MNIST数据集上,比较使用了BN与没有使用BN的模型效果。Figure 1可以看出,带BN的模型不仅得到了更高的准确率,其输入值分布还更加稳定。
接着再看个稍微复杂的例子。作者考虑了一个Inception网络的新变体,所用到的数据集是ImageNet,并比较了各种带BN的模型与原始不带BN的模型的效果有何区别。
Figure 2展示了网络的验证精度,横轴为训练步骤。步后,Inception达到72.2%的精度。Figure 3表明,对于每一个考虑的网络,达到同样的72.2%的精度,需要的步数,以及最大验证集精度。
可以看到,对于BN-Baseline,达到Inception网络同样的精度,只使用了少于inception达到同样精度的一半的步数。BN-X5, BN-X30, BN-X5-sigmoid这三个网络都极大的提高了训练速度。具体来说,BN-X5达到72.2%精度所花的步骤比Inception少了14倍。有趣的是,学习率更高的网络(BN-X30),会导致模型最初的训练速度稍慢,但它最终能达到更高的精度。在步数后,BN-X30达到74.8%的精度,即相比于Inception达到72.2%的精度所需步数(
)大概少了5倍的步数。
这同时也可以看到,ICS问题被BN缓解后,能够使得BN+Sigmoid可以训练,一般来说这样的网络是很难训练的。由Figure 3可以看到,BN-X5-Sigmoid达到了69.8%的精度,而没有BN的情况下,Inception+Sigmoid几乎训练不出来。
至此,关于Batch Normalization的介绍就到这里。总的来说,ICS问题会影响模型的训练,而BN通过将每一层网络的输入进行标准化以及scale和shift,保证了每层输入分布的均值与方差固定在一定范围内,保留了网络的表达能力,缓解了ICS问题,使得网络能够适应更高的学习率,并大大地加速了模型的训练速度。最后,由于BN训练过程中使用的是mini-batch的均值与方差作为总体均值与方差的估计,引入了随机噪声,在一定程度上对模型起到了正则化的效果。