Tokenization
理论基础
把 tokenization 视为两步映射:
- 编码器:把字符序列(Σ*)映射到 token 序列(Δ*)
- 解码器:把 token 序列(Δ*)映射回字符序列(Σ*)
BPE (Byte Pair Encoding)
核心思想
从最小单元(字节/字符)出发,通过统计语料库中的高频”字节对”,迭代合并,逐步构建词汇表。
完整流程
1. 训练阶段
Pre-tokenization(预分词)
- 用规则(正则表达式)把原始文本切成初始 segments
- 常见规则:按空格、标点、大小写边界切分(GPT-2 用
\p{L}+|\p{N}+|'s|...这类正则) <|endoftext|>是文档边界标记,防止跨文档合并,不是 pre-tokenization 的分界符本身
初始化词汇表
- 把每个 segment 转成 UTF-8 字节序列(256 个基础 token = byte 0~255)
- 用字节而非字符,好处是任意文本都能表示,无 OOV(out-of-vocabulary)问题
迭代合并
repeat N times (N = 目标词汇表大小 - 256):
1. 统计所有相邻 token pair 的出现频率
2. 找到频率最高的 pair (a, b)
3. 将 (a, b) → ab 加入词汇表(merge rule)
4. 在语料库中把所有 (a, b) 替换为 ab
- 合并规则按顺序保存 → 这就是最终的 merge table
2. 推理(编码)阶段
输入文本
→ pre-tokenization(同训练时一样的规则切 segments)
→ 每个 segment 转 UTF-8 字节
→ 按 merge table 的顺序依次尝试合并
→ 输出 token id 序列
- 合并顺序严格按训练时的优先级,不能 贪心地合并最长匹配
3. 解码
- Token id → token bytes → 拼接 → UTF-8 decode → 原始文本
- 因为以字节为基础,解码永远无损
关键细节
| 问题 | 答案 |
|---|---|
| 为什么用 UTF-8 字节而不是 Unicode 字符? | 保证词汇表有限(256)且无 OOV |
| 为什么 pre-tokenization 不能省? | 防止跨单词合并(如 dog 和 dog. 共享前缀但语义不同) |
| merge 的结果和顺序重要吗? | 非常重要,推理时必须按同样顺序 apply merge rules |
| 词汇表大小怎么定? | 超参数,GPT-2 用 50257,LLaMA 用 32000 |