導讀
Transformer 要解決的核心問題只有一個:讓同一個詞的向量,隨語境而改變。本文依照本週投影片(Jurafsky & Martin,Speech and Language Processing 第三版草稿)的順序和記號來編。每一節先給比喻,再給式子,最後附上例子或程式。下表先把五個常見術語翻成白話:
| 術語 | 白話 | 生活類比 |
|---|---|---|
| 向量 | 一串數字,代表空間中一個點的座標 | 地圖上的經緯度,只是維度多達上千 |
| 矩陣 | 排成表格的一大堆數字 | 一張 Excel 試算表 |
| 內積 | 兩串數字逐項相乘再加總;數值越大,兩者越相似 | 兩人問卷答案的吻合程度 |
| softmax | 把任意分數換成加總為 100% 的比例 | 把各候選人的票數換成得票率 |
| 參數(權重) | 模型從資料中學到、可以調整的數字 | 收音機上可以轉動的旋鈕 |
整體流程可以一句話說完:詞元先查出詞嵌入,加上位置嵌入,再經過 𝐿 個 Transformer 區塊逐層修改,最後由語言模型頭輸出下一個詞元的機率分布。
真正的「理解」發生在第④站:前三站只是把字變成數字,第⑤站只是把數字換回詞。
貫穿全文的例句是一組 Winograd 型照應句:「獎盃放不進行李箱,因為它太大了」與「……因為它太小了」。一字之差,「它」就從獎盃改指行李箱。投影片用的是英文版:The chicken didn't cross the road because it was too tired / wide.
進階・記號約定(第一次讀可以跳過):向量一律寫成列向量(row vector),所以是「向量乘矩陣」而非「矩陣乘向量」。
| 符號 | 意義 | 形狀 |
|---|---|---|
| 𝐱ᵢ | 第 𝑖 個詞元的輸入向量 | [1 × 𝑑] |
| 𝐗 | 整段輸入的 𝑁 個向量疊成的矩陣 | [𝑁 × 𝑑] |
| 𝐄 | 詞嵌入矩陣,每個詞元一列 | [詞彙表大小 × 𝑑] |
| 𝐖(上標 Q、K、V) | 查詢、鍵、值的投影矩陣 | [𝑑 × 𝑑ₖ]、[𝑑 × 𝑑ₖ]、[𝑑 × 𝑑ᵥ] |
| 𝐴 | 注意力頭的數量 | 純量 |
| 𝐡ᵢᴸ | 第 𝐿 層在位置 𝑖 的輸出,即上下文詞向量 | [1 × 𝑑] |
1. 詞元與詞嵌入
電腦不認得字,只認得數字。所以先把句子切成小塊,再替每一塊查出一串代表它的數字。
比喻:一本座標辭典。 斷詞器(tokenizer)先把句子切成辭典裡查得到的條目,再把每個條目換成編號;詞嵌入矩陣就是一本「座標辭典」,每個編號對應一列座標,也就是高維空間中的一個點。
進階:式子(可跳過)。 詞嵌入矩陣 𝐄 的形狀為 [|𝑉| × 𝑑],|𝑉| 為詞彙表大小。以 BPE 等方法把字串切成長度為 𝑁 的詞元編號序列 𝐰 = [𝑤₁, 𝑤₂, …] 之後,查表等同於用 one-hot 向量乘上 𝐄:
算算看。 假設詞彙表只有 4 個詞元、𝑑 = 3:
| 編號 | 詞元 | 𝐄 的那一列 |
|---|---|---|
| 0 | 獎盃 | [0.9, 0.1, 0.4] |
| 1 | 行李箱 | [0.8, 0.3, 0.1] |
| 2 | 它 | [0.2, 0.7, 0.5] |
| 3 | 大 | [0.6, 0.2, 0.9] |
「它」的編號是 2。表從 0 開始數,one-hot 向量是 [0, 0, 1, 0]。那個 1 對上的是由上往下數的第 3 列,也就是「它」,不是第 2 列的「行李箱」:
所以「乘上 one-hot」,其實就是查出編號 2 的那一列。兩個例句的「它」都拿到 [0.2, 0.7, 0.5]。
限制。 這張表是固定的。在「太大」與「太小」兩句中,「它」查出的是同一列向量,並不知道自己指向誰。投影片把這稱為靜態詞向量(如 word2vec)的問題;後面的注意力,就是要把這個向量依照語境推向對的方向。
另外要留意:切分本身並不中立。書寫系統和構詞類型不同的語言,同樣的內容可能被切成數量差很多的詞元,成本和用得到的上下文長度都會不一樣(Petrov et al. 2023)。
2. 位置嵌入 position embeddings
光知道有哪些詞還不夠,還得告訴模型每個詞排第幾,否則「狗咬人」與「人咬狗」無從分辨。
比喻:座位號碼。 注意力本身只看「誰和誰相似」,不看「誰坐在哪裡」。若不加處理,「狗咬人」與「人咬狗」對它而言是同一袋詞。位置嵌入就像在每個詞身上別一張座位號碼牌,讓詞序重新進入計算。
進階:式子(絕對位置)。 另學一個位置嵌入矩陣 𝐄ₚₒₛ,每個位置一列,從隨機初始化開始訓練。輸入向量是詞嵌入與位置嵌入相加:
算算看。 若「它」坐在第 11 號,而 𝐄ₚₒₛ 的第 11 列是 [0.1, −0.2, 0.0],則:
同一個「它」換到別的座位,加上的是另一列,得到的向量也就不同。
限制。 不加位置的話,「狗咬人」和「人咬狗」對模型來說是同一袋詞。上面的圖只是把主詞和受詞對調:詞還是那三個,座位卻換了。為什麼這樣就分不出誰咬誰,下一節先把注意力分數定義好,後面的進階節再算一次。
絕對位置還有一個實際限制:訓練時沒見過的位置,沒有向量可以查,上下文長度因此受到限制。正弦編碼和 RoPE 不必查表,放在自注意力之後;第一次讀可以先跳過。
3. 自注意力 Self-attention
每個詞環顧整句,挑出對自己最要緊的幾個詞,把它們的意思混進來,更新自己。
比喻:在圖書館找書。 你帶著一個問題(Query)走進書庫;每本書的書背貼著標籤(Key);問題與標籤越吻合,你就越仔細讀那本書的內容(Value)。每個詞同時扮演這三個角色:提出問題、貼出標籤、提供內容。讀完之後,你把各書的內容依投入程度加權,寫成一份筆記,這份筆記就是該詞的新表示。
投影片的定義更簡潔:注意力就是一種向量的加權總和,權重取決於相似度。
這就是「加權總和」的意思:「它」的新意思,約有一半來自先行詞,其餘零星來自其他詞。這張圖看得到整句。讀到「它」的時候,後面的「大」或「小」已經出現,權重就可以整片轉去獎盃,或整片轉去行李箱。下面的式子和手算只看左邊,而且手算的三個詞裡還沒有「大」和「小」。
進階:式子(單一注意力頭)。 先以三個可學習矩陣把 𝐱 投影成三種角色:
再以內積衡量吻合度、softmax 換成權重、對值向量加權求和。softmax 只加總 𝑗 ≤ 𝑖 的位置,也就是只看左邊的因果注意力。多頭最後用來彙整的 𝐖ᴼ 留到下一節;這一節的 𝐚ᵢ 就是值向量的加權總和:
算算看。 只取三個詞元(獎盃、行李箱、它),𝑑ₖ = 2。假設投影後得到:
| 詞元 | 鍵 𝐤ⱼ | 值 𝐯ⱼ |
|---|---|---|
| 獎盃 | [2, 1] | [1, 0] |
| 行李箱 | [1, 0] | [0, 1] |
| 它 | [0, 1] | [0.5, 0.5] |
「它」是第 3 個詞元,查詢向量為 𝐪₃ = [1, 2]。分四步算完:
- 內積 𝐪₃·𝐤ⱼ:獎盃 1×2 + 2×1 = 4;行李箱 1×1 + 2×0 = 1;它 1×0 + 2×1 = 2。
- 除以 √𝑑ₖ = √2 ≈ 1.414:得 2.83、0.71、1.41。
- softmax:exp(2.83) ≈ 16.9、exp(0.71) ≈ 2.0、exp(1.41) ≈ 4.1,總和約 23.1,所以 𝛼₃ⱼ ≈ 0.73、0.09、0.18。
- 加權總和:𝐚₃ = 0.73×[1, 0] + 0.09×[0, 1] + 0.18×[0.5, 0.5] ≈ [0.82, 0.18]。
結果有八成來自「獎盃」的值向量。「它」被拉向獎盃,在數學上就是這麼一回事。這三個詞裡沒有「大」,也沒有「小」。這裡的 0.73,是「它」分給前面「獎盃」的權重,和圖上那個會跟著「大」或「小」翻轉的數字不是同一回事。
進階:為何除以 √𝑑ₖ。 若 𝐪 與 𝐤 的各分量獨立、變異數為 1,內積的變異數就是 𝑑ₖ。維度越高,分數越極端,softmax 會把幾乎全部權重壓在單一詞元上,梯度也隨之消失。除以 √𝑑ₖ 把變異數拉回 1:
算算看。 設 𝑑ₖ = 64,三個分數原本是 [8, 4, 0]。不縮放與除以 √64 = 8 的結果對照如下:
前者幾乎全押在第一個詞;縮放之後,其他詞也分得到注意力。
進階:矩陣形式與遮罩。 實作時把 𝑁 個詞元一次算完。生成式模型不能偷看答案,因此把 𝐐𝐊ᵀ 上三角(未來位置)設為 −∞,softmax 後即為 0:
算算看。 沿用上例的順序(獎盃、行李箱、它)。加上遮罩後,「獎盃」那一列只剩自己,softmax 結果必為 [1, 0, 0];「它」排在最後,本來就看得到全部,權重仍是 [0.73, 0.09, 0.18]。
回到例句。 圖上那種整片轉過去的樣子,得要模型讀到「它」時就看得到「大」或「小」,也就是雙向注意力。加上因果遮罩之後,處理「它」時還看不到「大」或「小」,照應要等到後面的位置,再由那些詞把訊息帶回來。所以那張圖不是 GPT 在「它」這個位置上的注意力。
代價。 𝐐𝐊ᵀ 有 𝑁 × 𝑁 個分數,計算和記憶體都隨長度平方成長。長上下文這麼貴,原因就在這裡。
進階・程式(NumPy,單頭、含遮罩):
import numpy as np
def softmax(z, axis=-1):
z = z - z.max(axis=axis, keepdims=True) # 數值穩定
e = np.exp(z)
return e / e.sum(axis=axis, keepdims=True)
def causal_self_attention(X, WQ, WK, WV):
Q, K, V = X @ WQ, X @ WK, X @ WV # [N×dk], [N×dk], [N×dv]
N, dk = K.shape
scores = Q @ K.T / np.sqrt(dk) # [N×N]
future = np.triu(np.ones((N, N), dtype=bool), k=1)
scores[future] = -np.inf # 遮住未來
alpha = softmax(scores) # 每列和為 1
return alpha @ V, alpha
rng = np.random.default_rng(0)
N, d, dk = 5, 8, 4
X = rng.normal(size=(N, d))
WQ, WK, WV = (rng.normal(size=(d, dk)) for _ in range(3))
out, alpha = causal_self_attention(X, WQ, WK, WV)
print(alpha.round(2)) # 下三角矩陣
print(out.shape) # (5, 4)
進階・位置寫進內積
這一節補上第 2 節留下的兩件事:座位號碼怎麼改變注意力分數,以及怎麼不把位置加成向量,而是寫進內積裡。第一次讀可以整節跳過。
算算看:少了位置,「誰咬誰」就消失。 取 𝑑 = 2,詞嵌入為 狗 = [1.0, 0.2]、咬 = [0.0, 1.0]、人 = [0.8, 0.6];位置嵌入為 第 1 號 [0.4, 0.0]、第 2 號 [0.0, 0.2]、第 3 號 [−0.4, 0.0]。為了算起來簡單,假設三個投影矩陣 𝐖 都是單位矩陣,而且模型看得到整句。這裡只追蹤「咬」這個詞元。
以「狗咬人」加上位置為例:「咬」是第 2 個詞元,𝐱₂ = [0.0, 1.2],它對三個詞的分數為
不加位置的時候,softmax 可以手算。分數 0.2、1.0、0.6 的指數大約是 1.22、2.72、1.82,加起來約 5.76,權重約 0.21、0.47、0.32。加權之後,就是下面那張表的 [0.465, 0.704]。加上位置之後的 0.24、1.44、0.72,同樣算出表裡的 0.168、0.559、0.272。「人咬狗」只是把這三個分數左右對調。
兩句的完整結果如下:
| 步驟 | 狗咬人 | 人咬狗 |
|---|---|---|
| 不加位置:「咬」對第 1、2、3 個詞的分數 | 狗 0.2、咬 1.0、人 0.6 | 人 0.6、咬 1.0、狗 0.2 |
| 不加位置:「咬」的輸出 | [0.465, 0.704] | [0.465, 0.704] |
| 加位置後的 𝐱₁、𝐱₃ | 狗 [1.4, 0.2]、人 [0.4, 0.6] | 人 [1.2, 0.6]、狗 [0.6, 0.2] |
| 加位置:分數 | 0.24、1.44、0.72 | 0.72、1.44、0.24 |
| 加位置:softmax 權重 | 0.168、0.559、0.272 | 0.272、0.559、0.168 |
| 加位置:「咬」的輸出 | [0.345, 0.868] | [0.428, 0.868] |
不加位置時,兩句只是同一組「詞與分數」換了排列;加權總和不分先後,輸出一模一樣,模型確實分不出誰咬誰。這個性質稱為置換等變(permutation equivariance)。加上位置之後,「狗」當主詞時是 [1.4, 0.2],當受詞時是 [0.6, 0.2],兩種角色拿到不同向量,「咬」的輸出也就分開了。
兩種常見變體。 原始論文(Vaswani et al. 2017)用固定的正弦函數,不需學習,波長由短到長,像一組時針、分針、秒針同時報時:
算算看。 取 𝑑 = 4。𝑘 = 0 那一組的分母是 10000⁰ = 1,轉得快,像秒針;𝑘 = 1 那一組的分母是 10000 的 2/4 次方,即 √10000 = 100,轉得慢,像時針:
| 位置 𝑝 | sin(𝑝) | cos(𝑝) | sin(𝑝/100) | cos(𝑝/100) |
|---|---|---|---|---|
| 0 | 0.000 | 1.000 | 0.000 | 1.000 |
| 1 | 0.841 | 0.540 | 0.010 | 1.000 |
| 2 | 0.909 | −0.416 | 0.020 | 1.000 |
| 3 | 0.141 | −0.990 | 0.030 | 1.000 |
| 7 | 0.657 | 0.754 | 0.070 | 0.998 |
| 100 | −0.506 | 0.862 | 0.841 | 0.540 |
從第 1 號移到第 2 號,前兩格變化明顯,後兩格幾乎不動;要等位置相隔夠遠,慢的那一組才會拉開差距。
再看第 7 號:快的一組 [0.657, 0.754] 已轉回接近第 1 號的 [0.841, 0.540],因為秒針約每 2π ≈ 6.28 個位置轉一圈;這時要靠慢的一組(0.070 對 0.010)才分得開。到了第 100 號,慢的一組也轉了 1 弧度,四個數合起來仍是獨一無二的座標。
相對位置藏在內積裡。 利用 sin 𝑎 sin 𝑏 + cos 𝑎 cos 𝑏 = cos(𝑎 − 𝑏),兩個位置向量的內積只取決於距離:
代入數字:
只要相隔兩格,內積就相同。RoPE 正是把這個性質直接用在 𝐪 與 𝐤 上。
現在的大型語言模型多半使用旋轉位置編碼 RoPE(Su et al. 2024):位置不加在 𝐱 上,而是讓 𝐪 和 𝐤 依所在位置旋轉一個角度。兩個向量轉完之後,內積只看角度差,也就是相對距離 𝑗 − 𝑖。換句話說,模型在意的是「隔幾個詞」,不是「坐在第幾號」。
算算看。 以二維為例,設每往後一個位置就多轉 30°,且 𝐪 與 𝐤 轉動前都是 [1, 0]。位置 1 與位置 3 分別轉 30° 與 90°,夾角 60°,內積為 cos 60° = 0.5;位置 5 與位置 7 分別轉 150° 與 210°,夾角仍是 60°,內積仍是 0.5。只要相隔兩個詞,分數就相同。
再算一例:換成真正的 𝐪 與 𝐤。 沿用第 3 節「它」的 𝐪 = [1, 2] 與「獎盃」的 𝐤 = [2, 1],轉動前內積為 4。把向量 [𝑥, 𝑦] 轉 𝛼 度的公式,以及轉動後內積的一般形式如下(𝜑 為兩者原本的夾角,此例 cos 𝜑 = 0.8):
設「它」在位置 𝑚 = 3、「獎盃」在位置 𝑛 = 1,每個位置轉 𝜃 = 30°,則 𝐪 轉 90°、𝐤 轉 30°:
換幾組位置:
| 「它」位置 𝑚 | 「獎盃」位置 𝑛 | 轉後 𝐪 | 轉後 𝐤 | 內積 |
|---|---|---|---|---|
| 1 | 1 | [−0.134, 2.232] | [1.232, 1.866] | 4.000 |
| 2 | 1 | [−1.232, 1.866] | [1.232, 1.866] | 1.964 |
| 3 | 1 | [−2.000, 1.000] | [1.232, 1.866] | −0.598 |
| 7 | 5 | [0.134, −2.232] | [−2.232, 0.134] | −0.598 |
後兩列轉完的向量完全不同,內積卻一樣,因為兩種情況都相隔 2 個位置。相隔 0 個、1 個、2 個位置時,分數各不相同,模型因此可以學到自己比較在意隔多遠的詞。這個例子每一步轉 30°,只是為了讓變化看得清楚。實際的 RoPE 把 𝑑 維切成 𝑑/2 組,各組的角速度和正弦編碼的 𝜔ₖ 相同,多數組轉得很慢,不會像這個例子一樣,轉幾步就繞回原處。
進階・程式(NumPy,驗證本節數字):
import numpy as np
def sinusoidal_pe(p, d):
k = np.arange(d // 2)
omega = 1 / 10000 ** (2 * k / d) # 每組的角速度
pe = np.empty(d)
pe[0::2], pe[1::2] = np.sin(p * omega), np.cos(p * omega)
return pe
def rope_2d(v, pos, theta=np.pi / 6): # 每往後一個位置多轉 30°
a = pos * theta
R = np.array([[np.cos(a), -np.sin(a)],
[np.sin(a), np.cos(a)]])
return v @ R.T # 講義用列向量,旋轉寫在右邊
print(sinusoidal_pe(1, 4).round(3)) # [0.841 0.54 0.01 1. ]
for p, p2 in [(1, 3), (5, 7), (40, 42)]:
print(p, p2, round(sinusoidal_pe(p, 4) @ sinusoidal_pe(p2, 4), 3)) # 皆 0.584
q, k = np.array([1., 2.]), np.array([2., 1.]) # 第 3 節的 q_它、k_獎盃
for m, n in [(1, 1), (2, 1), (3, 1), (7, 5)]:
print(m, n, round(rope_2d(q, m) @ rope_2d(k, n), 3)) # 4.0、1.964、-0.598、-0.598
4. 多頭注意力與 Transformer 區塊
同時請好幾組「讀者」從不同角度讀句子,再把意見彙整;這樣的步驟疊上幾十層,就是一個 Transformer。
比喻:審稿委員會。 一組 𝐐、𝐊、𝐕 只能問一種問題,而語言中的關係不只一種:誰是主詞、哪個修飾語修飾哪個名詞、代名詞指向誰。多頭注意力就像請 𝐴 位審稿人各看一個面向,最後由主編(𝐖ᴼ)整合成一份意見。
其中 ⊕ 為串接;通常取 𝑑ᵥ = 𝑑 / 𝐴,使串接後仍是 𝑑 維,𝐖ᴼ 的形狀為 [𝑑 × 𝑑]。
算算看。 設 𝑑 = 8、𝐴 = 2,每個頭輸出 𝑑ᵥ = 4 維。兩個頭串接成 4 + 4 = 8 維,再乘上 [8 × 8] 的 𝐖ᴼ,輸出仍是 8 維,形狀和輸入一樣。
比喻:殘差流是一份共筆筆記。 每個詞元有自己的一條「殘差流」(residual stream),由下往上傳遞。每一層都不重寫整份筆記,只在上面加註。注意力是唯一能讀別人筆記的元件,負責在詞元之間搬運資訊;前饋網路則在各自的筆記上做加工(Elhage et al. 2021)。所以剛才那個 8 維的輸出,可以整段加回這份筆記,不必把原來的筆記換掉。
進階・一個區塊的五個步驟(pre-norm,投影片所用):
算算看。 沿用第 2 節「它」的 𝐱₁₁ = [0.3, 0.5, 0.5]。若注意力算出 𝐭² = [0.2, −0.1, 0.4],則第三步為
原本的筆記還在,只是每一格多了一點註記。
前饋網路提供非線性,通常先把維度放大約四倍再縮回:
算算看。 ReLU 把負數歸零、正數照留:ReLU([−1.2, 0.5, 3.0, −0.3]) = [0, 0.5, 3.0, 0]。至於維度,以 GPT-2 小型版為例,𝑑 = 768,𝐖₁ 先放大到 3072 維,𝐖₂ 再縮回 768 維。
LayerNorm 就是統計學的 z 分數,套用在單一向量的 𝑑 個分量上,再以可學習的 𝛾、𝛽 還原尺度。比喻是把各科原始分數換成標準分數,避免某一科只因分數尺度較大就主宰總分。這裡的標準差用全部 𝑑 個分量去除,分母是 𝑑。這和統計課的樣本標準差不同,樣本標準差要除以 𝑑 − 1:
算算看。 取 𝐱 = [2, 4, 6, 8],並設 𝛾 = 1、𝛽 = 0:
不論原始數字多大,正規化後平均都是 0、標準差都是 1。
因為輸入與輸出都是 𝑑 維,區塊可以一層層疊上去。以 GPT-3 為例,共 96 層、每層 96 個頭(Brown et al. 2020)。
5. 上下文詞向量、語言模型頭與預訓練
疊完所有層,每個詞都換上一組「讀過上下文」的新數字;模型用它來猜下一個詞,並靠反覆猜、反覆修正來學習。
比喻:辭典釋義與讀者的當下理解。 靜態詞向量像辭典:一詞一條,羅列所有可能的意思。上下文詞向量像讀者讀到某一句時腦中的理解:一詞一例,只留下此刻成立的那個意思。
定義。 詞元 𝑖 在第 𝐿 層的輸出 𝐡ᵢᴸ,就是它的上下文詞向量。「太大」與「太小」兩句裡的「它」,起點 𝐱ᵢ 相同,經過 𝐿 層注意力之後的 𝐡ᵢᴸ 卻不同。投影片的總結是:注意力讓每個詞的嵌入在不同語境中各不相同。
語言模型頭。 最後一層的向量要換回詞彙表上的機率。解嵌入(unembedding)層通常直接借用 𝐄 的轉置,稱為權重共享(weight tying):
算算看。 沿用第 1 節的 4 詞元 𝐄,設最後一層正規化後的向量為 𝐡 = [0.8, 0.2, 0.6]。乘上 𝐄ᵀ,等於拿 𝐡 和每個詞元那一列做內積:
| 詞元 | 內積 𝐮 | softmax 後 𝐲 |
|---|---|---|
| 獎盃 | 0.8×0.9 + 0.2×0.1 + 0.6×0.4 = 0.98 | 0.280 |
| 行李箱 | 0.76 | 0.225 |
| 它 | 0.60 | 0.192 |
| 大 | 0.8×0.6 + 0.2×0.2 + 0.6×0.9 = 1.06 | 0.303 |
模型認為下一個詞最可能是「大」,但把握不高;這是因為示範用的數字很小。
預訓練:預測下一個詞。 拿大量文本,在每個位置 𝑡 要求模型猜 𝑤ₜ₊₁,以交叉熵懲罰錯誤。因為正確分布是 one-hot,損失就等於正確詞元的負對數機率。訓練時無論模型猜什麼,下一步一律餵入正確的詞,稱為 teacher forcing:
算算看。 若正確的下一個詞是「大」,上表給它 0.303。比較三種把握程度:
依序對應現況、更有把握(給到 0.9)、幾乎猜錯(只給 0.01)。訓練就是不斷調整參數,讓這個數字變小。
生成與溫度。 貪婪解碼每一步都選機率最高的詞,結果平淡、容易重複。實務上改成抽樣,再用溫度 𝜏 調整分布有多尖。𝜏 越小越保守;𝜏 趨近 0 的時候,就變成貪婪解碼。𝜏 越大,選詞越分散:
算算看。 同一組分數 𝐮 = [0.98, 0.76, 0.60, 1.06],「大」是第 4 個詞元:
𝜏 = 0.5 等於把分數加倍,「大」從 0.303 升到 0.357,其餘都下降,分布變得更尖。
下面這張圖用的是「太_了」這個空格的示意分數,不必和上面那四個詞的內積對上。兩邊的算法一樣,都是 softmax(分數/𝜏)。
三種架構。 同一套區塊,換個遮罩與訓練目標,就成了不同家族:
| 架構 | 注意力範圍 | 訓練目標 | 代表模型 | 典型用途 |
|---|---|---|---|---|
| 僅解碼器 | 只看左側(因果遮罩) | 預測下一個詞 | GPT 系列、Llama | 生成、對話 |
| 僅編碼器 | 雙向,左右皆可見 | 遮蔽語言模型(填空) | BERT | 分類、序列標記、取上下文詞向量 |
| 編碼器-解碼器 | 編碼雙向,解碼因果並交叉注意 | 序列到序列 | 原始 Transformer、T5 | 翻譯、摘要 |
若目的是取上下文詞向量做詞義分析,雙向的 BERT 類模型較直接,因為「它」的向量同時看得到「大/小」。
6. 評估
困惑度問的是:模型每一步平均在幾個詞之間拿不定主意?數字越小越好。
比喻:每一步在幾個選項之間猶豫。 困惑度(perplexity)可以讀成模型的「等效分支數」。困惑度為 4,表示模型平均而言像在 4 個等可能的詞之間擲骰子;越低,代表它對真實文本越不意外。
進階:式子(可跳過)。 測試集的機率會隨長度變小,無法跨文本比較,因此取幾何平均再倒數,也就是對長度正規化:
算算看。 一段三個詞元的文本,模型給正確詞元的機率依序是 0.5、0.25、0.125:
改用最右邊的寫法,三個 −ln 𝑝 分別是 0.693、1.386、2.079,結果相同:
也就是說,這個模型平均像在 4 個詞之間挑一個。
最右邊一式顯示,困惑度正是第 5 節交叉熵損失的指數。
import numpy as np
def perplexity(p_true):
"""p_true:模型給每個正確下一詞元的機率"""
return float(np.exp(-np.mean(np.log(p_true))))
print(perplexity([0.25] * 8)) # 4.0:每步四選一
print(perplexity([0.9, 0.5, 0.05, 0.6])) # 約 2.93;一個 0.05 就拉高不少
使用限制。 困惑度以詞元為單位,只有斷詞器相同的時候才能比較。換模型或換語言時,最好改用每個位元組的位元數(bits per byte),這類和切分方式無關的指標。
困惑度之外。 投影片最後一頁提醒,評估不只是預測準確:
| 面向 | 問的問題 | 常見做法 |
|---|---|---|
| 語言模型品質 | 對沒看過的文本預測得多好 | 困惑度、每個位元組的位元數 |
| 下游任務 | 能否完成分類、問答、推理 | 準確率、F1、標準基準測驗 |
| 生成品質 | 輸出是否忠實、通順、有用 | BLEU、ROUGE、人工評估、以 LLM 擔任評審 |
| 表示品質 | 上下文詞向量是否編碼語言結構 | 探測分類器、結構探測(Hewitt & Manning 2019)、詞義消歧 |
| 跨語言與類型涵蓋 | 能否泛化到陌生的語言系統 | 多語基準、類型學平衡的推理題(如 LOBSTER,Lian et al. 2025) |
| 規模與能耗 | 訓練與部署要付出多少 | 參數量、GPU 時數、kWh 或 CO2 排放 |
| 公平性 | 是否強化刻板印象、對特定群體表現較差 | 偏見與群體差異基準 |
基準測驗另有資料汙染的風險:測驗題若已出現在預訓練語料中,高分只說明記憶,不說明能力。
7. 語言學回望與小結
分布語意學的動態化。 Firth(1957)說,要認識一個詞,就看它和誰作伴。靜態詞向量把「作伴」累積成一個平均值;注意力則把它改寫成當下的計算:詞義不是查表查出,而是由此刻身旁的詞共同決定。
語序與位置。 漢語這類缺乏詞形變化、倚重語序的語言,「狗咬人」與「人咬狗」的差別幾乎全落在位置上。RoPE 只編碼相對距離,與語言學中「依存距離」的觀念相近;但模型是否因此學到類型學意義上的語序規律,仍是開放問題。
注意力是線索,不是解釋。 部分注意力頭的行為與句法關係相當吻合,例如受詞注意其動詞、代名詞注意其先行詞(Clark et al. 2019)。然而權重高,不代表模型就依那個詞做判斷(Jain & Wallace 2019;Wiegreffe & Pinter 2019)。結構究竟是模型自己長出來,還是我們讀進去,需要探測與介入實驗共同檢驗。
困惑度與驚奇度。 −log 𝑃(𝑤ₜ ∣ 𝑤₁, …, 𝑤ₜ₋₁) 在心理語言學中稱為驚奇度(surprisal),能預測人類的閱讀時間(Hale 2001;Levy 2008)。困惑度因而不只是工程指標,也是連接模型與人類處理歷程的橋樑。
回到那一句。 「獎盃放不進行李箱,因為它太大了」可以沿著導讀的五站再走一遍。斷詞之後,詞嵌入讓兩個「它」查出同一列。加上位置之後,它才帶上自己的座位,向量就和句子裡其他位置的「它」不一樣。注意力再依照當下的詞改寫它:雙向模型看得到「大」,權重會轉去獎盃;只看左邊的生成式模型,讀到「它」時還沒看到「大」或「小」,要等後面的詞把先行詞的訊息帶回來。區塊疊了 𝐿 層之後,兩句裡的「它」雖然從同一個向量出發,最後卻分開了。語言模型頭只做一件事:把最後一個向量換回下一個詞的機率。
一句話小結。 詞嵌入給每個詞一個起點,位置嵌入標出它坐在哪裡,注意力讓它聽取鄰座的意見,層層堆疊後得到上下文詞向量,最後以困惑度與各種基準檢驗它說得好不好。
參考文獻
- Brown, Tom B., et al. 2020. Language models are few-shot learners. NeurIPS 33.
- Clark, Kevin, Urvashi Khandelwal, Omer Levy, and Christopher D. Manning. 2019. What does BERT look at? An analysis of BERT's attention. BlackboxNLP Workshop.
- Devlin, Jacob, Ming-Wei Chang, Kenton Lee, and Kristina Toutanova. 2019. BERT: Pre-training of deep bidirectional transformers for language understanding. NAACL-HLT.
- Elhage, Nelson, et al. 2021. A mathematical framework for transformer circuits. Transformer Circuits Thread.
- Firth, John R. 1957. A synopsis of linguistic theory, 1930–1955. In Studies in Linguistic Analysis. Oxford: Blackwell.
- Hale, John. 2001. A probabilistic Earley parser as a psycholinguistic model. NAACL.
- Hewitt, John and Christopher D. Manning. 2019. A structural probe for finding syntax in word representations. NAACL-HLT.
- Jain, Sarthak and Byron C. Wallace. 2019. Attention is not explanation. NAACL-HLT.
- Jurafsky, Daniel and James H. Martin. Speech and Language Processing, 3rd ed. draft. Transformers 與 Large Language Models 各章。
- Levy, Roger. 2008. Expectation-based syntactic comprehension. Cognition 106(3).
- Lian, Da-Chen, et al. 2025. LOBSTER: Linguistics Olympiad benchmark for structured evaluation and reasoning. ROCLING 2025.
- Petrov, Aleksandar, Emanuele La Malfa, Philip Torr, and Adel Bibi. 2023. Language model tokenizers introduce unfairness between languages. NeurIPS 36.
- Su, Jianlin, et al. 2024. RoFormer: Enhanced transformer with rotary position embedding. Neurocomputing 568.
- Vaswani, Ashish, et al. 2017. Attention is all you need. NeurIPS 30.
- Wiegreffe, Sarah and Yuval Pinter. 2019. Attention is not not explanation. EMNLP-IJCNLP.