EECS498 · Deep Learning Revisited · 02

Backpropagation and Computational Graphs

一个 scalar loss 为什么能更新几百万个参数?你在 A2/A3 里已经把答案逐层写过:每个 operation 保存局部信息,反向时把 upstream gradient 乘进来。

50–65 分钟 A2–A3 Manual backward

30-second recall

30 秒回忆

你实现过 Linear.backward、ReLU.backward、整网 backward,并用 numerical gradient 检查。

  1. dout 表示什么?
  2. cache 为什么来自 forward?
  3. 梯度在分支处如何组合?

Why Did We Need This?

Forward 只能告诉我们当前预测有多差;训练还需要回答:每个参数轻微变化时,loss 会怎样变化。直接对每个参数试探一次代价巨大。Backprop 复用计算图,按反向拓扑顺序一次求完。

scores [N,C]
dScores [N,C]
dW₂ / dH [H,C] / [N,H]
dW₁ [D,H]

What You Actually Implemented

A2/two_layer_net.py 手写整条 gradient;A3/fully_connected_networks.py 把它拆成可组合的 Linear、ReLU 以及 cache;eecs598/grad.py 提供数值梯度基线。后者是 assignment helper,不归因为用户实现。

Mental model

反向传播不是“loss 把答案广播给所有参数”,而是每个局部 operation 收到 ∂L/∂output,再用自己的局部导数产出 ∂L/∂inputs。

Mechanism · vector-Jacobian product

无需显式构造巨大 Jacobian。若 Y=XW+b,upstream 是 dY,局部反向只需矩阵乘法:

dX = dY Wᵀ      [N,H]·[H,D] → [N,D]
dW = Xᵀ dY      [D,N]·[N,H] → [D,H]
db = Σₙ dY[n]   [H]

ReLU 更局部:forward 时哪些位置 x>0,backward 才允许对应 gradient 穿过。

Your code · 2022

A3/fully_connected_networks.py · Linear.backward()

View source →
dx = dout.mm(w.t()).view(x.shape)
dw = x.view(x.shape[0], -1).t().mm(dout)
db = dout.sum(axis=0)

Why This Design?

Cache 用内存换计算:forward 保存 x,w,backward 不必重新构造它们。模块化 backward 又让任意深度网络都能由同样的 local contract 组合。

Common misconception: gradient 不是“参数应该变成什么”,而是当前点最陡的上升方向。优化器还要决定符号、步长、动量和尺度。

From manual backward to autograd

A4 开始用 PyTorch autograd;这不是换了训练原理,只是 framework 在 forward 时动态记录 operation,并调用相同类别的局部 backward。现代 Transformer 的 attention、MLP、LayerNorm 最终仍沿这张图回传。

Concept Checks

若 Y=XW,X=[32,128],W=[128,64],dY 的 shape 是什么?dW 呢?

dY=[32,64],dW=XᵀdY=[128,64]。每个 gradient 与它所描述的变量 shape 相同。

同一个 tensor 同时流入两个后续分支,backward 时怎么办?

来自两条路径的梯度相加,因为总 loss 对该 tensor 的变化是各路径影响之和。

为什么 numerical gradient 适合检查,却不适合训练?

每个参数至少需要额外 forward 扰动;参数量为 P 时成本约放大 O(P),而 backprop 一次图遍历即可。

Mechanism Walkthrough · 一条真实 two-layer graph

X [N,D] → z1=XW1+b1 [N,H] → h=ReLU(z1) [N,H]
→ scores=hW2+b2 [N,C] → L scalar

dL/dscores [N,C]
→ dW2=hᵀ·dscores [H,C]
→ dh=dscores·W2ᵀ [N,H]
→ dz1=dh⊙1[z1>0] [N,H]
→ dW1=Xᵀ·dz1 [D,H]

Scalar loss 不只产生一个 gradient。对 graph 上每个 tensor,backward 计算“若它微小变化,L 如何变化”;parameter 有多少元素,就有多少偏导。

  1. 从 dL/dL=1 开始。
  2. 每个 operation 用 local derivative 变换 upstream gradient。
  3. activation 流向多个 branches 时,gradient contributions 相加。
  4. matrix multiply backward 产生对两侧 operands 的 gradients。
  5. optimizer 只更新 parameters,不更新中间 activations。
如果 forward 不保存 z1 或 ReLU mask?

Backward 无法判断哪些 units 当时为正;可以重算,但必须恢复完全相同的 activation。cache 是计算便利,不是模型 memory。

若 z1=[-2,0.5,3]、upstream dh=[4,4,4],ReLU backward 得 dz1=[0,4,4]。负 unit 不是 parameter 被删除,而是本次 example 的局部 path 被 gate 掉。

Misconception diagnosis

Gradient 不是 parameter 应变成的值,而是 loss 的局部斜率;SGD 还需乘 learning rate 并沿负方向走。

为什么 dW2 shape 与 W2 相同?

每个 W2 元素需要自己的斜率;hᵀ@dscores 消去 batch axis,留下 [H,C]。

Residual 分成两条 paths,backward 汇合时怎么办?

两条 path 对共同 tensor 的 gradients 相加。

Transfer Question

Transformer loss 经 residual 流向 identity 与 attention branch。哪条 chain-rule 规则解释汇合处 gradient?

闭卷解释

  1. 从 scalar L 解释 dW1 为什么是矩阵。
  2. 按顺序说出 W2、hidden、ReLU、W1 的 backward。
  3. 区分 cache 与 parameter。

Connection

Backprop 给斜率;下一课决定沿方向走多远、怎样利用历史 gradients,并诊断训练失败。

Based on your implementation