大模型基座探秘:深入剖析 Tokenization(BPE、WordPiece 与 SentencePiece)
在浩瀚的大语言模型(LLM)浪潮中,我们常常将目光聚焦于 Transformer 架构、多头注意力机制或是千亿级的参数量。然而,在那些耀眼的智能涌现背后,有一个极其基础却决定模型上限的“幕后功臣”——Tokenization(分词)。
当你向 ChatGPT 输入一句问候,或者让 Claude 写下一首长诗时,它们的第一步并不是直接理解语义,而是将人类的自然语言“切”成一个个小块。这个过程就是 Tokenization。
为什么大模型不能直接处理字符?为什么 GPT-4 有时会“数不清”单词里的字母?这一切的答案,都藏在分词算法中。今天,我们将深入探讨大模型时代最核心的三种分词算法:BPE、WordPiece 与 SentencePiece,并通过硬核的代码实战,带你彻底搞懂大模型的“拼音输入法”。
一、 为什么我们需要 Tokenization?
在深入算法细节之前,我们需要明确一个问题:为什么我们要分词?
理想情况下,我们可以把一个完整的词作为一个 Token,这被称为 词级别分词。但它面临两个致命问题:
- 词表爆炸:英语中有几十万个单词,加上时态、复数、派生词,词表会无限膨胀。对于没有明显词边界的中文,情况更复杂。
- OOV(Out-of-Vocabulary,未登录词)问题:无论词表多大,训练数据中总会出现没见过的词(比如新的网络用语、错别字)。遇到 OOV,模型只能用
<UNK>代替,导致信息彻底丢失。
另一个极端是 字符级别分词,即把每个字母或汉字作为一个 Token。
- 序列过长:一句话 “I love you” 会变成
['I', ' ', 'l', 'o', 'v', 'e', ' ', 'y', 'o', 'u']。由于 Transformer 的计算复杂度随长度呈平方级增长(),这会导致计算资源瞬间爆炸。 - 语义表示弱:单独的字母 “a” 或 “b” 很难携带实际的语义信息。
为了在这两者之间取得平衡,子词分词 应运而生。它的核心哲学是:高频词保留,低频词拆解。
- 常见的词(如 “apple”)直接作为一个整体 Token。
- 罕见的词(如 “chatgptization”)被拆解为有意义的子词,例如 [“chat”, “gpt”, “ization”]。
这样既控制了词表大小(通常在 3万 到 10万 之间),又完美解决了 OOV 问题。接下来,我们来看看实现这一哲学的三大神器。
二、 核心算法详解
2.1 BPE (Byte-Pair Encoding):GPT 系列的标配
BPE 最初是一种数据压缩算法,在 2015 年被引入 NLP 领域。它是目前最流行的分词方法,OpenAI 的 GPT 系列(包括 GPT-2, GPT-3, GPT-4)以及 Meta 的 LLaMA 均采用了 BPE 的变体。
算法核心思想
BPE 的核心逻辑非常朴素:寻找出现频率最高的相邻 Token 对,并将它们合并为一个新的 Token。
训练步骤
- 准备语料:将所有单词拆分为字符序列,并在单词结尾添加特殊符号(如
</w>)表示词边界。 - 统计共现频率:统计语料中所有相邻字符对的出现频率。
- 合并:将频率最高的字符对合并为一个新的字符(子词)。
- 重复:重复步骤 2 和 3,直到达到预设的词表大小或设定的合并次数。
动手实现 BPE(Python 代码示例)
为了真正理解 BPE,我们来手写一个极简版的 BPE 训练器:
1 | import re |
运行结果解析:
你会发现,程序首先合并了 e 和 s,因为它们共同出现的频率最高(构成 est)。随后 n 和 e 可能会合并。通过这种自下而上的贪心策略,BPE 成功地从语料中自动挖掘出了常见的词根和词缀。
2.2 WordPiece:BERT 的选择
WordPiece 算法与 BPE 非常相似,同样是从字符级别开始,通过不断合并构建词表。Google 提出的 BERT 模型使用的就是这种算法。
与 BPE 的核心区别
BPE 选择合并的依据是频率。而 WordPiece 选择合并的依据是似然概率。
在 WordPiece 中,每次合并前,算法会评估所有可能的字符对合并后,对整个语言模型的 likelihood(似然)提升有多大。它倾向于合并那些合并后能让语言模型更好地预测文本的符号对,而不仅仅是看它们是否经常挨在一起。
前缀标记 ##
WordPiece 最显著的标志是它的子词前缀 ##。
如果一个词被切分,除了第一个子词外,后面的子词都会加上 ## 前缀,表示它不是一个完整的词,而是词的一部分。
示例:
- 单词
unfriendly可能会被切分为:['un', '##friend', '##ly']。 - 单词
hugging可能会被切分为:['hug', '##ging']。
这种标记方式让模型在处理时能够清晰地知道哪些 Token 是词的开头,哪些是词的延续。
代码实战:调用 BERT 的 WordPiece
我们可以通过 Hugging Face 的 transformers 库直观感受 WordPiece:
1 | from transformers import BertTokenizer |
输出解析:
对于中文,BERT(WordPiece)通常会以字为单位进行切分。如果遇到非常罕见的字或未知的符号,它会将其切分成更小的片段甚至单个字符,并使用 [UNK] 标记那些完全无法识别的 Token。相比 BPE,WordPiece 对中文的处理显得有些过于“字级别”,这也是后来中文大模型转向 BPE 的原因之一。
2.3 SentencePiece:打破语言边界的终极武器
无论是 BPE 还是 WordPiece,它们在训练前都有一个隐含的假设:文本已经通过空格进行了预分词。
这对于英文等印欧语系没问题,但对于**中文、日文、韩文(CJK 字符)**等不使用空格分隔单词的语言来说,就需要依赖复杂的上游分词工具(如 Jieba),这会引入额外的误差和系统复杂度。
SentencePiece 应运而生。它是一个开源的、语言无关的分词工具。当前最火的大模型,如 GLM-4、Llama 2/3、T5 等,几乎全部采用了基于 SentencePiece 训练的分词器。
核心创新:把文本当作字节流
SentencePiece 将输入的句子简单地视为一系列原始的字节流,甚至空格也被当作一个特殊的字符(通常被转义为 ▁,即下划线字符 U+2581)。它将分词问题直接转化为一个无监督的文本分割问题,完全摒弃了语言学的预处理。
核心算法:Unigram Language Model
虽然 SentencePiece 支持 BPE 模式,但它最著名的是引入了 Unigram 语言模型。
与 BPE(自下而上不断合并)相反,Unigram 是自上而下不断剪枝的:
- 初始化:首先用一个巨大的词表(比如包含所有字符、所有高频子串)。
- 计算概率:通过 EM 算法(期望最大化)计算每个子串在当前词表下的出现概率。
- 评估损失:计算移除某个子词后,对整体似然函数的影响。
- 剪枝:移除那些对似然函数影响最小(即对全局概率贡献极小)的子词。
- 重复:直到词表缩减到目标大小。
这种方法的好处是,它可以自然地为一个单词提供多种切分路径,并在解码时通过 Viterbi 算法找到概率最大的切分序列。
代码实战:使用 SentencePiece 训练自定义分词器
我们可以自己准备一段语料,体验 SentencePiece 的强大与便捷。
首先安装库:pip install sentencepiece
1 | import sentencepiece as spm |
通过这段代码你会发现,即使完全没有空格,SentencePiece 也能以极高的效率和准确度将中文切分成类似 ['▁自然', '语言', '处理', '改变', '了', '世界'] 的合理序列。
三、 横向对比:BPE vs WordPiece vs SentencePiece
为了便于大家在阅读论文或选择技术栈时快速区分,我们总结如下对比表:
| 特性 | BPE | WordPiece | SentencePiece (Unigram/BPE) |
|---|---|---|---|
| 提出者/代表模型 | OpenAI (GPT系列), LLaMA | Google (BERT, DistilBERT) | Google (T5, ALBERT), GLM |
| 合并/选择策略 | 最高频率的相邻对 | 最大提升似然概率的对 | 自上而下基于概率剪枝 |
| 对空格的依赖 | 依赖(需预先按空格分词) | 依赖(需预先按空格分词) | 不依赖(空格视为普通字符 ▁) |
| 切分标记 | 无特定前缀,依赖字母组合 | 词尾或词内子词带 ## 前缀 |
句首或词首常带 ▁ 标记 |
| 多语言支持 | 较弱(需结合 Byte-level) | 较弱 | 极强(原生跨语言设计) |
| 反向解码难度 | 较容易 | 需去除 ## 并拼接 |
极易(拼接后将 ▁ 替换为空格) |
四、 大模型 Tokenization 的进阶避坑指南
在真实的大模型研发和使用中,Tokenization 并非完美无缺。了解以下“坑点”,有助于你更深刻地理解大模型的行为。
1. 分词粉尘效应 与多语言不公平
BPE 在处理多语言时,如果词表分配不均,会导致严重的“粉尘效应”。
例如,由于英语在训练语料中占主导地位,GPT-3 等模型会将常见的英文单词(如 “apple”)视为 1 个 Token。但同样的模型在处理中文时,可能需要用 2 到 3 个 Token 才能拼出一个汉字(尤其是在某些生僻字或繁体字上,使用 Byte-level BPE 将汉字拆成 UTF-8 字节)。
后果:这导致在相同的上下文窗口下,中文用户能输入的有效信息实际上比英文用户少。这也是为什么国产大模型(如 GLM-4)在构建词表时,专门增加了中文字符的覆盖率,使得中文文本的压缩率大幅提升。
2. 大模型为什么连“草莓”里有几个“r”都数不清?
这是一个著名的社区梗。当你在 ChatGPT 中问它 “How many 'r’s in the word ‘strawberry’?” 时,它常常会回答 2 个。
原因就在于 Tokenization!
在 GPT 的词表中,“strawberry” 很可能是一个完整的 Token(ID: 38837)。
当模型看到 strawberry 这个 Token 时,它内部看到的是一个高维向量,它根本看不到组成这个词的独立字母 “r”。就像你在看“草莓”这个词时,你不会去想里面有几横几竖一样。
解决方法:如果你让它把单词拆开(例如加上空格 s t r a w b e r r y),或者写出逐个字母检查的 Python 脚本,它就能数对了。
3. 特殊 Token 的注入攻击
在分词器的设计中,通常会有特殊的控制符,例如 <|endoftext|>(表示文本结束)、<|im_start|>(表示指令开始)。
如果开源模型在微调或部署时,没有严格过滤用户输入中的这些特殊字符,攻击者就可以通过输入 <|endoftext|> 来强行截断模型的系统提示,从而实现越狱攻击。
五、 总结
Tokenization 是连接人类语言与神经网络数字世界的桥梁。回顾全文,我们可以看到分词技术的演进路线:
- WordPiece 引入了概率学视角,解决了 BERT 时代的语言表示问题。
- BPE 以其简单粗暴的统计概率,配合 Byte-level(BBPE)技术,成为了 GPT 系列霸主的基石。
- SentencePiece 打破了空格的枷锁,实现了真正意义上的语言无关性,成为当今多语言大模型(如 LLaMA、GLM)的标配。
尽管随着模型规模的扩大,一些研究者开始探索 Byte-level 甚至 无 Tokenizer(如 MegaByte)的架构,试图让模型直接阅读原始字节,但在未来相当长的一段时间内,基于 BPE 和 SentencePiece 的分词器仍将是工业界的主流。
理解了 Tokenization,你才算真正拿到了阅读大模型底层代码的钥匙。下一次,当你在向大模型输入 Prompt 并发现它出现了奇怪的截断或幻觉时,不妨想一想:是不是这里的 Token 边界 在作祟呢?