深度学习-数值稳定性和模型初始化
到目前为止,我们实现的每个模型都是根据某个预先制定的分布来初始化模型的参数,有人会认为初始化方案时理所当然的,忽略了如何做出这些选择的细节,甚至有人可能会觉得,初始化方案的选择并不是特别重要,实际上,初始化方案的选择在神经网络学习中起着举足轻重的作用,保持数值稳定性至关重要。此外,这些初始化方案的选择可以与非线性激活函数的选择有趣的结合在一起。我们选择哪个函数以及如何初始化参数可以决定优化算法收敛的速度有多快,糟糕的选择可能会导致我们在训练时遇到梯度爆炸或者梯度消失,本节将更详细的探讨这些主题,讨论一些有用的启发方式方法。这些启发方式在整个深度学习中都很有用。
4.8.1 梯度小时和梯度爆炸
考虑一个具有L层,输入x和输出o的深层网络。每一层l由变换f1定义,该变换的参数为权重W(l),隐藏变量是h(l) 令h(0) =x。我们的网络可以表示为
h(l) = Flh(l-1), 因此 o = FL .... Fl(x)
换而言之,该剃度是L-1 个矩阵M(L) ... M(l + 1) 与梯度向量v(l)的乘积。因此,我们容易受到数值下溢问题的影响,当将过多的概率在一起相承时,这种问题经常会出现。在处理概率时,一个常见的技巧是切换到对数空间,即将数值表示的压力从尾数转移到指数。遗憾的是,这会使上面的问题更为严重,矩阵M可能是各种各样的特征值,可能很小,也可能很大,而他们的乘积可能非常大,也可能非常小。
不稳定梯度带来的风险不仅在于数值表示,也威胁到优化算法的稳定性。我们可能面临一些问题,要么是梯度爆炸问题,参数更新过大,破坏了模型的稳定收敛,要么是梯度消失问题,参数更新过小,在每次更新时几乎不会移动。导致模型无法学习。
1 梯度消失
曾经sigmoid 函数很流行,因为它类似于阈值函数,由于早期的人工神经网络受到生物神经网络的启发,神经元要饿完全激活,要么完全不激活的想法很吸引力。然而,却是导致梯度消失的一个常见原因,我们仔细看着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个高斯随机矩阵,将他们与某个初始矩阵相乘,对于我们选择的尺寸,矩阵的乘积发生了爆炸,当这种情况是由深度网络的初始化所导致的,我们没有机会让梯度下降优化器收敛。
M = torch.normal(0, 1, size = (4,4))
printf('一个矩阵\n');
for i in range(100)
M = torch.mm(M, torch.normal(0, 1, size(4, 4)));
3 打破对成型
神经网络设计中的另一个问题是其参数化所固有的对称性。假设我们有一个简单的多层感知机,有一个隐藏层和两个隐藏单元。在这种情况下,我们可以对第一层的权重进行重排列,并且同样对输出层和权重进行重排列,可以获得相同的函数,第一个隐藏单元与第二个隐藏单元没有什么区别,换句话说,每一层的隐藏单元之间具有排列对称性。
4.8.2 参数初始化
解决上述问题的一种方法是进行参数初始化,优化期间的适当正则化也可以进一步提高稳定性。