梯度消失、梯度爆炸的原因

原因

梯度是反向传播时“链式法则”的乘积:每经过一层,梯度都会 乘以 该层的 权重,导致:

  • 如果权重的绝对值 平均小于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,梯度正常

解释

  1. 梯度消失的本质:每层梯度 乘以权重(0.1),10层后是 0.1^{10}=10^{-10},梯度趋近于0,浅层参数(如输入层)几乎无法更新。

  2. 梯度爆炸的本质:每层梯度 乘以权重(2.0),10层后是 2^{10}=1024,梯度变得极大,参数更新时会“跳飞”,模型无法收敛。

  3. 为什么层数少没事:2层时梯度是 0.1^2=0.01,数值正常;层数越多,乘积效应越明显,梯度消失/爆炸越严重。

补充:为什么激活函数会加剧这个问题?

比如sigmoid/tanh,它们的梯度最大值分别是0.25和1:

  • sigmoid的梯度 \sigma^\prime(x) \leq 0.25,多层相乘后梯度会更快消失;

  • 这也是 ReLU能缓解梯度消失 的原因(x>0时梯度=1,不会缩小梯度)。

总结

  1. 梯度消失/爆炸的核心是链式法则的乘积效应:深层网络中,梯度反复乘以权重(或激活函数梯度),数值被持续缩放;
  2. 权重平均<1 → 梯度越乘越小(消失),权重平均>1 → 梯度越乘越大(爆炸);
  3. 快捷连接的作用就是给梯度加一条“直通路径”(梯度+1),避免梯度被持续缩放至0或无穷大。

原文出处:/show_1GW2eVlKGbYD.html