Appearance
從零理解神經網路(四):反向傳播——誰該為錯誤負責?
本文是「從零理解神經網路」系列的第四篇。上一篇我們學了梯度下降,但還有一個問題:梯度怎麼算?這篇用「傳話遊戲」來解釋深度學習最核心的算法——反向傳播。
問題:梯度怎麼算?
上一篇我們知道,更新權重需要梯度:
w_new = w_old - η × ∂L/∂w這個 ∂L/∂w 就是「損失對權重 w 的偏導數」,也就是「如果我稍微調整 w,損失會變化多少」。
但神經網路可能有數百萬個權重,難道要一個一個手動算偏導數?
反向傳播(Backpropagation) 就是解決這個問題的算法。它能自動且高效地計算所有權重的梯度。
傳話遊戲比喻
讓我們用一個大家都玩過的遊戲來理解反向傳播。
場景設定
原始訊息:「6」
傳遞過程:
小明 ──→ 小華 ──→ 小美 ──→ 最終輸出
每個人會把聽到的數字乘上自己的「係數」:
- 小明的係數:0.5
- 小華的係數:0.8
- 小美的係數:0.3前向傳播(Forward Pass)
訊息從左到右傳遞:
原始:6
↓ × 0.5
小明傳出:3
↓ × 0.8
小華傳出:2.4
↓ × 0.3
小美傳出:0.72(最終輸出)最終輸出是 0.72,但目標是 6。差太多了!
這就是「前向傳播」——資料從輸入流向輸出。
發現錯誤
目標:6
輸出:0.72
誤差:6 - 0.72 = 5.28(差了這麼多!)反向追查:誰該負責?
現在要回答一個關鍵問題:這個錯誤,每個人該負多少責任?
這就需要「反向傳播」——從輸出往回追溯。
核心工具:鏈式法則(Chain Rule)
假設損失 L 對最終輸出的梯度是 ∂L/∂output,我們要算 L 對每個係數的梯度。
∂L
──
∂小美係數
從輸出往回算:
∂L ∂L ∂output
────── = ────── × ──────────
∂小美係數 ∂output ∂小美係數
↑ ↑
上一層傳來 這一層的貢獻
的梯度 (小美的輸入值)具體計算
假設我們用 MSE 損失:L = ½(target - output)²
Step 1:輸出層的梯度
∂L/∂output = -(target - output) = -(6 - 0.72) = -5.28Step 2:小美的責任(第三層)
∂L/∂w₃ = ∂L/∂output × ∂output/∂w₃
= -5.28 × 2.4(小美的輸入)
= -12.67Step 3:把梯度傳回去
∂L/∂(小美的輸入) = ∂L/∂output × w₃
= -5.28 × 0.3
= -1.58Step 4:小華的責任(第二層)
∂L/∂w₂ = ∂L/∂(小美的輸入) × ∂(小美的輸入)/∂w₂
= -1.58 × 3(小華的輸入)
= -4.75Step 5:繼續往回傳...
以此類推,我們可以算出每一層的梯度。
責任分配結果
假設計算完成後:
| 層 | 梯度(責任) | 責任比例 |
|---|---|---|
| 小明 (w₁) | -2.54 | 20% |
| 小華 (w₂) | -4.75 | 37% |
| 小美 (w₃) | -12.67 | 43% |
小美的責任最大!因為她是最後一關,對輸出的影響最直接。
鏈式法則:反向傳播的數學基礎
反向傳播的核心就是微積分的鏈式法則:
如果 y = f(g(x)),則 dy/dx = dy/dg × dg/dx對於多層網路:
L = L(a₃(a₂(a₁(x))))
∂L/∂w₁ = ∂L/∂a₃ × ∂a₃/∂a₂ × ∂a₂/∂a₁ × ∂a₁/∂w₁
↑ ↑ ↑ ↑
第3層 第3層 第2層 第1層
梯度 對第2層 對第1層 對w₁
的導數 的導數 的導數關鍵洞見:每一層只需要知道「上一層傳來的梯度」和「自己的局部導數」,就能算出「該傳給下一層的梯度」。
這就是為什麼反向傳播如此高效——它是一個局部計算的組合。
完整的訓練流程
現在讓我們把前向傳播和反向傳播組合起來:
┌─────────────────────────────────────────────────────┐
│ 一次訓練迭代 │
├─────────────────────────────────────────────────────┤
│ │
│ ① 前向傳播(Forward Pass) │
│ 輸入 ──→ 第1層 ──→ 第2層 ──→ 第3層 ──→ 輸出 │
│ ↓ │
│ 計算損失 │
│ ↓ │
│ ② 反向傳播(Backward Pass) │
│ ∂L/∂w₁ ←── ∂L/∂w₂ ←── ∂L/∂w₃ ←── ∂L/∂output │
│ │
│ ③ 更新權重(Gradient Descent) │
│ w₁ = w₁ - η × ∂L/∂w₁ │
│ w₂ = w₂ - η × ∂L/∂w₂ │
│ w₃ = w₃ - η × ∂L/∂w₃ │
│ │
└─────────────────────────────────────────────────────┘
↓
重複很多次反向傳播的細節
不同運算的局部導數
每種運算都有對應的局部導數規則:
加法:
c = a + b
∂c/∂a = 1, ∂c/∂b = 1
梯度「平均分配」給兩個輸入乘法:
c = a × b
∂c/∂a = b, ∂c/∂b = a
梯度「交叉傳遞」ReLU:
y = max(0, x)
∂y/∂x = 1 if x > 0 else 0
正數時梯度通過,負數時梯度被「關閉」Tanh:
y = tanh(x)
∂y/∂x = 1 - tanh²(x) = 1 - y²Sigmoid:
y = σ(x)
∂y/∂x = σ(x) × (1 - σ(x)) = y × (1 - y)梯度累加
如果一個節點被多個運算使用,它的梯度要累加:
a ──→ b
↘ ↗
c
如果 a 同時影響 b 和 c,則:
∂L/∂a = ∂L/∂b × ∂b/∂a + ∂L/∂c × ∂c/∂a這就是為什麼 micrograd 中用 grad += 而不是 grad =。
為什麼反向傳播很厲害?
計算效率
假設網路有 N 個權重。
暴力方法:
- 對每個權重,微調一點點,看損失變化多少
- 需要 N 次前向傳播
- 時間複雜度 O(N × 前向傳播時間)
反向傳播:
- 一次前向傳播 + 一次反向傳播
- 反向傳播的時間約等於前向傳播
- 時間複雜度 O(2 × 前向傳播時間)
對於有數百萬參數的網路,反向傳播快了數百萬倍!
模組化
每一層只需要實現:
forward():前向傳播backward():計算局部梯度並傳遞
這讓我們可以像樂高一樣組合不同的層,自動微分框架會處理所有梯度計算。
常見問題
梯度消失(Vanishing Gradient)
當網路很深時,梯度可能在傳遞過程中越來越小:
假設每層的局部梯度都是 0.25(Sigmoid 的最大值)
10 層後:0.25^10 ≈ 0.000001
梯度幾乎消失,淺層學不動!解決方案:
- 使用 ReLU(梯度是 0 或 1)
- 使用殘差連接(ResNet)
- 使用 BatchNorm
梯度爆炸(Exploding Gradient)
相反地,梯度也可能越來越大:
如果每層的局部梯度都是 4
10 層後:4^10 ≈ 1,000,000
梯度爆炸,參數更新太劇烈!解決方案:
- 梯度裁剪(Gradient Clipping)
- 適當的權重初始化
- 使用 BatchNorm
互動視覺化
我製作了一個互動工具,讓你可以:
- 觀察三層網路的前向傳播和反向傳播
- 看每一層的「責任」分配
- 調整權重和學習率,觀察訓練過程
- 追蹤詳細的梯度計算
建議你操作時注意:
- 反向傳播時,梯度如何從輸出層往回傳
- 每層的「責任」(梯度)比例
- 更新後,損失如何下降
小結
今天我們學到了:
- 反向傳播是高效計算梯度的算法
- 用傳話遊戲理解:從錯誤往回追,分配責任
- 鏈式法則是數學基礎:∂L/∂w = ∂L/∂output × ∂output/∂...× ∂.../∂w
- 每層只需要知道「上層梯度」和「局部導數」
- 梯度要累加(一個節點可能影響多個輸出)
- 反向傳播比暴力方法快數百萬倍
- 注意梯度消失和梯度爆炸問題
下一篇預告
反向傳播需要追蹤每個運算的依賴關係。這是怎麼做到的?
下一篇我們會介紹計算圖(Computational Graph)——深度學習框架(如 PyTorch)的核心數據結構。我們會實際看看 micrograd 如何用幾十行程式碼實現「自動微分」。
本文是「從零理解神經網路」系列的第四篇。如果你覺得有幫助,歡迎分享給也在學習 ML 的朋友。