PyTorch: 权值初始化
文章和代码已经归档至【Github 仓库:https://github.com/timerring/dive-into-AI 】或者公众号【AIShareLab】回复 pytorch 教程 也可获取。
Pytorch:权值初始化
在搭建好网络模型之后,首先需要对网络模型中的权值进行初始化。权值初始化的作用有很多,通常,一个好的权值初始化将会加快模型的收敛,而比较差的权值初始化将会引发梯度爆炸或者梯度消失。下面将具体解释其中的原因:
梯度消失与梯度爆炸
考虑一个 3 层的全连接网络。
,,,如下图所示,
其中第 2 层的权重梯度如下:
$\begin{array}{l}\mathrm{H}{2}=\mathrm{H}{1} * \mathrm{~W}{\mathbf{2}} \\Delta \mathrm{W}{\mathbf{2}}=\frac{\partial \text { Loss }}{\partial \mathrm{W}{2}}=\frac{\partial \mathrm{Loss}}{\partial \text { out }} * \frac{\partial \text { out }}{\partial \mathrm{H}{2}} * \frac{\partial \mathrm{H}{2}}{\partial \mathrm{W}{2}} \ =\frac{\partial \text { Loss }}{\partial \text { out }} * \frac{\partial \text { out }}{\partial \mathrm{H}{2}} * \mathrm{H}{1} \\end{array}$
由上式化简可知,如果 H_1 发生以下变化,那么对应的梯度也就会发生变化:
梯度消失: $\mathrm{H}{1} \rightarrow 0 \Rightarrow \Delta \mathrm{W}{2} \rightarrow 0$
梯度爆炸: $\mathrm{H}{1} \rightarrow \infty \Rightarrow \Delta \mathrm{W}{2} \rightarrow \infty $
因此,为了避免以上两种情况,就必须严格控制网络层输出的数值范围。
具体可以通过构建 100 层全连接网络,先不使用非线性激活函数,每层的权重初始化为服从 的正态分布,输出数据使用随机初始化的数据,这样的例子来直观地感受影响:
输出为:
通过输出可知,输出值均为 nan,即非数字类型,原因可能是数据太大(梯度爆炸)或者太小(梯度消失)。
为了具体知道是在哪一层开始出现 nan 的,我们可以在 forward 函数中添加判断得知,查看每一次前向转播的标准差是否是 nan,若是,则停止前向传播并输出。
这里判断是否为 nan 时采用了 torch.isnan
函数
输出如下:
可见,之际上输出的标准差是逐层递增的,具体为什么会导致这种情况:
:两个相互独立的随机变量的乘积的期望等于它们的期望的乘积。
:一个随机变量的方差等于它的平方的期望减去期望的平方
:两个相互独立的随机变量之和的方差等于它们的方差的和。
可以推导出两个随机变量的乘积的方差如下:
又由于输入变量是符合标准的正态分布的,因此 ,,可知
我们以输入层第一个神经元为例:
$\mathrm{H}{11}=\sum{i=0}^{n} X_{i} * W_{1 i}$
其中输入 X 和权值 W 都是服从 的正态分布,且由公式, 因此这个神经元的方差为:
$\begin{aligned}\mathbf{D}\left(\mathrm{H}{11}\right) &=\sum{i=0}^{n} D\left(X_{i}\right) * D\left(W_{1 i}\right) \&=n *(1 * 1) \&=n\end{aligned}$
可以求其标准差:$\operatorname{std}\left(\mathrm{H}{11}\right)=\sqrt{\mathrm{D}\left(\mathrm{H}{11}\right)}=\sqrt{n}$
可见,经过第一层网络,方差就会扩大 n 倍,标准差就扩大 倍,n 为每层神经元个数,直到超出数值表示范围。
从前面的输出中也可以看出来,n = 256,因此每一层的标准差输出都是 16 倍。再由公式可知,每一层网络输出的方差与神经元个数、输入数据的方差、权值方差有关(见上式),通过观察可知,比较好改变的是权值的方差 ,要控制每一层输出的方差仍然为 1 左右,因此需要 ,可知标准差为 。因此修改权值初始化代码为nn.init.normal_(m.weight.data, std=np.sqrt(1/self.neural_num))
再次输出时,结果如下:
修改之后,没有出现梯度消失或者梯度爆炸的情况,每层神经元输出的方差均在 1 左右。通过恰当的权值初始化,可以保持权值在更新过程中维持在一定范围之内。
但是上述的实验前提为未使用非线性函数的前提下,如果在forward()
中添加非线性变换例如tanh
,每一层的输出方差会越来越小,会导致梯度消失。
为了解决这个问题,进一步有了著名的 Xavier 初始化与 Kaiming 初始化。
Xavier 方法与 Kaiming 方法
Xavier 方法
Xavier 是 2010 年提出的,针对有非线性激活函数时的权值初始化方法。
目标是保持数据的方差维持在 1 左右
针对饱和激活函数如 sigmoid 和 tanh 等。
同时考虑前向传播和反向传播,需要满足两个等式
$\begin{array}{l}\boldsymbol{n}{\boldsymbol{i}} * \boldsymbol{D}(\boldsymbol{W})=\mathbf{1} \\boldsymbol{n}{\boldsymbol{i}+\mathbf{1}} * \boldsymbol{D}(\boldsymbol{W})=\mathbf{1} \\end{array}$
通过计算可知:。
为了使 Xavier 方法初始化的权值服从均匀分布,假设 服从均匀分布 ,那么方差 ,令 ,解得:,所以 服从分布
所以初始化方法改为:
并且每一层的激活函数都使用 tanh,输出如下:
可以看到每层输出的方差都维持在 0.6 左右。
也可以直接调用 PyTorch 中 Xavier 初始化方法:
nn.init.calculate_gain()
这里重点介绍一下nn.init.calculate_gain(nonlinearity,param=**None**)
方法。
主要功能是经过一个分布的方差经过激活函数后的变化尺度,主要有两个参数:
nonlinearity:激活函数名称
param:激活函数的参数,如 Leaky ReLU 的 negative_slop 等等。
下面是计算标准差经过激活函数的变化尺度的代码。
输出如下:
结果表示,原有数据分布的方差经过 tanh 之后,标准差会变小 1.6 倍左右。
Kaiming 方法
虽然 Xavier 方法提出了针对饱和激活函数的权值初始化方法,但是 AlexNet 出现后,大量网络开始使用非饱和的激活函数如 ReLU 等,这时 Xavier 方法不再适用。2015 年针对 ReLU 及其变种等激活函数提出了 Kaiming 初始化方法。
针对 ReLU,方差应该满足:;
针对 ReLu 的变种,方差应该满足:,a 表示负半轴的斜率,如 PReLU 方法,标准差满足 。
代码如下:nn.init.normal_(m.weight.data, std=np.sqrt(2 / self.neural_num))
,或者使用 PyTorch 提供的初始化方法:nn.init.kaiming_normal_(m.weight.data)
。
常用初始化方法
PyTorch 中提供了 10 中初始化方法
Xavier 均匀分布
Xavier 正态分布
Kaiming 均匀分布
Kaiming 正态分布
均匀分布
正态分布
常数分布
正交矩阵初始化
单位矩阵初始化
稀疏矩阵初始化
综上, 常用初始化的目标就是要保证每一层输出的方差不能太大,也不能太小,维持在一个稳定的范围内。
版权声明: 本文为 InfoQ 作者【timerring】的原创文章。
原文链接:【http://xie.infoq.cn/article/f805731a018d94f9702416fc2】。未经作者许可,禁止转载。
评论