数值稳定性和模型初始化

到目前为止,我们实现的每个模型都是根据某个预先指定的分布来初始化模型的参数。

有人会认为初始化方案是理所当然的,忽略了如何做出这些选择的细节。甚至有人可能会觉得,初始化方案的选择并不是特别重要。

相反,初始化方案的选择在神经网络学习中起着举足轻重的作用,它对保持数值稳定性至关重要。

此外,这些初始化方案的选择可以与非线性激活函数的选择有趣的结合在一起。

我们选择哪个函数以及如何初始化参数可以决定优化算法收敛的速度有多快。糟糕选择可能会导致我们在训练时遇到梯度爆炸或梯度消失。

本节将更详细地探讨这些主题,并讨论一些有用的启发式方法。这些启发式方法在整个深度学习生涯中都很有用。

梯度消失和梯度爆炸

考虑一个具有LLL层、输入x\mathbf{x}x和输出o\mathbf{o}o的深层网络。

每一层lll由变换flf_lfl定义,该变换的参数为权重W(l)\mathbf{W}^{(l)}W(l),其隐藏变量是h(l)\mathbf{h}^{(l)}h(l)(令 h(0)=x\mathbf{h}^{(0)} = \mathbf{x}h(0)=x)。
我们的网络可以表示为:

h(l)=fl(h(l−1)) 因此 o=fL∘…∘f1(x).\mathbf{h}^{(l)} = f_l (\mathbf{h}^{(l-1)}) \text{ 因此 } \mathbf{o} = f_L \circ \ldots \circ f_1(\mathbf{x}).h(l)=fl(h(l1)) 因此 o=fLf1(x).

如果所有隐藏变量和输入都是向量,
我们可以将o\mathbf{o}o关于任何一组参数W(l)\mathbf{W}^{(l)}W(l)的梯度写为下式:

∂W(l)o=∂h(L−1)h(L)⏟M(L)=def⋅…⋅∂h(l)h(l+1)⏟M(l+1)=def∂W(l)h(l)⏟v(l)=def.\partial_{\mathbf{W}^{(l)}} \mathbf{o} = \underbrace{\partial_{\mathbf{h}^{(L-1)}} \mathbf{h}^{(L)}}_{ \mathbf{M}^{(L)} \stackrel{\mathrm{def}}{=}} \cdot \ldots \cdot \underbrace{\partial_{\mathbf{h}^{(l)}} \mathbf{h}^{(l+1)}}_{ \mathbf{M}^{(l+1)} \stackrel{\mathrm{def}}{=}} \underbrace{\partial_{\mathbf{W}^{(l)}} \mathbf{h}^{(l)}}_{ \mathbf{v}^{(l)} \stackrel{\mathrm{def}}{=}}.W(l)o=M(L)=defh(L1)h(L)M(l+1)=defh(l)h(l+1)v(l)=defW(l)h(l).

换言之,该梯度是L−lL-lLl个矩阵M(L)⋅…⋅M(l+1)\mathbf{M}^{(L)} \cdot \ldots \cdot \mathbf{M}^{(l+1)}M(L)M(l+1)与梯度向量 v(l)\mathbf{v}^{(l)}v(l)的乘积。

因此,我们容易受到数值下溢问题的影响.
当将太多的概率乘在一起时,这些问题经常会出现。
在处理概率时,一个常见的技巧是切换到对数空间,即将数值表示的压力从尾数转移到指数。

不幸的是,上面的问题更为严重:
最初,矩阵 M(l)\mathbf{M}^{(l)}M(l) 可能具有各种各样的特征值。他们可能很小,也可能很大;他们的乘积可能非常大,也可能非常小。

不稳定梯度带来的风险不止在于数值表示;不稳定梯度也威胁到我们优化算法的稳定性。

我们可能面临一些问题。
要么是梯度爆炸(gradient exploding)问题:
参数更新过大,破坏了模型的稳定收敛;
要么是梯度消失(gradient vanishing)问题:
参数更新过小,在每次更新时几乎不会移动,导致模型无法学习。

(梯度消失)

曾经sigmoid函数1/(1+exp⁡(−x))1/(1 + \exp(-x))1/(1+exp(x))很流行,因为它类似于阈值函数。
在这里插入图片描述

由于早期的人工神经网络受到生物神经网络的启发,神经元要么完全激活要么完全不激活(就像生物神经元)的想法很有吸引力。

然而,它却是导致梯度消失问题的一个常见的原因,让我们仔细看看sigmoid函数为什么会导致梯度消失。

%matplotlib inline
import torch
from d2l import torch as d2l

x = torch.arange(-8.0, 8.0, 0.1, requires_grad=True)
y = torch.sigmoid(x)
y.backward(torch.ones_like(x))

d2l.plot(x.detach().numpy(), [y.detach().numpy(), x.grad.numpy()],
         legend=['sigmoid', 'gradient'], figsize=(4.5, 2.5))

在这里插入图片描述
正如上图,当sigmoid函数的输入很大或是很小时,它的梯度都会消失。

此外,当反向传播通过许多层时,除非我们在刚刚好的地方,这些地方sigmoid函数的输入接近于零,否则整个乘积的梯度可能会消失。

当我们的网络有很多层时,除非我们很小心,否则在某一层可能会切断梯度。
事实上,这个问题曾经困扰着深度网络的训练。
因此,更稳定的ReLU系列函数已经成为从业者的默认选择(虽然在神经科学的角度看起来不太合理)。

[梯度爆炸]

相反,梯度爆炸可能同样令人烦恼。
为了更好地说明这一点,我们生成100个高斯随机矩阵,并将它们与某个初始矩阵相乘。
对于我们选择的尺度(方差σ2=1\sigma^2=1σ2=1),矩阵乘积发生爆炸。
当这种情况是由于深度网络的初始化所导致时,我们没有机会让梯度下降优化器收敛。

M = torch.normal(0, 1, size=(4,4))
print('一个矩阵 \n',M)
for i in range(100):
    M = torch.mm(M,torch.normal(0, 1, size=(4, 4)))

print('乘以100个矩阵后\n', M)

在这里插入图片描述

打破对称性

神经网络设计中的另一个问题是其参数化所固有的对称性

假设我们有一个简单的多层感知机,它有一个隐藏层和两个隐藏单元。

在这种情况下,我们可以对第一层的权重W(1)\mathbf{W}^{(1)}W(1)进行重排列,并且同样对输出层的权重进行重排列,可以获得相同的函数。
第一个隐藏单元与第二个隐藏单元没有什么特别的区别。
换句话说,我们在每一层的隐藏单元之间具有排列对称性

假设输出层将上述两个隐藏单元的多层感知机转换为仅一个输出单元。
想象一下,如果我们将隐藏层的所有参数初始化为W(1)=c\mathbf{W}^{(1)} = cW(1)=cccc为常量,会发生什么?
在这种情况下,在前向传播期间,两个隐藏单元采用相同的输入和参数,产生相同的激活,该激活被送到输出单元。
在反向传播期间,根据参数W(1)\mathbf{W}^{(1)}W(1)对输出单元进行微分,得到一个梯度,其元素都取相同的值。
因此,在基于梯度的迭代(例如,小批量随机梯度下降)之后,W(1)\mathbf{W}^{(1)}W(1)的所有元素仍然采用相同的值。
这样的迭代永远不会打破对称性,我们可能永远也无法实现网络的表达能力。
隐藏层的行为就好像只有一个单元。
请注意,虽然小批量随机梯度下降不会打破这种对称性,但暂退法正则化可以。

参数初始化

解决(或至少减轻)上述问题的一种方法是进行参数初始化,
优化期间的注意和适当的正则化也可以进一步提高稳定性。

默认初始化

在前面的部分中,例如在之前的笔记中,我们使用正态分布来初始化权重值。如果我们不指定初始化方法,框架将使用默认的随机初始化方法,对于中等难度的问题,这种方法通常很有效。

Xavier初始化

让我们看看某些没有非线性的全连接层输出(例如,隐藏变量)oio_{i}oi的尺度分布。
对于该层ninn_\mathrm{in}nin输入xjx_jxj及其相关权重wijw_{ij}wij,输出由下式给出

oi=∑j=1ninwijxj.o_{i} = \sum_{j=1}^{n_\mathrm{in}} w_{ij} x_j.oi=j=1ninwijxj.

权重wijw_{ij}wij都是从同一分布中独立抽取的。

此外,让我们假设该分布具有零均值和方差σ2\sigma^2σ2
请注意,这并不意味着分布必须是高斯的,只是均值和方差需要存在。
现在,让我们假设层xjx_jxj的输入也具有零均值和方差γ2\gamma^2γ2,并且它们独立于wijw_{ij}wij并且彼此独立。
在这种情况下,我们可以按如下方式计算oio_ioi的平均值和方差:

E[oi]=∑j=1ninE[wijxj]=∑j=1ninE[wij]E[xj]=0,Var[oi]=E[oi2]−(E[oi])2=∑j=1ninE[wij2xj2]−0=∑j=1ninE[wij2]E[xj2]=ninσ2γ2. \begin{aligned} E[o_i] & = \sum_{j=1}^{n_\mathrm{in}} E[w_{ij} x_j] \\&= \sum_{j=1}^{n_\mathrm{in}} E[w_{ij}] E[x_j] \\&= 0, \\ \mathrm{Var}[o_i] & = E[o_i^2] - (E[o_i])^2 \\ & = \sum_{j=1}^{n_\mathrm{in}} E[w^2_{ij} x^2_j] - 0 \\ & = \sum_{j=1}^{n_\mathrm{in}} E[w^2_{ij}] E[x^2_j] \\ & = n_\mathrm{in} \sigma^2 \gamma^2. \end{aligned} E[oi]Var[oi]=j=1ninE[wijxj]=j=1ninE[wij]E[xj]=0,=E[oi2](E[oi])2=j=1ninE[wij2xj2]0=j=1ninE[wij2]E[xj2]=ninσ2γ2.

保持方差不变的一种方法是设置ninσ2=1n_\mathrm{in} \sigma^2 = 1ninσ2=1

现在考虑反向传播过程,我们面临着类似的问题,尽管梯度是从更靠近输出的层传播的。
使用与前向传播相同的推断,我们可以看到,除非noutσ2=1n_\mathrm{out} \sigma^2 = 1noutσ2=1,否则梯度的方差可能会增大,其中noutn_\mathrm{out}nout是该层的输出的数量。
这使得我们进退两难:我们不可能同时满足这两个条件。
相反,我们只需满足:

12(nin+nout)σ2=1 或等价于 σ=2nin+nout. \begin{aligned} \frac{1}{2} (n_\mathrm{in} + n_\mathrm{out}) \sigma^2 = 1 \text{ 或等价于 } \sigma = \sqrt{\frac{2}{n_\mathrm{in} + n_\mathrm{out}}}. \end{aligned} 21(nin+nout)σ2=1 或等价于 σ=nin+nout2.

这就是现在标准且实用的Xavier初始化的基础,它以其提出者第一作者的名字命名。
通常,Xavier初始化从均值为零,方差σ2=2nin+nout\sigma^2 = \frac{2}{n_\mathrm{in} + n_\mathrm{out}}σ2=nin+nout2的高斯分布中采样权重。

我们也可以将其改为选择从均匀分布中抽取权重时的方差。注意均匀分布U(−a,a)U(-a, a)U(a,a)的方差为a23\frac{a^2}{3}3a2
a23\frac{a^2}{3}3a2代入到σ2\sigma^2σ2的条件中,将得到初始化值域:

U(−6nin+nout,6nin+nout).U\left(-\sqrt{\frac{6}{n_\mathrm{in} + n_\mathrm{out}}}, \sqrt{\frac{6}{n_\mathrm{in} + n_\mathrm{out}}}\right).U(nin+nout6,nin+nout6).

尽管在上述数学推理中,“不存在非线性”的假设在神经网络中很容易被违反,但Xavier初始化方法在实践中被证明是有效的。

Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐