Tokenizer 是怎么工作的?

Tokenizer 是怎么工作的?
上篇我们聊了 Token 是什么——它是 AI 模型理解和处理文本的基本单位。但你有没有想过,这些 Token 到底是怎么从你输入的文字里切分出来的?
今天我们就来拆解这个"翻译官"——Tokenizer。
Tokenizer 就是大模型的"翻译官"
你跟 ChatGPT 说"写一首关于春天的诗",它能理解你在说什么。
但模型实际上看到的,不是"春"+"天"+"的"+"诗"这样的中文词汇,而是一串数字 ID。
这个把人类语言翻译成模型能"看懂"的数字的关键组件,就是 Tokenizer。
它的活儿说起来也简单:把一段文本切分成子串,同时保留语义和结构信息。每个子串对应一个数字 ID,整段文本就变成了一串数字。
直接按词切不行吗?"春天"就是一个词啊。
还真不行。
英文有 "unhappiness" 这种带前缀后缀的复合词,中文有无数个生僻字。如果按词切,词汇表会爆炸——举个例子,英文要覆盖 26 万个词,每个词一个向量,光存储这些 embedding 就要吃掉几十 G 显存。GPT-2 当年试过,全连接层占了差不多 30% 的显存。
所以现在的方案是子词分词:既不像字符那么碎,也不像完整单词那么大。
子词的好处是啥?打个比方,它就是"乐高积木"思维。
有限的积木块,能拼出无限多造型。有限的子词,能组合出几乎所有词。
这就是为什么你看到 Token 数往往比字数少——常用词被当成一整块了。
BPE:高频"搭档"自动成团
第一种算法叫 BPE,全称 Byte Pair Encoding,中文叫字节对编码。
核心思想很朴素:经常一起出现的字符对,就给它们打包成一个新的"词"。
怎么做到的?
假设语料里有这么一句英文:"h a p p y"。原始状态下,每个字符都是独立的 token。
但"p p"这个字符对在英文里出现频率极高——happy、apple、supper、happy 都少不了它。
BPE 就把它俩合并成一个新的 token:"pp"。
下一轮迭代,"ha"和"ppy"继续合并高频率的字符对。循环几万次之后,像"happy"这样的常见词就直接作为一个整体存在了。
整个过程是自动的,不需要人工定义规则。
你喂给它足够多的文本,它自己统计、合并、构建词汇表。
而且 BPE 用的字节级编码,对多语言特别友好。
不管你输入的是中文、阿拉伯文还是 emoji,都能转成 UTF-8 字节序列来处理。泛化能力很强。
现在主流的大模型,GPT 系列、LLaMA 都是用 BPE。我之前跑过 LLaMA-2-7B 的推理,词汇表大小是 32000,BPE 切出来的子词分布很均匀。
WordPiece:严格"拆字"的艺术家
第二种算法叫 WordPiece。
听起来差不多对吧?确实,它跟 BPE 一样是迭代合并的思路。
但区别在于:BPE 看频率,谁出现得多就合并谁;WordPiece 看语言模型似然率,合并后语义最连贯的才保留。
这就像两个人读同一本书:
- BPE 是那种"这本书里'机器学习'出现了两万次,我直接背下来当固定搭配"
- WordPiece 是那种"这个词拆开好像也能讲得通,那就拆开看看"
所以 WordPiece 会给子词加标记。
遇到 "happiness" 这样的词,它可能会拆成 "happy" + "##ness"。
注意那个 "##" 前缀——它不是装饰,是告诉模型:这个子词不是独立的,它需要接在前面那个词后面。
换句话说,"##ness" 专属于后缀场景,不能单独成词。
这种设计让语义更清晰,但代价是:中文、日文这些没有空格分隔的语言,需要先做一层预分词。
不然模型看到一长串汉字,也不知道该从哪里拆起。
Google 的 BERT 系列用的就是 WordPiece。我之前用 BERT 做中文情感分析的时候,发现它会把"今天"拆成"今"+"天","天气"拆成"天气",这跟我想象中不太一样,后来才知道是预分词在起作用。
BPE vs WordPiece:到底选哪个?
说到这里你可能会有疑问:既然思路差不多,到底该用哪个?
说白了,这是两个不同的取舍。
BPE 的优势在于跨语言能力。字节级编码让它天生就能处理各种语言,中文、泰文、emoji 全都支持。
而且它没有 ## 标记,切分结果更干净。
WordPiece 的优势在于语义精确。语言模型似然率的判断让它更"懂"英语的词根词缀,拆出来的子词语义完整性更好。
但它依赖 ## 标记,对多语言场景不够友好。
实际选型也印证了这一点:
- GPT-2、GPT-3、LLaMA:都用 BPE
- BERT:用的 WordPiece
你是做文本生成还是文本理解?专注单语言还是多语言?对 emoji 和特殊字符有没有需求?
如果偏多语言、对 emoji 等特殊字符有需求,选 BPE。
如果专注英文、追求语义精确,选 WordPiece。
没有绝对的好坏,只有合适的取舍。
SentencePiece:把一切变得更简单
说了这么多,你有没有发现一个问题?
BPE 和 WordPiece 都假设文本已经"预处理"过了——英文按空格分词,中文按某种工具先切一刀。
这对单语言场景还好,但多语言混合的时候就麻烦了。
代码里混着中文注释怎么办?论文里夹着德文术语怎么办?
SentencePiece 就是来解决这个问题的。
它不需要你提前做任何分词预处理。直接喂给它一碗"杂粮"——中英日韩各种语言混在一起,它自己就能处理。
这是因为它底层用的是 Unigram 模型,配合端到端的学习方式。
不需要语言学家设计的规则,不需要提前分词,一个模型搞定所有语言。
Google 的 T5、ALBERT 这些模型,用的都是 SentencePiece。我之前微调 T5 的时候,用的是 SentencePiece,不需要单独处理中文分词这个环节,确实省事不少。
为什么子词分词是"最优解"
说了这么多算法,你可能会问:为什么要搞这么复杂?
答案藏在三个痛点里。
第一个痛点:OOV。
Out-of-Vocabulary,未登录词。
你的词汇表再大,也不可能覆盖所有词。总会有生僻字、网络新词、人名地名蹦出来。
按词分词的话,这些词就变成 unknown,模型完全没法处理。
但子词分词不怕。"未##知"——模型认识"未",也认识"知",组合起来照样懂。
我之前做NER(命名实体识别)的时候,遇到了"冰墩墩"这个新词。用字符级分词的话,每个字都认识但组合起来模型一脸懵;用子词分词就好多了,"冰"+"墩"+"##墩"虽然也不是完美,但至少比全 unknown 要强。
第二个痛点:词汇表膨胀。
Transformer-XL 当年吃过这个亏。
它的词汇表有 26 万个词,每个词一个 embedding 向量。光是这个 embedding matrix,就占了十几 GB 显存。
子词分词把词汇表压缩到 3-5 万。参数量直接砍半,训练速度也快了不少。
第三个痛点:语义碎片化。
按字符分词呢?虽然词汇表只有几千个字符,但每个 token 的语义太碎了。
"happy"拆成"h"+"a"+"p"+"p"+"y",模型根本不知道这是个好词还是坏词。
子词分词刚好卡在中间——既有语义完整性,又不至于词汇表爆炸。
就像乐高积木,有限的基础块,能拼出无限多造型。
有限的基础子词,能表示几乎所有词汇。
你的任务决定你的选择
聊到这儿,你已经知道主流的 Tokenizer 算法是怎么工作的了。
但光懂原理还不够。真正用起来的时候,你会发现一个有意思的现象:
Tokenizer 的选择,往往不是技术问题,而是场景问题。
你是做文本生成还是文本理解?专注单语言还是多语言?对 emoji 和特殊字符有没有需求?
这些问题的答案,会指向完全不同的选择。
不过有个问题你可能已经发现了:中文 Tokenizer 的处理方式好像跟英文不太一样。
英文按空格分词,中文怎么办?"今天天气真好"怎么拆?
这就要聊到下一个话题了——为什么中文分词这么特殊?中英文的 Token 数为什么会差这么多?
