Batch Normalization 是近两年兴起的深度学习中的一种处理技巧。使用BN可以使模型收敛更快,加速训练。同时降低了网络对于参数的敏感程度,减少了梯度消失的情况发生。
关于BN的学习主要来源于论文《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》
概述
当前神经网络的训练有一个问题,就是训练过程很慢,需要花费大量的时间收敛。这个现象通常由两个原因造成,一个是因为初始的learning rate比较低,另一个是网络对于参数的初始化非常敏感。
论文中作者引入了一个BN层,对于每一个mini-batch,将其放入BN层后再进入激活函数。实验证明,这个BN层可以加速模型训练,同时训练效果非常好。
Internal Covariate Shift
在机器学习理论中,存在一个独立同分布的假设。假设训练数据与测试数据是满足相同分布的。但是在实际中,可能会出现两者并不服从完全相同的分布。这种现象就是covariate shift。Internal Covariate shift 可以理解为神经网络中隐层中存在的分布不相同情况。
这种现象会导致不少问题。
一,因为每层的分布都不同,所以每层的参数需要适应新的输入分布,会造成训练速度下降。
二,输入的变化可能会趋于变大或者变小,从而使输入落入激活函数的饱和区,使得学习停止。
在深度学习模型中,
BN的作用就是通过BN处理之后的隐层的输入,能够服从相同的分布。从而降低了模型的 internal covariate shift。
Batch Normalization
BN的处理思路是 对于每一个进入激活函数的输入,在其进入激活函数前,先进行BN处理。算法如下图所示。对于每一个作为输入的mini-batch,分别计算其均值与方差。normalize之后,再通过两个参数进行调整。
从算法中可以看出,数据进行了两次的平移和缩放。第一次平移和缩放得到了normalize的值,均值为0,方差为1。第二次的平移和缩放用了
对于每个隐层神经元,把逐渐向非线性函数映射后向取值区间极限饱和区靠拢的输入分布强制拉回到均值为0方差为1的比较标准的正态分布,使得非线性变换函数的输入值落入对输入比较敏感的区域,以此避免梯度消失问题。因为梯度一直都能保持比较大的状态,所以很明显对神经网络的参数调整效率比较高,就是变动大,就是说向损失函数最优值迈动的步子大,也就是说收敛地快。
以sigmoid激活函数为例,如果不做规范化处理的话,输入很有可能过大或过小,从而进入到函数的饱和区域,这里的梯度几乎为0,学习基本就停止不前了。经过规范化处理之后,可以让输入对应到中间的非饱和区域,从而使梯度保持比较大的状态。
这里有一个问题,在神经网络中,激活函数因为自身具备饱和区和非饱和区,使得模型具有了非线性的计算能力,所以相对线性模型,有了更好的表达能力。但是经过了规范化之后的输入,全部对应了激活函数的不饱和区域,这里类似于一个线性区。这样虽然有很大的梯度,但是会降低模型的表达力。为了针对这个问题,作者使用了第二次的平移和缩放来解决。
对于第二次的平移和缩放,从另一角度看也是用来增加模型的表达能力。因为前一层的输入无论进行了怎样的变化,在当前层都被暴力的规范化到同一个范围。这无疑是削弱了神经网络的表达能力。为了避免这个缺点,平移和缩放系数是针对当前层的可以学习的参数。这就使得每个不同的神经层都具有了自己的独自的表达能力。
这两个系数,更好的利用了前一层的输出,和激活函数的非线性表达,从而增强了整个模型的表达能力。
为什么有效?
对于BN为什么有效这里,主要的理解来自于《详解深度学习中的 Normalization,不只是 BN》这篇文章。
1 权重伸缩不变性
对于权重W进行伸缩变换时,均值和方差会等比例缩放。
其中
推导过程如下:
因为
前一层的权重越大,那么当前的梯度就越小,参数越稳定,震荡越小。相当于起到了正则化的作用。所以可以使用更高的学习率
2 数据伸缩不变性
数据伸缩不变性是针对x而言,
所以数据伸缩变化不会影响该层的权重参数更新。
对于某一层的神经元而言,其输入都是标准分布。与传统情况相比,减少了梯度弥散和梯度爆炸的情况,简化了对学习率的选择。
代码分析
这里构建了两个神经神经网络,唯一的区别就是一个进行了BN操作而另外一个没有。
#BN for the first layer input
if self.norm:
fc_mean,fc_var = tf.nn.moments(self.xs,axes=[0])
scale = tf.Variable(tf.ones([1]))
shift = tf.Variable(tf.zeros([1]))
epsilon = 0.001
self.xs = tf.nn.batch_normalization(self.xs,fc_mean,fc_var,shift,scale,epsilon)
接下来是对整个隐层中的处理
def add_layer(self,inputs,in_size,out_size,activation_function=None,norm = False):
# add layer
Weights = tf.Variable(tf.random_normal([in_size,out_size],mean = 0 ,stddev =1))
biases = tf.Variable(tf.zeros([1,out_size])+0.1)
Wx_plus_b = tf.matmul(inputs,Weights) + biases
if norm:
#compute mean,variance according to the axes
fc_mean,fc_var = tf.nn.moments(
Wx_plus_b,
axes=[0],
)
scale = tf.Variable(tf.ones([out_size]))
shift = tf.Variable(tf.zeros([out_size]))
epsilon = 0.001 # a smooth value, avoid the value of denominator is zero
Wx_plus_b = tf.nn.batch_normalization(Wx_plus_b,fc_mean,fc_var,shift,scale,epsilon)
接下来将两个网络模型的作用进行了可视化处理。
分析上图,初始输入的分布服从均匀分布,可以看出在接收输入时,无BN的模型的输入刚好满足这个分布,但是有BN的模型已经将均匀分布的数据做了标准化处理。可以看出在没有BN的情况下,数据很快就到了激活函数(sigmoid)的饱和区域。但是经过BN处理后,数据中很大一部分处在里激活函数的非饱和区域。
很明显,经过BN处理后的网络收敛的更快。下图显示了两者的loss的变化。
具体的代码链接
参考文献
[1]《Batch Normalization Accelerating Deep Network Training by Reducing Internal Covariate Shift》
[2]《Batch Normalization Accelerating Deep Network Training by Reducing Internal Covariate Shift》阅读笔记与实现
[3]Batch Normalization 批标准化
[4]Batch Normalization导读
[5]深度学习中的Normalization模型
[6]详解深度学习中的Normalization,BN/LN/WN