阶段 3 · 处理序列

词向量:让"国王 − 男人 + 女人"
真的等于"女王"

上一章把文本变成了整数编号。但编号是没有意义的——编号 42 和编号 43 之间没有任何关系。 词向量要解决的问题是:怎么让"意思相近"变成"距离相近",让语义可以被算术运算。

1

先看 one-hot 的三个死穴

最朴素的做法是 one-hot:给每个词一个专属的位置,是哪个词就哪位填 1。

猫 → [1, 0, 0, 0, 0]
狗 → [0, 1, 0, 0, 0]
汽车 → [0, 0, 1, 0, 0]

真正的麻烦不在"哪个位置是 1",而在这些 1 永远互不相干

一个词 = 10 万维里的一位(这里只画出前 10 位) 猫狗汽车 ← 第 6 位是 1 ← 第 1 位是 1 ← 第 3 位是 1 两条边永远互相垂直 夹角 90° → 余弦恒为 0 → 谁跟谁都"一样陌生" 猫狗
死穴为什么是问题
① 任意两个词的距离都一样 猫和狗的距离 = 猫和汽车的距离。所有词都同样"陌生", 模型完全无法利用"猫和狗都是动物"这个事实
② 维度爆炸 词表 10 万 → 每个词是一个 10 万维的向量。存储和计算都是灾难
③ 无法泛化 模型学会了"猫会叫",但对"狗会叫"一无所知——两个词的表示完全正交, 没有任何可以共享的东西
💡 关键洞察

要表达相似性,就需要稠密、低维、连续的表示——这三个词分别对着 one-hot 的稀疏、十万维、0/1 离散。 而衡量相似的那把尺子是余弦:两个箭头方向有多一致,1 = 完全同向,0 = 互不相干,−1 = 正好相反。 接下来讲怎么把词从“一位是 1”换成“一组有大小的小数”,这就是词向量。

2

分布式假设:邻居决定词义

J.R. Firth 在 1957 年说过一句话,成了整个领域的基石:

You shall know a word by the company it keeps.
你从它交的朋友就能认出这个词。

"猫" 和 "狗" 之所以意思相近,是因为它们的上下文高度相似—— 都可以说"喂___"、"___在叫"、"养了一只___"。 那么只要让模型去预测上下文,训练完之后,上下文相似的两个词自然就会得到相似的向量。 注意这是一个关于世界的断言,不是数学定理——它给模型装上了一块 归纳偏置:把"用得像"当成"意思像"。

🎯 类比

像通过社交圈判断一个人:如果你发现 A 和 B 出现在几乎相同的场合里、 和相同的人来往、做相同的事,那你不用见到他们本人也能判断:他们是同一类人。
词向量做的就是这件事——不定义词是什么意思,只看它和谁一起出现。

互动 · 这句话是可以数的:换两个词,看它们的邻居重合多少

这两个词
—
共享邻居
—
两份邻居分布的余弦
—

3

真的可以算:向量空间里的语义

训练完成之后,所有词都变成了空间里的箭头。方向承载了语义,距离承载了相似度。 点下面的按钮:箭头从“女人”出发,平移“男人到国王”那一段位移,看它落到哪。

互动 · 词向量空间里的皇帝国王男人女人

第一行是这个互动直接演示的;后两行在真实大语料上成立——本页的玩具语料只有几十个词,演示不了。

现象含义
vec(king) − vec(man) + vec(woman) ≈ vec(queen) "性别"这个语义维度被单独编码成了一个方向
vec(Paris) − vec(France) + vec(Italy) ≈ vec(Rome) "首都 ← 国家"这个关系也是一个稳定的方向
cos(猫, 狗) 远大于 cos(猫, 汽车) 相似度变成了可以计算的角度
为什么这让人震惊

没有人告诉过模型"性别"是什么概念,也没人给它标注过"法国和意大利是同类国家"。 这些结构是纯粹从"预测上下文"这个任务里自己长出来的—— 这就是"表示学习"的威力。

4

Word2vec 的两种玩法

2013 年 Google 的 Word2vec 之所以能火,是因为它又快又好。它有两个版本,思路正好相反:

CBOW(连续词袋) 用上下文猜中间词。
"喂___吃饭" → 猜是"猫"
快,常用词表示更好
Skip-gram 用中间词猜上下文。
"猫" → 猜 "喂""吃饭"
慢但更准,生僻词表示更好(这是被广泛采用的版本)

一个重要的工程细节:如果老老实实做 softmax,每一步都要在词表大小 V 个词上归一化—— V 常常是 10 万,慢到不可用。 所以 Word2vec 用了两个技巧——层次 Softmax(用二叉树把复杂度从 O(V) 降到 O(log V)) 和负采样(把"多分类"变成"二分类",只挑 K 个随机假词来对比;K 是每步抽几个假词)。

负采样:一个影响至今的技巧

负采样的思路是:不要求模型学会区分 10 万个词,只要求它区分"真正的邻居"和"随机抽的几个假词"。 这既大幅降低计算量,又让训练目标更聚焦。
今天大模型的对比学习(如 CLIP 的 InfoNCE)本质上是同一个思路的延续。

两个版本的方向正好相反(箭头往哪边指,就是拿谁猜谁)

CBOW:4 个邻居 → 平均一下 → 猜中心 Skip-gram:中心 → 分别去猜每一个邻居 我了一只小猫 养 养 我了一只小猫 一个位置只更新 1 次 一个位置要更新 4 次

互动 ·「外面 下雨 了 记得 带 伞」:两边各要训练多少次

CBOW 要训练
—
Skip-gram 要训练
—
倍数
—

负采样那一行损失的完整写法,以及 K 怎么把损失拆成两截,都在数学区《数学 · 一行损失,两截拉力》。

5

真的训一次:语义从噪声里长出来

到这里,"分布式假设"还只是一句断言——谁说预测上下文就能学出语义? 下面用 48 句自造语料、16 维向量、3000 轮 skip-gram + 负采样从头跑一遍 (一轮 = 把 48 句逐句读一遍:每个词当一次中心词,在左右随机 1~2 个词宽的范围里取真邻居; 高频词按概率跳过,所以一轮只处理约 54 对), 看语义结构能不能自己浮出来。下面散点图的坐标,是这 16 维向量在前两个主成分上的投影。

训练语料(全部 48 句,一句不多)

动物 食物 天气 交通

上色的 20 个词是后面要跟踪的主题词。看出门道了吗——它们的句子长得几乎一样: “我 喜欢 吃 ___” 后面能接米饭也能接面条,“___ 在 叫” 前面能放小猫也能放小狗。 模型手里唯一的线索,就是这种“位置和邻居的重合”。

互动 · 训练播放器:把 16 维压到 2 维看聚类

坐标 = 16 维向量在前两个主成分上的投影;颜色 = 四组主题词。两个方向的缩放比例相同,所以图上的距离可比。

你会看到的现象它说明什么
第 0 轮:四种颜色的点完全混在一起 随机初始化时向量里没有任何语义——和 one-hot 一样“什么都不知道”
同主题词对的余弦从 0 升到 0.3 左右,跨主题一直贴在 0 附近(余弦越大越像) 相似性不是人写进去的,是“预测邻居”这个任务逼出来的
“小猫”的最近邻是“小狗”(在这 20 个主题词里;默认种子下余弦 0.79),不是“汽车”(−0.25) 邻居重合 → 位置靠近。这就是分布式假设的字面含义
四种颜色各自成团,但团的大小和紧不紧不一样 语料里出现得越多、上下文越固定的词,学到的向量越稳
⚠️ 这张图里藏着失败案例——而且它比成功案例更有用

看“小鸟”:它跑到了自己主题的对面——因为它在语料里只出现在 2 句里,而“小猫”出现在 6 句里。 我们把 20 个随机种子都跑过一遍:同主题里余弦为负的每一对,都至少有一个词只出现过 1~2 句(20 个种子累计 186 次负值,没有一次反例)。 点 🎲 可以自己换一个种子复验:词向量的质量,直接取决于这个词被看见过多少次。 这条规律在今天的嵌入模型、向量检索里一模一样:没被见过的词,没有好向量。

💡 所以 Word2vec 到底做了什么

把上面整件事写成三行:① 给每个词初始化一个 16 维随机向量; ② 看到真实邻居就把两个向量拉近,看到随机抽的假词就把它们推远(这就是负采样); ③ 把这件事做 3000 遍。
没有一条规则说过“小猫和小狗都是动物”——这句话是训练结束时自己从数字里浮出来的。

互动 · 三千轮里,两条余弦是怎么分道扬镳的

当前
—
同主题词对
—
跨主题词对
—
两者之差
—

互动 · 点一个词,看它的最近邻是谁

6

格子编码:把 16 维向量摊开来看

上一节为了画在屏幕上,把 16 维压成了 2 维。现在反过来——把这 16 个数字原样铺成一排格子: 每个格子是一个维度的值,颜色表示数值大小(橙 = 正、蓝 = 负,越亮绝对值越大)。 于是“两个词像不像”就变成了“两排格子颜色像不像”,一眼就能看出来,不必先去理解余弦。

互动 · 选两个词,把两排格子并排摊开

🎯 为什么“画成格子”比“讲余弦”更管用

人对颜色和排布的感知比对数字敏感得多:16 个小数摊在面前,你说不出谁像谁; 换成 16 格颜色,“小猫”和“小狗”两排并排一放,你的眼睛会比计算器先给出答案。 (把向量画成彩色格子这个视觉设计,最出名的是 Jay Alammar 的《The Illustrated Word2vec》。)
但请注意:这 16 格里没有一格叫“动物性”。 语义是分布式地摊在整排格子上的——这正是“分布式表示”这个名字的来历, 也解释了为什么你无法指着某一维说“这一维表示性别”。

7

一个词一个向量,走到头了

一条时间轴:从"数共现"到"句子就是向量"

199020132014 201620182018+ LSAword2vecGloVe FastTextELMoBERT/GPT 拆矩阵预测上下文拟合计数 加子词随句子变被吸收掉 越往右,"一个词一个固定向量"这个前提就越站不住。
方法年份核心思路特点
LSA / SVD1990 把“每个词和每个词一起出现多少次”列成一张大表(共现矩阵),再对它做矩阵分解(SVD) 数学优雅,但矩阵巨大,难以增量更新
Word2vec2013 用浅层网络做上下文预测 快、效果好,引爆了整个领域
GloVe2014 还是那张共现计数表,直接拟合它(加权最小二乘) 用了全语料的统计量,不靠随机窗口抽样
FastText2016 把词拆成字符 n-gram(连续几个字母的小片段,例如 app、ppl、ple)再求向量 能处理没见过的词、拼写错误,对形态丰富的语言特别好
ELMo2018 用双向 LSTM(一种从左到右、从右到左各读一遍的循环网络,下两章讲)生成上下文相关的向量 让同一个词在不同句子里有不同向量
BERT / GPT2018+ 整个 Transformer 的输出就是上下文向量 词向量不再是一个"查询表",而是句子的函数

那为什么不直接数两个词一起出现的次数,偏要绕一圈去“预测”?因为两种路子的结果相当, 而预测可以在海量语料上一边读一边学——不用先把整张共现表存下来。

这条线的终点

注意最后两行——词向量这个概念被吸收进了更大的东西里。 今天我们说"嵌入(embedding)"时,指的往往是模型内部某一层的输出, 而不是一张预先训练好的静态查询表。
但底层思想没变:把离散符号映射到连续空间,让相似性变成距离。

M

数学 · 一行损失,两截拉力

第 4 节说 Word2vec 用了负采样。它到底在算什么,把那一行损失拆开就看得见: J = 这一步的损失;σ = 把任意分数压进 0~1 的函数(把握有多大); v 是中心词自己的 16 个数,u 是邻居的 16 个数。 下面这张图用的是本章第 5 节真训出来的那套向量,不是另编的数。

互动 · 中心词「小猫」的这一步:真邻居要贴到 1,K 个假词要压到 0

正样本那一项的 σ
—
抽了几个假词
—
总损失 J
—

互动 · 每个符号管图上的哪一块(卡片下面那张画的是「为什么要负采样」)

🎯 为什么非要两截

上半截只要求“把真邻居的把握推向 1”,它有一个偷懒的解—— 把所有词的向量都挪到同一个方向,这样随便怎么配都“对得上”。 下半截就是来堵这条路:K 个随机假词必须被推向 0,表示不能靠“和谁都像”蒙混过去。

🎬 自己验一遍

把「抽几个假词」拖到 K = 0:损失只剩上半截,所有词往同一个方向凑最省事—— 没有假词,就没人把不相干的词推开。
再把「正样本用谁」切到随机词:上面那根绿柱子会掉下来,损失立刻变贵—— 负采样训练的不是“像不像”,而是“能不能分辨出哪个才是真的”。

8

它修不了的四个硬伤

问题例子为什么修不了
① 多义词只能取平均 "苹果"在"吃苹果"和"苹果公司"里得到同一个向量 Word2vec 给每个词只分配一个向量,只能学成两种用法的平均
② 无法表示上下文 "银行"在"河边的银行"和"去银行取钱"里是同一个向量 训练完之后向量就固定了,不随句子变化
③ 词表外无解 没见过的新词、专有名词、网络新词 查询表里没有这个键(FastText 部分缓解)
④ 有偏见,而且会放大 vec(医生) − vec(男人) + vec(女人) ≈ vec(护士) 模型学的是语料里真实存在的统计规律,而语料里带着人类社会的偏见
⚠️ 别小看第四点

词向量的偏见在 2016 年被系统性揭示过。它不是一个可以"修掉"的 bug, 而是数据本身的镜子:只要训练语料里"护士"更多地和女性一起出现,模型就会学到这一点。 今天的做法是在训练后做去偏投影,但只能缓解特定方向,不能根治。 这个问题的所有现代版本(大模型的性别/种族偏见)都从这里开始。
另外别和第 7 节打架:ELMo / BERT 那类“每个句子重算一次向量”的方法,修的正是“多义词只能取平均”; 它们已经不算静态词向量了。

第一个硬伤长什么样:两种用法被压成同一个点

"我 喜欢 吃 ___" 邻居:甜、水果、香蕉 意思 A:一种水果 "___ 公司 发布 新机" 邻居:股票、发布会、手机 意思 B:一家公司 苹果 一张查询表里 只能放一个向量 → 只能取平均

互动 · 语料里没有「苹果派」这个词,但它的向量还是算得出来

算出来的最近邻
—
和它的余弦
—
最远的那个
—

训练要两张表,用的时候只要查表

训练:center 表 + nbr 表 真对推向 1、假对推向 0,两张都改 训完留下的向量表 本章把两张表相加当向量 用:查一次表 得到一个词的 16 个数,O(1)
9

小结

《第一性原理》里的七条线,词向量是「④ 学习即压缩」最直观的一次现身。 这一章从头到尾只做了一件事:把词压成几个数。

数据形状走了一遍 一句话 one-hot 查表 · 嵌入 正样本 + K 个假词 词表 [seq] [seq, V] [seq, 16] 一个标量损失 [V, 16] 两张表都被梯度更新;用的时候只查表——one-hot 和相似度都算完就扔。
它对应哪条线 ④ 学习即压缩——把 10 万维的 one-hot 压成 16 个数, 而且是明显地有损压缩(这是它最干净的一个例子)。
一句话 这一章的做法,本质上是在做一种极端压缩:把每个词的"意思"压成固定长度的几个数, 压缩的依据不是词长、也不是字母,而是"它和谁一起出现"—— 出现场合越像的词,压出来的编码就越接近。 “国王 − 男人 + 女人 ≈ 女王”不是设计出来的,是压缩的副产物。
它牺牲了什么 牺牲了上下文。压缩率越高,丢的东西越多: 因为每个词只准有一个编码,多义词("苹果"是水果还是公司)只能被压成 两种用法的平均值——而平均值往往不等于任一个(回扣暗线 B)。 同一个原因还带来了反义词问题:"热"和"冷"的上下文几乎一样, 所以它们在向量空间里挨得很近。
🎬 自己验一遍

回到第 6 节那个互动(标题是「选两个词,把两排格子并排摊开」)。 点「小猫」、再点「小狗」:两排格子的颜色几乎一一对应——两个完全不同的字符串, 被压成了两组几乎相同的数字。再点一个「汽车」,两排颜色就对不上了,下面那行余弦会明显掉下来。

16 个数字里没有一个叫“动物性”,但整个编码把这层意思记住了。这就是「学习即压缩」。

它在暗线里站在哪

《第一性原理》给了七条线(这门课为什么能行)和六条暗线(拆一个方法时该问的六件事)。 上面那条“学习即压缩”是七条线里的 ④;下面 A–F 是六条暗线对这一章的回答。

暗线这一章的回答
A 信息流动 数据形状:句子 [seq] 个整数 id → 查表 → [seq, 16] → 在每次随机取 1~2 个词宽的上下文窗口里 取邻居 → 相加/平均 → [16] → 乘输出矩阵 → [V] 打分 → 负采样后只对 1+5 个候选算 sigmoid。
关键一步在末尾:训练时有两张表——查询表(中心词用)和邻居表(上下文用), 两张都被梯度更新;两张都是同一个词的 16 个数,加起来还是一个 16 维向量, 所以本章把它们的和当「这个词的向量」(GloVe 论文同样取这个和,原话是 “we choose to use the sum W + W̃ as our word vectors”,Pennington 2014 §4.2)。 原版 word2vec 训练完通常只留查询表。用的时候只查表,不再跑模型—— 每个词得到一个与句子无关的固定 [16], 这就是静态向量最大的卖点
B 什么被牺牲了 牺牲了上下文和词表外:一个词只有一个编码,查询表里没这个键就无解。 换来的东西很值钱:O(1) 查询——不管语料有多少亿句, 把一个词变成向量只需查一次表,不需要跑任何网络。
这也解释了为什么词向量本身被淘汰了,但它留下的思想反而扩散了: 今天向量检索、RAG 里的每个向量库,靠的正是"算一次、存起来、以后只查表"
C 参数账本 参数量 = 词表 × 维度:
本章的 demo 是 98 词 × 16 维 = 1568 个数(加上输出矩阵共 3136 个数, 权重在浏览器里现训);
Google News 词向量是 300 万词 × 300 维 = 9 亿个数, float32 存下来 约 3.6 GB——2013 年一张表就能把普通机器撑爆;
GloVe 常用版是 40 万词 × 300 维 ≈ 1.2 亿个数。
算力:负采样把每一步的 O(V) softmax 降到 O(K+1)、K=5, 这才是它能在不到一天跑完 16 亿词(原著报告的数字)的原因
D 跑在什么上 带宽受限,且是 CPU 内存带宽。每一步干的是"查表 + 加几个数", 算术强度极低——算一下不到读一下的零头。 最贵的动作是把嵌入矩阵从内存里捞出来,而且负采样是随机挑词, 缓存命中率极差,所以它本质上是个内存访问问题。
这件事在历史上看得很清楚:Word2vec 靠 CPU 多线程就够了,并不需要 GPU。 它最现代的化身(向量检索)至今仍然是同一笔账: 完整的算术强度账在 《硬件与算力账本》
E 它假设了什么 分布式假设:用得像 = 意思像。这是整个 自监督 路线的地基——不问词是什么意思,只问它和谁一起出现。
它失败的地方恰恰最能说明它是个假设: "热"和"冷"的上下文高度重叠,于是它们被学到同一个角落; "好吃"和"难吃"也一样(否定词在环境里被丢掉了)。 它假设了相似的环境意味着相似的东西,而这个假设没有区分"同样多的"和"相反的"
F 违背了哪个直觉 “意思相反的词应该离得远”——错,静态词向量里它们往往离得最近, 因为决定距离的是上下文是否重合,而不是语义是否对立。
第二个:「语义不应该能做加减法」—— 但 king − man + woman 真的落在 queen 附近, 说明这堆数字里确实存在一个稳定的方向代表"性别"。
🎯 前后钩子

它接住了上一章的什么:分词(《分词与子词》) 把文本切成了整数编号,但编号之间没有距离—— 这一章给每一个编号配了一个可以算的向量。

它给下一章留了什么:词有了向量,但它们仍然是"一个词一个向量", 与句子无关。而句子的意思显然取决于词序和上下文。 要表示"按顺序读一遍",就必须先有一个带记忆的结构—— 这就是《RNN 循环神经网络》要回答的问题。

一句话带走进词向量

词向量 = 把词的“意思”变成一个可以算距离的箭头:
一句“和谁一起出现”的任务,逼出了“意思相近就靠得近”的空间;
代价是每个词只有一个固定向量——多义词和上下文都表示不了,这是后面所有方法要接着修的。

10

拓展阅读

上面讲的都是「够用」的版本。想往下挖,这里有三个入口—— 它们不是必修内容,是给想再往前走一步的读者准备的。

📄 这一章的说法从哪来

💻 工业界怎么写

∑ 更严格的形式