4.8. 数值稳定性和模型初始化¶ Open the notebook in SageMaker Studio Lab
到目前为止,我们实现的每个模型都是根据某个预先指定的分布来初始化模型的参数。 有人会认为初始化方案是理所当然的,忽略了如何做出这些选择的细节。甚至有人可能会觉得,初始化方案的选择并不是特别重要。 相反,初始化方案的选择在神经网络学习中起着举足轻重的作用, 它对保持数值稳定性至关重要。 此外,这些初始化方案的选择可以与非线性激活函数的选择有趣的结合在一起。 我们选择哪个函数以及如何初始化参数可以决定优化算法收敛的速度有多快。 糟糕选择可能会导致我们在训练时遇到梯度爆炸或梯度消失。 本节将更详细地探讨这些主题,并讨论一些有用的启发式方法。 这些启发式方法在整个深度学习生涯中都很有用。
4.8.1. 梯度消失和梯度爆炸¶
考虑一个具有
如果所有隐藏变量和输入都是向量,
我们可以将
换言之,该梯度是
不稳定梯度带来的风险不止在于数值表示; 不稳定梯度也威胁到我们优化算法的稳定性。 我们可能面临一些问题。 要么是梯度爆炸(gradient exploding)问题: 参数更新过大,破坏了模型的稳定收敛; 要么是梯度消失(gradient vanishing)问题: 参数更新过小,在每次更新时几乎不会移动,导致模型无法学习。
4.8.1.1. 梯度消失¶
曾经sigmoid函数
[07:03:21] ../src/storage/storage.cc:196: Using Pooled (Naive) StorageManager for CPU
[07:03:21] ../src/base.cc:48: GPU context requested, but no GPUs found.
%matplotlib inline
import warnings
from d2l import paddle as d2l
warnings.filterwarnings("ignore")
import paddle
x = paddle.arange(start=-8.0, end=8.0, step=0.1, dtype='float32')
x.stop_gradient = False
y = paddle.nn.functional.sigmoid(x)
y.backward(paddle.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系列函数已经成为从业者的默认选择(虽然在神经科学的角度看起来不太合理)。
4.8.1.2. 梯度爆炸¶
相反,梯度爆炸可能同样令人烦恼。
为了更好地说明这一点,我们生成100个高斯随机矩阵,并将它们与某个初始矩阵相乘。
对于我们选择的尺度(方差
一个矩阵
[[ 2.2122064 1.1630787 0.7740038 0.4838046 ]
[ 1.0434403 0.29956347 1.1839255 0.15302546]
[ 1.8917114 -1.1688148 -1.2347414 1.5580711 ]
[-1.771029 -0.5459446 -0.45138445 -2.3556297 ]]
乘以100个矩阵后
[[ 3.4459747e+23 -7.8040759e+23 5.9973355e+23 4.5230040e+23]
[ 2.5275059e+23 -5.7240258e+23 4.3988419e+23 3.3174704e+23]
[ 1.3731275e+24 -3.1097129e+24 2.3897754e+24 1.8022945e+24]
[-4.4951091e+23 1.0180045e+24 -7.8232368e+23 -5.9000419e+23]]
一个矩阵
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]])
一个矩阵
tf.Tensor(
[[ 0.2560883 0.00797761 -1.0682204 -0.16417116]
[ 2.0060067 0.3481633 -0.74592876 1.2011837 ]
[ 0.21468055 0.09542773 -0.40310776 0.17152807]
[-1.077096 -0.8505044 -2.3125033 -1.2070065 ]], shape=(4, 4), dtype=float32)
乘以100个矩阵后
[[-4.5526118e+23 1.2209238e+23 -5.6382419e+22 1.4789259e+23]
[-2.1546440e+24 5.7783439e+23 -2.6684469e+23 6.9994081e+23]
[-4.0427488e+23 1.0841861e+23 -5.0067922e+22 1.3132947e+23]
[-1.7901018e+23 4.8007174e+22 -2.2169757e+22 5.8151884e+22]]
一个矩阵
Tensor(shape=[4, 4], dtype=float32, place=Place(cpu), stop_gradient=True,
[[ 0.09555759, 0.33683282, 1.20171154, 1.25189281],
[-0.18284278, -0.65445828, 0.14309669, 0.55920464],
[ 0.97072512, -0.47076491, -2.04063487, -0.57572478],
[ 1.14641619, 1.06490767, 0.76046497, -0.27298930]])
乘以100个矩阵后
Tensor(shape=[4, 4], dtype=float32, place=Place(cpu), stop_gradient=True,
[[-1939696468398394579091456., -2089295959956477146300416.,
2721163381582732258705408., -3558180070471660887605248.],
[-307949261019363894362112. , -331699912388626188599296. ,
432016261022814285332480. , -564902317902972638461952. ],
[ 2247077303087190278930432., 2420383743083751137083392.,
-3152382253269922833498112., 4122040255420859993292800.],
[ 169666774892908099141632. , 182752361912601774391296. ,
-238022355993941958983680. , 311236780660953299550208. ]])
4.8.1.3. 打破对称性¶
神经网络设计中的另一个问题是其参数化所固有的对称性。
假设我们有一个简单的多层感知机,它有一个隐藏层和两个隐藏单元。
在这种情况下,我们可以对第一层的权重
假设输出层将上述两个隐藏单元的多层感知机转换为仅一个输出单元。
想象一下,如果我们将隐藏层的所有参数初始化为
4.8.2. 参数初始化¶
解决(或至少减轻)上述问题的一种方法是进行参数初始化, 优化期间的注意和适当的正则化也可以进一步提高稳定性。
4.8.2.1. 默认初始化¶
在前面的部分中,例如在 3.3节中, 我们使用正态分布来初始化权重值。如果我们不指定初始化方法, 框架将使用默认的随机初始化方法,对于中等难度的问题,这种方法通常很有效。
4.8.2.2. Xavier初始化¶
让我们看看某些没有非线性的全连接层输出(例如,隐藏变量)
权重
保持方差不变的一种方法是设置
这就是现在标准且实用的Xavier初始化的基础, 它以其提出者
(Glorot and Bengio, 2010) 第一作者的名字命名。
通常,Xavier初始化从均值为零,方差
尽管在上述数学推理中,“不存在非线性”的假设在神经网络中很容易被违反, 但Xavier初始化方法在实践中被证明是有效的。
4.8.2.3. 额外阅读¶
上面的推理仅仅触及了现代参数初始化方法的皮毛。 深度学习框架通常实现十几种不同的启发式方法。 此外,参数初始化一直是深度学习基础研究的热点领域。 其中包括专门用于参数绑定(共享)、超分辨率、序列模型和其他情况的启发式算法。 例如,Xiao等人演示了通过使用精心设计的初始化方法 (Xiao et al., 2018), 可以无须架构上的技巧而训练10000层神经网络的可能性。
如果有读者对该主题感兴趣,我们建议深入研究本模块的内容, 阅读提出并分析每种启发式方法的论文,然后探索有关该主题的最新出版物。 也许会偶然发现甚至发明一个聪明的想法,并为深度学习框架提供一个实现。
4.8.3. 小结¶
梯度消失和梯度爆炸是深度网络中常见的问题。在参数初始化时需要非常小心,以确保梯度和参数可以得到很好的控制。
需要用启发式的初始化方法来确保初始梯度既不太大也不太小。
ReLU激活函数缓解了梯度消失问题,这样可以加速收敛。
随机初始化是保证在进行优化前打破对称性的关键。
Xavier初始化表明,对于每一层,输出的方差不受输入数量的影响,任何梯度的方差不受输出数量的影响。
4.8.4. 练习¶
除了多层感知机的排列对称性之外,还能设计出其他神经网络可能会表现出对称性且需要被打破的情况吗?
我们是否可以将线性回归或softmax回归中的所有权重参数初始化为相同的值?
在相关资料中查找两个矩阵乘积特征值的解析界。这对确保梯度条件合适有什么启示?
如果我们知道某些项是发散的,我们能在事后修正吗?看看关于按层自适应速率缩放的论文 (You et al., 2017) 。