目录

04BPE、WordPiece、SentencePiece 详解

为什么需要分词?BPE、WordPiece、SentencePiece 详解

模型不认识字母,只认识数字。但在把文字变成数字之前,还有一个容易被忽略却极其关键的步骤——分词


引言:一个被咬了一口的苹果

想象一下:

我递给你一个完整的苹果,你可以直接吃。 但如果我递给你的是已经切成块的苹果,你只需要拿起一块放嘴里就行了——更省事。

LLM 处理文本也是这样:

  • 原始文本 = 整个苹果
  • 分词 = 切成一口大小的块
  • 每个块 = 一个 Token

分词就是把原始文本切分成模型能处理的“基本单元”。

这看起来很简单,但里面藏着很多精妙的设计。 为什么不能直接用“字”或“词”呢?BPE、WordPiece、SentencePiece 又是什么?

这篇文章帮你彻底搞清楚。


1. 为什么不能直接用“字”?

最直观的想法:把每个汉字当成一个 Token。

中文:“我爱吃苹果” → [我] [爱] [吃] [苹] [果]

但这有问题:

  • 信息太碎 单独出现几乎没意义
  • 词表太小:汉字只有约 9000 个常用字,但中文词汇量巨大
  • 丢失语义苹果 是一个完整概念,拆成 + 就丢了

2. 为什么不能直接用“词”?

那直接用词典里的词呢?

“我爱吃苹果” → [我] [爱] [吃] [苹果]

看起来不错。但有个致命问题:词太多了

  • 英语有几十万到上百万个词
  • 中文词语更是无限(复合词、新词层出不穷)

如果每个词都占一个 ID:

问题后果
词表太大模型最后一层(输出概率)计算量爆炸
生僻词/新词找不到对应的 ID → 直接报错
形态变化run / running / runs 都需要不同的 ID

词表太小会丢失信息,词表太大会算不动。 我们需要一个平衡方案:介于“字”和“词”之间。


3. 子词分词(Subword Tokenization)——黄金中间态

核心思想

高频词保留为完整 Token,低频词拆成常见的“子词片段”。

✅ 常用词 the苹果 → 完整 Token ✅ 生僻词 unhappinessun + happiness ✅ 未知词 ChatGPTChat + G + P + T(或更细)

好处:

  • 词表可控(通常 3 万 ~ 10 万)
  • 没有“未登录词”——任何词都能用子词拼出来
  • 语义保留:un 表示否定,happiness 是快乐

4. BPE(Byte Pair Encoding)——最经典的方法

BPE 的发明背景: 最早是 1994 年用于数据压缩的算法,后来被 GPT-2 / GPT-3 采用。

工作原理(一步步)

  1. 把所有词拆成字符级别applea p p l e
  2. 统计最常出现的相邻字符对: pp 经常一起出现 → 合并成 pp
  3. 重复合并,直到达到预设的词表大小
graph LR A["原始: a p p l e"] --> B["统计频率"] B --> C["p+p 最高 → 合并为 pp"] C --> D["a pp l e"] D --> E["pp+l 最高 → 合并为 ppl"] E --> F["a ppl e"] F --> G["继续合并..."]

BPE 的特点

  • ✅ 简单、高效
  • ✅ 适合英语等用空格分詞的语言
  • ⚠️ 基于空格分词,对中文不友好(中文没有天然空格)

5. WordPiece——Google 的选择

WordPiece 是 Google 为 BERT 设计的,与 BPE 非常相似,但有一个关键区别。

对比项BPEWordPiece
合并规则频率最高的相邻对使似然提升最大的对
似然计算合并后让训练数据的概率更高
特殊标记## 表示子词(如 ##ing## 同样用法

直观差异

BPE 合并“最常见的”。 WordPiece 合并“最有信息增益的”。

类比: BPE = 采访 100 个人,选最常见的回答 WordPiece = 采访 100 个人,选最能减少不确定性的回答

WordPiece 在 BERT、DistilBERT 中使用。


6. SentencePiece——统一中英文的利器

前面两种方法都有一个假设:文本里有空格。 这对英语没问题,但对中文、日文、韩文就很尴尬——它们没有空格。

SentencePiece 的解决方案:

把空格也当成一个普通字符(_),和其他字符一起编码。

SentencePiece 的特点

  • ✅ 不依赖空格,可以直接处理中文
  • ✅ 可以从原始文本直接学习子词
  • ✅ 支持 BPE 和 unigram 两种算法
  • ✅ 训练好之后,编码和解码完全可逆
graph LR A["我爱吃苹果"] --> B["加特殊空格符"] B --> C["_ 我 _ 爱 _ 吃 _ 苹果"] C --> D["SentencePiece 分词"] D --> E["['_我', '_爱', '_吃', '苹果']"]

注意:_ 表示一个词的开头

谁在用 SentencePiece?

  • LLaMA(Meta)
  • Gemma(Google)
  • XLM-RoBERTa(Facebook)

几乎所有现代多语言大模型都使用 SentencePiece。


7. 三种方法对比表

维度BPEWordPieceSentencePiece
提出者OpenAI / 数据压缩GoogleGoogle
合并/选择规则最高频最大似然增益BPE 或 unigram
依赖空格需要需要不需要
中文支持较弱较弱✅ 好
可逆性一般一般✅ 完全可逆
典型使用者GPT, RoBERTaBERTLLaMA, Gemma

8. 一个完整的例子:同一句话的不同分词

原句:"ChatGPT is great!"

BPE(GPT-4)

["Chat", "G", "PT", " is", " great", "!"]

WordPiece(BERT)

["Chat", "##GP", "##T", "is", "great", "!"]

SentencePiece(LLaMA)

["▁Chat", "G", "PT", "▁is", "▁great", "!"]

是 SentencePiece 表示空格的符号

你会发现:同一个词可能在不同方法下被切成不同的子词序列。


9. 为什么分词很重要?三个核心原因

原因 1:平衡词表大小与覆盖率

  • 纯字符:词表太小(~200),但序列太长
  • 纯词:词表太大(>50 万),且会漏词
  • 子词:两者之间最优

原因 2:处理未知词(OOV)

模型在训练时没见过 Transformerology 这个词。 但通过 BPE 可以拆成 Transformer + ology → 模型认识!

原因 3:影响模型性能

分词质量直接影响:

影响方面说明
语义保留unhappiness 分开仍有意义
计算效率太细的分词让序列变长,注意力 O(n²) 更贵
多语言能力SentencePiece 才能良好处理非空格语言

10. 一张图总结:从文本到 Token 的完整流程

flowchart TD A["原始文本<br/>'ChatGPT is great'"] --> B["分词器<br/>BPE/WordPiece/SentencePiece"] B --> C["Token 序列<br/>['Chat', 'G', 'PT', ' is', ' great']"] C --> D["词表映射<br/>每个 Token → 数字 ID"] D --> E["[234, 567, 123, 890, 456]"] E --> F["模型处理"]

写在最后

分词看起来是个“预处理”小步骤,但它决定了:

  • 模型能“看到”什么
  • 模型训练有多快
  • 模型能否支持多种语言
  • 模型能否处理没见过的新词

一个好的分词器,是 LLM 的第一道门槛。

场景推荐
英语为主(GPT)BPE
多语言/中文(LLaMA)SentencePiece
经典 BERTWordPiece