Skip to content

反向传播

反向传播是训练神经网络的核心算法。它用链式法则计算损失函数对每个参数的梯度。

flowchart LR
A[输入 x] --> B[层1<br/>W₁]
B --> C[层2<br/>W₂]
C --> D[损失 L]
D -->|∂L/∂W₂| C
C -->|∂L/∂W₁| B

前向传播计算输出,反向传播计算梯度。

对于复合函数 L=f(g(h(x)))L = f(g(h(x))):

∂L∂x=∂L∂f⋅∂f∂g⋅∂g∂h⋅∂h∂x\frac{\partial L}{\partial x} = \frac{\partial L}{\partial f} \cdot \frac{\partial f}{\partial g} \cdot \frac{\partial g}{\partial h} \cdot \frac{\partial h}{\partial x}
import torch
import 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=3
y = 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}")
# 手动实现一个计算图的反向传播
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 + b
loss = y ** 2
# 反向
loss.backward()
# 手动验证: ∂loss/∂w = 2y * x
y_val = (3.0 * 2.0 + 1.0) # = 7
expected_grad = 2 * y_val * 2.0 # = 28
print(f"自动求导: {w.grad.item()}, 手动计算: {expected_grad}")

深层网络中,梯度经过多层反向传播时反复相乘,导致:

flowchart TD
A[梯度 < 1] -->|反复相乘| B[梯度消失<br/>≈ 0]
C[梯度 > 1] -->|反复相乘| D[梯度爆炸<br/>→∞]
问题原因表现解决
梯度消失深层网络梯度过小浅层不更新ReLU、残差连接、LayerNorm
梯度爆炸深层网络梯度过大参数变为 NaN梯度裁剪、学习率调低

用一个简单例子演示完整的反向传播过程:

# 手动计算反向传播
# 前向: 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 = 6
b = a + bias # b = 7
c = torch.relu(b) # c = 7
loss = 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)") # 28
print(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() # 自动计算梯度