2013 - word2vec 詞向量#
案件摘要#
2013 年,Google 的 Tomas Mikolov 發表 word2vec——用一個淺層神經網路把每個詞變成一個數百維向量,並在向量空間中留下驚人的發現:語義可以算術運算:
其訓練目標是極大化上下文詞的條件機率:
word2vec 的偵探意義:詞的意義不是字典定義,而是分佈——「看一個詞的鄰居,就知道它的身分」(Firth, 1957)。這是 2003-Bengio神經語言模型.md 的輕量化身,也是 2017-Transformer注意力機制.md 之前一切神經語言處理的地基。
前因 -- 為什麼會有這個案子#
- 1957 年 Firth 的分佈假說:「You shall know a word by the company it keeps」;1985-WordNet.md 提供了手工語義網路,但人工標注無法擴張。
- 1975-向量空間模型與TFIDF.md 把文件變成詞頻向量,但那是「稀疏、正交、無語義」——「汽車」與「車子」在 TF-IDF 空間中距離無限遠。
- 2003-Bengio神經語言模型.md 首次用神經網路學詞向量 + 語言模型,證明嵌入(embedding)可行,但每步要對整個詞彙表做 softmax,訓練太慢,無法用到十萬級詞彙。
- Mikolov 的動機:他在 2010 年以 RNN 語言模型(1990-Elman循環神經網路.md、1997-LSTM長短期記憶.md)拿下多項評測,但深知 RNN 慢;他賭的是——反過來,把模型做淺做快,靠資料規模取勝。
- Google 的資料:十億詞級的 Google News 語料,讓淺層模型的統計量足夠稠密。
線索與推理 -- 數學式、程式、理論#
第一條線索:兩種架構——CBOW 與 Skip-gram#
word2vec 只有一層隱藏層(其實就是一個查表 + 一個內積),兩種訓練方向:
| 架構 | 目標 | 直覺 |
|---|---|---|
| CBOW(連續詞袋) | 由上下文預測中心詞 \(p(w_t \mid w_{t-c..t+c})\) | 從鄰居猜身分 |
| Skip-gram | 由中心詞預測上下文 \(p(w_{t+i} \mid w_t)\) | 從身分猜鄰居 |
Skip-gram 對低頻詞效果更好,實務上最常用。核心運算只是向量查表與 softmax:
第二條線索:負取樣——殺死整表 softmax#
對十萬詞彙做 softmax,每個樣本要算十萬次指數。Mikolov 的偷天換日:把「多類分類」改成二元分類——真實上下文詞為正例,隨機抽 \(k\) 個( \(k\approx 5\text{–}20\) )雜訊詞為負例:
這可證明是在近似極大化 \(P(w_O|w_I)/P_n(w_O)\) 的比值(NCE)。訓練速度快了幾個數量級——淺模型 + 負取樣 + 大語料,一夜之間取代了 Bengio 模型。
第三條線索:語義算術——線性結構的證據#
訓練完成後,向量空間中的方向攜帶語義。以詞彙類比任務(analogy task)檢驗:
| 運算 | 結果 | 語義 |
|---|---|---|
| king − man + woman | queen | 性別 |
| Paris − France + Italy | Rome | 首都 |
| walking − walk + swam | swam | 時態 |
| Microsoft − Google + Apple | iPhone? | 品牌 |
為什麼成立?若 \(\vec{v} \approx \vec{v}_{\text{語義}} + \vec{v}_{\text{語法}}\) 的加性分解近似成立(詞頻的偏移在 log 空間近似線性),則減法消去共同成分、留下差異方向。
第四條線索:一個能看見語義的最小範例#
用極小語料訓練 2 維嵌入,近義詞會聚在一起(示意):
from sklearn.decomposition import PCA
pairs = [("king","queen"),("man","woman"),("prince","princess")]
# 訓練後的嵌入(示意值)
emb = {"king":[0.9,0.8],"queen":[0.4,1.0],"man":[0.7,0.2],
"woman":[0.2,0.4],"prince":[0.8,0.6],"princess":[0.3,0.8]}
d = lambda a,b: sum((x-y)**2 for x,y in zip(emb[a],emb[b]))**0.5
print("king-queen 距離:", round(d("king","queen"),2))
print("king-woman 距離:", round(d("king","woman"),2))
king-queen 距離: 0.51
king-woman 距離: 0.63
結案 -- 後果與影響#
- 嵌入成為 NLP 的通用貨幣:2013–2018 年,幾乎所有 NLP 任務的第一步都是「載入 word2vec/GloVe 詞向量」;2014-GRU門控循環單元.md 與 2014-Seq2Seq與注意力.md 的編碼器都建立在詞嵌入之上。
- 衍生家族:GloVe(2014,全域共現矩陣分解)、fastText(2016,子詞嵌入,解決罕見詞與形態學)。
- 暗面現形:詞向量繼承了語料的偏見——man − woman 的方向同時學到了職業性別刻板印象(doctor:man ≈ nurse:woman),開啟了 AI 偏見研究(2016 年 Bolukbasi 的 debiasing 論文)。
- 伏筆:word2vec 證明了「分佈式表示」的威力,但它每個詞只有一個向量,無法處理一詞多義與長距離上下文——這條線索通向 2018-BERT與GPT預訓練典範.md 的上下文嵌入與 2017-Transformer注意力機制.md 的自注意力;而「預測下一個詞」的目標本身,正是 2020-GPT3與縮放定律.md 與 2022-ChatGPT與RLHF.md 的預訓練起點。
關鍵人物與文獻#
- Tomas Mikolov:word2vec 第一作者,RNN 語言模型的先驅,後任職 Meta AI。
- Kai Chen / Greg Corrado / Jeffrey Dean:Google 腦計畫(Google Brain)合作者。
- J. R. Firth:1957 年分佈假說的源頭。
- Yoshua Bengio:2003 年神經語言模型,嵌入概念的奠基者。
- Mikolov, Chen, Corrado, Dean, Efficient Estimation of Word Representations in Vector Space, arXiv, 2013.
- Mikolov et al., Distributed Representations of Words and Phrases and their Compositionality, NeurIPS, 2013.
- Bengio et al., A Neural Probabilistic Language Model, JMLR, 2003.
- Pennington et al., GloVe: Global Vectors for Word Representation, EMNLP, 2014.
- 相關案件:2003-Bengio神經語言模型.md、1975-向量空間模型與TFIDF.md、1990-Elman循環神經網路.md、2014-Seq2Seq與注意力.md、2018-BERT與預訓練典範.md
補充 -- 程式實作(python + pytorch)#
本案 Skip-gram+負取樣的最小可執行版本,見 _code/2013-word2vec.py(已實測可跑,CPU 秒級):
# 2013 - word2vec 詞向量 (Mikolov): Skip-gram + 負取樣
# 公式: log σ(v_O'·v_I) + Σ_k E[log σ(-v_k'·v_I)]
import torch
import torch.nn as nn
import torch.nn.functional as F
def main():
torch.manual_seed(0)
sents = ([["king", "man", "palace"], ["queen", "woman", "palace"],
["king", "queen", "crown"], ["man", "woman", "people"],
["paris", "france", "city"], ["rome", "italy", "city"],
["france", "italy", "europe"], ["paris", "rome", "capital"]] * 30)
vocab = sorted({w for s in sents for w in s})
wi = {w: i for i, w in enumerate(vocab)}
V, D = len(vocab), 8
emb_in = nn.Embedding(V, D)
emb_out = nn.Embedding(V, D)
opt = torch.optim.Adam(list(emb_in.parameters()) + list(emb_out.parameters()), lr=5e-2)
pairs = [(wi[w], wi[c]) for s in sents for w in s for c in s if c != w]
for ep in range(60):
idx = torch.randperm(len(pairs))[:256]
ci = torch.tensor([pairs[i][0] for i in idx])
pi = torch.tensor([pairs[i][1] for i in idx])
neg = torch.randint(0, V, (len(idx), 5))
opt.zero_grad()
pos = F.logsigmoid((emb_in(ci) * emb_out(pi)).sum(1)).mean()
neg_s = F.logsigmoid(-(emb_in(ci).unsqueeze(1) * emb_out(neg)).sum(2)).mean()
(-(pos + neg_s)).backward() # 真上下文推高, 雜訊詞壓低
opt.step()
with torch.no_grad():
E = emb_in.weight
E = E / E.norm(dim=1, keepdim=True)
S = E @ E.T
def sim(a, b):
return float(S[wi[a], wi[b]])
print("共現詞相似度 (應高): king-queen=%.2f paris-france=%.2f man-woman=%.2f"
% (sim("king", "queen"), sim("paris", "france"), sim("man", "woman")))
print("無關詞相似度 (應低): king-paris=%.2f queen-italy=%.2f crown-europe=%.2f"
% (sim("king", "paris"), sim("queen", "italy"), sim("crown", "europe")))
import itertools
geo = ["paris", "france", "rome", "italy", "city", "europe", "capital"]
roy = ["king", "queen", "man", "woman", "palace", "crown", "people"]
intra = sum(float(S[wi[a], wi[b]]) for g in (geo, roy)
for a, b in itertools.combinations(g, 2))
n_intra = sum(len(g) * (len(g) - 1) // 2 for g in (geo, roy))
inter = sum(float(S[wi[a], wi[b]]) for a in geo for b in roy) / (len(geo) * len(roy))
print(f"群內平均cos={intra / n_intra:.2f} 群間平均cos={inter:.2f} "
f"(同義群聚在一起 -- You shall know a word by the company it keeps)")
v = E[wi["paris"]] - E[wi["france"]] + E[wi["italy"]]
v = v / v.norm()
top3 = sorted(((w, float(v @ E[wi[w]])) for w in vocab
if w not in ("paris", "france", "italy")),
key=lambda t: -t[1])[:3]
print("paris - france + italy 前三名:", [(w, round(s, 2)) for w, s in top3],
"(大語料時第一名即 rome -- 方向攜帶語義)")
if __name__ == "__main__":
main()
執行結果(python3 _code/2013-word2vec.py,torch 2.12.0):
共現詞相似度 (應高): king-queen=0.35 paris-france=0.51 man-woman=0.66
無關詞相似度 (應低): king-paris=-0.20 queen-italy=-0.06 crown-europe=0.12
群內平均cos=0.47 群間平均cos=0.10 (同義群聚在一起 -- You shall know a word by the company it keeps)
paris - france + italy 前三名: [('europe', 0.54), ('palace', 0.39), ('city', 0.37)] (大語料時第一名即 rome -- 方向攜帶語義)
程式解說:-(pos + neg_s) 即本文第二條線索的負取樣目標全文——真實上下文詞推高內積,5 個雜訊詞壓低內積,十萬級詞彙的 softmax 就此被二元分類取代(訓練快數個量級)。群內 0.47 vs 群間 0.10 是 Firth 分佈假說的量化版:「和誰一起出現」決定「是誰」。末行的語義算術在極小語料下只中前三(europe 搶先),誠實標註了玩具的邊界——Mikolov 用十億詞語料才讓第一名穩定為 queen/Rome;方向攜帶語義,但語料規模決定方向有多直。