【Pytorch】MLP反向传播
把这份“MLP 反向传播”讲义拆成 3 层:
A. 最终要刻进脑子的“一张模板”
B. 讲义里哪些符号对应模板里的哪一格
C. 以后写代码/做实验时,怎样直接调用(或手写)这一步
下面按“模板 → 符号 → 代码”顺序带你填完,你就知道自己到底要学什么、用到什么、怎么用。
A. 一张永远通用的“反向传播模板”
任何深层网络,只要节点间是“可导”连接,梯度都能拆成 3 个局部量连环乘:
梯度 = 局部梯度₁ × 局部梯度₂ × 局部梯度₃
= (dLoss/dOutput) × (dOutput/dNet) × (dNet/dWeight)
把 3 个局部量各起一个名字,以后见任何新层都套用:
-
δ(output) ≡ dLoss/dOutput
-
σ′ ≡ dOutput/dNet(激活函数局部斜率)
-
x ≡ dNet/dWeight(前层输出,也叫“输入”)
所以
dLoss/dWeight = δ(output) · σ′ · x
如果当前层不是输出层,而是隐藏层,就把“上游传回来的 δ”先乘一次“权重”,再乘自己的 σ′,得到“本层 δ”,再继续往后传。
这就是讲义里反复出现的:
δⱼ = σ′(netⱼ) · Σₖ δₖ Wₖⱼ
B. 讲义符号 vs 模板符号 对照表
表格
复制
模板名字 | 讲义符号 | 含义 |
---|---|---|
δ(output) | (Oₖ – tₖ) | 输出层误差 |
σ′ | Oₖ(1–Oₖ) | sigmoid 局部斜率 |
x | Oⱼ(或 xⱼ⁰) | 前层输出(本层输入) |
δ(hidden) | δⱼ | 隐藏层待传量 |
Wjk | Wjk | 从隐藏单元 j → 输出单元 k 的权重 |
讲义里那 2 行最干净的结论,就是模板的具体化:
-
输出层权重梯度
∂E/∂Wjk = Oj · δk , 其中 δk = Ok(1–Ok)(Ok–tk) -
隐藏层权重梯度
∂E/∂Wij = Oi · δj , 其中 δj = Oj(1–Oj) Σk δk Wjk
把这两行背下来,你就拥有了“手写反向传播”的最低配置。
C. 你会怎么用——三种常见场景
场景 1:PyTorch 默认自动求导(99% 工作)
Python
import torch.nn as nn
net = nn.Sequential(nn.Linear(10, 20),nn.Sigmoid(),nn.Linear(20, 2))
loss_fn = nn.MSELoss()
# 前向
y_hat = net(x)
loss = loss_fn(y_hat, y)
# 反向 + 更新
loss.backward() # 自动跑完上面整套链式法则
optimizer.step()
你不需要手写 δ,但必须能看懂 loss.backward()
背后就是在跑“δk = Ok(1–Ok)(Ok–tk)”和“δj = Oj(1–Oj) Σk δk Wjk”这两行。
场景 2:自定义层,需要手写 backward()
Python
class MySigmoidLinear(nn.Module):def forward(self, x, W, b):z = x@W + bo = torch.sigmoid(z)return o, z # 把 z 保存,待会求 σ′def backward(self, grad_output, o, z, x, W):sigmoid_prime = o * (1 - o)delta = grad_output * sigmoid_prime # 本层 δgrad_W = x.t() @ delta # 模板:δ · xgrad_x = delta @ W.t() # 传给前层的梯度return grad_W, grad_x
这里你亲自实现了“δ = 上游梯度 × σ′”和“∂Loss/∂W = δᵀ · x”。
场景 3:数值梯度检查(debug 神器) 无论多复杂的网络,你都可以拿“这两行解析梯度”与“数值微分”比较,误差 < 1e−6 就说明代码没写错。
D. 你现在要掌握的“最小闭环”
-
空手写出输出层 δk 和隐藏层 δj 的表达式。
-
用 numpy 写 20 行代码,把 XOR 数据集训练到 100% 准确率(只准用两层 sigmoid + MSE,不准调包自动求导)。
-
把 PyTorch 的
loss.backward()
关掉,用手写梯度替换,验证 loss 下降曲线和自动求导完全一致。
完成这 3 步,你就真正“拥有”了反向传播——以后无论遇到 CNN、RNN、Attention,都只是把“σ′”换成别的激活函数,把“Σ δk Wjk”换成卷积或循环连接,骨架永远不变。