在深度学习中,数值稳定性和模型初始化是影响训练效果的重要因素。理解这些概念不仅有助于优化模型性能,还可以避免训练过程中的数值问题。本篇博客将从梯度消失与爆炸、打破对称性以及参数初始化三方面展开讨论,并通过实例与公式为读者提供易于理解的内容。
第一节:梯度消失与梯度爆炸
在深度神经网络中,梯度消失与梯度爆炸是两种常见的问题,主要与权重矩阵的特性及激活函数有关。
1.1 梯度消失
梯度消失问题通常发生在网络层数较深时,尤其当激活函数是 Sigmoid 时。
Sigmoid 函数的输出趋近于 0 或 1 时,其导数接近 0,使得反向传播时梯度不断缩小,无法有效更新权重。
-
公式推导: 假设深度网络共有 L 层,输入为 ,输出为 。每层的变换表示为:
其中 是激活函数。
梯度可以表示为:
如果 的特征值过小,则累积乘积会趋近于 ,导致梯度消失。
-
代码示例:
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()
代码解释:
-
torch.ones_like(x)- 这个函数生成一个与 形状相同、所有元素都为 的张量。
-
y.backward()backward()计算张量 对张量 的梯度,并将结果存储在 中。- 如果 是一个标量(单一值),可以直接调用
y.backward()。 - 如果 是向量(如本例中的
y = sigmoid(x)),需要指定一个与 形状相同的张量,告诉 PyTorch 每个元素对应的权重。
-
y.backward(torch.ones_like(x))的具体作用是:- 对向量 的每个元素赋予权重 ,这实际上是对 中的所有元素求导并累加。
- 计算得到 ,即 的梯度。
- 结果会存储在
x.grad中,可以通过访问x.grad查看。
-
x.detach().numpy()将 PyTorch 张量 转换为一个 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 默认初始化
现代框架中默认初始化方法是使用正态分布随机采样权重。例如:
其中, 是方差。
3.2 Xavier 初始化
为了控制前向传播和反向传播中每层输出的方差,Xavier 初始化设置:
其中, 和 分别是输入和输出的单元数。
Xavier初始化建议从一个均值为零,方差为 的分布中采样权重。
公式推导:
-
假设第 层的输出为:
-
权重满足 ,梯度传播的方差可以通过均匀分布推导为:
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 初始化等方法,专门为不同激活函数优化。
总结
在深度学习中,数值稳定性和初始化对模型的训练效果至关重要。通过正确选择激活函数与初始化方法,我们可以避免梯度问题,提高优化效率。这些技巧不仅适用于当前任务,还将贯穿整个深度学习学习过程。