🗺️ AI 學習與考證地圖
中級科目三程式實戰 · 深度學習訓練迴圈

六站生產線,剪刀只能站第五位:
PyTorch 一個 batch 的更新順序

PyTorch 把 Keras 藏在 fit() 裡的事攤開來寫——每個 mini-batch 是一條六站生產線:歸零、前向、損失、反向、剪梯、更新。順序不是背誦題,是因果題:剪刀必須夾在梯度誕生(backward)與梯度使用(step)之間。本頁把三個錯序全部實跑:剪在 backward 前=剪空氣(clip 回傳 0.0);剪在 step 後=馬後炮(權重已被 40 倍超速更新);backward 後才歸零=冷凍庫(20 個 epoch 權重一動不動)。

閱讀模式

00題目

PyTorch 分類模型要在每個 mini-batch 完成一次更新,並在更新前做 gradient clipping。下列執行順序何者正確?

for x, y in loader:                                        # 一圈 = 一個 mini-batch
    # 依正確順序排列:                                     # 六張工作卡等你排
    # optimizer.zero_grad()                                # 歸零:清掉上一圈的梯度
    # logits = model(x)                                     # 前向:算預測
    # loss = criterion(logits, y)                           # 損失:對答案
    # loss.backward()                                       # 反向:算梯度
    # torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)  # 剪梯
    # optimizer.step()                                      # 更新:真正改權重
    pass                                                      # 佔位:排好就把 pass 換掉

先說:六站各在做什麼,為什麼是這個順序

把一圈想成一次小考檢討① 擦黑板(zero_grad——上一題的檢討筆記不能留著混進這一題)→ ② 作答(model(x) 前向算預測)→ ③ 對答案(criterion 算損失)→ ④ 逐科檢討(backward——算出每個參數「該往哪改、改多大」=梯度)→ ⑤ 檢討太激動就收斂點(clip——梯度太大先修剪)→ ⑥ 真正改(step——照梯度更新權重)。

順序的因果只有兩條:剪刀剪的是梯度——梯度在 ④ 誕生、在 ⑥ 被使用,所以剪刀只能站第五位黑板要在寫字前擦——PyTorch 的梯度預設會累積(本頁實跑:連 backward 兩次,梯度恰為 2 倍),所以 ① 必須在 ④ 之前。

口訣(本頁反覆驗證):歸零、前向、損失、反向、剪梯、更新——剪刀夾在「反向」與「更新」之間:梯度誕生之後、被使用之前。順帶一提:歸零放迴圈開頭或上一圈結尾都行,鐵律只有一條——在 backward 之前

先點開看:六站生產線總覽gradient clippingzero_grad 為什麼必要

不熟 AI 專有名詞?你需要先認識下列名詞

點擊後出現漸進式說明:白話說明 → 說清楚一點 → 常見錯誤與考點。

六站生產線
訓練迴圈六站zero_grad前向傳播criterion 與損失反向傳播 backwardoptimizer.step
剪刀的世界
gradient clippingmax_norm 與范數梯度爆炸clip_grad_value_ 對照
梯度的性質
梯度是什麼梯度累積:bug 與技巧歸零時機錯=冷凍
框架與批次
mini-batchoptimizer 家族Keras 對照

不熟 Python?你需要先認識下列名詞

點擊後出現漸進式說明

迴圈與縮排
for 迴圈x, y 雙變數解包縮排pass 佔位
呼叫與取用
( ) 呼叫括號. 點運算子方法 method關鍵字引數結尾底線=就地修改
基本功
import模組 module= 指派變數與命名# 註解

01逐站拆解:六張工作卡,一站一站排

把六行按正確順序排好,一站一站走。右上角的「看位置」可以把這一站放回完整程式裡看。

第 ① 站optimizer.zero_grad()——先擦黑板
    optimizer.zero_grad()   # 清掉上一圈留下的梯度

PyTorch 的梯度預設會累積——backward 算出的梯度是「加」到 .grad 上,不是覆蓋。不歸零就開算,上一圈的梯度會混進這一圈。本站實跑:不歸零連做三次 backward,梯度范數 8.72 → 17.44 → 26.16——恰好 1 倍、2 倍、3 倍(03 節實驗室可以自己按)。

為什麼要設計成累積?因為有正當用途:RNN 的多段 loss、以及「梯度累積」技巧(故意攢幾圈再更新,模擬大 batch——06 節)。預設累積、需要時自己歸零,是 PyTorch 的哲學:把控制權給你。

相關名詞:zero_grad梯度累積

第 ② 站logits = model(x)——前向:作答
    logits = model(x)   # 資料流過網路,吐出每類的分數

把這個 batch 的特徵 x 流過網路,得到 logits(每筆一排「各類分數」——上一頁 softmax 之前的那種原始分數)。PyTorch 會同時默默記下計算的每一步(計算圖),等一下 backward 就是沿著這份紀錄反著走。

相關名詞:前向傳播mini-batch

第 ③ 站loss = criterion(logits, y)——對答案
    loss = criterion(logits, y)   # 預測 vs 正解,算出一個「爛的程度」

criterion 是損失函數的慣用名——分類通常是 nn.CrossEntropyLoss()(它自帶 softmax,所以吃 logits 就好——與上一頁 Keras 配方同一家數學)。輸出一個純量 loss:這個 batch 猜得有多爛。

注意 ②③ 拆成兩行是 PyTorch 的日常寫法:logits 留著還能算準確率;Keras 的 fit 把這兩站藏在肚子裡。

相關名詞:criterion 與損失Keras 對照

第 ④ 站loss.backward()——反向:梯度在這裡誕生
    loss.backward()   # 沿計算圖反著走,算出每個參數的梯度

對 loss 呼叫 backward,PyTorch 沿著前向記下的計算圖反向傳播,替每個參數算出梯度、存進各自的 .grad這一站之後,梯度才存在——所以任何「對梯度動手」的事(剪梯)都必須排在它後面。這正是選項 B 的死因:backward 之前剪,實跑 clip 回傳 0.0——沒有東西可剪。

相關名詞:反向傳播 backward梯度是什麼

第 ⑤ 站clip_grad_norm_——剪梯:太大就等比縮小
    torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)   # 總范數 > 1 就整組等比縮到 1

所有參數的梯度拼起來量總范數(總長度):若超過 max_norm,就整組等比縮小到剛好等於它——方向不變、只縮步伐。本站實跑:裁前總范數 22.957 → 裁後恰為 1.0,每個梯度乘上 0.0436;函式的回傳值就是裁前范數(順手可以拿來監控)。名字結尾的底線=就地修改:直接改 .grad 本人。

它存在的理由:梯度爆炸。深網路或 RNN 偶爾會算出天文數字的梯度,一步把權重踹飛。本站實跑同一組高學習率設定:不剪——第 1 個 epoch loss 就 NaN 到底;有剪——活著收斂。剪刀是保險絲。

相關名詞:gradient clippingmax_norm 與范數梯度爆炸結尾底線

第 ⑥ 站optimizer.step()——更新:梯度在這裡被使用
    optimizer.step()   # 照 .grad 的指示,真正修改權重

optimizer(SGD、Adam⋯⋯)讀取每個參數的 .grad,照自己的配方更新權重——整條生產線只有這一站真正動到權重。所以剪梯必須在它之前:step 一過,梯度就用掉了——選項 C 在 step 後才剪,實跑這一步權重位移 3.98(正確版上限 lr × 1.0 = 0.1)——40 倍超速,剪刀成了馬後炮

相關名詞:optimizer.stepoptimizer 家族

02順序檢查器:四個選項排上生產線

把 A、B、C、D 各自的排法放上六站生產線——出問題的站直接標紅,配上實跑的後果:

互動實驗室:順序檢查器A 全綠;B 剪空氣、C 馬後炮、D 冷凍庫
判順序不用背,用兩條因果就能推: 剪刀剪的是梯度 → 必須在梯度誕生後(backward)、使用前(step)——B、C 各違反一半。 黑板要在寫字前擦 → zero_grad 必須在 backward ——D 在 backward 後擦,把剛算好的梯度擦掉了。四個選項裡只有 A 同時滿足兩條。

03梯度累積器:親手按出「為什麼要歸零」

PyTorch 的 backward 是「到 .grad 上」不是「蓋過去」。同一個 batch,自己連按幾次看看:

互動實驗室:梯度累積器連按 backward,范數 1×、2×、3×
梯度總范數尚未 backward
本站實跑(同一個 batch、不歸零連按三次):梯度范數 8.7213 → 17.4426 → 26.1639——恰好 1 倍、2 倍、3 倍(比值實測 2.0000、3.0000);按下 zero_grad 後回到 0。這就是第 ① 站存在的理由:不擦黑板,上一圈的檢討會混進這一圈,方向和步伐都被污染。

相關名詞:zero_grad梯度累積:bug 與技巧

04削波器:clip_grad_norm_ 到底剪了什麼

它量的是全體參數梯度拼起來的總范數;超過 max_norm 就整組等比縮小——方向不變、只縮步伐。拉拉看:

互動實驗室:削波器裁前范數 × max_norm,看縮放係數
裁前總范數 23.0
max_norm(題目設 1.0)
裁前范數
23.0
clip_grad_norm_ 的回傳值
縮放係數
0.043
每個梯度都乘上它
裁後范數
1.0
min(裁前, max_norm)
本站實跑對照:某個放大輸入的 batch,裁前總范數 22.957 → 裁後恰為 1.0,每個參數的梯度都乘上 0.0436;函式回傳值就是裁前范數(實務常拿來監控梯度健康)。若裁前 ≤ max_norm 則完全不動——剪刀只管太長的。

相關名詞:max_norm 與范數clip_grad_value_ 對照

05完整實跑:正確順序的成績單,與剪刀的救命現場

六站排對之後,整條線跑 20 個 epoch(300 筆三群資料);再把同一組「瘋狂設定」(高學習率+深網路)分別在有剪/沒剪下跑:

互動實驗室:兩份成績單正確順序 vs 剪刀救命
loss:epoch 1 → 20
0.927 → 0.075
一路下降,健康
最終準確率
0.977
300 筆三群資料
梯度動過的證據
0.658
權重最大變化量 max|Δw|

06考場加碼:合法變體與兩個對照

順序的鐵律只有兩條(剪刀夾中間、歸零在反向前)——其他地方其實有彈性,考題也愛考:

變體合法嗎說明
zero_grad 放迴圈開頭合法題目與本頁的寫法——最不容易忘。
zero_grad 放 step 之後(圈尾)合法等效:反正都在下一次 backward 之前。鐵律是「backward 前」,不是「第一行」。
故意 N 圈不歸零、攢滿才 step合法技巧梯度累積:記憶體塞不下大 batch 時,累積 N 個小 batch 的梯度再一次更新——把「預設累積」變成功能。剪梯與歸零都移到 step 那一圈做。
clip 移出「backward 之後、step 之前」不合法B(剪空氣)與 C(馬後炮)的實跑下場見 02 節——這條沒有彈性。
backward 之後、step 之前 zero_grad不合法D 的冷凍庫:把剛算好的梯度擦掉,step 拿零更新——實跑 20 epoch 權重一動不動。

兩個對照收尾:① clip_grad_value_——另一把剪刀,逐元素砍到 ±value(會改變梯度方向);clip_grad_norm_ 等比縮放、方向不變,實務與考題的主流是後者。② Keras 對照——上一頁的 model.fit() 把這六站全部藏進肚子裡自動執行;PyTorch 攤開讓你手寫,換來的是自由(想在哪站插東西都行),代價是順序要自己負責——這題考的就是那份責任

相關名詞:梯度累積clip_grad_value_Keras 對照

07四個選項收工

Azero_grad → forward → loss → backward → clip_grad_norm_ → step正確

兩條因果全滿足:歸零在反向(黑板先擦——實跑不擦的話梯度 1×2×3× 累積);剪刀夾在梯度誕生後(backward)與使用前(step)。實跑 20 epoch:loss 0.927 → 0.075、準確率 0.977——六站生產線的標準運轉。

Bzero_grad → forward → loss → clip_grad_norm_ → backward → step剪空氣

剪刀排在 backward 之前——那時梯度還不存在。實跑:clip 回傳 0.0(沒有東西可剪),接著 backward 生出范數 39.787 的大梯度、step 原封不動拿去更新——削波完全沒有發生,卻不會報任何錯。

Czero_grad → forward → loss → backward → step → clip_grad_norm_馬後炮

剪刀排在 step 之後——梯度已經被用掉了。實跑:step 直接吃下未剪的 39.787,這一步權重位移 3.98(正確版的上限是 lr × max_norm = 0.1——40 倍超速);事後才剪的是「已經沒人要用的梯度」,白剪。

Dforward → loss → backward → zero_grad → clip_grad_norm_ → step冷凍庫

歸零排在 backward 之後——把剛算好的梯度整組擦掉。clip 面對零梯度(無事可做)、step 拿零更新。實跑 20 個 epoch:權重最大變化量 0.0、loss 全程卡在 2.62、準確率 0%——訓練跑完了,模型凍在初始狀態,訓了個寂寞

回到題目:現在再作答一次

再看一次同一個迴圈。這次你知道剪刀為什麼只能站第五位了。

PyTorch 分類模型要在每個 mini-batch 完成一次更新,並在更新前做 gradient clipping。下列執行順序何者正確?

for x, y in loader:                     # 一圈一個 batch
    optimizer.zero_grad()                # ① 歸零:黑板先擦(backward 前)
    logits = model(x)                    # ② 前向
    loss = criterion(logits, y)          # ③ 損失
    loss.backward()                      # ④ 反向:梯度誕生
    torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)  # ⑤ 剪梯
    optimizer.step()                     # ⑥ 更新:梯度被使用

08自我檢測

八題,全部都是本題的延伸。答錯會直接告訴你錯在哪。

09重點整理

  1. 六站生產線:① zero_grad 擦黑板 → ② model(x) 作答 → ③ criterion 對答案 → ④ backward 檢討(梯度誕生)→ ⑤ clip 修剪 → ⑥ step 真正改權重。整條線只有 step 動到權重。
  2. 正確答案 A:口訣「歸零、前向、損失、反向、剪梯、更新」。實跑 20 epoch:loss 0.927 → 0.075、準確率 0.977。
  3. 順序不用背,推兩條因果:剪刀剪的是梯度 → 夾在「誕生(backward)」與「使用(step)」之間;黑板寫字前要擦 → zero_grad 在 backward 前。
  4. 梯度預設累積(zero_grad 的理由):實跑同一 batch 連按三次 backward,范數 8.72 → 17.44 → 26.16——恰好 1×、2×、3×;zero_grad 後歸 0。
  5. clip_grad_norm_ 的機制:量全體梯度的總范數,超過 max_norm 就整組等比縮小(方向不變)——實跑 22.957 → 1.0(每個梯度 × 0.0436);回傳值=裁前范數;不超標則完全不動。
  6. B 剪空氣:backward 前梯度不存在——實跑 clip 回傳 0.0,step 照用 39.787 的未剪梯度,且全程不報錯。
  7. C 馬後炮:step 已把梯度用掉——實跑單步權重位移 3.98(正確上限 lr×max_norm=0.1,40 倍超速),事後剪的是沒人要用的梯度。
  8. D 冷凍庫:backward 後歸零=擦掉剛算好的梯度——實跑 20 epoch 權重最大變化量 0.0、loss 卡 2.62、acc 0%。訓了個寂寞,而且不報錯。
  9. 剪刀存在的理由:梯度爆炸——實跑高學習率+深網路:不剪第 1 個 epoch loss 就 NaN 到底;有剪活著收斂(RNN 訓練的經典保險絲)。
  10. 合法變體:zero_grad 放圈頭或圈尾都行(鐵律:backward 前);梯度累積技巧——故意 N 圈不歸零、攢滿才 step,模擬大 batch(把預設累積變成功能)。
  11. 兩把剪刀:clip_grad_norm_ 等比縮放(方向不變,主流);clip_grad_value_ 逐元素砍到 ±value(會改變方向)。
  12. Keras 對照:model.fit() 把六站藏進肚子裡;PyTorch 攤開手寫——自由換責任,本題考的就是那份責任。
完整程式碼