Tokenizer 分词器
模型眼里的「字」是什么?为什么 strawberry 数不对 r 的个数?
模型读不了文字,只读得了向量
先补一个前两课一直跳过没说的问题。注意力也好、多头也好,我们从头到尾摆弄的都是「词向量」——一串数字。 可你键盘上敲进去的「我 是 一只 小猫」是文字,不是数字。中间缺了一步:谁把文字变成向量?
模型内部藏着一张事先定死的对照表(查找表):表里列着几万个「单元」,每个单元都预先配好一个向量。 文字进来,模型并不逐字去「理解」,而是拿它到表里查——查到哪个单元,就取出对应那一行向量,再送进注意力。 关键在于:这张表必须是有限的,不可能为世界上每一种可能的文字串都留一行。 所以文字进门第一步,必须先切成「表里查得到的单元」。这一步就叫分词(tokenize),切出来的每一块就是一个 Token。
这套「先切块、再查表」的机制,有个常被拿来嘲笑的副作用。问一个顶尖大模型:「strawberry 里有几个字母 r?」 它常常一本正经地答「2 个」——可正确答案是 3 个(strawberry)。 不是它语文没学好:strawberry 常常整个就是表里的一个单元,一个不可分割的 token。 模型眼里它不是 10 个字母,而是一坨整体,就像你把「中国」当一个词,从不会去数它有几个笔画—— 那 3 个 r 藏在这坨整体内部,对模型根本不可见,自然数不准。
两条朴素的路,各撞各的墙
把两种朴素方案的账算清楚,就知道为什么需要第三条路:
按「词」切。一个词一个 token,看着自然。可英语的词形几乎是无穷的—— 单复数、时态、人名、拼写错误、新造词……词表得开到上百万都兜不住。 更糟的是,只要遇到词表里没有的词,模型就彻底懵,只能吐一个「未知」占位符 [UNK],信息全丢。 中文更尴尬:根本没有空格告诉你词的边界。
按「字符」切。词表一下小到几千(汉字)甚至 256(字节),再也不会有「不认识的词」。 但代价是序列长得吓人:「understanding」=13 个 token,一篇文章的 token 数翻好几倍—— 而上一课说过,注意力的账单是 O(n²),序列翻倍、成本翻四倍。模型还得费劲从头学会「c-a-t 三个字符合起来才是猫」。
两堵墙逼出一个折中:子词(subword)。常见的整词当一个 token,生僻的长词拆成几个常见片段, 实在没见过的,退到字符/字节,保证永远不会「完全不认识」。问题只剩一个:这些片段从哪来、按什么规则切?
BPE:从字符出发,反复合并最常见的一对
现代大模型几乎都用 BPE(字节对编码)。它造词表的办法像在玩积木——从零开始,谁老黏在一起,就把谁俩焊成一块:
- 先把所有词拆到最小单位(字符/字节),每个字符就是一个 token;
- 扫一遍语料,数哪一对相邻 token 出现得最频繁;
- 把这一对合并成一个新 token,加进词表;
- 重复第 2、3 步,直到词表攒到目标大小(比如 5 万)。
拿三句真实的话当迷你语料,手动走两轮,体会一下「数 → 合并」:
② 红苹果和绿苹果都是水果。
③ 水果店卖水果。
全拆成单字,数所有相邻字对 → 「苹 果」最多:①里 2 次、②里 2 次,共 4 次
第 1 轮合并:苹 果 → 苹果
重新数 → 「水 果」跟上:②里 1 次、③里 2 次,共 3 次,最多
第 2 轮合并:水 果 → 水果
就这么数、合并、再数、再合并,几万轮之后:高频词整个变成一个 token("苹果"、"水果" 都是单 token), 中频词留成几个常见片段,生僻字才碎到单字。把刚学到的这两步合并,套回「苹果是水果」这句话上,就这样切:
掀开真相:模型眼里其实是一串整数
切出 token 只是第一步。token 并不会以「文字」的样子走进模型——每个 token 在词表里有个固定编号(ID), 模型真正吃进去的,是一串整数。每个 ID 再去一张巨大的嵌入表里,取出自己那一行数字—— 这就是第 15 课的词向量(大模型里叫 embedding)。所以一句话进模型的完整入口是这样的:
回头看开头那个谜题就通透了:模型看到的是 ID 7124(straw)和 15717(berry)两个整数, 以及它们对应的向量。字母 r 从一开始就没进过模型的视野。它答不准 r 的个数,不是笨,是因为这道题问的东西,在它的输入里压根不存在。
中文更吃亏,以及一类「人类不会犯」的错
GPT-4 的词表(tiktoken,约 10 万 token)以英文语料为主,中文 token 占比小。 后果是同样的意思,中文往往要花更多 token——上下文窗口能装的汉字更少,按 token 计费也更贵:
既然 token 不对应字符,模型就会栽在一些只跟字母有关的任务上——根源全是同一个:它看不见 token 内部的字母。
- 数字母:strawberry 是一个 token → 数不清里面的 r(开头那道题)。
- 逆序拼写:把「HELLO」倒过来拼成「OLLEH」要逐字符操作,可 token 不是字符。
- 押韵 / 藏头:要求每行首字母拼成一个词,模型常对不齐——它操作的是 token,不是字母。
这些都不是「智力」问题,而是 tokenization 的固有盲区。 缓解的思路:让模型能访问字符级信息,或干脆用字节级 tokenizer——但代价又回到「序列太长」那堵墙上,仍是权衡。
总结
模型看到的不是文字,是 token 的整数编号。按词切会爆词表、按字符切序列太长,BPE 折中——从字符出发、反复合并最频繁的相邻对。 「strawberry 数不清 r」不是笨:它被切成 token、变成整数,字母从一开始就不在模型的视野里。
这一课你亲手推导了
- 两条朴素路:按词切(词表爆炸 + [UNK])、按字符切(序列暴长 + O(n²) 翻倍)。
- 子词折中:常见词整体、生僻词拆片段、永不「完全不认识」。
- BPE 规则:数频率 → 合并最常见的一对 → 重复几万轮(low/lower/newer 手算两轮)。
- 真正的输入:文本 → token → 整数 ID → 查嵌入表得向量 → 加位置 → 进注意力。
- 固有盲区:数字母、逆序、藏头,都因为 token 内部的字符不可见;中文还更费 token。
学习小测验
做完这一课,来检测一下核心知识点。选出你的答案后点击「提交」,即可看到正确选项与讲解。
编码器、解码器与大语言模型
同样是 Transformer,为什么 BERT 不能写文章、GPT 不擅长填空?——三种架构的分流与取舍。