反向传播
反向传播是训练神经网络的核心算法。它用链式法则计算损失函数对每个参数的梯度。
flowchart LR A[输入 x] --> B[层1<br/>W₁] B --> C[层2<br/>W₂] C --> D[损失 L] D -->|∂L/∂W₂| C C -->|∂L/∂W₁| B前向传播计算输出,反向传播计算梯度。
对于复合函数 :
import torchimport torch.nn as nn
# 一个简单的两层网络class SimpleNet(nn.Module): def __init__(self): super().__init__() self.fc1 = nn.Linear(10, 5) self.fc2 = nn.Linear(5, 1)
def forward(self, x): h = torch.relu(self.fc1(x)) return self.fc2(h)
model = SimpleNet()x = torch.randn(3, 10) # batch=3y = torch.randn(3, 1) # 目标值
# 前向传播pred = model(x)loss = nn.MSELoss()(pred, y)
# 反向传播loss.backward()
# 查看梯度print(f"fc1.weight 梯度形状: {model.fc1.weight.grad.shape}")print(f"fc2.weight 梯度形状: {model.fc2.weight.grad.shape}")手动计算梯度
Section titled “手动计算梯度”# 手动实现一个计算图的反向传播x = torch.tensor([2.0], requires_grad=True)w = torch.tensor([3.0], requires_grad=True)b = torch.tensor([1.0], requires_grad=True)
# 前向: y = w * x + b, loss = y²y = w * x + bloss = y ** 2
# 反向loss.backward()
# 手动验证: ∂loss/∂w = 2y * xy_val = (3.0 * 2.0 + 1.0) # = 7expected_grad = 2 * y_val * 2.0 # = 28print(f"自动求导: {w.grad.item()}, 手动计算: {expected_grad}")梯度消失与爆炸
Section titled “梯度消失与爆炸”深层网络中,梯度经过多层反向传播时反复相乘,导致:
flowchart TD A[梯度 < 1] -->|反复相乘| B[梯度消失<br/>≈ 0] C[梯度 > 1] -->|反复相乘| D[梯度爆炸<br/>→∞]| 问题 | 原因 | 表现 | 解决 |
|---|---|---|---|
| 梯度消失 | 深层网络梯度过小 | 浅层不更新 | ReLU、残差连接、LayerNorm |
| 梯度爆炸 | 深层网络梯度过大 | 参数变为 NaN | 梯度裁剪、学习率调低 |
逐步手动反向传播
Section titled “逐步手动反向传播”用一个简单例子演示完整的反向传播过程:
# 手动计算反向传播# 前向: a = w*x, b = a+bias, c = relu(b), loss = c²x = torch.tensor([2.0])w = torch.tensor([3.0], requires_grad=True)bias = torch.tensor([1.0], requires_grad=True)
# 前向传播(记录每一步)a = w * x # a = 6b = a + bias # b = 7c = torch.relu(b) # c = 7loss = c ** 2 # loss = 49
# 反向传播:从 loss 往回推# ∂loss/∂c = 2c = 14# ∂c/∂b = 1 (ReLU 在 b>0 时导数为 1)# ∂loss/∂b = ∂loss/∂c * ∂c/∂b = 14 * 1 = 14# ∂b/∂bias = 1, ∂loss/∂bias = 14 * 1 = 14# ∂b/∂a = 1, ∂loss/∂a = 14 * 1 = 14# ∂a/∂w = x = 2, ∂loss/∂w = 14 * 2 = 28
loss.backward()print(f"∂loss/∂w: {w.grad.item()} (手动: 28)") # 28print(f"∂loss/∂bias: {bias.grad.item()} (手动: 14)") # 14为什么 PyTorch 需要 .backward() 而不是自动求导?
Section titled “为什么 PyTorch 需要 .backward() 而不是自动求导?”PyTorch 采用动态计算图——每次前向传播时构建图,反向传播后释放。这样设计的好处是:支持动态控制流(if/while),每次迭代可以有不同的图结构,这是静态图框架(如 TensorFlow 1.x)做不到的。
# 动态图的优势:每次迭代可以有不同的计算路径def dynamic_forward(x, threshold=0): if x.sum() > threshold: return x * 2 else: return x * 3
x = torch.randn(5, requires_grad=True)y = dynamic_forward(x) # 图结构取决于运行时数据y.sum().backward() # 自动计算梯度