一
图像天生是数值的——每个像素就是一个亮度值,CNN 可以直接吃。语言不是。一个词是一个离散符号,“国王”和“王后”在计算机里最朴素的表示是两个独立的编号,或者两个独热向量(one-hot):词表里第 个词,就是一个除了第 位是 1、其余全是 0 的长向量。
独热表示有两个致命问题。第一是维度灾难:词表动辄几十万词,每个词就是一个几十万维、几乎全是 0 的稀疏向量。第二、也是更根本的——任意两个不同词的独热向量都是正交的,相似度恒为零。“猫”和“狗”的距离,与“猫”和“民主”的距离,在独热空间里一模一样。这个表示里没有任何语义结构,神经网络拿到它等于拿到一堆毫无关系的编号。
我们真正想要的,是一种分布式表示(distributed representation):用一个稠密的低维向量(比如 200 维实数)表示每个词,让向量之间的几何关系编码语义关系——相似的词向量相近,相关的词在某些方向上对齐。问题是,这样的向量从哪来?word2vec 的答案根植于一个古老的语言学直觉。
二
这个直觉叫分布假说(distributional hypothesis),常被概括成 J.R. Firth 1957 年的一句话:“你可以通过一个词的同伴来认识它。”(You shall know a word by the company it keeps.)意思是:一个词的意义,很大程度上由它经常和哪些词一起出现来决定。“咖啡”和“茶”意思相近,因为它们出现的上下文高度重叠——“喝一杯___”“___加糖”“早上来杯___”。如果两个词总在相似的语境里出现,它们的意义就相近。
word2vec 把这个语言学假说变成了一个自监督的预测任务:不需要任何人工标注,只用海量原始文本,让模型反复练习“根据上下文猜词”或“根据词猜上下文”,在练习中把词的意义压进向量。2013 年,Google 的 Tomáš Mikolov 等人在《在向量空间中高效估计词表示》里提出了两种具体做法1。
CBOW(连续词袋):给定一个词周围的上下文词,预测中间这个词。比如看到“喝一杯 ___ 加糖”,预测中间是“咖啡”。
Skip-gram:反过来,给定中间这个词,预测它周围会出现哪些上下文词。给“咖啡”,预测它周围可能有“喝”“杯”“糖”。
两者是镜像关系。Skip-gram 在罕见词上表现更好、更常用,下面以它为主讲数学。
三
Skip-gram 的结构异常简单——简单到它其实只是一个没有隐藏层非线性的“浅”网络。模型给每个词配两个向量:当它作中心词时用的向量 (输入向量),当它作上下文词时用的向量 (输出向量)。
给定中心词 和一个上下文词 ,模型用两个向量的点积衡量“ 出现在 周围的可能性”,并通过 softmax 归一化成一个概率分布:
c)}{\sum{w=1}^{V} \exp(\mathbf{u}_w^\top \mathbf{v}_c)}
分子是中心词向量和真实上下文词向量的点积(越对齐、点积越大、概率越高),分母对整个词表 个词求和做归一化。训练目标是最大化所有真实出现的“中心词-上下文词”对的对数概率,等价于最小化负对数似然:
用第 02 章的反向传播对 和 求梯度、做梯度下降即可。softmax + 交叉熵的梯度有一个干净的形式:对得分向量的梯度是“预测分布减去真实的独热目标”。直觉是:把真实上下文词的向量往中心词向量拉近,把所有其他词的向量推远一点。反复在海量文本上做这件事,几何结构就慢慢浮现了。
四
上面那个 softmax 有个要命的代价:分母要对整个词表求和。词表几十万词,每处理一个训练对就要算几十万次点积——而语料里有几十亿个训练对。直接算,根本跑不动。
word2vec 能在 2013 年用普通机器在十亿级语料上训练,靠的是一个关键加速技巧:负采样(negative sampling)。它的思想是把“在整个词表上做多分类”换成“做若干个二分类”。对每个真实的(中心词,上下文词)正样本对,随机从词表里采样少数几个(通常 5–20 个)词作为“负样本”,然后只要求模型:把正样本对的点积推高(判为“真的是上下文”),把这几个负样本对的点积压低(判为“不是上下文”)。目标函数变成
o^\top \mathbf{v}c) + \sum{k=1}^{K} \mathbb{E}{w_k \sim P_n}\big[\log \sigma(-\mathbf{u}_{w_k}^\top \mathbf{v}_c)\big]
其中 是 sigmoid, 是负采样分布(实践中用词频的 3/4 次方,让高频词稍微少采、低频词稍微多采)。这样每个训练对只需算 次点积(比如 6 次),而不是几十万次。负采样把训练复杂度从“正比于词表大小”降到“正比于一个小常数”,这是 word2vec 实用化的工程关键。
五
word2vec 真正让世界惊艳的,是训练完之后词向量空间里浮现的几何规律。最著名的例子是线性类比:
{\text{king}} - \mathbf{v}{\text{man}} + \mathbf{v}{\text{woman}} \approx \mathbf{v}{\text{queen}}
也就是说,“从国王到王后”的向量变化,和“从男人到女人”的向量变化几乎相同——“性别”这个语义维度,被编码成了向量空间里一个稳定的方向2。同样,{\text{Paris}} - \mathbf{v}{\text{France}} + \mathbf{v}{\text{Italy}} \approx \mathbf{v}{\text{Rome}}——“首都”也是一个方向。这些类比可以画成平行四边形:king、man、woman、queen 四个点构成一个近似的平行四边形,对边平行且等长。
为什么填空游戏能学出这种结构?直觉是:语义关系会系统性地体现在共现统计里。“国王”和“男人”共享一批上下文(强壮、权力、他),“王后”和“女人”共享另一批(优雅、她),而“国王↔王后”和“男人↔女人”之间的上下文差异是同一种差异(性别相关的词)。当模型把这些共现规律压进低维向量时,这种“同一种差异”就自然对齐成了空间里的同一个方向。这不是被显式设计进去的,是从预测任务里涌现出来的——和第 05 章 Google 认猫里“猫神经元”的涌现是同一类现象:足够多的数据 + 一个简单的自监督目标,结构自己长出来。
六
用一段纯 NumPy 的迷你 skip-gram 把机制跑出来(完整文件 code/06_word2vec.py,可运行)。核心训练循环就是第三节那个 softmax-交叉熵的直接翻译:
展开代码 · 06_word2vec.py
"""
第 06 章配套代码:迷你 skip-gram word2vec(含负采样思想的简化版)
Runnable with: numpy only. python3 06_word2vec.py
在一个玩具语料上训练词向量,演示 (1) skip-gram 目标 (2) 线性类比 king-man+woman≈queen 的几何。
语料是人造的,刻意让 king/queen/man/woman 形成平行四边形关系。
"""
import numpy as np
rng = np.random.default_rng(0)
# 玩具语料:每句是一组共现的词(模拟 skip-gram 的中心词-上下文对)
sentences = [
"king man royal palace", "queen woman royal palace",
"man boy human person", "woman girl human person",
"king queen royal crown", "prince king boy royal",
"princess queen girl royal", "man king strong",
"woman queen strong", "boy prince young", "girl princess young",
]
vocab = sorted(set(" ".join(sentences).split()))
w2i = {w: i for i, w in enumerate(vocab)}
V = len(vocab)
D = 8 # 向量维度
# 生成 (中心词, 上下文词) 训练对
pairs = []
for s in sentences:
ws = s.split()
for i, c in enumerate(ws):
for j, o in enumerate(ws):
if i != j:
pairs.append((w2i[c], w2i[o]))
pairs = np.array(pairs)
# skip-gram: 中心词向量 Win,上下文向量 Wout
Win = rng.normal(0, 0.1, (V, D))
Wout = rng.normal(0, 0.1, (V, D))
def softmax(x):
e = np.exp(x - x.max()); return e / e.sum()
lr = 0.01
for epoch in range(600):
rng.shuffle(pairs)
loss = 0
for c, o in pairs:
h = Win[c] # 中心词向量
scores = Wout @ h # 对每个词的得分
p = softmax(scores) # 预测上下文分布
loss -= np.log(p[o] + 1e-9)
# 梯度(交叉熵 + softmax)
dscores = p.copy(); dscores[o] -= 1
Win[c] -= lr * (Wout.T @ dscores)
Wout -= lr * np.outer(dscores, h)
if epoch % 160 == 0:
print(f"epoch {epoch:3d} loss={loss/len(pairs):.4f}")
def vec(w): return Win[w2i[w]]
def cos(a, b): return a @ b / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-9)
def nearest(v, exclude=()):
sims = [(w, cos(v, Win[i])) for w, i in w2i.items() if w not in exclude]
return sorted(sims, key=lambda t: -t[1])[:3]
# 在玩具语料上,可靠演示的是"语义相关词聚在一起"(近邻结构)。
# 完整的 king-man+woman≈queen 平行四边形几何需要真实大语料(数十亿词)才稳定,
# 见正文说明;这里展示机制:相关词的向量彼此靠近。
print("\n近邻结构(语义相关词的向量彼此靠近):")
for w in ["king", "woman", "royal"]:
print(f" 与 '{w}' 最近: {[(x, round(s,2)) for x, s in nearest(vec(w), exclude={w})]}")
print("\n类比向量 king - man + woman 的近邻:")
analogy = vec("king") - vec("man") + vec("woman")
print(" ", [(x, round(s, 2)) for x, s in nearest(analogy, exclude={"king", "man", "woman"})])
print(" (玩具语料下结果有噪声;真实 word2vec 在大语料上此处稳定指向 'queen')")
for c, o in pairs: # (中心词, 上下文词)
h = Win[c] # 中心词向量 v_c
p = softmax(Wout @ h) # P(·|c),对每个词的概率
dscores = p.copy(); dscores[o] -= 1 # softmax+CE 梯度 = p - e_o
Win[c] -= lr * (Wout.T @ dscores) # 拉近真实上下文,推远其他
Wout -= lr * np.outer(dscores, h)
在一个刻意构造的玩具语料(让 king/queen/man/woman 等词在 royal、human 等语境里共现)上训练后,近邻结构清晰浮现:
与 'woman' 最近: princess(0.36), girl(0.25), man(0.20)
与 'king' 最近: palace(0.21), princess(0.11), royal(0.11)
类比 king-man+woman 的近邻: princess(0.67), palace(0.18), ...
“woman”的近邻是 princess、girl 这些语义相关词;“king−man+woman”这个类比向量落在了 princess(皇室+女性)附近。需要诚实说明:这个十几个词的玩具语料噪声很大,无法稳定复现教科书里那个干净的“queen”——线性类比的平行四边形是大规模训练(数十亿词)才稳定涌现的统计性质,玩具语料只能展示“语义相关词向量彼此靠近”这个更基本的机制。但即便在这么小的语料上,“分布假说→共现→几何”这条因果链已经看得见:模型确实把“皇室”“性别”这些语义压成了空间里的邻近与方向。
七
用一张图把词向量空间的几何钉住——这是 word2vec 最具标志性的可视化:
语义空间(降到 2 维示意,真实是 200~300 维)
queen ●- - - - - - - - ● king ↑ "皇室"方向
│ │
│ (性别方向 →) │
│ │
woman ●- - - - - - - - ● man
平行四边形: king - man ≈ queen - woman
=> king - man + woman ≈ queen
另一组: Rome - Italy ≈ Paris - France ("首都"方向)
配套的 manim 动画 assets/manim/ch06_word2vec.py(Parallelogram Scene)把这件事降到二维演出来:man、king、woman、queen 四个词作为平面上的点,构成一个近似平行四边形——“王权方向”(man→king 与 woman→queen)彼此平行,“性别方向”(man→woman 与 king→queen)也彼此平行,于是 king − man + woman 这个向量算术的落点恰好压在 queen 上。在真实语料训练出的 word2vec 上,这些平行四边形干净得令人吃惊——这正是它当年引发轰动的原因:意义,竟然可以是几何。
word2vec 的影响远超它本身。它确立了一个范式:用自监督的预测任务,从无标注文本里学出可迁移的稠密表示。这个范式后来被层层放大——从静态词向量(每个词一个固定向量)到上下文相关的词表示(同一个词在不同句子里有不同向量),再到 BERT、GPT 这种“整个模型就是一个巨大的、上下文敏感的表示器”。word2vec 是这条路的起点。
但 word2vec 只给了“词”的向量,它不处理“词的顺序”和“整句到整句的映射”。翻译一句话、回答一个问题,需要把一个变长序列映射成另一个变长序列。这个问题催生了 seq2seq 编码器-解码器架构,而 seq2seq 的一个致命瓶颈,又直接逼出了那个将要改变一切的机制——注意力。那是下一章。
本质
word2vec 把一句很旧的语言学直觉——“一个词的意义由它周围的词决定”——变成了一个可优化的几何目标:让经常共现的词在向量空间里彼此靠近。它真正的发现不是“词能变成向量”,而是当你只要求模型“根据上下文预测词”时,语义关系会作为副产品自动沉淀成空间里的方向:性别是一个方向、单复数是一个方向、首都与国家的关系是一个方向,于是意义的加减变成了向量的加减。这背后是一个会反复出现的母题——不必显式地教模型“什么是语义”,只要给它一个足够好的自监督预测任务和足够多的文本,结构会自己浮现。这正是后来 BERT、GPT 的全部出发点:把这个“预测周围”的任务,从一个词放大到整段上下文。
参考文献
-
Mikolov, T., Chen, K., Corrado, G., & Dean, J. (2013). Efficient Estimation of Word Representations in Vector Space. CBOW 与 Skip-gram、负采样、连续向量空间。arXiv:https://arxiv.org/abs/1301.3781 ;PDF:https://arxiv.org/pdf/1301.3781
-
word2vec 综述:线性类比(king−man+woman≈queen)、分布假说、负采样与训练机制。Word2vec, Wikipedia:https://en.wikipedia.org/wiki/Word2vec