原因
梯度是反向传播时“链式法则”的乘积:每经过一层,梯度都会 乘以 该层的 权重,导致:
如果权重的绝对值 平均小于1,多次相乘后梯度会 趋近于
0(消失)如果权重的绝对值 平均大于1,多次相乘后梯度会 趋近于无穷大(爆炸)
代码演示
import torch
import torch.nn as nn
# ========== 核心:定义一个仅含线性层的深层网络(无激活/快捷连接) ==========
class DeepLinearNet(nn.Module):
def __init__(self, layer_num, hidden_dim, weight_scale):
super().__init__()
# 构建多层线性层(无激活、无快捷连接,放大梯度问题)
layers = []
for _ in range(layer_num):
# weight_scale 控制权重的初始大小:
# - weight_scale=0.1 → 权重平均<1 → 梯度消失
# - weight_scale=2.0 → 权重平均>1 → 梯度爆炸
linear = nn.Linear(hidden_dim, hidden_dim)
# 手动初始化权重(控制缩放程度)
nn.init.constant_(linear.weight, weight_scale)
nn.init.constant_(linear.bias, 0.0) # 偏置置0,简化计算
layers.append(linear)
self.layers = nn.Sequential(*layers)
def forward(self, x):
# 前向传播:仅过线性层,无任何激活/快捷连接
return self.layers(x)
# ========== 演示1:梯度消失(权重scale=0.1,平均<1) ==========
print("===== 梯度消失演示(权重scale=0.1) =====")
# 超参数:10层网络,隐藏维度1,权重scale=0.1(<1)
net_vanish = DeepLinearNet(layer_num=10, hidden_dim=1, weight_scale=0.1)
x_vanish = torch.tensor([[1.0]], requires_grad=True) # 输入,开启梯度
y_vanish = net_vanish(x_vanish)
y_vanish.backward() # 反向传播计算梯度
# 打印输入x的梯度(梯度经过10层后的值)
print(f"输入x的梯度值(10层后): {x_vanish.grad.item():.10f}")
# 理论值:0.1^10 = 1e-10 → 梯度趋近于0,消失!
# ========== 演示2:梯度爆炸(权重scale=2.0,平均>1) ==========
print("\n===== 梯度爆炸演示(权重scale=2.0) =====")
# 超参数:10层网络,隐藏维度1,权重scale=2.0(>1)
net_explode = DeepLinearNet(layer_num=10, hidden_dim=1, weight_scale=2.0)
x_explode = torch.tensor([[1.0]], requires_grad=True)
y_explode = net_explode(x_explode)
y_explode.backward()
# 打印输入x的梯度(梯度经过10层后的值)
print(f"输入x的梯度值(10层后): {x_explode.grad.item():.1f}")
# 理论值:2^10 = 1024 → 梯度变得极大,爆炸!
# ========== 补充:对比少层数的情况(梯度正常) ==========
print("\n===== 对比:仅2层网络(梯度正常) =====")
net_normal = DeepLinearNet(layer_num=2, hidden_dim=1, weight_scale=0.1)
x_normal = torch.tensor([[1.0]], requires_grad=True)
y_normal = net_normal(x_normal)
y_normal.backward()
print(f"2层网络下x的梯度值: {x_normal.grad.item():.4f}") # 0.1^2=0.01,正常
代码运行结果
===== 梯度消失演示(权重scale=0.1) =====
输入x的梯度值(10层后): 0.0000000001 # 1e-10,几乎为0 → 梯度消失
===== 梯度爆炸演示(权重scale=2.0) =====
输入x的梯度值(10层后): 1024.0 # 2^10=1024,极大 → 梯度爆炸
===== 对比:仅2层网络(梯度正常) =====
2层网络下x的梯度值: 0.0100 # 0.1^2=0.01,梯度正常
解释
梯度消失的本质:每层梯度 乘以权重(0.1),10层后是 0.1^{10}=10^{-10},梯度趋近于0,浅层参数(如输入层)几乎无法更新。
梯度爆炸的本质:每层梯度 乘以权重(2.0),10层后是 2^{10}=1024,梯度变得极大,参数更新时会“跳飞”,模型无法收敛。
为什么层数少没事:2层时梯度是 0.1^2=0.01,数值正常;层数越多,乘积效应越明显,梯度消失/爆炸越严重。
补充:为什么激活函数会加剧这个问题?
比如sigmoid/tanh,它们的梯度最大值分别是0.25和1:
sigmoid的梯度 \sigma^\prime(x) \leq 0.25,多层相乘后梯度会更快消失;
这也是 ReLU能缓解梯度消失 的原因(x>0时梯度=1,不会缩小梯度)。
总结
- 梯度消失/爆炸的核心是链式法则的乘积效应:深层网络中,梯度反复乘以权重(或激活函数梯度),数值被持续缩放;
权重平均<1→ 梯度越乘越小(消失),权重平均>1→ 梯度越乘越大(爆炸);- 快捷连接的作用就是给梯度加一条“直通路径”(梯度+1),避免梯度被持续缩放至0或无穷大。