JOVANA
Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

反向傳播揭秘

反向傳播不過是把鏈式法則反著穿過整個網路——一種精巧的記帳技巧,告訴每一個權重它究竟把誤差推動了多少。本文講清「責任」如何向後流動,沒有魔法,也不需要微積分博士學位。

問題:該怪誰?

上一篇裡,你把一個輸入送過網路——也就是前向傳播——末端吐出一個預測。你拿它和正確答案比較,得到一個數:損失(loss),衡量網路錯得有多離譜。現在難題來了。網路的各層裡散佈著成千上萬個權重。要想變好,它得對*每一個*權重弄清楚同一件事:如果我把你調大一丁點,損失會上升還是下降,幅度多大?

那個「損失會變多少」的數就是梯度(gradient)——嚴格說,是損失對那一個權重的偏導數。把每個權重的梯度都收集起來,你就得到一個巨大的箭頭,指向讓損失上升最快的方向。朝反方向邁一步——這就是梯度下降,下一篇會講——網路就變好了。反向傳播*不是*那個學習步驟。它是在學習發生之前,高效地把所有這些梯度算出來的那套機制。

你可以想像一種蠻力辦法:動一下某個權重,把整個前向傳播重跑一遍,看損失變了多少,對全部一百萬個權重重複。這能行,但一百萬個權重意味著每個訓練樣本要跑一百萬次完整前向傳播,慢得沒指望。反向傳播能算出*完全一樣*的答案,代價卻大約只是一次額外的傳播——而正是這份高效,才讓深度網路根本能夠被訓練。

一招制勝:鏈式法則

神經網路就是一長串簡單運算層層疊起來:乘以權重、加上偏置、過一遍激活函數擠壓一下、把結果餵給下一層,如此往復直到損失。當函數像這樣層層嵌套時,微積分有一個精確的工具來求出最前端的一點變化如何一路波及到最末端:鏈式法則。它的想法簡單到近乎讓人難以置信——*把這條路徑上各處的局部變化率乘起來*。

想像一排齒輪。若齒輪 A 轉得比 B 快三倍,B 又比 C 快兩倍,那麼轉動 A 就讓 C 快六倍——你只需相乘,3 × 2。鏈式法則對函數說的正是這件事:損失對某個靠前權重的敏感度,等於從那個權重到損失之間每一環上微小敏感度的乘積。每一環都是一個*局部*問題——「當我的輸入變化時,我的輸出怎麼變?」——而局部問題很好答。一個乘法節點、一個加法節點、一個 ReLU,它們的局部導數都簡單得要命。

計算圖:每一步運算的地圖

要機械地執行鏈式法則,我們先把整個運算畫成一張計算圖:圖中每個節點是一個小運算(一次乘法、一次加法、一次激活),箭頭表示哪個結果餵給了哪個節點。前向傳播不過是從左到右走一遍這張圖,在每個節點上填進一個數。關鍵在於,每個節點還會記住自己的輸入——回程時它會用得上。

然後我們反著走這張圖,從右到左——*反向*傳播的名字正源於此。我們從損失處出發,帶著一個等於 1 的梯度(損失對自身的敏感度恰好是 1),把這個信號往回推。在每個節點上,我們問那個局部問題,把傳進來的梯度乘以局部導數,再把乘積交給上游的節點。經過所有這些乘法之後,到達任何一個權重處的信號,*就是*它的梯度。一次回程,所有梯度一次到手。

當一個節點的輸出分叉、餵給下游好幾個地方時,從那幾處流回來的梯度只需在該節點處*相加*——它影響過的每條路徑上的「責任」被匯總起來。這一條規則(沿路徑相乘、跨路徑相加)就是整個演算法的全部。正因如此,靠前的隱藏層裡某個權重——它通過許多下游路線觸及損失——才會正確地累積起所有這些路線上的責任。

# Forward: remember each node's inputs
z = w * x + b          # node sees x
a = relu(z)            # node sees z
loss = (a - target)^2  # node sees a

# Backward: start at 1, multiply local derivatives
g_loss = 1
g_a = g_loss * 2*(a - target)   # d loss / d a
g_z = g_a * (1 if z > 0 else 0) # d relu / d z
g_w = g_z * x                   # d z / d w  -> gradient for w!
g_b = g_z * 1                   # d z / d b  -> gradient for b!
一個神經元,先前向再後向。每一行後向計算都只是「傳入梯度 × 局部導數」。

自動微分:讓機器替你做微積分

令人解脫的地方來了:你永遠不必手推這些。現代框架會在前向傳播執行時替你把計算圖搭好,再自動反著回放一遍。這就是自動微分(常叫 autodiff 或 autograd)。它既不是你上學時做的符號代數,也不是第一節裡那種搖搖晃晃的有限差分微調——它透過把每個基本運算已知的局部導數組合起來,算出精確的梯度。

實際操作中這意味著:你只用普通程式碼寫出前向計算——各層、激活、損失——然後呼叫一句類似 loss.backward()。框架早已知道它跑過的每個乘法、加法、ReLU、softmax 的局部導數,於是反向遍歷記錄好的圖,把梯度沉澱到每一個參數上。反向傳播是「反向模式自動微分」在網路損失上的*具體*應用;自動微分則是那台通用引擎。

當梯度信號衰減時

那條「沿路徑相乘」的規則有其陰暗面。把梯度送過很多層,你就是在把許多數連乘。如果這些局部導數大多小於 1——像 sigmoid 這類老激活函數就容易如此,因為除了一個窄帶之外它的斜率都很小——乘積就會向零收縮。等信號傳到最靠前的幾層時,已是細若游絲。那些層幾乎不更新,學得慢得令人痛苦。這就是著名的梯度消失問題。

反過來的情形同樣會發生:如果局部因子大於 1,乘積可能炸成巨大的數,訓練直接崩潰。這些都不是什麼奇異的 bug——它們是鏈式法則連乘最直接、最誠實的後果,多年來正是它們讓深度網路幾乎無法訓練。解鎖現代深度學習的諸多進展,很大程度上就是在設法讓這個反向信號活下去:像 ReLU 這種不擠壓斜率的激活、謹慎的權重初始化,以及給梯度一條乾淨捷徑回家的架構技巧。

誠實地說清反向傳播是什麼、不是什麼很有必要。它精確、高效,是你聽說過的幾乎每一個網路背後的主力。但它不是大腦學習的方式——真實神經元並沒有一個全域的反向通道,把誤差信號沿著它們傳上來的那些導線原路送回。它也不是智能;它是一套求斜率的程序。真正了不起的是:這套樸素的程序重複幾十億次,竟能把一堆數字帶到那麼遠。

串起來看

於是這就是完整的循環——網路學習時,它對每一批資料重複,重複幾百萬次。反向傳播是其中的第 2、3 步——一次性算出對每個權重而言「下坡」是哪個方向的那一部分。

  1. 前向傳播:把輸入送過各層,同時把每一步運算記錄進計算圖,最後算出損失。
  2. 為反向傳播播種:從損失處出發,帶上一個等於 1 的梯度。
  3. 反向傳播:從右到左掃過計算圖,在每個節點把傳入梯度乘以局部導數、在路徑匯合處相加,直到每個權重都拿到屬於自己的梯度。
  4. 更新(這是優化器的活,不是反向傳播):讓每個權重朝其梯度的反方向邁一小步。然後帶上下一批資料回到第 1 步。

這就是整台機器。鏈式法則提供數學,計算圖提供組織,自動微分提供勞力,梯度下降把算出的梯度變成真正的學習。在本階最後一篇裡,我們將讓一個完整的網路在真實資料上跑這個循環,看著損失曲線一路下落——那正是網路真正自我教學的時刻。