提出问题
大模型没有快捷连接时,网络层数超过几十层后,梯度会在反向传播中逐渐衰减为0,导致浅层参数无法更新;
例子
下面会演示
解释
梯度消失的本质:每层梯度 乘以权重(0.1),10层后是 0.1^{10}=10^{-10},梯度趋近于0,浅层参数(如输入层)几乎无法更新。
梯度爆炸的本质:每层梯度 乘以权重(2.0),10层后是 2^{10}=1024,梯度变得极大,参数更新时会“跳飞”,模型无法收敛。
为什么层数少没事:2层时梯度是 0.1^2=0.01,数值正常;层数越多,乘积效应越明显,梯度消失/爆炸越严重。
解决
这里使用 快捷连接 解决
快捷连接
快捷连接 也叫 跳跃连接,通过“跨层直连”,让梯度可以直接从 输出层流回浅层,同时让网络学习“残差”(输入与输出的差值),降低学习难度。
简单说:快捷连接让深层网络“学增量”,而非“学全部”,大幅提升训练稳定性和效率。
快捷连接通过 跳过一个或多个层,为梯度在网络中的流动提供了一条 可替代且更短的路径。
原理
通过 将一层的输出 添加到 后续层的输出 中实现的。这也是为什么这种连接被称为跳跃连接。在反向传播训练中,它们在维持梯度流动方面扮演着至关重要的角色

代码演示
准备代码
之前实现的 GELU 激活函数类:
import torch
import torch.nn as nn
class GELU(nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
return 0.5 * x * (1 + torch.tanh(
torch.sqrt(torch.tensor(2.0 / torch.pi)) * (x + 0.044715 * torch.pow(x, 3))
))
创建神经网络类
实现一个具有 5层 的深度神经网络,每层由一个 线性层 和一个 GELU激活函数组成。在前向传播过程中,通过各层迭代地传递输入。
如果 self.use_shortcut 属性被设置为 True,就会选择性地添快捷连接,如上面原理图
class ExampleDeepNeuralNetwork(nn.Module):
def __init__(self, layer_sizes, use_shortcut):
super().__init__()
self.use_shortcut = use_shortcut
# 添加5个层
self.layers = nn.ModuleList([
nn.Sequential(nn.Linear(layer_sizes[0], layer_sizes[1]), GELU()),
nn.Sequential(nn.Linear(layer_sizes[1], layer_sizes[2]), GELU()),
nn.Sequential(nn.Linear(layer_sizes[2], layer_sizes[3]), GELU()),
nn.Sequential(nn.Linear(layer_sizes[3], layer_sizes[4]), GELU()),
nn.Sequential(nn.Linear(layer_sizes[4], layer_sizes[5]), GELU()),
])
def forward(self, x):
for layer in self.layers:
# 计算当前层的输出
layer_output = layer(x)
# 检查是否可以使用快捷连接,如果use_shortcut为True就使用快捷连接
if self.use_shortcut and x.shape == layer_output.shape:
x = x+layer_output
else:
x = layer_output
return x
初始化普通神经网络
初始化没有快捷连接的神经网络:
- 前4层接受包含
3个输入值的样本,并返回3个输出值 - 最后1层会返回
1个输出值
"""
前4层为接受包含3个输入值的样本,并返回3个输出值。
最后1层会返回1个输出值:
"""
layer_sizes = [3, 3, 3, 3, 3, 1]
# 定义输入张量
sample_input = torch.tensor([[1., 0., -1.]])
# 指定随机种子,初始化权重,复现结果
torch.manual_seed(123)
# 初始化没有快捷连接的神经网络。
model_without_shortcut = ExampleDeepNeuralNetwork(layer_sizes, use_shortcut=False)
实现计算梯度函数
实现用于在模型的反向传播过程中计算梯度的函数
定义损失函数,用于计算模型输出与用户指定目标(为简化处理,这里设为 0)的接近程度。然后,当调用loss.backward()时,PyTorch会计算模型中每一层的损失梯度。可以通过 model.named_parameters() 迭代权重参数。假设某一层有一个 3×3 的权重参数矩阵,那么该层将有 3×3 的梯度值。打印这 3×3 的 梯度值的平均绝对值,以得到 每一层的单一梯度值,更容易比较层与层之间的梯度。
简而言之,.backward()方法是PyTorch中的一个便捷方法,它可以在模型训练中计算所需的损失梯度,而无须我们自己手动实现复杂的梯度计算过程,这极大地简化了深度神经网络的使用。
def print_gradients(model, x):
"""
用于在模型的反向传播过程中计算梯度的函数
:param model: 模型
:param x: 输入张量
:return:
"""
# 前向传播
output = model(x)
# 用户指定目标,为简化处理,这里设为0。
target = torch.tensor([[0.]])
loss = nn.MSELoss()
# 计算损失。计算模型输出与用户指定目标的接近程度
loss = loss(output, target)
# 反向传播,计算模型中每一层的损失梯度
loss.backward()
# 通过model.named_parameters()迭代权重参数
for name, param in model.named_parameters():
if 'weight' in name:
# 打印梯度值的平均绝对值,以得到每一层的单一梯度值,更容易比较层与层之间的梯度
print(f"{name} 平均梯度: {param.grad.abs().mean().item()}")
执行
使用 print_gradients 函数,并将其应用于没有快捷连接的模型:
print("\n\n不包含跳跃连接的模型----------")
print_gradients(model_without_shortcut, sample_input)
执行结果:
layers.0.0.weight has gradient mean of 0.00020173587836325169
layers.1.0.weight has gradient mean of 0.0001201116101583466
layers.2.0.weight has gradient mean of 0.0007152041653171182
layers.3.0.weight has gradient mean of 0.001398873864673078
layers.4.0.weight has gradient mean of 0.005049646366387606
结论:梯度在从最后一层( layers.4 )到第1层( layers.0 )的过程中逐渐变小,这种现象称为 梯度消失问题
初始化包含跳跃连接的模型
# 初始化包含跳跃连接的模型
torch.manual_seed(123)
model_with_shortcut = ExampleDeepNeuralNetwork(
layer_sizes, use_shortcut=True
)
print("\n\n包含跳跃连接的模型----------")
print_gradients(model_with_shortcut, sample_input)
执行结果:
layers.0.0.weight has gradient mean of 0.22169792652130127
layers.1.0.weight has gradient mean of 0.20694105327129364
layers.2.0.weight has gradient mean of 0.32896995544433594
layers.3.0.weight has gradient mean of 0.2665732502937317
layers.4.0.weight has gradient mean of 1.3258541822433472
结论:最后一层( layers.4 )的梯度仍然大于其他层。然而,梯度值在逐渐接近第1层( layers.0 )时趋于稳定,并且没有缩小到几乎消失的程度。
总结
快捷连接在解决深度神经网络中梯度消失问题的限制方面非常重要。快捷连接是诸如大语言模型等超大规模模型的核心构建块,它们将通过确保各层之间梯度的稳定流动来帮助实现更有效的训练。