深度学习之权重初始化

在深度学习中,神经网络的权重初始化方法($weight$ $initialization$)对模型的收敛速度和性能有着至关重要的影响。说白了,神经网络其实就是对权重参数$w$的不停迭代更新,以达到更好的性能。因此,对权重$w$的初始化则显得至关重要,一个好的权重初始化虽然不能完全解决梯度消失和梯度爆炸的问题,但是对于处理这两个问题是有很大的帮助的,并且十分有利于模型性能和收敛速度。

本文将介绍以下五种常见的权重初始化的方法:

  • 权重初始化为$0$

  • 权重随机初始化

  • $Xavier$ $initialization$

  • $He$ $initialization$

  • 预训练权重

权重初始化为$0$

如果将权重初始化全部为$0$的话,这样的操作等同于等价于一个线性模型,将所有权重设为$0$时,对于每一个$w$而言,损失函数的导数都是相同的,因此在随后的迭代过程中所有权重都具有相同的值,这会使得隐藏单元变得对称,并继续运行设置的$n$次的迭代,会导致网络中同一个神经元的不同权重都是一样的。下面代码为权重初始化为$0$的代码:

def initialize_parameters_zeros(layers_dims):
    """
    Arguments:
    layer_dims -- python array (list) containing the size of each layer.
    Returns:
    parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL":
                    W1 -- weight matrix of shape (layers_dims[1], layers_dims[0])
                    b1 -- bias vector of shape (layers_dims[1], 1)
                    ...
                    WL -- weight matrix of shape (layers_dims[L], layers_dims[L-1])
                    bL -- bias vector of shape (layers_dims[L], 1)
    """
    parameters = {}
    np.random.seed(3)
    L = len(layers_dims)  # number of layers in the network
    for l in range(1, L):
        parameters['W' + str(l)] = np.zeros((layers_dims[l], layers_dims[l - 1]))
        parameters['b' + str(l)] = np.zeros((layers_dims[l], 1))
    return parameters

让我们来看看权重初始化为$0$之后其$cost$ $function$是如何变化的,从图中可以看出,当代价函数降到$0.64$(迭代$1000$次)后,梯度逐渐消失,再训练迭代已经不起什么作用了。

图 $1$ 权重初始化为$0$的$cost$ $function$变化图

权重随机初始化

权重随机初始化是比较常见的做法,即$W$随机初始化。随机初始化的代码如下:

def initialize_parameters_random(layers_dims):
    """
    Arguments:
    layer_dims -- python array (list) containing the size of each layer.
    Returns:
    parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL":
                    W1 -- weight matrix of shape (layers_dims[1], layers_dims[0])
                    b1 -- bias vector of shape (layers_dims[1], 1)
                    ...
                    WL -- weight matrix of shape (layers_dims[L], layers_dims[L-1])
                    bL -- bias vector of shape (layers_dims[L], 1)
    """
    np.random.seed(3)  # This seed makes sure your "random" numbers will be the as ours
    parameters = {}
    L = len(layers_dims)  # integer representing the number of layers
    for l in range(1, L):
        parameters['W' + str(l)] = np.random.randn(layers_dims[l], layers_dims[l - 1])*0.01
        parameters['b' + str(l)] = np.zeros((layers_dims[l], 1))
    return parameters

上述代码中权重乘$0.01$是因为要把$W$随机初始化到一个相对较小的值,因为如果$X$很大的话,$W$又相对较大,会导致$Z$非常大,这样如果激活函数是$sigmoid$,就会导致$sigmoid$的输出值$1$或者$0$,然后会导致一系列问题(比如$cost$ $function$计算的时候,$log$里是$0$,这样会有点麻烦)。随机初始化后,$cost$ $function$随着迭代次数的变化示意图如下图$2$所示为:

图$2$ 权重随机初始化$cost$ $function$变化图

能够看出,$cost$ $function$的变化是比较正常的。但是随机初始化也有缺点,$np.random.randn()$其实是一个均值为$0$,方差为$1$的高斯分布中采样。当神经网络的层数增多时,会发现越往后面的层的激活函数(使用$tanH$)的输出值几乎都接近于$0$,极易出现梯度消失。如下图$3$所示:

图$3$

$Xavier$ $initialization$

在使用以上两种方法来初始化权重极易出现梯度消失的问题,而$Xavier$ $initialization$出现就解决了上面问题。其思想倒就是尽可能的让输入和输出服从相同的分布,这样就能够避免后面层的激活函数的输出值趋向于$0$。本文主要介绍$Pytorch$当中$Xavier$均匀分布和$Xavier$正态分布初始化这两种方式。

我们来简单推导一下$Xavier$初始化的原理:首先我们定义一层的卷积运算为如下公式,其中${n_i}$表示输入的个数。

y=w1x1++wnixni+by = w_1x_1 + ··· + w_{ni}x_{ni}+ b

根据我们学过的概率论统计知识可以得到如下的方差公式:

Var(wixi)=E[wi]2Var(xi)+E[xi]2Var(wi)+Var(wi)Var(xi)Var(w_ix_i)=E[w_i]^2Var(x_i) + E[x_i]^2Var(w_i) + Var(w_i)Var(x_i)

当我们假设输入和输入的权重的均值都是$0$(使用BN层以后很容易就可以满足)时,上式可以简化为:

Var(wixi)=Var(wi)Var(xi)Var(w_ix_i)=Var(w_i)Var(x_i)

进一步我们假设输入$x$和权重$w$都是独立同分布,则可以得到:

Var(y)=niVar(wi)Var(xi)Var(y) = n_iVar(w_i)Var(x_i)

于是按照$Xavier$的要求保证输入和输出的方差要一致,则可以得到:

Var(wi)=1niVar(w_i) = \frac{1}{n_i}

对于一个多层的网络,某一层的方差可以用累积的形式表达:

Var[zi]=Var[x]i=0i1niVar[Wi]Var[z^i] = Var[x]\prod_{i^{`}=0}^{i-1}n_{i^`}Var[W^{i^`}]

对于误差反向传播也有类似的表达形式,如下所示,其中$n_{i+1}$表示输出个数

Var[Costsi]=Var[Costsd]i=idni+1Var[Wi]Var[\frac{\partial Cost}{\partial s^i}] = Var[\frac{\partial Cost}{\partial s^d}]\prod_{i^{`}=i}^{d}n_{i^`+1}Var[W^{i^`}]

综上,为了保证前向传播和反向传播时每一层的方差一致,应满足:

iniVar[Wi]=1\forall_i ,n_iVar[W^i]=1
ini+1Var[Wi]=1\forall_i ,n_{i+1}Var[W^i]=1

但是,实际当中输入与输出的个数往往不相等,于是为了均衡考量输出和输入,最终我们的权重的方差应满足如下要求:

iVar[Wi]=2ni+ni+1\forall_i ,Var[W^i]= \frac{2}{n_i + n_{i+1}}

1、$Xavier$均匀分布初始化

对于均匀分布来说,我们都知道区间$[a,b]$的方差为:

Var=(ba)212Var=\frac{(b-a)^2}{12}

那么就需要将均匀分布的方差等于我们在上面推导出来的$Xavier$的权重方差,即:

(ba)212=2ni+ni+1\frac{(b-a)^2}{12} = \frac{2}{n_i + n_{i+1}}

经过化解后$(a+b=0)$可以得到$Xavier$均匀初始化后权重的取值范围为:

WU[6ni+ni+1,6ni+ni+1]W - U[-\frac{\sqrt{6}}{\sqrt{n_i+n_{i+1}}}, \frac{\sqrt{6}}{\sqrt{n_i+n_{i+1}}}]

原理我们讲完了,现在来看一下在$Pytorch$中是如何调用$Xavier$均匀分布初始化的:

# tensor表示要初始化的张量,gain表示缩放因子
torch.nn.init.xavier_uniform(tensor, gain=1)

# 举例说明:
w = torch.Tensor(3, 5)
nn.init.xavier_uniform(w, gain=math.sqrt(2))

2、$Xavier$正态分布初始化

我们都知道均值为$0$,标准差为$\sigma$的正态分布方差为

Var=σ2Var=\sigma^2

同样的,需要将正态分布的方差等于我们在上面推导出来的$Xavier$的权重方差,即:

σ2=2ni+ni+1\sigma^2 = \frac{2}{n_i + n_{i+1}}

经过化解后可以得到$Xavier$正态分布初始化后权重的标准差为:

σ=2ni+ni+1\sigma = \sqrt{\frac{2}{n_i + n_{i+1}}}

那么我们再来看一下在$Pytorch$中是如何调用$Xavier$正态分布初始化的:

# tensor表示要初始化的张量,gain表示缩放因子
torch.nn.init.xavier_normal(tensor, gain=1)

# 举例说明:
w = torch.Tensor(3, 5)
nn.init.xavier_normal(w)

3、$Xavier$权重初始化表现效果

如下图$4$所示为采用$Xavier$ $initialization$后每层的激活函数输出值的分布,从图中我们可以看出,深层的激活函数输出值还是非常服从标准高斯分布

图$4$ $Xavier$权重初始化后$tanH$激活层输出分布

虽然$Xavier$ $initialization$能够很好的适用于 $tanH$ 激活函数,但对于目前神经网络中最常用的$ReLU$激活函数,还是无能能力,如下图$5$所示为采用$ReLU$激活函数后,$Xavier$ $initialization$初始化的每层激活函数输出值的分布,从图中可以看出当达到$5$、$6$层后几乎又开始趋向于$0$,更深层的话很明显又会趋向于$0$

图$5$ $Xavier$权重初始化后$ReLU$激活层输出分布

由此可见,$Xavier$权重初始化方式比较适用于$tanH$和$Sigmoid$激活函数,而对于$ReLU$这种非对称性的激活函数还是容易出现梯度消失的现象。

$He$ $initialization$

$He$ $initialization$是由何凯明大神提出的一种针对$ReLU$激活函数的初始化方法。$He$ $initialization$的思想是:和$Xavier$初始化方式一样,都希望初始化使得正向传播时,状态值的方差保持不变,反向传播时,关于激活值的梯度的方差保持不变。由于小于$0$的值经过$ReLU$激活函数都会变成$0$,而大于$0$的值则保持原值。因此在$ReLU$网络中,假定每一层有一半的神经元被激活,另一半为$0$,所以,要保持$variance$不变,只需要在$Xavier$的基础上再除以2即可。本文主要介绍$Pytorch$当中$He$ $initialization$均匀分布和$He$ $initialization$正态分布初始化这两种方式。

对于$He$ $initialization$的推导来说前面和$Xavier$ $initialization$是相似的,但在方差推到过程中,需要将式子左侧除以$2$,如下所示:

Var(y)=12niVar(wi)Var(xi)Var(y) = \frac{1}{2}n_iVar(w_i)Var(x_i)

为了保证输出和输入的方差一直,则可以得到权重的方差为:

Var(wi)=2niVar(w_i) = \frac{2}{n_i}

对于$Backward$来说和$Forward$思路是相似的,只不过需要考虑到链式求导法则,这里不予以推导,只给出最终的结果为:

Var(wi+1)=2ni+1Var(w_{i+1}) = \frac{2}{n_{i+1}}

1、$He$ $initialization$均匀分布初始化

和$Xavier$均匀分布初始化操作一样我们得到$He$ $initialization$的取值范围为:

WU[6ni+(a2+1),6ni+(a2+1)]W - U[-\frac{\sqrt{6}}{\sqrt{n_i+(a^2+1)}}, \frac{\sqrt{6}}{\sqrt{n_i+(a^2+1)}}]

在$Pytorch$中$He$ $initialization$也叫做$kaiming$,调用代码如下:

# tensor表示要初始化的张量
# a表示这层之后使用的rectifier的斜率系数(ReLU的默认值为0)
# mode可以为“fan_in”(默认)或“fan_out”。
# “fan_in”保留前向传播时权值方差的量级,“fan_out”保留反向传播时的量级。
torch.nn.init.kaiming_uniform(tensor, a=0, mode='fan_in')

# 举例说明:
w = torch.Tensor(3, 5)
nn.init.kaiming_uniform(w, mode='fan_in')

2、$He$ $initialization$正态分布初始化

和$Xavier$正态分布初始化操作一样我们得到$He$ $initialization$的标准差为:

σ=2ni+(a2+1)\sigma = \sqrt{\frac{2}{n_i + (a^2+1)}}

在$Pytorch$中$Xavier$正态分布初始化的调用代码如下:

# tensor表示要初始化的张量
# a表示这层之后使用的rectifier的斜率系数(ReLU的默认值为0)
# mode可以为“fan_in”(默认)或“fan_out”。
# “fan_in”保留前向传播时权值方差的量级,“fan_out”保留反向传播时的量级。
torch.nn.init.kaiming_normal(tensor, a=0, mode='fan_in')

# 举例说明:
w = torch.Tensor(3, 5)
nn.init.kaiming_normal(w, mode='fan_out')

3、$He$ $initialization$权重初始化表现效果

如下图$6$所示为采用$He$ $initialization$方式初始化权重后,隐藏层使用$ReLU$时,激活函数的输出值的分布情况,从图中可知,针对$ReLU$激活函数,$He$ $initialization$效果是比$Xavier$ $initialization$好很多。

图$6$ $He$ $initialization$权重初始化后$ReLU$激活层输出分布

由此可见,$He$ $initialization$权重初始化方式是非常适用于$ReLU$激活函数。

预训练模型

目前更多的使用已经针对相似任务已经训练好的模型,称之为预训练模型。在训练开始时就已经有了非常好的初始化参数,只需要将最后的全连接层进行冻结,训练其他部分即可。

总结

1、权重采用初始化为$0$和随机初始化都比较容易出现梯度消失的问题,因此不常用。

2、$Xavier$权重初始化方式主要针对于$tanH$和$sigmoid$激活函数。

3、$He$ $initialization$权重初始化方式主要针对于$ReLU$激活函数。

4、如果有相似任务已经训练好的模型,也可以考虑采用预训练模型来作权重初始化。

引用

  • https://zhuanlan.zhihu.com/p/25110150

  • https://www.jianshu.com/p/cf2dcc624f47

  • https://zhuanlan.zhihu.com/p/103825243

  • https://pytorch.org/docs/stable/nn.html#torch-nn-init

  • https://pytorch-cn.readthedocs.io/zh/latest/package_references/nn_init/

  • https://blog.csdn.net/dss_dssssd/article/details/83959474?locationNum=16&fps=1

  • http://machinelearning.wustl.edu/mlpapers/paper_files/AISTATS2010_GlorotB10.pdf

  • https://blog.csdn.net/u011534057/article/details/53930199?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_baidulandingword-0&spm=1001.2101.3001.4242

Last updated