[自然語言處理](https://app.notion.com/p/3e6fc631b03081b4a9e8f91f3411f61f) › [W4(10/1)](https://app.notion.com/p/3ecfc631b03081b8aa2cf4a97467ed41) › 08|PyTorch 教學(助教,2024 錄影)影片 [0:40:04–1:04:53](https://www.youtube.com/watch?v=sg22677pUEs&t=2404s)|投影片 pytorch_tutorial_NTHU_NLP p.33–50|上一章 [07 PyTorch:用模組搭模型(0:25–0:40)](https://app.notion.com/p/3ecfc631b03081d0869df37240433e19)|下一章 [09 PyTorch:NLP 資料流與 BERT(1:04–1:26)](https://app.notion.com/p/3ecfc631b0308169a03bfc1e2049b467)
跳過提示:(0:42:30–0:48:55) 助教一步步追偏微分怎麼沿著計算圖傳回去、待執行佇列怎麼進出,聽不懂可以直接跳到 [0:48:55](https://www.youtube.com/watch?v=sg22677pUEs&t=2935s),接著講「實際上只要寫一行 loss.backward()」。
跳過的這段在做什麼(白話):助教在示範 PyTorch 內部怎麼「從 loss 倒著把責任分給每個參數」:先處理沒有人要等的節點,處理完再通知上游,最後參數 a、b 各拿到自己的梯度。你只要記得結果:一行 loss.backward() 就全自動做完。
## 重點
- PyTorch 往前算的時候,會順手記下「每個結果是怎麼算出來的」,畫成一張計算圖;呼叫 loss.backward() 就沿著這張圖往回,用連鎖律自動算出每個參數的梯度,存在參數的 .grad 裡。
- 不想算梯度時有三招:requires_grad=False、with torch.no_grad()、detach()。
- 訓練迴圈每一步是「清梯度 → 餵資料 → 算 loss → backward → optimizer 更新」;梯度不清掉會一直累加,這是助教特別提醒的地方。
## Exam-ready
- **Backward**: "The backward function in PyTorch is used to perform backpropagation."(PyTorch 投影片 p.34)
- 中文:PyTorch 的 backward 函式就是用來做反向傳播的。白話:叫它一聲,它就幫你把梯度從輸出往回算。backpropagation(反向傳播)。
- **Gradient Calculation**: "PyTorch computes the gradient of the target tensor with respect to all the tensors that have requires_grad=True, using the chain rule of calculus."(PyTorch 投影片 p.34)
- 中文:PyTorch 用微積分的連鎖律,算出目標 tensor(通常是 loss)對每個「requires_grad=True」的 tensor 的梯度。白話:只有被標記「要追蹤」的東西才會拿到梯度。with respect to(對⋯⋯而言)、chain rule(連鎖律)。
- **Gradient Storage**: "The computed gradients are stored in the .grad attribute of the tensors."(PyTorch 投影片 p.34)
- 中文:算好的梯度存在那些 tensor 的 .grad 屬性裡。白話:每個參數身上有一格口袋叫 .grad,梯度放在那裡。attribute(屬性)。
- **Automatic Differentiation**: "PyTorch builds a computation graph dynamically during the forward pass, and when backward() is called, it traverses this graph in reverse order, applying the chain rule to calculate gradients step-by-step from the output back to the inputs."(PyTorch 投影片 p.34)
- 中文:PyTorch 在往前算的過程中即時建一張計算圖;呼叫 backward() 時,它把這張圖倒著走一遍,每走一步套一次連鎖律,從輸出一路算回輸入。白話:先記帳,再倒著對帳。dynamically(動態地、邊算邊建)、traverse(走過一遍)、in reverse order(倒序)。
- **requires_grad = False**: "When you set the requires_grad attribute of a tensor to False, even if it participates in computations, gradients will not be tracked for it."(PyTorch 投影片 p.40)
- 中文:把 tensor 的 requires_grad 設成 False 之後,就算它有參與計算,也不會追蹤它的梯度。白話:給它貼「免算」標籤。participate(參與)、track(追蹤)。
- **torch.no_grad()**: "This is a context manager that temporarily disables gradient tracking for all operations within its scope."(PyTorch 投影片 p.40)
- 中文:這是一個上下文管理器,包在它範圍裡的所有運算都暫時不追蹤梯度。白話:畫一個「這區不記帳」的圈。context manager(with 區塊管理器)、scope(範圍)、temporarily(暫時地)。
- **detach()**: "The detach() method returns a new tensor that shares the same data as the original tensor but is detached from the computation graph."(PyTorch 投影片 p.40)
- 中文:detach() 回傳一個新 tensor,資料跟原本那個共用,但已經從計算圖上拆下來。白話:同一份數字,換一張不連回去的名牌。shares the same data(共用同一份資料)、detached(脫離的)。
- **Loss function**: "A loss function is a critical component that measures how well or poorly a model's predictions match the actual (true) values."(PyTorch 投影片 p.42)
- 中文:損失函數是關鍵元件,用來衡量模型的預測跟真正答案吻合得多好或多差。白話:幫模型打「錯多少」的分數。critical component(關鍵元件)、actual values(真實值)。
- **Role of loss**: "The primary role of a loss function is to provide feedback to the model during the training process, helping the model improve its predictions over time."(PyTorch 投影片 p.42)
- 中文:損失函數最主要的工作,是在訓練過程中給模型回饋,讓模型的預測隨著時間越來越好。白話:loss 是模型的成績單,模型看著它改進。primary role(主要角色)、feedback(回饋)。
- **AdamW**: "A variant of the Adam optimizer on weight decay formulation"(PyTorch 投影片 p.44)
- 中文:AdamW 是 Adam 優化器的變體,差在權重衰減的寫法。白話:Adam 加上「別讓參數長太大」的約束。variant(變體)、weight decay(權重衰減)。
- **Training steps**: "1. clear gradients 2. Input data to the model 3. compute loss 4. compute gradients 5. optimize parameters 6. back to 1."(PyTorch 投影片 p.45)【助教強調】(0:58:08)
- 中文:訓練一步的順序:清梯度、把資料餵給模型、算 loss、算梯度、更新參數、回到第一步。白話:每一輪都從「把上一輪的梯度歸零」開始。optimize parameters(更新參數)。
## [0:40:04](https://www.youtube.com/watch?v=sg22677pUEs&t=2404s) 反向傳播:自動往回算梯度
訓練分兩步。往前(forward):資料進模型,得到預測(投影片 p.33 寫 output logits,就是還沒轉成機率的原始分數),再跟答案比出 loss。往回(backward):從 loss 出發,一層層用連鎖律算偏微分,得到每個參數的梯度(參數該往哪個方向調、調多少)。
梯度只存在參數的 .grad 裡,中間計算結果不存。為什麼?因為我們最後只會去改參數,中間值下一輪就重算了,存了也沒用(我補充)。
要先懂的幾個詞(我補充):梯度(gradient)是一組數字,告訴你「每個參數往哪邊動一點點,loss 會變大還是變小、變多快」;照著讓 loss 變小的方向走一小步,就是梯度下降(人工智慧導論第 3 週學過:沿著最陡的下坡方向一步步走到谷底,[AI W3](https://app.notion.com/p/3e6fc631b0308137bbd8e73f5c6f1b62))。偏微分是「只動其中一個參數、其他都不動時,結果會變多少」,寫成 ∂e/∂d,讀作「e 對 d 的偏微分」。連鎖律(chain rule)是「A 影響 B、B 影響 C,那 A 對 C 的影響=兩段影響相乘」,像匯率換算:台幣換美金、美金換日圓,台幣對日圓的匯率就是兩個匯率相乘(NLP 第 2 週學過:反向傳播就是用連鎖律把錯誤一層層往回分,[NLP W2](https://app.notion.com/p/3e6fc631b03081a18b13fad72a8874fd))。tensor(張量)是 PyTorch 裡裝數字的容器,可以是一個數、一排數或一張表(本週第 06 章學過,[W4](https://app.notion.com/p/3ecfc631b03081b8aa2cf4a97467ed41))。
```mermaid
flowchart LR
A["資料"] --> B["模型往前算"]
B --> C["預測"]
C --> D["跟答案比出 loss"]
D --> E["loss.backward() 往回算"]
E --> F["梯度存進參數的 .grad"]
```
上圖左半是 forward,右半是 backward。PyTorch 內部用有向無環圖(DAG,箭頭只往一個方向、不會繞回原點的圖)記錄誰算出誰。
用生活例子講,反向傳播在做什麼?
像工廠出了一批不良品(loss 很大)。品管從成品往回查:包裝站錯多少、組裝站錯多少、原料站錯多少,每一站的責任再按比例分給上游。最後每個原料(參數)都知道自己要負多少責任,這份責任就是梯度。
## [0:41:22](https://www.youtube.com/watch?v=sg22677pUEs&t=2482s) 小例子:記下每一步怎麼算的
投影片用兩個參數 a、b 示範:b 先過 ReLU 得到 c,a 和 c 做矩陣乘法得到 d,c 和 d 相加得到輸出 e。往前算的同時,每個中間結果都記住兩件事。
ReLU 是一個很簡單的運算:負數變 0,正數照抄不變。矩陣乘法(程式寫 a @ c,圖上標 mm)在這個例子裡,因為 a、c 都只是一個數,就等於普通乘法。
第一件是「我是用哪種運算算出來的」,對應一個往回算用的函式(c 記 ReluBackward0、d 記 MmBackward0、e 記 AddBackward0)。第二件是依賴數 D:要等幾條路的梯度傳回來,才能輪到自己往回算。
[[IMG: C:\D槽\TAICA課程\_work\notes-v2\nlp-w4\img\brief_pytorch_tutorial_NTHU_NLP_p035.png | 往前算時建好的計算圖:a、b 是參數(不記函式,標 None),c、d、e 各記自己的 backward 函式與依賴數 D]]
圖上重點:標題 A simple example(一個簡單例子),這張圖在講「往前算的時候,PyTorch 偷偷記了什麼」。(1) Sample code (forward)=往前算的範例程式:a、b 用 torch.randn(1, requires_grad=True) 各產生一個隨機數,並標記「要追蹤梯度」;c = relu(b);d = a @ c(矩陣乘法,箭頭標 mm);e = c + d(箭頭標 add)。(2) 右上紅箭頭那句 Record backward functions and dependency D and build a DAG=「記下每個節點的往回函式和依賴數 D,建成一張 DAG」。(3) 節點下的橘字是它記的往回函式:ReluBackward0=ReLU 的往回版、MmBackward0=矩陣乘法的往回版、AddBackward0=加法的往回版;a、b 下面寫 None,因為它們是參數本身,不是算出來的。(4) e 是終點,D=0,所以往回算時第一個開始。
圖裡 c 的 D=2,因為從 e 回到 c 有兩條路:e 直接到 c,以及 e 經過 d 再到 c。d 只有一條路(e → d),所以 D=1。
為什麼要記依賴數?
因為連鎖律要「先拿到下游的梯度,才能往上游算」。c 的梯度要把兩條路的貢獻加起來才完整;如果只收到一條路就急著往 b 傳,b 拿到的梯度會少一半。依賴數就是「還欠幾份資料」的計數器。
## [0:44:42](https://www.youtube.com/watch?v=sg22677pUEs&t=2682s) 一步步把梯度傳回參數
呼叫 backward 後,PyTorch 只把「依賴數已經是 0」的節點放進待執行佇列(FunctionTask queue)。每算完一個節點,就把它上游節點的依賴數減一;誰減到 0,誰就排進佇列。這很像 BFS(廣度優先搜尋)一層一層往回推。
佇列(queue)就是排隊:先排進來的先處理。BFS 是人工智慧導論第 2 週學過的搜尋法:先把離起點最近的一層全部看完,再往下一層走([AI W2](https://app.notion.com/p/3e6fc631b0308175a433e5fa4bc500df));這裡的起點是 loss,一層層往參數的方向推。
兩條路通到同一個節點時,兩條路的梯度相加。最後只有葉節點(leaf node,也就是參數 a、b)把梯度存進 .grad。實際寫程式只要一行 loss.backward(),這整套 PyTorch 都做好了。
[[IMG: C:\D槽\TAICA課程\_work\notes-v2\nlp-w4\img\brief_pytorch_tutorial_NTHU_NLP_p039.png | 跑完後:a 的梯度只有一條路,b 的梯度是兩條路相加]]
圖上重點:這頁是同一個例子 Backward(往回算)跑完的樣子。(1) c、d、e 變淡,函式名稱和 D 被劃掉,代表這些節點都處理完了。(2) 左上 a 的 .grad = (∂e/∂d)*(∂d/∂a):a 只有一條路 e → d → a,兩段影響相乘。(3) 左下 b 的 .grad 有兩項相加:一項是 e 直接到 c,一項是 e 經過 d 再到 c,兩項最後都乘上 c 到 b 那段。(4) 右邊 FunctionTask queue(待執行佇列)已經空了,下面 Pop ReluBackward0=最後一個被取出來執行的是 ReLU 的往回函式。(5) a、b 下面仍寫 None,因為它們沒有往回函式;它們拿到的梯度存在自己的 .grad 裡。
圖下方 b 的式子有兩項,正好對應「e 直接到 c」和「e 經過 d 到 c」兩條路,最後都要再乘上 ReLU 那一段 ∂c/∂b。
下面摺疊用 a = 2、b = 3 把上圖真的算一遍,目的是親眼確認「兩條路的梯度要相加」。算出來 a、b 的梯度都是 3,意思是:a 或 b 多加一點點,輸出 e 大約會增加那一點點的 3 倍。
它到底怎麼運作?(進階,可跳過)
用一組小數字真的跑一次。設 a = 2、b = 3。
往前:c = ReLU(3) = 3;d = a × c = 6;e = c + d = 9。
往回第 1 步(AddBackward0,e 的 D=0 先做):∂e/∂d = 1,∂e/∂c(直接那條)= 1。d 的 D 從 1 變 0,c 的 D 從 2 變 1。
往回第 2 步(MmBackward0):d = a × c,所以 ∂d/∂a = c = 3、∂d/∂c = a = 2。
- a.grad = ∂e/∂d × ∂d/∂a = 1 × 3 = 3
- c 收到第二條路:1 × 2 = 2,加上第一條路的 1,c 的梯度 = 3;c 的 D 變 0
往回第 3 步(ReluBackward0):b = 3 > 0,ReLU 的斜率是 1,所以 b.grad = 3 × 1 = 3。
驗算:e = c(1 + a),對 a 偏微分得 c = 3,對 b 偏微分得 (1 + a) × 1 = 3,對得上。中間的 c、d 雖然也算出了梯度,但不會存進它們的 .grad。
## [0:49:18](https://www.youtube.com/watch?v=sg22677pUEs&t=2958s) 不想算梯度的三種方法
有些時候我們不想讓某些東西被更新,或只是要拿模型來預測、不訓練,這時算梯度只是浪費記憶體。投影片列了三種關掉梯度的方法,差別在「關的範圍」。
| 方法 | 關掉的範圍 | 什麼時候用(我補充) |
|---|---|---|
| requires_grad = False | 單一個 tensor | 想凍結某些參數,不讓它被訓練 |
| with torch.no_grad() | 整個程式區塊 | 驗證、測試時,只要預測不要訓練 |
| detach() | 從計算圖摘出一個中間結果 | 想拿某個中間值來用,但不想讓梯度從這裡傳回去 |
助教的說法:requires_grad 設成 False 後,這個節點就被當成像中間結果一樣,算到這裡的梯度不會存下來;no_grad 區塊裡的運算也一樣。detach() 跟前兩個不一樣:它產生一個新 tensor,跟原本共用同一份資料(是新的 view,連續性不變),只是已經跟 forward/backward 的系統脫鉤。
上面那句的兩個詞(我補充):view 是 PyTorch 的說法,指「同一份數字換個名牌來看」,沒有另外複製一份;連續性(contiguous)是指這些數字在記憶體裡是不是排成一整排。合起來只是在說:detach 不會搬動或複製資料,只是切斷跟計算圖的連線。
用生活例子講,三種方法差在哪?
想像公司在記每個人的功過。requires_grad=False 是「這個人不列入考核」;no_grad 是「這個會議全程不記錄」;detach 是「把一份報告影印出來給別人用,影本上的修改不會算回原作者頭上」。
## [0:51:20](https://www.youtube.com/watch?v=sg22677pUEs&t=3080s) 訓練一個模型要準備什麼
訓練主迴圈之外,要先準備五樣東西:資料集、模型(輸入和輸出之間是什麼關係,可能是線性、卷積或 RNN)、優化器(用什麼方式做梯度下降)、損失函數(量預測和標準答案差多遠),以及訓練完拿來驗證的測試集。
模型那三種關係的白話(我補充):線性=輸入乘上權重再加一個偏移;卷積(convolution)=拿一個小視窗在資料上滑過去,找局部的特徵;RNN=一個字一個字讀,邊讀邊記住前文(NLP 第 3 週學過:RNN 把讀過的前文壓成一個向量往後傳,[NLP W3](https://app.notion.com/p/3e6fc631b03081ff9776f36fab3e6e50))。
測試集必須是訓練時沒看過的資料。為什麼?用練習題來考試,分數一定好看,卻看不出模型遇到新題目會不會。助教也說:不要用訓練的資料來測試。
用生活例子講,這五樣是什麼?
像準備考試:資料集是課本和練習題、模型是你的大腦、損失函數是對答案後算出錯幾題、優化器是你決定下一步怎麼改讀書方法(像導航決定下一步往哪走)、測試集是期末考那份你沒看過的考卷。
## [0:53:08](https://www.youtube.com/watch?v=sg22677pUEs&t=3188s) 常見的損失函數
損失函數把「預測和標準答案差多少」變成一個數字,連分類任務也一樣有這個量化指標。任務不同,用的損失函數不同;選錯了,模型就會往錯的方向學。
| 損失函數 | 用在哪 | 白話 |
|---|---|---|
| Cross entropy(交叉熵) | 分類 | 正確類別的機率越高,loss 越小 |
| BCE(二元交叉熵) | 只有兩類的分類 | 跟 cross entropy 的差別是只能用在兩個類別 |
| MSE(均方誤差) | 迴歸(預測一個數值,例如打分數) | 答案減預測,平方後取平均 |
| KL divergence | 知識蒸餾 | 量兩個機率分布有多不像,讓小模型(學生)的輸出分布貼近大模型(老師) |
| Contrastive loss | 對比學習 | 讓向量靠近正樣本、遠離負樣本 |
| Entropy 項 | 加在別的 loss 裡 | 預測集中在某一類時 entropy 小,用來讓模型更有信心 |
後兩個 PyTorch 沒有直接寫好,助教說未來實作可能常遇到。
這張表用到的前置概念(我補充):機率分布是把 100% 分給所有可能的答案,例如「貓 70%、狗 20%、兔 10%」;模型通常用 softmax 把原始分數變成這種加起來等於 1 的機率(NLP 第 3 週學過:softmax 把分數轉成機率,再用 cross entropy 算語言模型猜下一個字錯多少,[NLP W3](https://app.notion.com/p/3e6fc631b03081ff9776f36fab3e6e50))。entropy(熵)量的是「有多不確定」:機率平均分散時最大,集中在一類時最小。知識蒸餾是讓小模型模仿大模型的答案,把大模型的本事濃縮進小模型。正樣本是「應該相像」的一對,負樣本是「不該相像」的一對。
為什麼分類不用 MSE?
MSE 是在量「兩個數字差多遠」,適合答案本身是一個數值的題目。分類的答案是「哪一類」,模型輸出的是每一類的機率;cross entropy 直接看「正確那一類分到多少機率」,比較符合分類要的東西(我補充)。
## [0:56:59](https://www.youtube.com/watch?v=sg22677pUEs&t=3419s) 優化器與每輪清梯度
損失函數要看任務挑,優化器就單純多了。投影片 p.44 列了三個最常用的:SGD(隨機梯度下降)、Adam、AdamW。沒有特殊情況,預設用 Adam;AdamW 是加了權重衰減(weight decay)的 Adam。
這幾個名詞的白話(我補充):優化器(optimizer)是「拿到梯度後,決定參數實際怎麼改」的工具。SGD 每次只抽一小批資料算梯度、照方向走一步,「隨機」指的是每次抽一小批,不是看全部資料。Adam 會依每個參數過去的梯度,自動調整每個參數的步伐大小,通常比較快穩定下來。權重衰減是每一步都把參數往 0 拉一點,避免參數長太大、把訓練資料背死。
一步訓練是:資料餵給模型得到預測 → 預測和 ground truth(標準答案)算出 loss → loss 反向傳播算出梯度 → 梯度交給 optimizer 更新參數。【助教強調】(0:58:08) 每一輪都要先把上一輪的梯度清掉,不然梯度會一直累加。
```mermaid
flowchart LR
A["清梯度 zero_grad"] --> B["資料餵給模型"]
B --> C["算 loss"]
C --> D["backward 算梯度"]
D --> E["optimizer 更新參數"]
E --> A
```
這張圖就是投影片 p.45 的六步,最後一步「回到 1」畫成箭頭繞回去。
為什麼梯度不清掉會出事?
PyTorch 算出新梯度時,是「加到」.grad 裡原本的值上,不是蓋掉。如果上一輪的 w.grad 是 -4,這一輪又算出 -4,沒清的話 .grad 會變成 -8,參數就被推了兩倍遠,訓練會亂跳。所以每一步都要先 optimizer.zero_grad()。順序上,清梯度放在 backward 之前任何一個位置都可以(投影片 p.47 的範例就放在算 loss 之後)(我補充)。
老師原話是什麼?
「注意就是每一輪、每一個 step 訓練,每一輪梯度計算之後,我們會需要把這個 optimizer 的前一輪算出來的梯度給清除掉」(0:58:08)
「我們不斷的去算梯度的過程當中,這個梯度他會不斷的累加累加累加」(0:58:24)
## [0:58:37](https://www.youtube.com/watch?v=sg22677pUEs&t=3517s) 線性迴歸範例
目標是用一條直線 y = wx + b 去貼近一群散布的紅點。真實資料不會剛好排成直線,每筆都有自己的偏差,我們只求一條最能代表趨勢的線。
模型是一個輸入、一個輸出的線性層(預設有 bias),損失用 MSE(因為要預測數值),優化器用隨機梯度下降 SGD,並設定學習率(每次沿梯度走多大一步)。跑下來 loss 越來越小,最後得到一條貼近紅點趨勢的直線。
bias(偏移)就是 y = wx + b 裡的 b,讓直線可以上下平移、不必穿過原點。epoch(輪)是「把全部訓練資料從頭到尾看過一遍」,看一遍算一輪。
[[IMG: C:\D槽\TAICA課程\_work\notes-v2\nlp-w4\img\brief_pytorch_tutorial_NTHU_NLP_p048.png | 左邊是每 5 個 epoch 的 loss,從 11.2 降到 0.18;右邊是紅點和學到的直線]]
圖上重點:標題 An Example of Linear Regression=線性迴歸範例。(1) 左邊 Outputs(輸出)是訓練時印出的紀錄:Epoch [5/60] 表示總共訓練 60 輪、現在到第 5 輪;Loss 是當下的誤差。(2) loss 從 11.2 一路降到 0.18,前 20 輪降得最快,後面幾乎持平,代表模型已經學得差不多。(3) 右圖紅點 Original data=原始資料,藍線 Fitted line=模型學到的直線;藍線沒有穿過每個點,但抓到了「x 越大、y 越大」的趨勢。
助教接著把 w、b 的梯度印出來看:zero_grad 之後梯度是空的(None),backward 之後梯度有了值,step 之後 w、b 被小幅更新,再 zero_grad 又清回 None,準備下一輪。
注意:助教說這個範例大家可能在作業說明裡看過,那是 2024 年的作業,今年作業以今年公告為準。
下面摺疊用一個資料點手算一輪,目的是看清楚「預測 → 算 loss → 算梯度 → 更新參數」這四步怎麼讓直線變準:算完一輪,預測從 1 變成 2,loss 從 1 降到 0。
它到底怎麼運作?(進階,可跳過)
用一個點手算一輪。設 w = 0.5、b = 0,資料點 x = 2、答案 y = 2,學習率 0.1。
1. 預測:0.5 × 2 + 0 = 1;loss(MSE)=(1 − 2)² = 1。
2. backward:∂loss/∂w = 2 ×(1 − 2)× 2 = −4;∂loss/∂b = 2 ×(1 − 2)= −2。
3. step(SGD:參數 − 學習率 × 梯度):w = 0.5 − 0.1 ×(−4)= 0.9;b = 0 − 0.1 ×(−2)= 0.2。
4. 再預測一次:0.9 × 2 + 0.2 = 2.0,loss 變成 0。
這組數字也接上一段:如果第二輪前沒 zero_grad,w.grad 會在 −4 上再累加。
學習率的量級,助教的經驗是:大模型約 10⁻⁵,小模型約 10⁻³ 甚至 10⁻²。
## Self-check
Q1. Why must we call optimizer.zero_grad() in every training step?(中文:為什麼每一步訓練都要呼叫 optimizer.zero_grad()?)
**Answer**: Because PyTorch accumulates gradients: each backward() adds the new gradients to the values already stored in .grad. If we do not clear them, gradients from previous steps pile up and the parameter update becomes wrong.
中文:因為 PyTorch 的梯度是累加的,每次 backward() 都把新梯度加到 .grad 原本的值上。不清掉的話,前幾輪的梯度會一直疊上去,參數更新的方向和大小都會錯。所以每一步都先清梯度,再算這一輪的梯度。
Q2. Explain how PyTorch computes gradients automatically when loss.backward() is called.(中文:說明呼叫 loss.backward() 時,PyTorch 怎麼自動算出梯度。)
**Answer**: During the forward pass, PyTorch builds a computation graph (a DAG) that records how each tensor is computed. When backward() is called, it traverses the graph in reverse order from the loss, applying the chain rule step by step, and stores the gradients in the .grad attribute of the leaf tensors (parameters) that have requires_grad=True.
中文:往前算的時候,PyTorch 一邊建一張計算圖(DAG),記下每個 tensor 是由哪些運算得到的。呼叫 backward() 後,它從 loss 開始把這張圖倒著走,每一步套一次連鎖律,最後把梯度存進參數(葉節點、requires_grad=True)的 .grad。中間結果的梯度不保存。
Q3. Compare requires_grad=False, torch.no_grad(), and detach().(中文:比較 requires_grad=False、torch.no_grad() 和 detach()。)
**Answer**: All three avoid gradient computation, but at different scopes. requires_grad=False stops tracking gradients for one specific tensor. torch.no_grad() is a context manager that disables gradient tracking for all operations inside its block, often used for evaluation. detach() returns a new tensor that shares the same data but is cut off from the computation graph, so gradients do not flow back through it.
中文:三個都是避免算梯度,差在範圍。requires_grad=False 只針對某一個 tensor;torch.no_grad() 是一個 with 區塊,區塊裡所有運算都不追蹤梯度,常用在驗證、測試;detach() 回傳一個共用資料、但從計算圖斷開的新 tensor,梯度不會從它傳回去。
Q4. Which loss function would you use for regression, multi-class classification, and knowledge distillation? Why?(中文:迴歸、多類別分類、知識蒸餾各該用哪個損失函數?為什麼?)
**Answer**: Regression uses MSE, because both the prediction and the answer are numbers and MSE measures their squared distance. Multi-class classification uses cross entropy, because it measures how much probability the model gives to the correct class (BCE only works for two classes). Knowledge distillation uses KL divergence, because it measures the distance between the student's and the teacher's probability distributions.
中文:迴歸用 MSE,因為預測和答案都是數值,MSE 量的是兩個數字差的平方。多類別分類用 cross entropy,因為它看模型給正確類別多少機率;BCE 只能用在兩類。知識蒸餾用 KL divergence,因為它量的是學生模型和老師模型兩個機率分布有多接近。
讀完了嗎?下一章:[09 PyTorch:NLP 資料流與 BERT(1:04–1:26)](https://app.notion.com/p/3ecfc631b0308169a03bfc1e2049b467)|回到週頁:[W4(10/1)](https://app.notion.com/p/3ecfc631b03081b8aa2cf4a97467ed41)