step 01 / 19Part II 模型原理(選修)
S1 神經網路基礎
S0 全局地圖裡回答問題的模型是怎麼來的?Part II 模型原理(S1–S5)由下往上回答,這一章先回答最底層的問題:模型為什麼能「學」。答案是一個迴圈:定義一個衡量「錯多少」的 loss,算出每個參數該往哪個方向調、調多少(梯度),調一小步,重複幾千萬次。整章用「預測名字的下一個字元」當例子,因為這和 LLM 的訓練目標是同一件事,只是規模小到能在筆電上跑。
- 能說出訓練迴圈的四步各做了什麼,並從「給正確答案的機率」估出 loss 與 perplexity。(講義與自我檢查)
- 能用「倍率相乘」解釋 backprop,並看一條 loss 曲線指出可能的問題(學習率太大、初始化不好、overfitting)。(講義與自我檢查)
- 從零寫出一個 scalar autograd,並用數值梯度驗證它是對的;親手畫出學習率太大時的 loss 曲線。(必做)
- 訓練一個小型字元級語言模型,說出它的 loss 和 perplexity 代表什麼,並從 activation 分布圖找出飽和。(進階)
- 用實驗追蹤工具比較多組超參數。(進階)
先看全貌。所有神經網路的訓練都是同一個迴圈:
- 前向(forward):把一筆資料送進模型,得到預測。
- 算 loss:用一個數字表示預測錯了多少。
- 反向(backward):算出每個參數的梯度,也就是「這個參數往上調一點,loss 會變大還是變小、變多快」。這一步叫 backprop。
- 更新:每個參數往讓 loss 變小的方向走一小步。怎麼走由 optimizer 決定。
回到第 1 步,訓練固定的步數,並定期用沒拿來訓練的 validation data 檢查 loss(→ 提前停止)。下面各節依序放大:先定義任務與 loss,再看梯度怎麼算、optimizer 怎麼用梯度,最後是訓練不順時怎麼診斷。
★ 模型要學什麼、怎麼打分?
Section titled “★ 模型要學什麼、怎麼打分?”語言模型做的事是:給定前面的內容,輸出下一個單位的機率分布。LLM 的單位是 token(→ S2 Transformer 與 decoding),這一章的單位是字元。
例子:名字的下一個字元。 資料是約 3.2 萬個英文名字(makemore 的 names.txt)。字元集是 26 個小寫字母,加上一個代表「開頭 / 結尾」的符號 .,共 27 個。名字 emma 會拆成 5 題練習:
| 已知前文 | 要預測 |
|---|---|
. |
e |
.e |
m |
.em |
m |
.emm |
a |
.emma |
.(結束) |
最簡單的模型:bigram。 只看前一個字元,用計數表估機率:數一數整份資料裡 m 後面接什麼。假設(示意數字)m 後面接 a 佔 40%、i 15%、e 15%、結束 10%、m 5%、其他字母合計 15%,模型就把這些比例當成預測的機率。
Loss:給正確答案的機率越低,罰越重。 每一題的 loss 是 −ln(模型給正確答案的機率),叫 cross-entropy loss。不用會推導,只要記住這張對照表:
| 給正確答案的機率 | 1 | 0.5 | 0.25 | 0.1 | 1/27 | 0.01 |
|---|---|---|---|---|---|---|
| loss | 0 | 0.69 | 1.39 | 2.30 | 3.30 | 4.61 |
emma 的第 3 題(m 後面接 m)模型只給 0.05,loss 是 −ln 0.05 ≈ 3.00;第 4 題(m 後面接 a)給 0.40,loss ≈ 0.92。整份資料的 loss 是所有題目的平均。1/27 那一欄很重要:27 個字元完全平均亂猜時,loss ≈ 3.30,這是一個還沒學任何東西的模型應有的起點。
Perplexity:換成「在幾個選項之間猶豫」。 Perplexity = e 的(平均 loss)次方。如果模型每一題都給正確答案 0.25,loss 是 1.39,perplexity 是 4,意思是模型的困惑程度相當於每次在 4 個字元之間平均亂猜。完全亂猜時 perplexity 是 27。Loss 的「差」對應 perplexity 的「比」:loss 從 2.0 降到 1.9,perplexity 從 7.39 降到 6.69(← 數學補給:對數尺度)。
從計數表到神經網路。 計數表只能看前一個字元;要看前 3 個,組合數變成 27³ 種,大多數組合在資料裡幾乎沒出現,計數不可靠。神經網路的做法是:
- 每個字元對應一個學出來的小向量(例如 10 個數字),叫 embedding。意思相近的字元(例如母音)訓練後會有相近的向量。
- 把前 3 個字元的 embedding 接起來(30 個數字),送進一層 hidden layer 加工,最後輸出 27 個分數,每個字元一個,叫 logit。
- softmax 把 27 個分數轉成加總為 1 的機率:分數越高機率越高,分數差 1,機率差約 2.72 倍(2.72 就是 e,← 數學補給:softmax)。
一層在做什麼:加權總和,也就是矩陣乘法。 Hidden layer 裡的每個神經元做兩件事:把所有輸入各乘上自己的一個權重再加起來(再加一個常數,叫 bias),然後把這個加權總和送進 tanh,壓到 −1 到 1 之間。一層有很多個神經元,每個都有自己的一組權重;把這些權重一列一列排成一張表(矩陣),整層的加權總和就能一次算完,這個運算叫矩陣乘法。小例子:輸入是 3 個數字 [1, 2, 3],這一層有 2 個輸出,權重矩陣是 2 × 3:
| 對輸入 1 | 對輸入 2 | 對輸入 3 | 加權總和 | |
|---|---|---|---|---|
| 輸出 1 | 1 | 0 | −1 | 1×1 + 2×0 + 3×(−1) = −2 |
| 輸出 2 | 0.5 | 0.5 | 0.5 | 1×0.5 + 2×0.5 + 3×0.5 = 3 |
輸出是 [−2, 3]:每個輸出都是輸入的一組加權總和,矩陣的每一列就是一組權重。M1 makemore MLP 的 hidden 層就是 30 個輸入、200 個輸出的同一件事,輸出層是 200 個輸入、27 個 logit(這是進階題,選做;代號說明)。下一章的 Transformer 裡,大部分計算也都是這種矩陣乘法(→ S2 Transformer 與 decoding)。
這就是 M1 makemore MLP 要做的語言模型。模型裡所有的數字(embedding、每層的權重)都是參數,一開始是隨機的,靠下面幾節的迴圈慢慢調好。之後每一章的 LLM,本質上都是更大、更好的 next-token predictor。
容易誤會:loss 不會、也不該降到 0。 m 後面本來就可能接很多字母,資料本身有不確定性。在 bigram 這種只看一個字元的設定下,最好的模型也只能給出真實的比例,loss 有下限(實測 bigram 約 2.45)。訓練 loss 降到接近 0,通常代表模型在背答案(見 overfitting)。
準備動手做:公式、平滑化與 MLP 的形狀
- 公式:softmax 是 pᵢ = e^(zᵢ) ÷ Σⱼ e^(zⱼ),zᵢ 是第 i 個字元的 logit。一份資料 N 題的 cross-entropy 是 L = −(1/N) Σ ln p(正確答案),perplexity = e^L。
- 平滑化:計數表裡沒出現過的組合機率是 0,−ln 0 是無限大。常見做法是每格先加 1 再算比例(add-one smoothing)。
- 一層線性 + softmax 等於計數表:把前一個字元表示成長度 27、只有一格是 1 的向量(one-hot),乘上一個 27 × 27 的權重矩陣,結果就是取出其中一列當 logits。用梯度下降訓練這個矩陣,會學到和計數表幾乎一樣的機率,所以 M1 makemore MLP 的步驟 2 兩個版本 val loss 應該接近。
- MLP 的形狀(以 embedding 10 維、hidden 200 為例):embedding 表 27 × 10 = 270;hidden 層 30 × 200 權重加 200 個 bias = 6,200;輸出層 200 × 27 加 27 = 5,427;共 11,897 個參數。
- 前文不足 3 個字元:用
.補在前面,emma的第一題是「...→ e」。
★ 參數該往哪裡調?
Section titled “★ 參數該往哪裡調?”梯度告訴每個參數「往哪調、調多快」。Backprop 是一次算出所有參數梯度的方法:把整個計算拆成小步,每一步只算自己的「放大倍率」,再從 loss 往回乘。
先用最笨的方法量梯度:把權重推一點,看 loss 怎麼變。 把 bigram 縮到最小:m 後面只有 a、m 兩個候選,而且這一題的正確答案是 a。模型只有一個權重 w,它是 a 的分數;m 的分數固定是 0。計算分兩步:
- 第一步,w 經過 softmax 變成 a 的機率 p。兩個候選時 p = eʷ ÷ (eʷ + 1)。w = 0 時兩個分數一樣,p = 0.5。
- 第二步,p 變成 loss = −ln p = 0.6931。
把 w 推到 0.01(代入上式,用計算機算):p 變成 0.5025,loss 變成 0.6882,少了約 0.0050。w 動 0.01、loss 動 −0.005,倍率約 −0.5,這就是 loss 對 w 的梯度。負號代表:w 往上調,loss 會變小。
Backprop:每一步各量各的,再相乘。 不必每次從頭推。分開量兩步的倍率:
| 步驟 | 推一點 | 結果變化 | 局部倍率 |
|---|---|---|---|
| w → p | w 從 0 到 0.01 | p 從 0.5 到 0.5025 | +0.25 |
| p → loss | p 從 0.5 到 0.501 | loss 從 0.6931 到 0.6911 | −2 |
兩個局部倍率相乘:0.25 × (−2) = −0.5,和直接量的一樣。道理很直觀:w 動一點,p 動 0.25 倍那麼多;p 每動一點,loss 又動 −2 倍,合起來就是 −0.5 倍。這就是 backprop:前向時記下每一步的運算,反向時從 loss 出發,把「後面傳回來的倍率 × 自己的局部倍率」一路傳到每個參數。模型再大,也只是步驟更多、分岔更多,每個小運算(加、乘、tanh……)只需要知道自己的局部倍率。
一個值被用兩次,兩條路的梯度要相加。 例如 y = x × x,x = 3。x 從兩條路影響 y,每條路的局部倍率都是另一邊的 x,也就是 3,所以 y 對 x 的梯度是 3 + 3 = 6。驗算:x 推到 3.01,y 從 9 變成 9.0601,倍率約 6.01。如果程式裡用「覆蓋」而不是「累加」,就會得到錯的 3。必做題特別要求你指出處理這件事的那一行。
為什麼不一直用「推一點」的方法。 M1 makemore MLP 的模型(embedding 10 維、hidden 200)約有 1.2 萬個參數,逐一推一下就要多跑 1.2 萬次前向;backprop 跑一次反向就拿到全部梯度,成本約是一次前向的兩倍。「推一點」的方法還是很有用:它是檢查 backprop 寫對沒有的標準方法,叫數值梯度檢查。
容易誤會:backprop 算的不是近似值。 它算出的是精確梯度,只有浮點誤差;「推一點」的數值梯度才是近似。兩者本來就有極小的差,所以驗收看相對誤差、不要求完全相等;差得明顯,通常是 backprop 寫錯。
深入一點:用連鎖律寫出來
上面的「局部倍率」就是導數,「相乘」就是連鎖律(← 數學補給)。
- 兩個候選、一個分數 w 時,p = 1 ÷ (1 + e^(−w)),dp/dw = p(1 − p),w = 0 時是 0.25。
- loss = −ln p,d loss / dp = −1/p,p = 0.5 時是 −2。
- 連鎖律:d loss / dw = p(1 − p) × (−1/p) = p − 1 = −0.5。
- 一般情況:softmax 加 cross-entropy 對每個 logit 的梯度是「預測機率 − 正確答案」,正確字元那格減 1,其他格是 0。所以實作時常把兩者合成一個運算,數值上也更穩定。
準備動手做:autograd 引擎的結構
- 每個節點存:數值、梯度(初始為 0)、產生它的輸入節點,以及一個「把自己的梯度乘上局部導數、加到輸入節點梯度上」的函式。
- 常用局部導數:a + b 對 a、b 都是 1;a × b 對 a 是 b、對 b 是 a;aⁿ(n 是常數)是 n·aⁿ⁻¹;eᵃ 是 eᵃ;tanh(a) 是 1 − tanh(a)²;a ÷ b 可以寫成 a × b⁻¹,不必另寫。
- 反向的順序:對計算圖做拓樸排序,確保一個節點的所有使用者都處理完,才把它的梯度往前傳。輸出節點的梯度設為 1。
- 梯度用
+=累加:這就是「用兩次要相加」。也因為累加,每個訓練步驟開始前要把梯度清回 0。 - 中央差分:(f(x + h) − f(x − h)) ÷ 2h 比單邊的 (f(x + h) − f(x)) ÷ h 準得多,誤差和 h² 成正比而不是 h。float64 下 h = 1e-6 是常用的折衷。
★ 拿到梯度後怎麼走?
Section titled “★ 拿到梯度後怎麼走?”Optimizer 決定拿到梯度之後怎麼走。最基本的是沿梯度反方向走一小步;現代 LLM 的預設是 AdamW,它讓每個參數有自己的步長。
梯度下降。 新的參數 = 舊的參數 − 學習率 × 梯度。學習率(learning rate)控制步長,是最重要的超參數。延續上一節,但這次看 m 後面的 4 題:3 題是 a、1 題是 m。平均 loss 在 w = 0 時是 0.693,用「推一點」量出梯度約 −0.25。
| 學習率 | 新的 w | 新的平均 loss | 結果 |
|---|---|---|---|
| 1 | 0 − 1 × (−0.25) = 0.25 | 0.638 | 變好 |
| 20 | 0 − 20 × (−0.25) = 5 | 1.257 | 一步走過頭,比原來還差 |
學習率 1 時重複這個迴圈,w 會停在約 1.10,這時 a 的機率是 0.75,正好等於資料裡的比例,平均 loss 是 0.562,不會再降(上一節說的下限)。學習率 20 時 a 的機率衝到 0.993,正確答案是 m 的那題,loss 變得很大。學習率太大的典型症狀就是 loss 震盪、上升,甚至變成 NaN。
SGD:每次只看一小批。 名字資料拆出約 22.8 萬題,每走一步都算全部太慢。實際做法是每次隨機抽一小批(mini-batch,例如 32 題)算梯度。這個梯度有雜訊,但便宜得多,可以走更多步,這就是 SGD(stochastic gradient descent)。
Adam:每個參數自己的步長。 不同參數的梯度大小可能差很多。假設參數 A 的梯度一直在 100 左右,參數 B 一直在 0.01 左右,學習率 0.001:SGD 讓 A 一步走 0.1、B 一步只走 0.00001,同一個學習率對 A 太大、對 B 太小。Adam 對每個參數記兩個移動平均:梯度的平均(決定方向,順便把雜訊平均掉),和梯度平方的平均(估計這個參數的梯度通常多大),走之前把步長除以後者的平方根(約等於這個參數梯度的典型大小)。結果 A、B 每步都走大約 0.001。所以上億個尺度差很多的參數能共用同一個學習率,對超參數也比較不敏感。
AdamW:把 weight decay 拆出來。 Weight decay 是每一步把參數往 0 拉一點(例如學習率 0.001、decay 0.1,每步把參數縮小 0.01%),防止參數長得太大,是一種正規化。原本的 Adam 把 weight decay 加進梯度裡,於是它也被「除以梯度大小」:梯度大的參數,被正規化得比較少。AdamW 把 weight decay 從梯度更新中分離出來,直接作用在參數上,每個參數都一致地生效。這是目前訓練 LLM 的預設選擇。
容易誤會:用了 Adam 不代表不用調學習率。 Adam 讓學習率比較好選,但它仍是第一個要調的超參數。學習率在訓練中通常也不是固定的,大規模訓練會先慢慢升、再逐步降(→ S3 Pretraining 與 scaling)。
深入一點:Adam 與 AdamW 的更新式
每一步,對每個參數 θ(梯度 g、學習率 η):
- m ← β₁·m + (1 − β₁)·g(梯度的移動平均,也就是 momentum)
- v ← β₂·v + (1 − β₂)·g²(梯度平方的移動平均)
- 偏差修正:m̂ = m ÷ (1 − β₁ᵗ)、v̂ = v ÷ (1 − β₂ᵗ)。m、v 從 0 開始,前幾步會偏小,t 是步數。
- Adam:θ ← θ − η·m̂ ÷ (√v̂ + ε)。加 L2 正規化時,是把 λθ 加進 g,於是它也被 √v̂ 除掉。
- AdamW:θ ← θ − η·(m̂ ÷ (√v̂ + ε) + λθ),decay 項不經過 √v̂。
PyTorch 的預設值是 β₁ = 0.9、β₂ = 0.999、ε = 1e-8;AdamW 的 weight_decay 預設 0.01。
★ 訓練為什麼一開始就卡住?
Section titled “★ 訓練為什麼一開始就卡住?”初始權重的大小決定訓練能不能順利開始。太大,訊號會飽和、梯度消失;太小,訊號一層層縮小。Normalization 層則在訓練過程中持續把數值拉回穩定的範圍。
第一個檢查:初始 loss。 還沒訓練的模型應該對 27 個字元差不多一視同仁,初始 loss 應該接近 ln 27 ≈ 3.30。如果第一步的 loss 是 15,代表模型給正確答案的機率只有 e⁻¹⁵,大約三百萬分之一:它沒學過任何東西,卻非常有把握地猜錯。原因通常是輸出層的初始權重太大,27 個分數差距很大,softmax 把幾乎所有機率給了某個隨機字元。修法是把輸出層的初始權重設得很小,讓 27 個分數接近,機率接近平均。這樣訓練的前幾百步不會浪費在「把過度自信壓下來」。
第二個檢查:tanh 有沒有飽和。 Hidden layer 常用 tanh 把數值壓進 −1 到 1 之間。輸入超過 ±3 時,輸出已經是 ±0.995,再推輸入一點,輸出幾乎不動,局部倍率只剩約 0.01。依 backprop 的規則,往回傳的梯度要乘上這個倍率,於是幾乎傳不回去,前面的參數學不到東西。M1 makemore MLP 用 |tanh| > 0.97 當飽和的標準:這時局部倍率不到 0.06。
為什麼會飽和?M1 makemore MLP 的 hidden 層每個神經元把 30 個輸入加權相加。如果每個輸入和權重的大小都在 1 左右,30 項的正負號隨機,大多互相抵消,所以加起來的典型大小只長到約 √30 ≈ 5.5,而不是 30;即使如此,也大部分落在 tanh 的平坦區。修法是依輸入數量縮小初始權重:乘上約 1/√30 ≈ 0.18,加總的典型大小就回到 1 左右。
Normalization 層。 初始化只管第一步,訓練中權重一直在變,每層輸入的尺度也跟著漂移。Normalization 層把每層的輸入重新調整成平均約 0、分散程度約 1,再乘上可學的縮放係數。BatchNorm 是對一個 batch 裡的同一個特徵做;LayerNorm 是對一筆資料自己的所有特徵做,Transformer 用的是後者,下一章會再換成更簡單的 RMSNorm(→ S2 Transformer 與 decoding)。M1 makemore MLP 不要求加 normalization,但網路一深就少不了它。
容易誤會:初始 loss 接近 3.30 不代表一切正常。 輸出層正確,hidden 層仍可能大量飽和。所以兩件事都要量:初始 loss,以及每層 activation 的分布(例如畫直方圖,看是不是大量堆在 ±1)。
準備動手做:初始化公式與診斷圖
- 縮放公式:若每個輸入的分散程度約 1,權重用標準差 gain ÷ √fan_in 的隨機數,fan_in 是輸入數量。tanh 常用 gain = 5/3,ReLU 用 √2(Kaiming 初始化)。PyTorch 的
torch.nn.init.kaiming_normal_可以直接用。 - BatchNorm 的麻煩:它用 batch 的平均與標準差,所以訓練時一筆資料的輸出會受同一批其他資料影響;推論時改用訓練中累積的平均值,
model.train()/model.eval()切換錯會得到奇怪的結果。LayerNorm 只看單筆資料,沒有這個問題。 - 該畫的三張圖:每層 activation 的直方圖(是否堆在 ±1,或全是 0)、每層梯度的直方圖(各層大小是否差好幾個數量級)、每個參數「每步更新量 ÷ 參數本身大小」的比例。最後一個的常見經驗值約 1e-3:遠小於它代表學習率太小,遠大於它代表太大。
★ 模型在背答案怎麼辦?
Section titled “★ 模型在背答案怎麼辦?”模型在訓練資料上越來越好、在沒看過的資料上卻變差,叫 overfitting:它開始背答案,而不是學規律。正規化(regularization)是各種防止背答案的做法。
怎麼看出來:把資料分成三份。 M1 makemore MLP 把名字切成 train / val / test(80 / 10 / 10)。Train 用來更新參數;val 用來比較設定、決定何時停;test 只在最後用一次,報告最終成績。只看 train loss 無法判斷模型好不好,因為模型可以把 2.6 萬個訓練名字背下來,loss 很低,卻產生不出新名字。
例子。 訓練 loss 一路從 3.30 降到 1.2,val loss 降到 2.0 後開始回升。val 回升的那一刻起,模型學到的東西只對訓練資料有用。這時有幾種對策:
- 提前停止(early stopping):保留 val loss 最低的那個時間點的參數。最簡單,幾乎不花成本,通常先試。
- Weight decay:上一節的「每步往 0 拉一點」,讓模型不容易靠很大的權重記住個別例子。
- Dropout:訓練時每一步隨機關掉一部分神經元,模型不能依賴任何單一路徑。
- 更多資料,或更小的模型:背不下來,就只能學規律。
容易誤會:train 和 val loss 有差距不一定是 overfitting。 兩者有點差距很正常;關鍵是 val loss 有沒有停止下降甚至回升。反過來,train 和 val 都很高而且很接近,是模型太小或還沒訓練夠(underfitting),這時加正規化只會更糟。
深入一點:dropout 與 test set 的規矩
- Dropout 的細節:訓練時以機率 p 把每個 activation 設為 0,留下的除以 (1 − p),讓期望值不變;推論時不丟,也不縮放。大型 LLM 預訓練通常資料量夠大、只過資料一兩遍,很多模型直接不用 dropout。
- Test set 只用一次:如果反覆看 test 成績來挑設定,test 就變成另一個 val,報出來的成績會偏樂觀。
★ 訓練不順怎麼診斷?
Section titled “★ 訓練不順怎麼診斷?”訓練是實驗科學:同一份程式換一個學習率,結果可能天差地遠。所以要會讀 loss 曲線,並把每次執行記錄下來比較,而不是靠記憶。
Loss 曲線的常見形狀。
| 看到的曲線 | 最可能的原因 | 先試 |
|---|---|---|
| 第一步就遠高於 3.30 | 初始化讓模型過度自信 | 縮小輸出層初始權重 |
| 劇烈震盪、往上爬,或出現 NaN | 學習率太大 | 學習率除以 3–10 |
| 從頭到尾幾乎是平的 | 學習率太小、梯度沒傳回去(飽和),或程式有 bug | 先做下面的除錯步驟 |
| 下降但很抖 | batch 太小,梯度雜訊大 | 加大 batch 或降學習率 |
| train 持續降、val 回升 | overfitting | 見上一節 |
除錯的順序。 先讓模型在極少量資料上 overfit(例如只拿 1 個 batch 反覆訓練,loss 應該降到遠低於初始值,只拿幾個例子時可接近 0;降不下去代表程式有錯)→ 確認初始 loss 合理 → 調學習率 → 才開始調架構。順序反過來,會在一個有 bug 的程式上調好幾天的架構。
實驗追蹤。 實驗追蹤工具(例如 Weights & Biases)幫你記錄每次執行的超參數、loss 曲線、程式版本與隨機種子(seed),並排比較。例如 M1 makemore MLP 跑三組:學習率 0.1、0.03、0.01,其他都一樣,在同一個步數比較 val loss,就能看出哪組最好、好多少。
容易誤會:差一點點就是比較好。 同一組設定換一個 seed 重跑,val loss 也會有小差異。先重跑一兩次看自然的雜訊有多大,小於雜訊的差距不算數。比較時也要在相同步數下比,訓練較久的那組本來就容易佔便宜。
○ 詞向量的想法從哪來?
Section titled “○ 詞向量的想法從哪來?”word2vec、GloVe 用「一起出現的字意思相近」的原理,為每個字學一個向量。它們是 embedding 想法的起點,但和 S2 Transformer 與 decoding 講的模型內部 token embedding、S9 檢索與 RAG 用的 sentence embedding 是不同的東西(見術語表)。知道歷史有助理解,不是後面各章的前置。
理解路線(不寫程式):做完「先試一下」就可以跳到自我檢查。
先試一下(約 10 分鐘)
Section titled “先試一下(約 10 分鐘)”-
模型每一題都給正確答案 0.1 的機率。用講義的對照表,平均 loss 和 perplexity 各是多少?
對照答案
loss = −ln 0.1 ≈ 2.30,perplexity = e^2.30 ≈ 10:困惑程度相當於每次在 10 個字元之間平均亂猜。每題都給正確答案機率 p 時,perplexity 就是 1/p。
-
某個權重 w = 1,量到的梯度是 +0.4,學習率 0.5。走一步之後 w 是多少?loss 預期變大還是變小?
對照答案
1 − 0.5 × 0.4 = 0.8。梯度為正代表 w 往上調 loss 會變大,所以往下走;步長不太大時 loss 會變小。學習率太大時可能一步走過頭,反而變大。
必做:手寫 scalar autograd
Section titled “必做:手寫 scalar autograd”目標:寫出一個能對任意 scalar 運算式做 backprop 的小引擎,並證明梯度是對的。
步驟
- 定義一個
Value型別:存數值、梯度,以及產生它的運算與輸入。 - 支援
+、*、-、/、pow(常數次方)、exp、tanh。每個運算要寫出自己的局部導數。 - 實作
backward():對計算圖做拓樸排序,從輸出開始反向累加梯度。注意同一個節點被用兩次時,梯度要相加而不是覆蓋。 - 寫數值梯度檢查:對每個輸入加減 h = 1e-6,用中央差分估計梯度,和 backprop 結果比較。
- 用你的引擎組一個 2 層小網路(例如 2 → 8 → 1),用 SGD 擬合一個簡單資料集(例如 XOR 或 4–20 個點的二元分類),畫出 loss 曲線。
- 把學習率調大 10 倍以上再跑一次,畫在同一張圖上。
驗收
- 至少 5 個隨機運算式(每個至少 6 個運算、包含重複使用同一變數)通過梯度檢查,相對誤差 |a − b| ÷ max(|a|, |b|, 1e-8) 小於 1e-5,或絕對誤差 |a − b| 小於 1e-7(a、b 是兩種方法算出的梯度;分母加下限,是因為梯度接近 0 時相對誤差會失真;梯度很小或運算式的值很大時,數值梯度本身就不準,所以另設絕對誤差的門檻)。
- 小網路訓練 loss 單調下降(允許小幅震盪),最後在訓練資料上 100% 分類正確。
- 兩條 loss 曲線並列,用一兩句話說明大學習率那條哪裡不對(震盪、發散或卡住)。
- 能指出你的程式中哪一行處理「同一節點被用兩次」。
提示
- TypeScript:一個 class 加上 closure 存局部導數即可,不需要任何套件;JS 的 number 是 float64,梯度檢查精度足夠。
- Python:純 Python 就好;寫完可以用 PyTorch 的
requires_grad對同一個運算式算一次,交叉比對。 - 卡住時:
進階:模型專案 M1:makemore MLP 與訓練診斷
Section titled “進階:模型專案 M1:makemore MLP 與訓練診斷”目標:在 names 資料集上訓練字元級語言模型,從 bigram 一路做到 MLP,並學會用數字診斷訓練。
步驟
- 從 karpathy/makemore 取得
names.txt,切成 train / val / test(80 / 10 / 10)。 - 做 bigram baseline:計數版與「一層線性 + softmax」的梯度下降版,兩者 val loss 應該接近。
- 做 MLP 版本:character embedding → 串接前 3 個字元 → hidden layer → softmax。先用 PyTorch tensor 寫,必做題的 scalar 引擎太慢。
- 修正初始化:讓初始 loss 接近 ln 27,並讓 tanh 不再大量飽和。畫出修正前後每層 activation 的直方圖。
- 用 W&B(或任何實驗追蹤工具)跑至少 3 組超參數(學習率、hidden 大小、embedding 維度),記錄 val loss。
- 從最好的模型 sample 20 個名字。
驗收
- 報告 bigram 與 MLP 的 val loss 與 perplexity,MLP 的 val loss 比 bigram 低至少 0.15。
- 初始 loss 在 3.30 ± 0.2 之內。
- 在初始化時(第 0 步)量 hidden layer 中 |tanh| > 0.97 的比例:修正後要比修正前明顯下降(例如從 50% 以上降到 25% 以下);另外附上訓練結束時的比例(訓練後比例上升是正常的)。
- 實驗追蹤頁面上至少 3 次執行,能指出哪組最好、差多少。
提示
- Python:PyTorch(CPU 或 Apple MPS 都夠用,一次訓練幾分鐘);W&B 有免費個人方案,不想註冊可用 TensorBoard 或自己寫 CSV。
- 模型專案全程用 Python,環境設定見模型專案總覽。
- 卡住時:
- 重讀:模型要學什麼、怎麼打分?(含 MLP 形狀)、訓練為什麼一開始就卡住?、實驗追蹤
- 延伸:Zero to Hero 第 3 支(MLP)與第 4 支(activations、gradients、BatchNorm)
-
backprop 算出來的梯度是近似值還是精確值?數值梯度檢查又在檢查什麼?
提示
判斷重點:backprop 是精確值(只有浮點誤差),「推一點」的數值梯度才是近似;檢查的是 backprop 有沒有寫錯。常見錯法:要求兩者完全相等。回讀:參數該往哪裡調?
-
一個 27 類的分類模型,訓練第一步的 loss 是 15。你會先懷疑什麼?
提示
判斷重點:合理起點是 ln 27 ≈ 3.30;15 代表模型非常有把握地猜錯,先懷疑輸出層初始權重太大。常見錯法:先去調學習率或架構。回讀:訓練為什麼一開始就卡住?
-
Adam 和 SGD 的差別是什麼?為什麼 LLM 訓練幾乎都用 AdamW?
提示
判斷重點:Adam 依每個參數梯度的典型大小調整步長;AdamW 再把 weight decay 從梯度更新中拆出來,讓每個參數一致地被正規化。常見錯法:以為用了 Adam 就不必調學習率。回讀:拿到梯度後怎麼走?
-
情境題:訓練 loss 一路降到 1.2,val loss 降到 2.0 後開始回升。你會怎麼處理?列出至少兩種做法,以及你會先試哪一個。
提示
判斷重點:這是 overfitting;對策有提前停止、weight decay、dropout、更多資料或更小的模型,提前停止成本最低、通常先試。常見錯法:繼續訓練到 train loss 更低。回讀:模型在背答案怎麼辦?
-
Perplexity 從 8 降到 6,用白話說代表什麼?
提示
判斷重點:模型的困惑程度從「在 8 個選項間平均亂猜」降到「在 6 個之間」。常見錯法:把它當成 loss 的差;perplexity 要看比例,loss 才看差。回讀:模型要學什麼、怎麼打分?
-
不看講義,自己走一次「推一點」的例子:兩個候選、w = 0、正確答案是 a。分別量出 w → p 與 p → loss 的局部倍率,相乘後和直接推 w 量到的梯度比較。如果正確答案改成 m,梯度的正負號會怎麼變?
提示
判斷重點:0.25 × (−2) = −0.5,和直接量的一樣;答案改成 m 時,w 往上調會讓 loss 變大,梯度變成 +0.5。常見錯法:以為 p → loss 的倍率仍是 −2,其實這時 loss 看的是 m 的機率 1 − p。回讀:參數該往哪裡調?
-
為什麼 tanh 大量飽和時,前面幾層的參數幾乎學不到東西?用 backprop「倍率相乘」的說法解釋。
提示
判斷重點:飽和時 tanh 的局部倍率接近 0,往回傳的梯度要乘上它,前面的梯度跟著趨近 0。常見錯法:只說「數值太大」,沒連到倍率相乘。回讀:訓練為什麼一開始就卡住?
講義已經涵蓋這一章需要的內容。下面的資料適合想看得更深、或想跟著別人一行一行寫程式的人,都不是必讀。
- karpathy/nn-zero-to-hero — 前 5 支影片:micrograd、makemore 的 bigram、MLP、activations / gradients / BatchNorm、手寫 backprop。和本章的必做題、M1 makemore MLP 一一對應,適合邊看邊打程式;第 6 支(WaveNet 式的階層架構)把 MLP 改成更深的結構,是走向 S2 Transformer 與 decoding 的過渡。系列 2024 後未更新,但這幾支講的基礎不會過時。
- rasbt/LLMs-from-scratch — Appendix A(PyTorch 入門)與 Appendix D(訓練迴圈的 warmup、cosine decay、gradient clipping)。
- MiniTorch(Cornell)— 把 autograd 擴充到 tensor 與平行運算,適合寫完必做題還想再深入的人。
- Stanford CS224N — 前三講:word vectors、神經網路與 backprop 的數學推導,比講義的深入區塊更完整。
- 台大 李宏毅 Introduction to GenAI and ML 2025 秋 — 繁中;機器學習與深度學習基礎的講次。
- 台大 陳縕儂 ADL 2026 — 繁中;開頭的深度學習基礎講次。
- d2l-zh 動手學深度學習 — 簡中;第 2–5 章(預備知識、線性模型、MLP、深度學習計算)與第 11 章(最佳化演算法)有更多公式與程式。2024 後未更新。
- datawhalechina/diy-llm — 簡中;第 1 章 W&B 實驗追蹤的操作。
講義內容以 CC BY-NC-SA 4.0 授權:可以轉載與改作,但要標示出處、不得用於商業用途,改作也要用相同授權。引用的第三方內容與商標屬於原作者。