数值稳定性和模型初始化
初始化方案的选择在神经网络学习中起着举足轻重的作用, 它对保持数值稳定性至关重要。 此外,这些初始化方案的选择可以与非线性激活函数的选择有趣的结合在一起。 我们选择哪个函数以及如何初始化参数可以决定优化算法收敛的速度有多快。 糟糕选择可能会导致我们在训练时遇到梯度爆炸或梯度消失。
# 1.梯度消失和梯度爆炸
考虑一个具有L层、输入x和输出o的深层网络。每一层l由变换fl定义,该变换的参数为权重W(l), 其隐藏变量是h(l)(令 h(0)=x)。我们的网络可以表示为:
因此h(l)=fl(h(l−1)) 因此 o=fL∘…∘f1(x).
如果所有隐藏变量和输入都是向量,我们可以将o关于任何一组参数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.
换言之,该梯度是L−l个矩阵M(L)⋅…⋅M(l+1)与梯度向量 v(l)的乘积。因此,我们容易受到数值下溢问题的影响.当将太多的概率乘在一起时,这些问题经常会出现。在处理概率时,一个常见的技巧是切换到对数空间,即将数值表示的压力从尾数转移到指数。不幸的是,上面的问题更为严重:最初,矩阵 M(l) 可能具有各种各样的特征值。他们可能很小,也可能很大;他们的乘积可能非常大,也可能非常小。
不稳定梯度带来的风险不止在于数值表示;不稳定梯度也威胁到我们优化算法的稳定性。我们可能面临一些问题。要么是_梯度爆炸_(gradient exploding)问题:参数更新过大,破坏了模型的稳定收敛;要么是_梯度消失_(gradient vanishing)问题:参数更新过小,在每次更新时几乎不会移动,导致模型无法学习。
# 1.1 梯度消失
曾经sigmoid函数1/(1+exp(−x))( 4.1节 (opens new window) (opens new window)提到过)很流行, 因为它类似于阈值函数。 由于早期的人工神经网络受到生物神经网络的启发, 神经元要么完全激活要么完全不激活(就像生物神经元)的想法很有吸引力。 然而,它却是导致梯度消失问题的一个常见的原因.
%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))
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
正如上图,当sigmoid函数的输入很大或是很小时,它的梯度都会消失。 此外,当反向传播通过许多层时,除非我们在刚刚好的地方, 这些地方sigmoid函数的输入接近于零,否则整个乘积的梯度可能会消失。 当我们的网络有很多层时,除非我们很小心,否则在某一层可能会切断梯度。 事实上,这个问题曾经困扰着深度网络的训练。 因此,更稳定的ReLU系列函数已经成为从业者的默认选择(虽然在神经科学的角度看起来不太合理)。
# 2. 梯度爆炸
相反,梯度爆炸可能同样令人烦恼。 为了更好地说明这一点,我们生成100个高斯随机矩阵,并将它们与某个初始矩阵相乘。 对于我们选择的尺度(方差σ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)
2
3
4
5
6
1
2
3
4
5
6
结果
一个矩阵
tensor([[-0.7872, 2.7090, 0.5996, -1.3191],
[-1.8260, -0.7130, -0.5521, 0.1051],
[ 1.1213, 1.0472, -0.3991, -0.3802],
[ 0.5552, 0.4517, -0.3218, 0.5214]])
乘以100个矩阵后
tensor([[-2.1897e+26, 8.8308e+26, 1.9813e+26, 1.7019e+26],
[ 1.3110e+26, -5.2870e+26, -1.1862e+26, -1.0189e+26],
[-1.6008e+26, 6.4559e+26, 1.4485e+26, 1.2442e+26],
[ 3.0943e+25, -1.2479e+26, -2.7998e+25, -2.4050e+25]])
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11
# 1.3 打破对称性
神经网络设计中的另一个问题是其参数化所固有的对称性。 假设我们有一个简单的多层感知机,它有一个隐藏层和两个隐藏单元。 在这种情况下,我们可以对第一层的权重()w(1)行重排列, 并且同样对输出层的权重进行重排列,可以获得相同的函数。 第一个隐藏单元与第二个隐藏单元没有什么特别的区别。 换句话说,我们在每一层的隐藏单元之间具有排列对称性。
假设输出层将上述两个隐藏单元的多层感知机转换为仅一个输出单元。想象一下,如果我们将隐藏层的所有参数初始化为W(1)=c,c为常量,会发生什么?在这种情况下,在前向传播期间,两个隐藏单元采用相同的输入和参数,产生相同的激活,该激活被送到输出单元。在反向传播期间,根据参数W(1)对输出单元进行微分,得到一个梯度,其元素都取相同的值。因此,在基于梯度的迭代(例如,小批量随机梯度下降)之W(1)的所有元素仍然采用相同的值。这样的迭代永远不会打破对称性,我们可能永远也无法实现网络的表达能力。隐藏层的行为就好像只有一个单元。请注意,虽然小批量随机梯度下降不会打破这种对称性,但暂退法正则化可以。
# 2. 参数初始化
解决(或至少减轻)上述问题的一种方法是进行参数初始化, 优化期间的注意和适当的正则化也可以进一步提高稳定性。
# 2.1 默认初始化
我们使用正态分布来默认初始化权重值。如果我们不指定初始化方法, 框架将使用默认的随机初始化方法,对于中等难度的问题,这种方法通常很有效。
# 2.2 Xavier初始化
让我们看看某些_没有非线性_的全连接层输出(例如,隐藏变量)oi的尺度分布。对于该层nin输入xj及其相关权重wij,输出由下式给出
oi=∑j=1ninwijxj.
权重wij都是从同一分布中独立抽取的。此外,让我们假设该分布具有零均值和方差σ2。请注意,这并不意味着分布必须是高斯的,只是均值和方差需要存在。现在,让我们假设层xj的输入也具有零均值和方差γ2,并且它们独立于wij并且彼此独立。在这种情况下,我们可以按如下方式计算oi的平均值和方差:
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.
保持方差不变的一种方法是设置ninσ2=1。现在考虑反向传播过程,我们面临着类似的问题,尽管梯度是从更靠近输出的层传播的。使用与前向传播相同的推断,我们可以看到,除非noutσ2=1,否则梯度的方差可能会增大,其中nout是该层的输出的数量。这使得我们进退两难:我们不可能同时满足这两个条件。 相反,我们只需满足:
或等价于12(nin+nout)σ2=1 或等价于 σ=2nin+nout.
这就是现在标准且实用的_Xavier初始化_的基础.通常,Xavier初始化从均值为零,方差 σ2=2nin+nout的高斯分布中采样权重。我们也可以将其改为选择从均匀分布中抽取权重时的方差。 注意均匀分布U(−a,a)的方差为a23。将a23代入到σ2的条件中,将得到初始化值域:
U(−6nin+nout,6nin+nout).
尽管在上述数学推理中,“不存在非线性”的假设在神经网络中很容易被违反,但Xavier初始化方法在实践中被证明是有效的。
# 小结
- 梯度消失和梯度爆炸是深度网络中常见的问题。在参数初始化时需要非常小心,以确保梯度和参数可以得到很好的控制。
- 需要用启发式的初始化方法来确保初始梯度既不太大也不太小。
- ReLU激活函数缓解了梯度消失问题,这样可以加速收敛。
- 随机初始化是保证在进行优化前打破对称性的关键。
- Xavier初始化表明,对于每一层,输出的方差不受输入数量的影响,任何梯度的方差不受输出数量的影响。