前言
Batch Normalization是深度学习发展过程中出现的最重要的算法之一,Batch Normalization会使你的参数搜索问题变得容易,使神经网络对超参数的选择更加稳定,也使你更容易训练深层神经网络。
论文链接:https://arxiv.org/abs/1502.03167
一些符号说明
假设现在有如下神经网络:
神经网络示例
我们把输入记为
![]()
(
向量),输入当作第0层,最后一层为第
![]()
层。第
![]()
层的节点数记为
![]()
,则上图中
![]()
,
![]()
,
![]()
,
![]()
。
上图中每个节点的计算方式如下,其中
![]()
是激活函数的输出值,
![]()
是激活函数。
我们把第
![]()
层中的激活函数输出值记为
![]()
(
是个向量) ,则
![]()
。我们把输入
![]()
看做第0层的激活值
![]()
,最后一层的激活值为网络的预测输出,即
![]()
。计算
![]()
的权重矩阵记为
![]()
,第
![]()
层的偏移值记为
![]()
(
是个向量)。
Batch Normalization
我们知道在训练神经网络时,归一化输入特征可以加速学习过程。那么在深层神经网络中,我们归一化
![]()
是否也还有益于
![]()
和
![]()
的训练呢?严格来说,我们这里归一化的是
![]()
而不是
![]()
。
这里关于到底是归一化
还是
有一些争议
,在实际使用中我更常使用的是归一化
![]()
,所以下面我介绍的是归一化
![]()
的Batch Normalization。
Batch Normalization算法步骤如下:
1.给定1个batch的输入
,在神经网络第
层对应的中间隐藏值为
,这里的记号省略了
,实际应为
。
2.计算均值:
3.计算方差:
4.归一化:
,其中
是为了防止除0。
5.缩放和平移:
,其中
和
是要训练的超参数。
第4步后,
![]()
变成了均值为0,方差为1。但是我们不想让隐藏单元
![]()
总是均值为0,方差为1。也许隐藏单元有了不同的分布会更有利于训练。所以我们在第5步将
![]()
的分布进一步平移和缩放,超参数
![]()
和
![]()
可以让
![]()
达到任意的均值和方差。
例如当激活函数是
![]()
函数时,
![]()
若总是均值为0,方差为1,那么通过
![]()
函数激活后的值只能是.5周围的一个很小的区间,这个区间是接近线性的。而我们使用
![]()
函数通常是为了得到一个更大方差的值,使得输出更有区分度,所以我们需要输入的方差更大一些更好,同时也可以利用到
![]()
函数的非线性区域。所以我们使用超参数
和
![]()
来跳调整
![]()
的均值
事实上,如果
![]()
,
![]()
,那么
![]()
.
Batch Normalization后,我们将
![]()
输入到下一层。
Batch Normalization将归一化的过程不仅仅用于输入层,也应用于神经网络中的深度隐藏层。
假设现在有如下神经网络:
则加入BN层的上述神经网络的计算过程如下:
则我们的神经网络的参数有:
![]()
,
![]()
,
![]()
,
![]()
,...,
![]()
,
![]()
;
![]()
,
![]()
,
![]()
,
![]()
,...,
![]()
,
![]()
。
假设第
![]()
层有
![]()
个隐藏单元,即
![]()
的维度为
![]()
,那么
![]()
的维度为
![]()
。
这些神经网络参数都可以用梯度下降算法(SGD,Adam等)来更新优化。值得注意的是,由于在BN层中会对
先减去均值再除以方差,那么无论
的只是多少,在BN层减去均值过程中都是要被减去的,所以如果神经网络中有BN层,那么可以不用偏移量参数
,或将
设为0。其实BN层中的
参数也有替代
设置偏移量的作用。
BN层的训练过程
在实际训练过程中,神经网络是通过一个个小的batch数据训练的。关于反向传播算法的详细解释可以参见这篇文章:
小糊糊:反向传播算法详解zhuanlan.zhihu.com
假设第
个batch输入为
。
首先对输入
计算包含BN层的前向传播结果,得到
,
,
。
然后使用反向传播算法计算
,
(含BN的层没有
),
,
。
更新参数:
,
,
,
BN层为什么奏效?
我们知道对于输入特征
![]()
,与其让特征值
![]()
的各个特征有不同的范围(例如
![]()
范围从0到1,
![]()
范围 从1到1000),不如通过归一化让所有的输入特征到同样的范围(例如各个特征都归一化到均值为0,方差为1),这样可加速模型的学习过程。
所以BN层起作用的第一个原因是它在做类似的归一化,但这种归一化不仅仅对于这里的输入值,还有隐藏单元的值。
BN层有效的第二个原因是它让后面层的权重受前面层权重变化的影响减小,例如第一层的权重变化对第十层的权重的影响。
现在假设有一个多层的神经网络,我们知道若
![]()
,
![]()
,
![]()
,
![]()
发生改变,那么隐藏单元
![]()
的也会改变。而网络训练过程是通过梯度下降法来更新参数的,所以
![]()
,
![]()
,
![]()
,
![]()
的值是不断更新的,那么隐藏单元
![]()
的值是不断变化的,
![]()
的值会影响
![]()
,
![]()
的值。那么也就是说前面层权重的变化会影响后面层权重的变化,深层网络更是如此,前层网络一个小小的改变会使得深层网络的值发生巨大变化。
BN层所做的是减少了隐藏单元值分布变化的量,使隐藏单元的值保持在同样的均值和方差。它减少了前层的参数更新对数值分布的影响程度,BN层使隐藏单元值更稳定。在前面层更新参数时,后层需要适应的程度减小了。BN层减少了前层参数与后层参数之间的联系,它使得网络每层都可以自己学习,稍稍独立与其它层,这有助于加速整个网络的学习。
BN层还有轻微的正则化的效果。因为BN层每次使用的是当前batch的均值和方差而不是整个数据集的均值和方差,由这样一小部分数据估计得到的均值和方差将会有一些误差(噪声),那么由这些有噪声的均值和方差缩放得到的
![]()
也将有噪声,这样在网络的每个隐藏单元上都引入了噪声。BN层引入噪声的原理有些类似于dropout,dropout是只让部分神经元起作用,BN层是只使用小部分数据来估计整体的均值和方差,都是使用部分来代表整体。类似于dropout,BN层也有轻微的正则化的作用,因为给隐藏单元添加了噪声,这迫使后部单元不过分依赖于任何一个隐藏单元。因为添加的噪声很小,所以只有轻微的正则化效果。你的batchsize越大,正则化效果越微弱。
测试时的BN层
BN层在训练时将你的数据以batch的形式处理,我们计算每个batch的
![]()
和
![]()
。
但在测试时,我们可能需要实时预测每个输入的预测值,每次只有一个输入值,一个输入值的均值和方差没有意义,这样我们就需要用其他的方式来得到
![]()
和
![]()
。
在测试阶段,我们使用指数加权平均来估算
![]()
和
![]()
,这个平均值涵盖了所有的batch。假设有如下batch数据:
![]()
,
![]()
,
![]()
,...。这些batch数据对应在网络第
![]()
层的BN层的均值和方差为:
![]()
,
![]()
,
![]()
,...,
![]()
,
![]()
,
![]()
,...。则我们使用这些batch的均值和方差的指数加权平均值来作为测试阶段的
![]()
和
![]()
。
指数加权平均的公式为:
其中
![]()
是第
![]()
天(batch)的指数加权平均值,
![]()
是第
![]()
天(batch)的指数加权平均值,
![]()
是第
![]()
天(batch)的观测值(实际值)。
![]()
和
![]()
的指数加权平均值代入上面公式计算即可。
总结
使用BN层可以让你训练更深的网络,并且让你的模型训练得更快。