论文《Batch Normalization: Accelerating Deep Network Training by ReducingInternal Covariate Shift》
如果做过dnn的实验,大家可能会发现在对数据进行预处理,例如白化或者zscore,甚至是简单的减均值操作都是可以加速收敛的,例如下图所示的一个简单的例子:
图中红点代表2维的数据点,由于图像数据的每一维一般都是0-255之间的数字,因此数据点只会落在第一象限,而且图像数据具有很强的相关性,比如第一个灰度值为30,比较黑,那它旁边的一个像素值一般不会超过100,否则给人的感觉就像噪声一样。由于强相关性,数据点仅会落在第一象限的很小的区域中,形成类似上图所示的狭长分布。
这时,如果我们将数据减去其均值,数据点就不再只分布在第一象限,这时一个随机分界面落入数据分布的概率增加了多少呢?2^n倍!如果我们使用去除相关性的算法,例如PCA和ZCA白化,数据不再是一个狭长的分布,随机分界面有效的概率就又大大增加了。
上一节我们讲到对输入数据进行预处理,减均值->zscore->白化可以逐级提升随机初始化的权重对数据分割的有效性,还可以降低overfit的可能性。我们都知道,现在的神经网络的层数都是很深的,如果我们对每一层的数据都进行处理,训练时间和overfit程度是否可以降低呢?Google的这篇论文给出了答案。
文中使用了类似z-score的归一化方式:每一维度减去自身均值,再除以自身标准差,由于使用的是随机梯度下降法,这些均值和方差也只能在当前迭代的batch中计算,故作者给这个算法命名为Batch Normalization。这里有一点需要注意,像卷积层这样具有权值共享的层,Wx+b的均值和方差是对整张map求得的,在batch_size * channel * height * width这么大的一层中,对总共batch_size*height*width个像素点统计得到一个均值和一个标准差,共得到channel组参数。
算法原理到这差不多就讲完了,下面是大家 最不喜欢的公式环节了,求均值和方差就不用说了,在BP的时候,我们需要求最终的损失函数对gamma和beta两个参数的导数,还要求损失函数对Wx+b中的x的导数,以便使误差继续向后传播。求导公式如下:
在训练的最后一个epoch时,要对这一epoch所有的训练样本的均值和标准差进行统计,这样在一张测试图片进来时,使用训练样本中的标准差的期望和均值的期望(好绕口)对测试数据进行归一化,注意这里标准差使用的期望是其无偏估计:
(2) 移除或使用较低的dropout。 dropout是常用的防止overfitting的方法,而导致overfit的位置往往在数据边界处,如果初始化权重就已经落在数据内部,overfit现象就可以得到一定的缓解。论文中最后的模型分别使用10%、5%和0%的dropout训练模型,与之前的40%-50%相比,可以大大提高训练速度。
(5) 减少图像扭曲的使用。由于现在训练epoch数降低,所以要对输入数据少做一些扭曲,让神经网络多看看真实的数据。
- d_xhat = bsxfun(@times, d{i}(:,2:end), nn.gamma{i-1});
- x_mu = bsxfun(@minus, nn.a_pre{i}, nn.mu{i-1});
- inv_sqrt_sigma = 1 ./ sqrt(nn.sigma2{i-1} + nn.epsilon);
- d_sigma2 = -0.5 * sum(d_xhat .* x_mu) .* inv_sqrt_sigma.^3;
- d_mu = bsxfun(@times, d_xhat, inv_sqrt_sigma);
- d_mu = -1 * sum(d_mu) -2 .* d_sigma2 .* mean(x_mu);
- d_gamma = mean(d{i}(:,2:end) .* nn.a_hat{i});
- d_beta = mean(d{i}(:,2:end));
- di1 = bsxfun(@times,d_xhat,inv_sqrt_sigma);
- di2 = 2/m * bsxfun(@times, d_sigma2,x_mu);
- d{i}(:,2:end) = di1 + di2 + 1/m * repmat(d_mu,m,1);
在训练的最后一个epoch,要对所有的gamma和beta进行统计,代码很简单就不贴了,完整代码在我的Github上有:https://github.com/happynear/DeepLearnToolbox
1、sigmoid激活函数的过饱和问题
经测试发现算法对sigmoid激活函数的提升非常明显,解决了困扰学术界十几年的sigmoid过饱和的问题,即在深层的神经网络中,前几层在梯度下降时得到的梯度过低,导致深层神经网络变成了前边是随机变换,只在最后几层才是真正在做分类的问题。
下面是使用一个10个隐藏层的nn网络,对mnist进行分类,每层的梯度值:
使用Batch Normalization前:
可以看到,如果不使用gamma和beta,激活值基本上会在[0.1 0.9]这个近似线性的区域中,这与深度神经网络所要求的“多层非线性函数逼近任意函数”的要求不符,所以引入gamma和beta还是有必要的,深度网络会自动决定使用哪一段函数(这是我自己想的,其具体作用欢迎讨论)。
对于ReLU来说,gamma的作用可能不是很明显,因为relu是分段”线性“的,对数值进行伸缩并不能影响relu取x还是取0。但beta的作用就很大了,试想一下如果没有beta,经过batch normalization层的特征,都具有0均值的期望,这样岂不是强制令ReLU的输出有一半是0一半非0么?这与我们的初衷不太相符,我们希望神经网络自行决定在什么位置去设定这个阈值,而不是增加一个如此强的限制。另外,因为这个beta我曾经还闹了个大笑话
四、总结
五、一些资源