02. Token 化:文本如何变成数字
02. Token 化:文本如何变成数字
模型不认识文字。它只认识数字。Token 化就是把人类的文本翻译成模型能处理的数字序列。
一、为什么不能直接用字符
最直觉的想法:给每个字符分配一个 ID。
'a' → 0, 'b' → 1, ..., 'z' → 25
'A' → 26, 'B' → 27, ..., 'Z' → 51
'0' → 52, ..., '9' → 61
' ' → 62, '.' → 63, ...
问题:
-
词汇表太小,序列太长。“Transformer” 这个词有 11 个字符,模型需要处理 11 个步骤。文章越长,计算量就越大(Attention 的计算量和序列长度的平方成正比)。
-
单个字符没有语义。“t” 本身什么意思都没有,模型需要从底层拼凑出 “the”、“this” 等词的含义,学习效率低。
另一个极端——按完整单词分:
"the" → 0, "cat" → 1, "transformer" → 2, ...
问题:
-
词汇表爆炸。英语有几十万个单词,加上变形(run/runs/running/ran)、专有名词、各种语言的词汇——词汇表会达到数百万,embedding 矩阵太大。
-
未登录词 (OOV)。遇到词汇表外的新词(比如 “ChatGPT”),直接无法处理。
Token 化是两者的折中:比字符粒度大,比完整单词粒度小。
二、BPE:最主流的分词算法
BPE(Byte Pair Encoding,字节对编码)是目前 GPT 系列、LLaMA、GLM 等主流模型使用的分词算法。
2.1 训练过程(构建词汇表)
BPE 的训练不是在模型训练时发生的,而是预先在大量文本上统计出来的。
第一步:初始化
从所有单字节(256 个)开始作为初始词汇表:
词汇表 = {
所有 ASCII 字符: a, b, c, ..., z, A, B, ..., Z, 0-9, 空格, 标点, ...
所有 UTF-8 字节: 0x00 到 0xFF (256 个)
}
此时,任何文本都可以被表示——最坏情况就是拆成单字节。
第二步:统计相邻 token 对的出现频率
假设训练语料(简化示例):
"the cat sat on the mat the cat"
初始时每个字符是一个 token:
[t, h, e, _, c, a, t, _, s, a, t, _, o, n, _, t, h, e, _, m, a, t, _, t, h, e, _, c, a, t]
统计所有相邻 token 对:
(t, h) → 3 次 ← 最高频!
(h, e) → 3 次
(e, _) → 3 次
(c, a) → 2 次
(a, t) → 4 次 ← 最高频!
...
选出现频率最高的一对(假设是 (a, t)),合并成新 token:
词汇表新增: "at"
语料变成: [t, h, e, _, c, at, _, s, at, _, o, n, _, t, h, e, _, m, at, _, t, h, e, _, c, at]
第三步:重复合并
再次统计:
(t, h) → 3 次 ← 最高频
(h, e) → 3 次
...
合并 (t, h) → “th”:
词汇表新增: "th"
语料变成: [th, e, _, c, at, _, s, at, _, o, n, _, th, e, _, m, at, _, th, e, _, c, at]
继续:合并 (th, e) → “the”:
词汇表新增: "the"
语料变成: [the, _, c, at, _, s, at, _, o, n, _, the, _, m, at, _, the, _, c, at]
继续:合并 (c, at) → “cat”:
词汇表新增: "cat"
语料变成: [the, _, cat, _, s, at, _, o, n, _, the, _, m, at, _, the, _, cat]
持续合并,直到词汇表达到目标大小。
GPT-2 的词汇表大小:50,257 GPT-4 的词汇表大小:约 100,000 GLM 系列的词汇表大小:约 150,000
2.2 编码过程(使用训练好的词汇表)
训练好词汇表后,对新文本进行 Token 化:
输入: "unhappiness"
BPE 查找最长匹配:
1. 检查 "unhappiness" 是否在词汇表中 → 不在
2. 检查所有可能的拆分,找到词汇表中存在的最优拆分
3. 结果: ["un", "happiness"] 或 ["un", "happi", "ness"]
(具体取决于哪些子串在词汇表中)
每个 token 对应一个 ID:
"un" → ID 348
"happi" → ID 23991
"ness" → ID 1108
2.3 实际 Token 化的例子
输入: "Hello, world! 你好世界"
GPT-4 tokenizer 的结果:
["Hello", ",", " world", "!", " 你", "好", "世", "界"]
→ [9906, 11, 1917, 0, 220, 57668, 53901, 3574, 244, 41519, 230]
注意:
- "Hello" 是一个完整 token(高频词)
- " world" 包含前面的空格(BPE 会把空格编码进 token)
- 中文通常一个字一个 token(中文字不像英文那么高频合并)
2.4 Token 数量直觉
英文:
1 个 token ≈ 4 个字符 ≈ 0.75 个单词
"The quick brown fox jumps over the lazy dog"
→ 9 个单词 → 大约 9-10 个 token
中文:
1 个 token ≈ 1-2 个汉字
"今天天气真好"
→ 6 个字 → 大约 6 个 token
代码:
通常 token 更多(符号、缩进都是 token)
"def hello():\n print('hi')"
→ 大约 12-15 个 token
三、其他分词算法
3.1 WordPiece(BERT 使用)
和 BPE 类似,但合并策略不同。BPE 选频率最高的对合并,WordPiece 选能最大化训练数据似然的对合并。
BPE: 选 count(AB) 最大的
WordPiece: 选 count(AB) / (count(A) * count(B)) 最大的
WordPiece 更偏好合并那些”总是一起出现”的对,而不仅仅是”出现次数多”的对。
标记方式也不同——WordPiece 用 ## 标记非词首的子词:
"unhappiness" → ["un", "##happi", "##ness"]
3.2 SentencePiece(多语言友好)
不依赖预先的空格分词,直接在原始字节/Unicode 上训练。对中文、日文等没有空格分隔的语言更友好。
LLaMA、T5 等模型使用 SentencePiece。
3.3 对比
| 算法 | 使用模型 | 特点 |
|---|---|---|
| BPE | GPT 系列, GLM | 简单高效,频率驱动 |
| WordPiece | BERT | 似然驱动,学术经典 |
| SentencePiece | LLaMA, T5 | 多语言友好,无需预分词 |
实际差异不大,核心思想都是:从小到大逐步合并高频子串。
四、特殊 Token
除了文本 token,词汇表中还有一些特殊用途的 token:
<|endoftext|> (ID: 50256) → 文本结束标记
<|im_start|> → 对话开始
<|im_end|> → 对话结束
<PAD> → 填充(使 batch 内序列等长)
<UNK> → 未知 token(理论上 BPE 不需要)
对话格式示例(ChatML):
<|im_start|>system
你是一个有帮助的助手。<|im_end|>
<|im_start|>user
你好<|im_end|>
<|im_start|>assistant
你好!有什么可以帮助你的吗?<|im_end|>
这些特殊 token 告诉模型”这段是系统指令”、“这段是用户说的”、“这段是你该生成的回答”。
五、Token 化对模型的影响
5.1 词汇表大小的权衡
词汇表大 (100K+):
✅ 常见词/短语是单个 token,序列更短,推理更快
❌ Embedding 矩阵更大 (100K × d 个参数)
❌ 生成时 softmax 计算量更大
词汇表小 (10K):
✅ Embedding 矩阵小
❌ 文本被拆得很碎,序列很长,计算量大
❌ 每个 token 携带的信息少
5.2 “Token 税”
中文在以英文为主的 tokenizer 中通常效率较低:
英文: "Hello world" → 2 tokens
中文: "你好世界" → 4 tokens (同样的语义,token 数翻倍)
这意味着:
- 中文的有效上下文窗口更短(同样 128K token 限制,能放的中文内容更少)
- 中文的推理成本更高(按 token 计费时更贵)
这就是为什么一些专注中文的模型(如 GLM)会用更大的词汇表(150K),把更多中文字词组合纳入,减少中文的 token 数量。
5.3 Token 边界问题
模型只能看到 token,看不到字符。这会导致一些有趣的问题:
问: "strawberry 中有几个 r?"
模型可能答错,因为:
"strawberry" → ["str", "aw", "berry"]
模型看到的是 3 个 token,不是 10 个字母
它不能直接"数"字母
这不是模型”不聪明”,而是它的输入粒度就是 token 级别的。
本章总结
- Token 是介于字符和单词之间的文本单位
- BPE 通过反复合并高频字节对来构建词汇表
- 高频词(如 “the”)是单个 token,罕见词被拆成多个子词
- 每个 token 有一个整数 ID,模型的输入就是 ID 序列
- 词汇表大小通常在 50K-150K,是效率和参数量的权衡
- Token 化对不同语言有不同效率(“token 税”)
下一篇:03. Embedding:从数字到向量空间 — Token ID 怎么变成模型能计算的向量