跳轉到

03.2 反向傳播演算法#

本章導讀#

泛近似定理說「學得到」的函數存在,但沒說怎麼找。 答案是微積分裡最古老的一行公式——鏈鎖律——配上聰明的計算順序。 反向傳播(Backpropagation)不是新理論,而是鏈鎖律的動態規劃:每個參數只算一次。

淺顯說明#

想像一條工廠生產線:原料(輸入)→ 組裝 → 品檢 → 出貨(輸出)。 出貨品質不合格(損失很大),要追責任:

  • 前向:順著生產線算出輸出與損失。
  • 反向:從損失往回走,把「責任」(梯度)一層層分回去—— 離損失越近的工序,責任越大;每一站的責任 = 上游責任 × 本站敏感度。

關鍵省力技巧:先用前向把每站的中間結果存起來,回程直接查表乘一乘—— 否則每個參數都要重跑整條生產線,計算量會爆炸。

正規理論#

問題設定#

\(L\) 層前饋網路:

\[ \mathbf{a}^{(0)} = \mathbf{x}, \qquad \mathbf{z}^{(l)} = W^{(l)} \mathbf{a}^{(l-1)} + \mathbf{b}^{(l)}, \qquad \mathbf{a}^{(l)} = \sigma(\mathbf{z}^{(l)}), \qquad L = \ell(\mathbf{a}^{(L)}, y) \]

目標:計算 \(\dfrac{\partial L}{\partial W^{(l)}}\) (所有層)。

鏈鎖律與誤差項#

定義第 \(l\) 層的誤差向量:

\[ \boldsymbol{\delta}^{(l)} = \frac{\partial L}{\partial \mathbf{z}^{(l)}} \in \mathbb{R}^{n_l} \]

反向遞推(鏈鎖律的核心):

\[ \boldsymbol{\delta}^{(L)} = \nabla_{\mathbf{a}^{(L)}} \ell \odot \sigma'(\mathbf{z}^{(L)}), \qquad \boldsymbol{\delta}^{(l)} = \left( W^{(l+1)\top} \boldsymbol{\delta}^{(l+1)} \right) \odot \sigma'(\mathbf{z}^{(l)}) \]

其中 \(\odot\) 是逐元素乘(Hadamard 積)。梯度:

\[ \boxed{\; \frac{\partial L}{\partial W^{(l)}} = \boldsymbol{\delta}^{(l)} \, \mathbf{a}^{(l-1)\top}, \qquad \frac{\partial L}{\partial \mathbf{b}^{(l)}} = \boldsymbol{\delta}^{(l)} \;} \]

推導#

對單一權重 \(W^{(l)}_{ij}\) (連接 \(a^{(l-1)}_j\) 到 \(z^{(l)}_i\) ), \(z^{(l)}_i = \sum_k W^{(l)}_{ik} a^{(l-1)}_k + b^{(l)}_i\) 直接偏導得 \(\partial z^{(l)}_i / \partial W^{(l)}_{ij} = a^{(l-1)}_j\) ,故

\[ \frac{\partial L}{\partial W^{(l)}_{ij}} = \frac{\partial L}{\partial z^{(l)}_i}\cdot\frac{\partial z^{(l)}_i}{\partial W^{(l)}_{ij}} = \delta^{(l)}_i\, a^{(l-1)}_j \]

而 \(\boldsymbol{\delta}^{(l)}\) 的遞推由鏈鎖律:

\[ \delta^{(l)}_j = \frac{\partial L}{\partial z^{(l)}_j} = \sum_i \frac{\partial L}{\partial z^{(l+1)}_i}\frac{\partial z^{(l+1)}_i}{\partial a^{(l)}_j}\frac{\partial a^{(l)}_j}{\partial z^{(l)}_j} = \sum_i \delta^{(l+1)}_i W^{(l+1)}_{ij}\, \sigma'(z^{(l)}_j) \]

計算複雜度#

反向傳播把梯度計算從 \(O(P \cdot F)\) ( \(P\) 個參數、每次前向 \(F\) )降到與前向同階: 總計算量約為前向的 2–3 倍。這是自動微分「反向模式」的特例—— 一個輸出、多個輸入時,反向模式壓倒性地快,而神經網路訓練恰好就是這種情形(損失是唯一輸出)。

自動微分#

現代框架(PyTorch/JAX)把每個基本運算記錄成計算圖,自動執行上述過程:

\[ \frac{\partial L}{\partial v} = \sum_{u \in \mathrm{children}(v)} \frac{\partial L}{\partial u}\, \frac{\partial u}{\partial v} \]

Diff-Physics 等可微物理引擎更讓梯度穿透物理模擬流動(08.1 節)。

簡易範例:兩層網路手算#

網路: \(z^{(1)} = w_1 x\) , \(a^{(1)} = \sigma(z^{(1)})\) , \(z^{(2)} = w_2 a^{(1)}\) , \(L = \frac{1}{2}(z^{(2)} - y)^2\) 。 取 \(x = 1\) , \(w_1 = 1\) , \(w_2 = 1\) , \(y = 0\) , \(\sigma = \tanh\) 。

前向: \(z^{(1)} = 1\) , \(a^{(1)} = \tanh 1 \approx 0.7616\) , \(z^{(2)} = 0.7616\) , \(L \approx 0.290\) 。

反向:

\[ \delta^{(2)} = \frac{\partial L}{\partial z^{(2)}} = z^{(2)} - y \approx 0.7616 \]
\[ \delta^{(1)} = w_2\,\delta^{(2)} \cdot \tanh'(z^{(1)}) = 1 \times 0.7616 \times (1 - 0.7616^2) \approx 0.3208 \]

梯度:

\[ \frac{\partial L}{\partial w_2} = \delta^{(2)}\, a^{(1)} \approx 0.580, \qquad \frac{\partial L}{\partial w_1} = \delta^{(1)}\, x \approx 0.321 \]

用 \(\eta = 0.5\) 更新後 \(L\) 立即下降——誤差自己教了網路。

歷史典故#

習題#

  1. 對上例多算一步:更新後的 \(w_1, w_2\) 與新的 \(L\) ,驗證損失下降。
  2. 證明:對 softmax 輸出加交叉熵損失, \(\boldsymbol{\delta}^{(L)} = \hat{\mathbf{y}} - \mathbf{y}\) (形式極簡)。
  3. 若網路有 10 個輸出與 1 個輸入,前向模式與反向模式自動微分哪個快?為什麼?

補充#

1. 數學推導證明#

證明(習題 2):softmax + 交叉熵損失的誤差項 \(\boldsymbol{\delta}^{(L)} = \hat{\mathbf{y}} - \mathbf{y}\) 。

設輸出 \(\hat{y}_k = \mathrm{softmax}(\mathbf{z})_k = \dfrac{e^{z_k}}{\sum_{k'} e^{z_{k'}}}\) ,損失用交叉熵( \(y\) 為 one-hot):

\[ L = -\sum_k y_k \log \hat{y}_k \]

先算 softmax 的偏導:記 \(S = \sum_{k'} e^{z_{k'}}\) ,則

\[ \frac{\partial \hat{y}_k}{\partial z_j} = \frac{e^{z_k}\delta_{kj} S - e^{z_k} e^{z_j}}{S^2} = \hat{y}_k \,\mathbb{1}[k = j] - \hat{y}_k\, \hat{y}_j \]

再由鏈鎖律( \(y\) 是常數):

\[ \frac{\partial L}{\partial z_j} = -\sum_k y_k \frac{1}{\hat{y}_k} \frac{\partial \hat{y}_k}{\partial z_j} = -\sum_k y_k \frac{\hat{y}_k \mathbb{1}[k=j] - \hat{y}_k \hat{y}_j}{\hat{y}_k} = -y_j + \hat{y}_j \sum_k y_k \]

因 \(\sum_k y_k = 1\) (one-hot),得

\[ \boxed{\;\boldsymbol{\delta}^{(L)} = \hat{\mathbf{y}} - \mathbf{y}\;} \]

意義:誤差項就是「預測減答案」——預測錯得越離譜,梯度越大。這是深度學習框架中 softmax 輸出層幾乎總是搭配交叉熵的原因:兩者相減後對數項恰好抵消,梯度既簡潔又數值穩定。 \(\blacksquare\)

2. 簡易範例#

範例一(習題 1):更新一步驗證損失下降。 沿用本章手算例( \(x=1\) , \(y=0\) , \(\sigma=\tanh\) ),用 \(\eta = 0.5\) 更新後 \(w_1 \approx 0.8396\) 、 \(w_2 \approx 0.7099\) ,新損失 \(L \approx 0.152\) ,比原來的 \(0.290\) 下降近一半——誤差自己教了網路。

範例二:手算 backprop 並用數值差分驗證。 程式見 _code/03.2-backprop-manual.py:實作一個 2-2-1 的兩層網路(tanh 活化、均方誤差),先用鏈鎖律解析算出 \(\partial L / \partial W\) ,再用中心差分

\[ \frac{\partial L}{\partial \theta} \approx \frac{L(\theta + \varepsilon) - L(\theta - \varepsilon)}{2\varepsilon} \]

逐參數驗證。輸出顯示解析梯度與數值梯度在 \(10^{-9}\) 級完全一致——反向傳播確實是鏈鎖律的正確實作。