在任何大型语言模型(LLM)踏足训练阶段之前,分词(tokenization)总是第一道关卡。分词决定了模型究竟用怎样的“分辨率”看待语言,它直接影响推理速度、内存占用乃至最终表现。本篇将带你走完一次从朴素字符编码到高效 BPE 的全过程,并附赠完整的 Python 实战代码思路,助你亲手训练专属分词器。
什么是分词
分词就是把原始文本转化为一系列最小语义单元——token。token 可以是字符、子词、整词,甚至标点符号。
示例:
“你好,世界!” → [‘你好’, ‘,’, ‘世界’, ‘!’]
这些 token 会被映射到唯一整数 id,再查表成为高质量向量,送入模型。一个恰到好处的分词策略能显著压缩序列长度,降低算力开销。
为什么离不开分词?
- 文本→数字:模型只吃数字。
- 降维降噪:把动态无穷的自然语言映射到有限、稳定的向量空间。
- 统一标准:无论中文、英文还是 Emoji,最终都会“对齐”到同一个嵌入表。
如果你手握 10 GB 多语种语料,却缺少一套锋利的分词方案,训练出来的 LLM 很可能在“未见过的字符”前直接宕机。
朴素方案一览
| 粒度 | 优点 | 缺陷 |
|---|---|---|
| 字符级 | 词汇固定(一般 ≤ 50 k),不会 OOV | 序列 tyrannosaurus 级拉长,transformer 能看到的有效信息骤减 |
| 词级 | 信息密集,序列短 | 词表 bulge 到百万级,跨语种极难收敛,且对黏着语极不友好 |
两者夹击之下,“子词”成为折中方案:既压缩长度,又控制词表规模。BPE 正是这条中间道路上最闪耀的明星。
👉 想了解如何一句话把词表压缩到 32 k 仍滴水不漏?最快路径在这里。
Unicode ord、UTF-8?先别急
- Unicode ord 直接给每个字符发 4 字节“身份证”,词表 100 k+,序列还是“字”级别,两者均伤。
- UTF-8 本身只用 1–4 字节编码字符,天然受限 256 字节空间,遇到长文本仍旧“壮观”。
只有当 UTF-8 遇上 Byte Pair Encoding(BPE),序列可被“二次压缩”成 32 k–50 k 的词表,才真正实现精悍与兼容 (multilingual bytes) 的双赢。
Byte Pair Encoding (BPE) 算法原理解析
核心思想一句话:把出现频率最高的连续字节对反复替换为新 token,直至预设词表填满。
示例流程:
原始序列
aaabdaaabac- 合并 “aa” → ‘Z’
- 合并 “ab” → ‘Y’
- 合并 “ZY” → ‘X’
路径记录:Z=aa, Y=ab, X=ZY;逆向即可解码。
结果极度紧凑:
XdXac该思路延伸到所有 UTF-8 字节流后,优点突出:
- 长度与容量的动态平衡:词表增大,token 变短,transformer 的视野瞬时宽广。
- 零 OOV:再冷门的字符、Emoji、生僻汉字,都能拆成已知字节段。
Python 手把手:20 行代码训练你的专属分词器
以下伪代码演示完整套路,替换 text 即可即刻复用:
# 载入语料
text = open("corpus.txt", encoding="utf-8").read()
tokens = list(map(int, text.encode("utf-8"))) # 先切成字节
# 统计频率 > 合并 > 循环
merges = {}
vocab_size_final = 32768
for _ in range(vocab_size_final - 256):
stats = get_stats(tokens)
top = max(stats, key=stats.get)
new_id = 256 + len(merges)
merges[top] = new_id # 记录合并表
tokens = merge(tokens, top, new_id)
# 解码测试
def decode(ids):
tokens = b"".join(vocab[i] for i in ids)
return tokens.decode("utf-8", errors="replace")
print(decode(encode("中文🎉NLP真酷")))把 merge list 序列化保存成 merges.txt,便能在任何环境秒加载,真正做到“从零到上线”的一体分词方案。
拓展思考:多语种小词表的大威力
研究反复验证:同样的 50 k 词表规模下,训练集覆盖 100+ 语言,比单一英语词表平均压缩序列长度 30%—45%,下游任务 BLEU/Rouge 反而更高。原因在于同形异义、共享子词带来的泛化红利,几乎免费。
FAQ:关于分词,你可能想立刻问
Q1:词表越大越好吗?
A:不是。过大词表会拉长嵌入矩阵,显存爆炸;过小则序列太长,同样伤性能。实验显示,30 k–100 k 是在开源 LLM 场景中最平衡区间。
Q2:BPE 会不会把中文整句切成不可读的碎片?
A:不会。高频词“你好”“谢谢”会直接作为整体 token 存在;低频人名则拆成偏旁+部首,仍保存可逆字节路径。
Q3:如何评估分词器好坏?
A:核心指标有两组:
1) 词对句的压缩率(越小越好);
2) 最终下游任务分数(越高越好)。做一个交叉验证即可筛选。
Q4:要不要把停用词和符号单独拎出来?
A:不需要。让 BPE 自己学;强制人工标记反而破坏频次统计。
Q5:一定要重写代码吗?有没有现成可用?
A:transformers 库里的 ByteLevelBPETokenizer 已默认采用相同思路,10 行 API 就能无痛启动。但理解原理可帮助你定制特殊领域词表。
结论
分词不是“前菜”,而是让 LLM 能够饱餐文本的“刀叉”。从字符到子词的每一次抉择,都会影响显存、速度与泛化天花板。掌握 BPE 你便能亲手打磨一套贴合自己数据的利刃,真正做到“小词表、大格局”。祝你调参愉快,打造独一无二的大模型!