动手学人工智能-多层感知机8-数值稳定性与模型初始化

190 阅读2分钟

在深度学习中,数值稳定性和模型初始化是影响训练效果的重要因素。理解这些概念不仅有助于优化模型性能,还可以避免训练过程中的数值问题。本篇博客将从梯度消失与爆炸打破对称性以及参数初始化三方面展开讨论,并通过实例与公式为读者提供易于理解的内容。

第一节:梯度消失与梯度爆炸

在深度神经网络中,梯度消失梯度爆炸是两种常见的问题,主要与权重矩阵的特性及激活函数有关。

1.1 梯度消失

梯度消失问题通常发生在网络层数较深时,尤其当激活函数是 Sigmoid 时。

Sigmoid 函数的输出趋近于 0 或 1 时,其导数接近 0,使得反向传播时梯度不断缩小,无法有效更新权重。

  • 公式推导: 假设深度网络共有 L 层,输入为 xx,输出为 yy。每层的变换表示为:

    h(l)=f(W(l)h(l1)+b(l))\mathbf{h}^{(l)} = f(\mathbf{W}^{(l)} \mathbf{h}^{(l-1)} + \mathbf{b}^{(l)})

    其中 ff 是激活函数。

    梯度可以表示为:

    LW(l)=i=l+1LW(i)Lh(L)\frac{\partial \mathcal{L}}{\partial \mathbf{W}^{(l)}} = \prod_{i=l+1}^L \mathbf{W}^{(i)} \cdot \frac{\partial \mathcal{L}}{\partial \mathbf{h}^{(L)}}

    如果 W(i)W^{(i)} 的特征值过小,则累积乘积会趋近于 00,导致梯度消失。

  • 代码示例

import torch
from d2l import plt

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

y.backward(torch.ones_like(x))

plt.plot(x.detach().numpy(), y.detach().numpy(), label="Sigmoid")
plt.plot(x.detach().numpy(), x.grad.numpy(), label="Gradient")
plt.legend()
plt.show()

myplot.png

代码解释:

  • torch.ones_like(x)

    • 这个函数生成一个与 xx 形状相同、所有元素都为 11 的张量。
  • y.backward()

    • backward() 计算张量 yy 对张量 xx 的梯度,并将结果存储在 x.gradx.grad 中。
    • 如果 yy 是一个标量(单一值),可以直接调用 y.backward()
    • 如果 yy 是向量(如本例中的 y = sigmoid(x)),需要指定一个与 yy 形状相同的张量,告诉 PyTorch 每个元素对应的权重。
  • y.backward(torch.ones_like(x)) 的具体作用是:

    • 对向量 yy 的每个元素赋予权重 11,这实际上是对 yy 中的所有元素求导并累加。
    • 计算得到 yx\frac{\partial y}{\partial x},即 𝑥𝑥 的梯度。
    • 结果会存储在 x.grad 中,可以通过访问 x.grad 查看。
  • x.detach().numpy() 将 PyTorch 张量 xx 转换为一个 NumPy 数组,具体分两步:

    • detach():分离计算图,避免梯度追踪。
    • numpy():将张量数据转换为 NumPy 数组,与张量共享内存(修改会相互影响)。 用于在计算图外操作张量数据,比如可视化或与其他库集成。

1.2 梯度爆炸

梯度爆炸是梯度的反面问题。当权重矩阵的特征值较大时,矩阵的累积乘积可能会导致梯度值过大,进而影响模型的收敛。

  • 代码示例:
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.4838, -0.6498,  0.1953,  1.1185],
        [-0.8227, -1.2225,  0.1345,  0.8002],
        [-0.4570, -0.7937, -0.4154, -2.2243],
        [ 0.7707, -0.3028,  0.8746,  0.6277]])
乘100次之后:
 tensor([[-5.1975e+21, -8.8109e+22,  6.7852e+22, -1.9293e+22],
        [-2.8240e+21, -4.7874e+22,  3.6867e+22, -1.0483e+22],
        [ 1.2399e+22,  2.1020e+23, -1.6187e+23,  4.6026e+22],
        [ 2.2798e+21,  3.8647e+22, -2.9762e+22,  8.4624e+21]])

第二节:打破对称性

在神经网络中,如果每个神经元的权重和偏置从相同的初始值开始,它们的输出和更新过程将始终相同,导致模型无法有效学习。这种现象被称为对称性问题

想象你和朋友一起学习画画,但你们使用完全相同的模板、画笔和技法。无论练习多少次,你们画出来的作品都几乎一样,因为没有任何差异化的学习方式。同样,神经网络的对称性问题会让所有神经元都学到“同一种画法”,无法捕捉数据中的多样性。

本质:对称性会限制网络的表达能力,因为所有神经元都在执行相同的功能。为了解决这个问题,我们通过随机初始化权重来打破对称性,使每个神经元从一开始就有所不同,能够独立学习。

如何打破对称性?

要解决这个问题,相当于让每个人使用不同的画笔和颜色,从一开始就培养各自的风格。在神经网络中,这意味着通过随机初始化权重来让每个神经元从不同的“起点”开始学习

  • 权重可以随机从正态分布或均匀分布中采样。
  • 偏置通常初始化为零(有时也可以随机化)。
import torch

torch.manual_seed(42)  # 设置随机种子,确保可重复性
weights = torch.randn(2, 2)  # 随机初始化一个 2x2 权重矩阵
print(weights)
""" 输出:

tensor([[0.3367, 0.1288],
        [0.2345, 0.2303]])
"""

第三节:参数初始化

3.1 默认初始化

现代框架中默认初始化方法是使用正态分布随机采样权重。例如:

WN(0,σ2)W \sim \mathcal{N}(0, \sigma^2)

其中,σ2\sigma^2 是方差。

3.2 Xavier 初始化

为了控制前向传播和反向传播中每层输出的方差,Xavier 初始化设置:

σW2=2nin+nout\sigma_W^2 = \frac{2}{n_{\text{in}} + n_{\text{out}}}

其中,ninn_{\text{in}}noutn_{\text{out}} 分别是输入和输出的单元数。

Xavier初始化建议从一个均值为零,方差为 2nin+nout\frac{2}{n_{\text{in}} + n_{\text{out}}} 的分布中采样权重。

公式推导:
  • 假设第 ll 层的输出为:

  • 权重满足 Var(𝑊)=1𝑛inVar(\mathbf{𝑊})=\frac{1}{𝑛_{\text{in}}},梯度传播的方差可以通过均匀分布推导为:

    WU(6nin+nout,6nin+nout)W \sim U\left( \sqrt{-\frac{6}{n_{\text{in}} + n_{\text{out}}}}, \sqrt{\frac{6}{n_{\text{in}} + n_{\text{out}}}} \right)
def xavier_init(shape):
    fan_in, fan_out = shape
    limit = torch.sqrt(torch.tensor(6.0) / (fan_in + fan_out))
    return torch.empty(shape).uniform_(-limit, limit)


weights = xavier_init((256, 512))
print(weights.shape)
print(weights)
  • torch.empty(shape).uniform_() 用于创建一个指定形状(shape)的张量,并用均匀分布随机填充它的元素。

    • torch.empty(shape):创建一个未初始化的张量,形状为 shape。该张量的值是未定义的(垃圾值),即没有经过初始化。
    • .uniform_():对张量的每个元素进行就地操作(原地修改),使用均匀分布(默认区间 [0,1) 来填充张量的元素。

    这行代码的作用是创建一个随机填充(均匀分布)的张量,形状由 shape 确定。

输出:

torch.Size([256, 512])
tensor([[ 0.0279,  0.0545,  0.0189,  ..., -0.0357, -0.0423, -0.0283],
        [-0.0773,  0.0121,  0.0118,  ...,  0.0105, -0.0835,  0.0619],
        [ 0.0315,  0.0841, -0.0682,  ...,  0.0347, -0.0456, -0.0686],
        ...,
        [-0.0632,  0.0159, -0.0189,  ...,  0.0477, -0.0875, -0.0592],
        [ 0.0577,  0.0243, -0.0210,  ...,  0.0692,  0.0058, -0.0171],
        [-0.0427,  0.0411,  0.0802,  ...,  0.0045,  0.0727,  0.0203]])

3.3 其他初始化方法

除了 Xavier 初始化,现代深度学习中还包括 He 初始化等方法,专门为不同激活函数优化。

总结

在深度学习中,数值稳定性和初始化对模型的训练效果至关重要。通过正确选择激活函数与初始化方法,我们可以避免梯度问题,提高优化效率。这些技巧不仅适用于当前任务,还将贯穿整个深度学习学习过程。