為什麼編號本身毫無用處
第 1 篇結束時,我們手上有一串token 編號(token IDs),像 [1820, 5169, 7493]。直覺上很想把這些整數直接丟進運算。但這個數字的大小毫無意義:token 5169(` cat`)並不是「比」token 1820(`the`)「大」,也不是它的「三倍」。編號只是清單裡的位置,就像戲院的座位號碼。對座位號碼做算術是沒有意義的。
經典的修法是 獨熱編碼(one-hot encoding):把 token 5169 表示成一個向量,除了第 5169 個位置是 1 之外,其餘全是 0。這消除了假的大小順序,但對十萬字的詞彙表來說,那是一個長達十萬、其中九萬九千九百九十九個都是 0 的向量——浪費,而且它對語意依然一無所知:每個 token 跟其他每個 token 的距離都一樣。我們需要更稠密、更聰明的東西。
嵌入矩陣:一張巨大的查找表
解法是嵌入矩陣(embedding matrix):一張學出來的大數字表,詞彙表裡每個 token 對應一列(row)。要把 token 5169 變成有用的東西,你只要把第 5169 列抽出來。那一列是一串,比方說 4096 個實數——這個 token 的嵌入(embedding),一個稠密的向量(vector),在模型內部代表這個詞。不用乘法、不費工夫;嵌入其實就是按列查表。
embedding_matrix: shape [vocab_size, d_model] = [100000, 4096]
lookup(5169) -> embedding_matrix[5169]
-> [0.12, -0.85, 0.33, ..., 0.07] # 4096 numbers, the " cat" vector用独热向量乘以嵌入矩阵 E,其实就是选出第 t 行——查表只是这同一运算的高效写法。
嵌入維度:一個詞有多「寬」?
每一列有幾個數字?這個數量就是嵌入維度(embedding dimension),常寫成 `d_model`——768、4096、8192 之類的值很常見。可以把它想成描述每個 token 的座標數量,或是模型其餘部分賴以運行的那條管道有多寬。維度越大,模型就有越多空間去編碼細微的差別(時態、語氣、主題、正式程度),但也耗費更多記憶體與運算。關鍵是:個別座標並沒有人類標好的意義,沒有哪一格專門代表「複數性」。意義是攤散在所有數字之中的——這一點第 4 篇會講得很生動。
這些數字從哪來?
最美的部分來了:沒有人手動填這張嵌入表。它一開始是隨機數字,然後被學出來。每個嵌入值都是一個參數(parameter),在訓練中被調整。當模型用下一個 token 預測來訓練時,每次它猜錯,誤差就被回溯,嵌入被輕輕推動,讓行為相似的 token 朝相似的向量靠攏。經過數十億個範例後,` cat` 和 ` dog` 最終會落在彼此附近,因為它們出現在相似的脈絡裡。這張表完全是被「用法」雕塑出來的。
交互式梯度下降:沿损失曲面滚向最小值。
附帶一招:把兩端綁在一起
在輸出端,模型面對一個鏡像般的任務:把它最後的內部向量轉回成詞彙表裡每個 token 的分數,才能挑出下一個詞。這需要第二張大表——而它的形狀跟輸入的嵌入矩陣一模一樣。因此許多模型採用綁定輸入/輸出嵌入(tied input/output embeddings):讀入 token 和輸出 token 評分時,重複使用同一張表。這省下一大塊參數,而且常常還能提升品質,因為一個 token 的「輸入語意」和「輸出身分」保持一致。第 5 篇我們會再回到這個主題。