《动手学深度学习(PyTorch版)》笔记4.8

注:书中对代码的讲解并不详细,本文对很多细节做了详细注释。另外,书上的源代码是在Jupyter Notebook上运行的,较为分散,本文将代码集中起来,并加以完善,全部用vscode在python 3.9.18下测试通过。

Chapter4 Multilayer Perceptron

4.8 Numerical Stability and Model Initialization

4.8.1 Gradient Vanishing and Gradient Exploding

考虑一个具有 L L L层、输入 x \mathbf{x} x和输出 o \mathbf{o} o的深层网络。每一层 l l l由变换 f l f_l fl定义,该变换的参数为权重 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 ) = f l ( h ( l − 1 ) ) 因此 o = f L ∘ ... ∘ f 1 ( 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(l−1)) 因此 o=fL∘...∘f1(x).

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

∂ W ( l ) o = ∂ h ( L − 1 ) h ( L ) ⏟ M ( L ) = d e f ⋅ ... ⋅ ∂ h ( l ) h ( l + 1 ) ⏟ M ( l + 1 ) = d e f ∂ W ( l ) h ( l ) ⏟ v ( l ) = d e f . \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)=def ∂h(L−1)h(L)⋅...⋅M(l+1)=def ∂h(l)h(l+1)v(l)=def ∂W(l)h(l).

换言之,该梯度是 L − l L-l L−l个矩阵 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)问题:参数更新过小,在每次更新时几乎不会移动,导致模型无法学习。

4.8.1.1 Gradient Vanishing

sigmoid函数 s i g m o i d ( x ) = 1 1 + exp ⁡ ( − x ) sigmoid(x)=\frac{1}{1 + \exp(-x)} sigmoid(x)=1+exp(−x)1是导致梯度消失问题的一个常见的原因。

import matplotlib.pyplot as plt
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))
plt.show()

如上图所示,当sigmoid函数的输入很大或是很小时,它的梯度都会消失。此外,当反向传播通过许多层时,除非sigmoid函数的输入都刚刚好接近于零,否则整个乘积的梯度可能会消失。因此,更稳定的ReLU系列函数已经成为默认选择。

4.8.1.2 Gradient Exploding
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)

结果:

一个矩阵 
tensor([[-0.4430,  1.8467,  1.2274,  0.2537],
        [ 1.6749, -1.5996,  0.6402,  0.1141],
        [-0.1859, -0.4506,  2.5819, -1.3329],
        [ 2.7346,  0.1642, -0.6078, -0.0507]])
乘以100个矩阵后
tensor([[ 6.9875e+23,  5.5570e+23,  7.6843e+23, -1.9781e+23],
        [-6.3054e+23, -5.0146e+23, -6.9342e+23,  1.7850e+23],
        [ 6.4354e+23,  5.1180e+23,  7.0772e+23, -1.8218e+23],
        [-1.1732e+24, -9.3301e+23, -1.2902e+24,  3.3212e+23]])
4.8.1.3 Symmetry

另一个问题是参数化所固有的对称性。假设我们有一个简单的多层感知机,它有一个隐藏层和两个隐藏单元。在这种情况下,我们可以对第一层的权重 W ( 1 ) \mathbf{W}^{(1)} W(1)进行重排列,并且同样对输出层的权重进行重排列,可以获得相同的函数。第一个隐藏单元与第二个隐藏单元没有什么特别的区别。换句话说,我们在每一层的隐藏单元之间具有排列对称性。

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

4.8.2 Xavier Initialization

解决(或至少减轻)上述问题的一种方法是进行参数初始化,如果我们不指定初始化方法,框架将使用默认的随机初始化方法。

现在深度学习中标准且实用的还有Xavier初始化 。让我们看看某些没有非线性 的全连接层输出(例如,隐藏变量) o i o_{i} oi的尺度分布。对于该层 n i n n_\mathrm{in} nin输入 x j x_j xj及其相关权重 w i j w_{ij} wij,输出由下式给出

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

权重 w i j w_{ij} wij都是从同一分布中独立抽取的。此外,让我们假设该分布具有零均值和方差 σ 2 \sigma^2 σ2(这并不意味着分布必须是高斯的,只是均值和方差需要存在)。

让我们假设层 x j x_j xj的输入也具有零均值和方差 γ 2 \gamma^2 γ2,并且它们独立于 w i j w_{ij} wij并且彼此独立,在这种情况下,我们可以按如下方式计算 o i o_i oi的平均值和方差:

E [ o i ] = ∑ j = 1 n i n E [ w i j x j ] = ∑ j = 1 n i n E [ w i j ] E [ x j ] = 0 , V a r [ o i ] = E [ o i 2 ] − ( E [ o i ] ) 2 = ∑ j = 1 n i n E [ w i j 2 x j 2 ] − 0 = ∑ j = 1 n i n E [ w i j 2 ] E [ x j 2 ] = n i n σ 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=1∑ninE[wijxj]=j=1∑ninE[wij]E[xj]=0,=E[oi2]−(E[oi])2=j=1∑ninE[wij2xj2]−0=j=1∑ninE[wij2]E[xj2]=ninσ2γ2.

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

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

1 2 ( n i n + n o u t ) σ 2 = 1 or σ = 2 n i n + n o u t . \begin{aligned} \frac{1}{2} (n_\mathrm{in} + n_\mathrm{out}) \sigma^2 = 1 \text{ or } \sigma = \sqrt{\frac{2}{n_\mathrm{in} + n_\mathrm{out}}}. \end{aligned} 21(nin+nout)σ2=1 or σ=nin+nout2 .

通常,Xavier初始化从均值为零,方差 σ 2 = 2 n i n + n o u t \sigma^2 = \frac{2}{n_\mathrm{in} + n_\mathrm{out}} σ2=nin+nout2的高斯分布中采样权重。也可以利用Xavier的直觉来选择从均匀分布中抽取权重时的方差(注意均匀分布 U ( − a , a ) U(-a, a) U(−a,a)的方差为 a 2 3 \frac{a^2}{3} 3a2),将 a 2 3 \frac{a^2}{3} 3a2代入到 σ 2 \sigma^2 σ2的条件中,将得到初始化域:

U ( − 6 n i n + n o u t , 6 n i n + n o u t ) . 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初始化方法在实践中被证明是有效的。

相关推荐
Amor风信子6 分钟前
【力扣】2376. 统计特殊整数
算法·leetcode·职场和发展
极客小张6 分钟前
基于正点原子Linux开发板的智能监控与家电控制系统设计:深度解析Video4Linux和TCP/IP技术栈
linux·运维·c++·物联网·网络协议·tcp/ip·算法
程序员小王꧔ꦿ7 分钟前
python植物大战僵尸项目源码【免费】
python·游戏
拓端研究室TRL8 分钟前
Python用TOPSIS熵权法重构粮食系统及期刊指标权重多属性决策MCDM研究|附数据代码...
开发语言·python·重构
JustCouvrir1 小时前
代码随想录算法训练营Day5
算法
o(╯□╰)o亚比囧囧囧1 小时前
李沐 过拟合和欠拟合【动手学深度学习v2】
人工智能·深度学习
钡铼技术1 小时前
通过MCGS在ARMxy边缘计算网关上实现物流自动化
人工智能·自动化·边缘计算·钡铼技术·armxy边缘计算网关
吃面不喝汤661 小时前
Flask + Swagger 完整指南:从安装到配置和注释
后端·python·flask
周哈里窗的编程2 小时前
CSP-CCF★201912-2回收站选址★
c++·算法·图论
OpenTiny社区2 小时前
茶思屋直播|TinyEngine+AI:聚焦主航道,在实践中探索低代码技术黑土地
人工智能·低代码