JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

從編號到向量:嵌入層

像 5169 這樣的 token 編號只是一張名牌——數字本身毫無意義。嵌入層把每個編號換成一串學出來的數字,讓模型真正能拿來推理。來認識模型最前面的那組權重。

為什麼編號本身毫無用處

第 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
每個 token 編號對應一列。整張表都是在訓練過程中學出來的。
\mathbf{e}_t = \mathrm{onehot}(t)^{\top} E = E_{t,\,:}

用独热向量乘以嵌入矩阵 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 篇我們會再回到這個主題。