03 课 · 词与词表
LESSON 03 · 卷三 文字进场

文字怎么变成数字

第 2 课的数字都是我们手工打的。可大模型要读几百万条句子,谁替它打? 这一课我们自己动手:造一套中文算术语料,用三种刀法把它切开,给每一块编号, 再让模型把每个字的向量自己跑出来。 全程只有加减乘除和一个「猜下一个字」的游戏。

STAGE 01
造教材
4,000 条中式算术题,先教会「十五」怎么念
STAGE 02
三种刀法
按字切 / 按词切 / 让机器自己长
STAGE 03
编号本
词表=座位表,one-hot 点亮一格
STAGE 04
向量诞生
从一格 1,到训练出来的 8 个小数
第 1 站

先造一本自己的教材

大模型不读课本,它读语料(corpus)——一大坨句子。你喂它什么,它就成为什么。

第 2 课的「国王 − 男人 + 女人 = 女王」,数字是我们手工打的。 手工打分能撑住 2 个维度、9 个词;可真实语料是几百万条句子、几十万个词——没人打得起这个分。

所以这一课我们换个计划:不手工打分,让模型自己学。为了让「它到底学没学会」一眼可验证,我们给它出最干净的一类题——只有加减乘除的中文算式,答案唯一,对错分明:

十五加一百一十五等于? 谜底是 一百三

四种运算各出一批:加法、减法在 0~999 里随便挑(结果不超过 999), 乘法和除法只取 2~9 的乘法口诀表范围内(除法一律除得尽)。 这套语料一共 4,000 条,每一条都长这样:

写语料之前先想一个「没用」的问题:15 这个数,念出来是「十五」还是「一十五」?为什么语料里非要写对这件事?

于是造语料之前,先把「中文怎么念数字」的规则钉死。下面这台机器你随便拨一个数, 它会告诉你正确的读法,以及为什么这么读:

动手实验读数机 —— 拨一个数,看它该怎么念、为什么
115
一百一十五
百位一百十位一十个位
· 十位是 1,可它前面还有「百」→ 必须把「一」读出来:一十
语料里的每一句,都是这样拼出来的(蓝色 = 题目琥珀色 = 答案,中间的?就是「轮到模型接话了」的信号):
十五加一百一十五等于?一百三
一百三减十五等于?一百一十五
七乘八等于?五十六
五十六除以七等于?
六十三除以七等于?
三乘四等于?十二
1 ~ 6 条,一共 4,000
语料条数
4,000
一共多少个字
51,288
平均每条几个字
12.8
全书用到多少个不同的字
20
这个数字下一站要用
三个必看的例子:15 → 十五(最前面那个「一」省掉)、115 → 一百一十五(「百」后面那个「一」必须读出来)、130 → 一百三(个位是 0,尾巴上的「十」省掉)。
图 3-1 · 每一句语料都是这样拼出来的:题目 + ? + 答案

语料一共 4,000,加起来 51,288 个字, 平均一条 12.82 个字。 整本书翻来覆去只用到 20 个不同的字

? 一 七 三 乘 九 二 于 五 以 八 六 减 加 十 四 百 等 除 零

注意这里面有 ? 和「等于」——它们是给模型的路标? 的意思就是「轮到你接话了」。

语料是教材,
读法是人间规矩,
而模型只是个特别听话的学生
第 2 站

第一刀:一个字一块

有了语料,马上撞上第一个问题:模型不认识汉字,它只认识数字。所以第一步必须把句子切成小块,再给每块发一个编号。这个「切」的动作, 专业叫法就是分词;切开后的一块,叫一个标记(token)

最省事的刀法:一个字切一块。不需要任何词典,规则一句话讲完。 拿第 1 站那道题试一刀:

图解按字切 —— 一个字一块,再数一数谁出现得多
12345678910?11121314
一共切出 14 块 —— 一个字一块,切得干干净净,没有一块需要翻词典。
把这 4000 条语料全切碎、再数一数,一共只有 20 个不同的字 (这就是按字建出来的词表),它们出现的次数差别很大:
6682
4785
4000
4000
?4000
3090
2782
2594
2585
2462
2423
2253
2204
1973
1000
1000
1000
1000
1000
455
条子越长、颜色越深,说明出现得越多。出现最多的是「6682 次, 最少的是「455 次,差了 15 倍。 记住这个排名 —— 第 4 站的 BPE 就是照着「谁挨着谁、挨了多少次」来动手的。
图 3-2 · 「十五加一百一十五等于?一百三」按字切成 14

切完把 4,000语料全剁碎、倒进一个大筐里数一数: 一共只有 20不同的块。 这就是我们手上最小的一个词表——小到可以整个背下来。

一个字一块听着挺完美:词表只有 20 个格子,永远不会遇到不认识的字。那它有什么毛病?
第 3 站

第二刀:按词切,先准备一本词典

很自然的下一个念头:「一百一十五」明明是一个整体,那就把它当成一块

于是我们需要一本词典,里面写满「哪些字组合在一起算一个词」。 对这套算术语料,词典其实好写得很:把 0~999 的读法一条条列进去,再加上除以等于?——一共1006

动手实验最大匹配切词 —— 拿着词典从头扫,能切多长切多长
1006
条词条
= 0~999 的读法 1000
+ 运算符与路标 6
词典长这样(节选)
十五二十一百一百零三一百一十五一百三四百二十八九百九十九除以等于?
每条语料都由这些词拼成 —— 「十五」「一百一十五」是词典里的一个词, 不是「一、百、一、十、五」五个字
十五词典里有词典里有一百一十五词典里有等于词典里有?词典里有一百三词典里有
按词切,这句切成 6 块(按字要 14 块)。
块数只有按字切的一半不到,而且每一块都是有意义的完整词。 代价是:这本词典必须把 0~999 全部读法一条条写死进去。
切法叫最大匹配:从左往右扫,每次挑词典里能对上的最长那一条。 第 1 站那道题于是切成 6 块:十五 | 加 | 一百一十五 | 等于 | ? | 一百三
图 3-3 · 词典里有 1006 条词,最长的一条 5 个字

效果好得惊人:14 块变成了 6, 全语料平均一条只要 6.00 块(按字要 12.82 块)。 而且每一块都是有意义的完整词。

但代价立刻显形。试试词典外的世界——比如结果超过 999(语料里每一道题的结果都 ≤ 999, 所以这种数它压根没见过):

一千零二十四减五等于?一千零一十九

词典里只有「一」和「千」两个,没有「一千」这个。 最大匹配切到这里就卡住,只能给它打上一个标记:[UNK](unknown,不认识)—— 这句话会被切成 13,其中 2 块是 [UNK],两块都是那个「千」字。词典有多全,模型的世界就有多大—— 词典外的一切,对模型来说都是同一个「我不知道」。

词典是人写给机器看的清单:
写全了,词表爆炸;写漏了,满屏 UNK。
第 4 站

第三刀:不写词典,让机器自己长

两份方案都不完美。有没有第三条路——不写词典,让机器自己决定怎么切?

有,而且规则简单到小学生也能自己推出来:谁和谁最常挨在一起,就把它俩粘成一块。粘完重新数,再粘下一对。重复 40,词表就自己长出来了。 这套办法叫 BPE(Byte Pair Encoding,字节对编码)。

核心实验BPE 机器 —— 一步一步看词表怎么长出来
词表大小(多少个格子)
20
一条语料平均切成几块
12.82
起点 12.82 → 终点 6.51
现在语料里最常挨在一起的一对是 —— 高亮的那一行会被粘成新的一块
候选相邻对一起出现了几次
+等于4000
+?于?4000
+除以1000
+二十963
+十二891
已经长出来的新块:(还没有,先点一下「合并 1 次」)
拿第 1 站那道题试试,看它现在被切成什么样:
?
现在 14 块(按字是 14 块,按词是 6 块)。注意 BPE 不认识「加」和「一百」的区别,它只看谁挨着谁 —— 再点几次合并, 你会看到「加一百」这种块冒出来:运算符和数字被粘在了一起。
第一次合并毫无悬念:几乎每句话里都跟着「等于?」, 所以「等」+「于」在 4,000 条语料里挨着 4,000, 第一刀就把它们粘成了「等于」。
图 3-4 · 词表从 20 个格子长到 60 个;平均块数从 12.82 降到 6.51

跑完之后,它确实长出了「一百」「等于」「二十」这些像样的块。但它也长出两块怪东西:一块 加一百——把运算符「加」和数字「一百」粘在了一起; 还有一块 五等于?——把数字、等号和问号一起装了进去。 这些块跨过了语义的边界,因为「加」后面常常紧跟「一百」、而「?」总跟在「等于」后头。BPE 不懂语法,它只懂「谁挨着谁」。

三把刀摆在一起,各自的脾气就清楚了:

刀法词表大小平均一条切几块这道题被切成什么脾气
按字切2012.82十 | 五 | 加 | 一 | 百 | 一 | 十 | 五 | 等 | 于 | ? | 一 | 百 | 三词表极小,永远没有生词;但每块没有意义、句子长
按词切(最大匹配)10066.00十五 | 加 | 一百一十五 | 等于 | ? | 一百三块少、每块有意义;但词典要人写死,生词只能 UNK
BPE(合并 40 次)606.51十 | 五 | 加一百 | 一十 | 五等于? | 一百 | 三词表不大、生词也能拆成字兜底;但会切出跨边界的怪块

真实的大模型全都选了第三条路。Qwen2.5-7B 的词表里有 151,936 个 token: 既有「的」「一」这样的单字,也有「今天」「人工智能」这样的整词,还有些谁也读不出来的半截词。它们全是被这样一次次「粘」出来的。

我们的选择 · 这一课后面用哪把刀?

第一把刀(按字切),理由很实在:这套语料只有 20 个不同的字、 不可能出现生词,词表小到可以直接印在纸上给你看。等第 14 课真的训练算术小模型时, 这个选择还会再出现一次——那时候你会看到它够不够用。

还有一个连带的选择,现在就交代清楚,免得后面返工:数字只留汉字这一套写法。语料里永远是 十五加一百一十五等于?一百三 这样的汉字数字,词表里不会出现0~9 这十个字符。这样模型只需要学一套读法、一套进位规则, 不用把同一个数认两遍。

可真实用的时候,人打的是 123加456。解法是在模型外面加一层翻译: 喂进去之前先把阿拉伯数字转成汉字,模型吐出来的汉字答案再转回阿拉伯数字给人看。 转换规则就是第 1 站那台读数机已经讲过的那些(「零」要占位、「一十」要读成「十」)。对模型来说外面这层不存在,它永远只看见汉字。

第 5 站

编号本:词表就是一本座位表

刀法定了,接下来给每一块发座位号。 翻开这本册子:第 0 格是 ?,第 1 格是 ,…, 第 19 格是 。点一下就能看到每个块的编号:

动手实验编号本 —— 每一块的座位号,反着也能查
你点中的那一块
等于
它是这本册子的第 20
从头往后数,数到第 20 个就是它。这个数字是它的编号(id)
反过来查:给我一个编号
编号 20等于
这本册子一共 60 行。前 20 行是语料里出现过的全部单字 (?一七三乘九……),后面 40 行是 BPE 自己长出来的 (等于、等于?、除以、二十……)—— 单字按字典序排座次,长出来的块按「谁先长出来」往后接。
编号只是座位号:它不代表大小、也不代表意思。这本册子从头到尾,没有一个格子告诉模型「十五有多大」。
图 3-5 · 这本册子共 60 格:前 20 格是单字,后 40 格是 BPE 长出来的块

现在,把这几个名词一次钉死——它们的区别,很多人学了很久还在混:

词 · word
人眼里的意义单位。「一百一十五」是一个词。是人定的,不是机器定的。
词典 · dictionary
一本能查的清单,写的是「哪些算一个词」。第 3 站那本 1006 条的册子就是它。
标记 · token
切完之后的一块东西。可能是一个字、一个词、也可能是半个词。它才是模型真正看见的东西。
词表 · vocabulary
模型认识的全部 token,每个配一个编号。大小取决于你用的是哪把刀。

最后一句要紧的话:编号只是座位号。

看编号:? 是 0 号, 是 1 号,19 号。 按编号看,「零」比「一」大 18——可这两个编号之间,没有任何数学关系。 编号不表示大小、不表示顺序、不表示相似。它只是「第几格」。这本册子从头到尾,没有告诉模型「十五有多大」——一个字都没说。

既然编号什么都不知道,那模型怎么才能知道「十五」和「十四」是近亲、 「老虎」和「猫」是近亲?
第 6 站

第一种长相:one-hot —— 只点亮一格

有编号,最顺手的做法就来了:准备 20 个格子,第几号就在第几格写 1,其余写 0。比如 是 1 号,它的向量就是 [0, 1, 0, 0, …]——20 个数里只有一个 1。这种做法叫 one-hot(独热)。

动手实验one-hot 矩阵 —— 20 个字,20 个格子
012345678910111213141516171819点积100000000000000000001010000000000000000000001000000000000000000000100000000000000000000010000000000000000000001000000000000000000000100000000000000000000010000000000000000000001000000000000000000000100000000000000000000010000000000000000000001000000000000000000000100000000000000000000010000000000000000000001000000000000000000000100000000000000000000010000000000000000000001000000000000000000000100000000000000000000010
?」的编号是 0 → 第 0 位亮起 1,其余 19 位全是 0:
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
一共 20 个格子,19 个是 0(95% 都是废格子)。再看右边那列点积:除了和自己点积是 1,和谁点积都是 0 —— 在 one-hot 的世界里, 每个词都「六亲不认」,谁都跟谁都不像。真实词表有 151,936 个词,那每一行就是 151,935 个 0。
👆 点左边任意一行,看它的向量长什么样(选中行的编号,就是这行里 1 的位置)。
图 3-6 · 每一行是一个 token 的向量;整张表绝大部分格子都是 0,所以它叫「稀疏表示」

点几行看看,你会立刻发现两个硬伤:

  • 浪费20 个格子里有 19 个是 0 (95% 都是废格子)。 真实词表 151,936 个词,那就意味着每一行有 151,935 个 0。
  • 六亲不认:任意两个不同词的向量,怎么比都「不像」—— 点乘是 0,距离永远一样远。「二」和「三」之间、「十五」和「十四」之间,在 one-hot 的世界里毫无关系, 跟「二」和「猫」的关系一模一样近。

第 2 课我们辛辛苦苦把「像不像」变成可以算的距离。 可 one-hot 这张表里,所有人跟所有人的距离都是同一个数—— 距离算出来了,却什么都没告诉你。

一个 1 加一大堆 0,
装得下一块 token 的名字
装不下它的意思
第 7 站

第二种长相:稠密向量,每一格都有数

换个思路:别用一个格子表示一块 token,用 8 个格子,而且每一格都填上有意义的数字。这就叫稠密表示,这样的一串数,就是我们第 2 课说的向量—— 在语言模型里,它有个专门的名字:词向量,也叫词嵌入(embedding)

下面这张表,是刚才那 20 个字真正的向量——不是我们手工打的, 是模型自己学出来的(怎么学的,下一站讲)。颜色越琥珀越正,越蓝越负:

核心实验稠密向量表 —— 20 行 × 8 列,点一行看它像谁
01234567
同一个字,老办法(one-hot)
10000000000000000000
20 个数:只有 1 个是 1,其余 19 个是 0
新办法(稠密向量):每个格子都有数
0.03-0.950.42-0.63-0.770.29-0.610.48
只有 8 个数,却一个 0 都不浪费 —— 比 one-hot 还短,装的信息反而更多
训练完之后,模型自己排出来的「像不像」
和「?」最像的三个字:
0.860.870.95
数字 = 两个向量在 8 维空间里的距离,越小越像。 这些数字全是训练出来的,没有人手工标注过「一」和「二」应该像。
👆 点左边任意一行:上面看它的 one-hot 老样子,下面看它的稠密向量, 以及训练完之后模型觉得谁和它最像。整张表就是第 2 课说的「嵌入矩阵」的一个迷你版:20 行 × 8 列,每一行是一个字的向量。
这就是第 2 课那个 [151936, 3584]嵌入矩阵的迷你版: 这里是 【20 × 8】——行是 token,列是维度,查一个 token 的向量,就是查它那一行。
图 3-7 · 每行 8 个小数;和 one-hot 一比,格子少了,装的意思反而多了

点开「」,看它最像的三个字;再点「」。 你会发现:这些字自己聚成了一堆一堆—— 数字和数字近、运算符和运算符近。 没有人告诉过模型「二」和「三」应该像,这个「像」是它自己数出来的。

再用一个 3D 玩具,把「稀疏」和「稠密」的差别看得更狠一点。 假设词表里只有三个词,每个词只用 3 个格子:

3D 实验稀疏 vs 稠密 —— 拖动三个滑块,把稠密向量挪到任何地方
· [1,0,0] · [0,1,0] · [0,0,1]稠密向量
稠密向量 = [0.50, 0.50, 0.50](三个格子,每格都有数)
到「」的距离 0.87到「」的距离 0.87到「」的距离 0.87
最近的词是「」(0.87)—— 你把三个滑块拖到哪儿, 这个点就能挪到哪儿,想离谁近就离谁近。
对比一下:三个 one-hot 词钉死在三条轴的端点,彼此距离永远是 √2 ≈ 1.41、夹角永远是 90° —— 一动都动不了。「谁也不像谁」是 one-hot 的出厂设置,改不了。
三个 one-hot 词被钉死在三条轴的端点上,一动都动不了; 而稠密向量是一支可以随便挪的箭头——想离谁近就离谁近。 这就是「六亲不认」和「自己聚堆」的差别。
图 3-8 · one-hot 之间的夹角永远是 90°、距离永远是 √2 ≈ 1.41,这是个改不掉的出厂设置

于是整套流程终于接通了:文字 → 切成 token → 查编号 → 查嵌入矩阵的那一行 → 一串数字。从此模型面前不再有汉字,只有数字。

第 8 站

这些数字从哪来:训练

关键问题还没回答:「二」的向量凭什么是那几个小数?谁写的?

答案是:没人写,是它自己跑出来的。而且起步时它什么都不会——第 0 步的向量就是一堆随机数, 和瞎猜的水平一模一样(loss = 3.00)。 要让它长出本事,只需要给它一个游戏:猜下一个字。

训练三步曲 · 每一步都很朴素

① 猜:给模型看一个字,让它猜下一个字是谁。② 算分:猜错了就记一笔,叫 loss(损失)——数字越大表示越「不会」。③ 挪一点:把每个向量往「猜得更准」的方向微调一下。重复。

就这么简单。它没有背规则,只是在同一个游戏里玩 300,000 次。 下面的播放器,就是这台机器 300,000 步的全程录像(真跑出来的,不是动画):

核心实验训练录像 —— 拖动进度条,看向量怎么从一团乱变成一堆堆
20 个字在空间里的位置 1/7
?
同一个取景框从头用到尾 —— 动的是点,不是镜头。第 0 步是一团乱麻;跑到末帧, 它们自己分成了几窝:数字扎一堆、运算符扎一堆。最像的一对是「」和「」,两个向量夹角小到余弦 0.99没有任何人告诉过模型谁该跟谁近。
loss:模型有多「不会」 越小越好
瞎猜 3.003.001.5001千5千2万6万15万30万
横轴是训练步数(前密后疏,不然前 1000 步的陡降会被挤成一条缝)。 橙色那一小段就是前 1 万步 —— 模型几乎全部本事都是在那儿学的。
此刻的 loss
3.00
起点 3.00 → 现在 3.00(瞎猜的水平是 3.00
训练步数
0
一共只跑了 300,000 步,全程约 1 秒
学得怎么样?随便挑一个字,让模型猜「它后面跟什么」——
模型此刻的猜测 softmax(w · 向量)
?5.0%
5.0%
5.0%
5.0%
5.0%
模型把「」后面可能出现的字,按可能性从大到小排了个队。 它猜第一个是「?」。这些数字全是自己算出来的,没有一个格子是人填的。
语料里的标准答案 直接数出来的
44.1%
26.3%
13.0%
7.4%
6.6%
4000 条语料里,「」一共出现 1726 次, 总共只接过 6 种不同的字。出现最多的是「」(761 次)。
两栏共用一把尺子:0% ~ 100%,横条长多少,旁边就印多少,两边的横条能直接比长短。 把进度条拖回起点试试 —— 那时候五条一样长(各 5.0%,20 个字平分),因为它是真在瞎猜;现在「」的第一名是 5.0%
❌ 没猜对:模型说接「?」,语料里最多的其实是「」。毕竟它才第 0 步,向量还是乱的,这时候的猜测跟抛硬币差不多。
点上面的字换一个试试,再拖动进度条回到起点:20 个字里,第 0 步只猜中 1 个, 跑到 2 万步能猜中 13 个。这个「从瞎猜变得会猜」的过程,就叫训练。
三处值得盯着看: 第 0 步的图是一团乱麻,loss 是 3.00(等于瞎猜); 前 1 万步就干完了大半的活,loss 从 3.00 掉到 1.64 到最后 loss 掉到 1.62,数字们自己分堆了——没人告诉它谁该跟谁亲。
图 3-9 · 20 个 token × 8 维,300,000 步训练的全部关键帧

现在回头看第 2 课那句「所有数字都是模型从海量文本里自己学出来的」—— 你刚刚亲眼看完了它的迷你版:从随机数开始,一路把 loss 压小,向量自己长成了有结构的样子。

至于 loss 曲线最后那一大段几乎是平的——2 万步时是 1.65, 又跑了 28 万步,也只压到 1.62, 中间还起起落落。多花 15 倍的时间,几乎没换来进步——先记着这个疑问,第 13 课「泛化与过拟合」会专门算这笔账:训练不是越久越好。

词向量不是出来的,
出来的。
第 9 站 · 收官

把这一课钉在墙上

本课核心 · TAKEAWAY

模型只认识数字,所以文字要经过一条流水线:语料 → 分词 → 词表编号 → 查嵌入矩阵 → 一串数字。 切分有三种刀法——按字(词表最小,但每块没意义)、按词(块最少最有意义,但要人写词典、生词就 UNK)、BPE(让机器数「谁最常挨着谁」,自己长出词表,真实大模型都用它)。 编号之后的第一个长相是 one-hot:一格亮 1,其余全 0——省事,但所有 token 互相「六亲不认」; 第二个长相是稠密词向量:每一格都有数,于是「像不像」终于变成了距离。 而这些数字不是谁写的,是模型在「猜下一个字」的游戏里一步步练出来的

这一课你亲手做完了

  • 造语料4,000 条中文加减乘除题,51,288 个字, 并且先钉死读法——15 念「十五」、115 念「一百一十五」、130 念「一百三」。
  • 三种分词:按字 20 格 / 平均 12.82 块; 按词 1006 格 / 6.00 块;BPE 60 格 / 6.51 块。
  • 词表与编号:token 是切出来的块,词表是「模型认识的全部块」, 编号只是座位号——它不表示大小,也不表示意思。
  • 稀疏 vs 稠密:one-hot 里 20 格只有一个 1, 所有词彼此距离相同;稠密向量每格都有数,8 个数就能表达「像不像」。
  • 训练:从随机数出发,用「猜下一个字」把 loss 从 3.00 压到 1.62, 向量自己聚成了堆——第 2 课说的「学出来的数字」,你这次是看着它长出来的。

学习小测验

已完成 0 / 50.0%答对 0
还没提交过 —— 每题先选一个选项,再点「提交」,答完就能看到诊断。
Q1语料里为什么要写「一百三」,而不是「一百三十」?
Q2按字建词表,这本册子只有 20 个格子,最大的毛病是什么?
Q3按词典最大匹配切词,最致命的伤是哪一个?
Q4BPE 的训练规则,一句话说是什么?
Q5稠密词向量里那些小数(比如「二」= [-0.68, -0.35, ...]),是谁定下来的?
NEXT · 第 4 课

矩阵乘法:一次算完一整批点积

到这里,两个向量像不像,你已经能亲手量了。可一句话有几十个向量,每一对都要量一次—— 一个一个算,显卡再快也等不起。下一课我们把第 2 课那把尺子升级: 让成千上万个点积一次算完。 这张「一次算一批」的表格,就是 Transformer 心脏里唯一的那个动作。

从零手册 —— 下一课:矩阵乘法:一次算完一整批点积