深度学习知识及资源分享,学习交流,共同进步~
本文介绍一下深度学习参数初始化问题中耳熟能详的参数初始化方法——Xavier初始化。
文章:Understanding the difficulty of training deep feedforward neural networks
下载地址:https://machinelearning.wustl.edu/mlpapers/paper_files/AISTATS2010_GlorotB10.pdf
“Xavier”初始化方法是一种很有效的神经网络初始化方法,方法来源于2010年的一篇论文《Understanding the difficulty of training deep feedforward neural networks》。
通常,一般的深度学习文章上来就是实验,告诉读者这个实验结果好,然后由实验结果再反向给出一些无从验证的可能对可能不对的原因。而这篇文章虽然整体来看比较简单,但结构非常严谨:首先通过实验分析标准初始化方法的问题;然后根据两个目标——状态方差和梯度方差保持不变推导出参数的特点,给出Xavier初始化方法的具体形式;最后通过实验验证Xavier初始化的效果确实不错。
在开始阅读下面的内容之前,我们需要牢记参数初始化的目的是为了让神经网络在训练过程中学习到有用的信息,这意味着参数梯度不应该为0。而我们知道在全连接的神经网络中,参数梯度和反向传播得到的状态梯度以及入激活值有关——激活值饱和会导致该层状态梯度信息为0,然后导致下面所有层的参数梯度为0;入激活值为0会导致对应参数梯度为0。所以如果要保证参数梯度不等于0,那么参数初始化应该使得各层激活值不会出现饱和现象且激活值不为0。我们把这两个条件总结为参数初始化条件:
需要说明的是这篇论文只研究了解决分类问题的5层的全连接神经网络,每个隐层的神经元个数为1000,并且仅探讨了三种激活函数:sigmoid、tanh、softsign。
把权重矩阵初始化为如下形式:
其中U[−a,a]是区间(−a,a)上的均匀分布,n是入神经网络的大小。易知
激活值计算表达式:
论文中的符号和我的有些区别:论文中用zz表示激活值,ss表示状态值。我这里按自己的习惯来写。
现在把输入xx的每一维度xx看做一个随机变量,并且假设E(x)=0E(x)=0,Var(x)=1Var(x)=1。假设WW和xx相互独立,则隐层状态的方差为
可以看出标准初始化方法得到一个非常好的特性:隐层的状态的均值为0,方差为常量1313,和网络的层数无关,这意味着对于sigmoid函数来说,自变量落在有梯度的范围内。
但是因为sigmoid激活值都是大于0的,会导致下一层的输入不满足E(⋅)=0E(⋅)=0。其实标准初始化也只适用于满足下面将要提到的Glorot假设的激活函数,比如tanh。
训练过程中的激活值特性
作者探究了三种激活函数对应的神经网络在训练过程中各层激活值的分布情况,得到下面的结果图。
可以看到在tanh神经网络中第一层最先饱和,然后是后面各层按顺序出现饱和。softsign神经网络中饱和现象并不严重,而且各层差不多同时出现饱和情况。
作者提到出现这种现象的原因有待于探究。
训练完成后tanh神经网络和softsign神经网络各层的激活值的直方图
tanh神经网络的低层出现了饱和现象:-1和1对应的神经元非常多。
在文章开始部分我们给出了参数初始化的必要条件。但是这两个条件只保证了训练过程中可以学到有用的信息——参数梯度不为0。而Glorot认为:优秀的初始化应该使得各层的激活值和状态梯度的方差在传播过程中的方差保持一致:
形式
在某些假设下反向传播梯度和参数梯度表达式
首先给出关于状态的梯度和关于参数的梯度的表达式:
为了使得网络中信息更好的流动,每一层输出的方差应该尽量相等。
基于这个目标,现在我们就去推导一下:每一层的权重应该满足哪种条件。
文章先假设的是线性激活函数,而且满足0点处导数为1,即
现在我们先来分析一层卷积:
其中ni表示输入个数。
根据概率统计知识我们有下面的方差公式:
特别的,当我们假设输入和权重都是0均值时(目前有了BN之后,这一点也较容易满足),上式可以简化为:
进一步假设输入x和权重w独立同分布,则有:
综上,为了保证前向传播和反向传播时每一层的方差一致,应满足:
但是,实际当中输入与输出的个数往往不相等,于是为了均衡考量,最终我们的权重方差应满足:
下面,我们来看一下caffe中具体是怎样实现的,代码位于include/caffe/filler.hpp文件中。
template <typename Dtype>
class XavierFiller : public Filler<Dtype> {
public:
explicit XavierFiller(const FillerParameter& param)
: Filler<Dtype>(param) {}
virtual void Fill(Blob<Dtype>* blob) {
CHECK(blob->count());
int fan_in = blob->count() / blob->num();
int fan_out = blob->count() / blob->channels();
Dtype n = fan_in; // default to fan_in
if (this->filler_param_.variance_norm() ==
FillerParameter_VarianceNorm_AVERAGE) {
n = (fan_in + fan_out) / Dtype(2);
} else if (this->filler_param_.variance_norm() ==
FillerParameter_VarianceNorm_FAN_OUT) {
n = fan_out;
}
Dtype scale = sqrt(Dtype(3) / n);
caffe_rng_uniform<Dtype>(blob->count(), -scale, scale,
blob->mutable_cpu_data());
CHECK_EQ(this->filler_param_.sparse(), -1)
<< "Sparsity not supported by this Filler.";
}
};
由上面可以看出,caffe的Xavier实现有三种选择
(1) 默认情况,方差只考虑输入个数:
(2) FillerParameter_VarianceNorm_FAN_OUT,方差只考虑输出个数:
(3) FillerParameter_VarianceNorm_AVERAGE,方差同时考虑输入和输出个数:
之所以默认只考虑输入,个人觉得是因为前向信息的传播更重要一些。
因为Xavier的推导过程是基于几个假设的,其中一个是激活函数是线性的。这并不适用于ReLU激活函数。另一个是激活值关于0对称,这个不适用于sigmoid函数和ReLU函数。所以可以看到图11中并没有对sogmoid网络应用Xavier初始化。
可以实验验证sigmoid激活函数用Xavier初始化后的初始化激活值、反向梯度、参数梯度特性:以MNIST做训练数据,发现标准初始化和Xavier初始化得到的初始激活、参数梯度特性是一样的。激活值的方差逐层递减,参数梯度的方差也逐层递减。