如果你读过 BERT 的论文,一定见过这样的分词结果:["token", "##ization"]。那个 ## 前缀是 WordPiece 的标志性记号。它和 BPE 系出同源,但在一个关键决策上做出了不同的选择。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
在BPE 理论篇中,我们讲了 BPE 的核心逻辑:反复合并频率最高的相邻符号对。在BPE实践篇中,我们用 Python 从零实现了它。这篇文章聚焦 WordPiece—BERT、DistilBERT、ELECTRA 等模型背后的分词算法。你会发现,它和 BPE 的差异不大,但那一点差异背后的思想值得深究。
一、从 BPE 到 WordPiece:差异在哪
1.1 同样是合并,凭什么选?
BPE 和 WordPiece 的整体框架几乎一样:都是从小词表出发,逐步合并子词,直到词表达到目标大小。
-
核心区别在于一个决策点:每轮合并选哪个 pair?
- BPE:选出现频率最高的 pair
- WordPiece:选使语言模型似然提升最大的 pair
-
这是什么意思?
BPE 只看"谁出现得多就合并谁",而 WordPiece 更精明,它会问把这两个子词合并后,整个训练语料被"解释"得更好了吗?
-
用一个具体场景来感受这个区别。假设语料中有两个候选 pair:
pair 共现次数 各自频率 (e, r)100 次 e出现 5000 次,r出现 3000 次(q, u)50 次 q出现 52 次,u出现 2000 次
BPE 的选择很简单:
er共现 100 次 >qu共现 50 次,选er。 但 WordPiece 会多想一步:
e和r都是极高频字符,它们在语料中到处出现,即使完全随机排列也会频繁相邻。100 次共现只是"基础噪音",谈不上强关联。而q虽然只出现了 52 次,但其中 50 次后面都跟着u,命中率高达 96%。这两个字符之间有极强的依赖关系。WordPiece 会优先合并qu。 -
简单总结:BPE 偏爱高频组合,WordPiece 偏爱强关联组合。
1.2 似然公式
WordPiece 评估每个候选 pair
(
x
,
y
)
(x, y)
(x,y) 的合并收益时,用的是以下公式:
score
(
x
,
y
)
=
P
(
x
y
)
P
(
x
)
⋅
P
(
y
)
\text{score}(x, y) = \frac{P(xy)}{P(x) \cdot P(y)}
score(x,y)=P(x)⋅P(y)P(xy)
其中,
P
(
x
y
)
P(xy)
P(xy) 是子词
x
y
xy
xy(合并后)在语料中的出现概率,
P
(
x
)
P(x)
P(x) 和
P
(
y
)
P(y)
P(y) 分别是子词
x
x
x 和
y
y
y 单独出现的概率。
-
这个公式的本质就是点互信息(Pointwise Mutual Information, PMI):衡量两个符号共现的程度是否超过了"各自独立出现"的预期。
-
对比 BPE 的选择标准:
BPE: score ( x , y ) = count ( x , y ) \text{BPE: } \text{score}(x, y) = \text{count}(x, y) BPE: score(x,y)=count(x,y)
BPE 只看绝对频率,而 WordPiece 看的是相对关联强度。用一个类比来说:BPE 选"出场次数最多的搭档",WordPiece 选"默契度最高的搭档"。出场多未必默契,出场少未必不默契。
1.3 用公式验证直觉
回到 1.1 的例子,用 PMI 公式算一下。假设语料总 pair 数为 100 , 000 100,000 100,000:
| pair | 共现次数 | P ( x y ) P(xy) P(xy) | P ( x ) P(x) P(x) | P ( y ) P(y) P(y) |
|---|---|---|---|---|
(e, r) | 100 | 0.001 | 0.05 (e 5000次) | 0.03 (r 3000次) |
(q, u) | 50 | 0.0005 | 0.00052 (q 52次) | 0.02 (u 2000次) |
score ( e , r ) = 0.001 0.05 × 0.03 = 0.001 0.0015 ≈ 0.67 \text{score}(e, r) = \frac{0.001}{0.05 \times 0.03} = \frac{0.001}{0.0015} \approx 0.67 score(e,r)=0.05×0.030.001=0.00150.001≈0.67
score ( q , u ) = 0.0005 0.00052 × 0.02 = 0.0005 0.0000104 ≈ 48.1 \text{score}(q, u) = \frac{0.0005}{0.00052 \times 0.02} = \frac{0.0005}{0.0000104} \approx 48.1 score(q,u)=0.00052×0.020.0005=0.00001040.0005≈48.1
根据PMI 分数可以发现,(q, u) 约 48,(e, r) 不到 1,差了将近两个数量级。公式精确地量化了 1.1 中的直觉:q 和 u 之间的关联强度远远超过 e 和 r。
-
两种算法的选择
BPE: score(e,r) = 100 > score(q,u) = 50 → 选 (e, r) WordPiece: score(e,r) = 0.67 < score(q,u) = 48.1 → 选 (q, u) 可以看到,对于同样的数据,两种算法选出了不同的合并对象。
er虽然出现得多,但e和r本身都是高频字符,它们共现 100 次并不意外。而st虽然出现得少,但相对于s和t各自的频率来说,它们共现的比例更高。说明s和t之间的关联更强。
二、训练过程详解
2.1 整体流程
WordPiece 的训练流程和 BPE 结构相同,只是评分函数不同:
- 初始化:将训练语料中的每个词拆成字符序列。词首字符保持原样,非词首字符加
##前缀 - 迭代合并:每轮计算所有相邻 pair 的 PMI 分数,合并分数最高的 pair
- 终止:词表达到目标大小时停止
2.2 ## 前缀的作用
## 是 WordPiece 最显眼的标志。它的作用是区分词首和非词首子词。
-
以单词
tokenization为例,初始化时拆为:t ##o ##k ##e ##n ##i ##z ##a ##t ##i ##o ##n 只有第一个字符
t没有前缀,其余都带##。这意味着token和##token是两个不同的子词:前者出现在词首,后者出现在词中。 -
为什么要这样设计?
因为同一个字符串在词首和词中的语义角色不同。比如
re在return的开头是"再次"的前缀,在care的结尾只是词根的一部分。同样两个字母,语义角色截然不同。WordPiece 通过##把两者区分开:词首的re和词中的##re是两个不同的 token,模型可以分别学习它们的语义。BPE 则不做这种区分,所有位置的re共享同一个 token。 -
这个设计还带来一个实用好处:解码时能无损还原原始文本。看到
##就知道要和前一个 token 拼接(不加空格),没有##就知道是一个新词的开头(前面加空格)。["token", "##ization"] → "tokenization" ["un", "##happy"] → "unhappy" ["the", "quick", "fox"] → "the quick fox"
2.3 训练示例
我们使用和 BPE 理论篇相同的语料走一遍 WordPiece 的训练过程:
| corpus | 次数 |
|---|---|
| “low” | 5 |
| “lowest” | 2 |
| “newer” | 6 |
| “Wider” | 3 |

(1) 第一步:初始化
将每个词拆成字符序列,非词首加 ##:
| Corpus | Split |
|---|---|
| low | l, ##o, ##w |
| lowest | l, ##o, ##w, ##e, ##s, ##t |
| newer | n, ##e, ##w, ##e, ##r |
| wider | w, ##i, ##d, ##e, ##r |
得到初始词表:{l, n, w, ##o, ##w, ##e, ##s, ##t, ##r, ##i, ##d}(共11 个子词)
(2) 第二步:统计所有相邻 pair 的 PMI 分数
- 先统计每个子词的出现次数(把每个词的字符序列乘以词频后累加):
| 子词 | 出现次数 | 来源 |
|---|---|---|
##e | 17 | lowest×2 + newer×6×2 + wider×3 |
##w | 13 | low×5 + lowest×2 + newer×6 |
##r | 9 | newer×6 + wider×3 |
l | 7 | low×5 + lowest×2 |
##o | 7 | low×5 + lowest×2 |
n | 6 | newer×6 |
w | 3 | wider×3 |
##i | 3 | wider×3 |
##d | 3 | wider×3 |
##s | 2 | lowest×2 |
##t | 2 | lowest×2 |
子词总出现次数: 17 + 13 + 9 + 7 + 7 + 6 + 3 + 3 + 3 + 2 + 2 = 72 17+13+9+7+7+6+3+3+3+2+2 = 72 17+13+9+7+7+6+3+3+3+2+2=72
-
再统计所有相邻 pair 的共现次数,然后用 PMI 公式 P ( x y ) P ( x ) ⋅ P ( y ) \frac{P(xy)}{P(x) \cdot P(y)} P(x)⋅P(y)P(xy) 计算分数
pair 共现次数 P ( x y ) P(xy) P(xy) P ( x ) ⋅ P ( y ) P(x) \cdot P(y) P(x)⋅P(y) PMI 分数 (##s, ##t)2 2/72 (2/72)×(2/72) 36.00 (w, ##i)3 3/72 (3/72)×(3/72) 24.00 (##i, ##d)3 3/72 (3/72)×(3/72) 24.00 (l, ##o)7 7/72 (7/72)×(7/72) 10.29 (##o, ##w)7 7/72 (7/72)×(13/72) 5.54 (##e, ##r)9 9/72 (17/72)×(9/72) 4.24 (##w, ##e)8 8/72 (13/72)×(17/72) 2.61 重点看两组对比:
(##e, ##r)共现 9 次(频率最高),但 PMI 只有 4.24。 因为##e出现了 17 次、##r出现了 9 次,两者都是高频子词,即使随机排列也会频繁相邻——9 次共现并不意外。(##s, ##t)共现仅 2 次,PMI 却高达 36.00。 因为##s和##t各自只出现 2 次,而这 2 次全都相邻(都来自lowest),命中率 100%——关联性极强。
-
如果用 BPE(纯频率),第一轮会选
(##e, ##r)(共现 9 次最高)。但 WordPiece 选 PMI 最高的(##s, ##t)——一个共现仅 2 次的 pair。
实践篇中的训练器代码会完整跑出每一轮的合并结果,这里聚焦第一轮的 PMI 计算过程,展示 PMI 评分如何改变合并优先级。
(3) 第三步:合并PMI最高的pair
(##s, ##t) 得分 36.00,合并为 ##st:
词表从 11 个增长到 12 个。然后重新统计所有 pair 的 PMI,进入下一轮。后续几轮的合并顺序(实践篇中有完整运行结果)大致为:(w, ##i) → (##i, ##d) → (l, ##o) → …
-
注意:和 BPE 对比,如果用纯频率选择,前几轮会合并
(##e, ##r)、(##w, ##e)这些高频 pair。而 WordPiece 优先合并的是(##s, ##t)、(w, ##i)这些低频但强关联的 pair——合并顺序完全不同。 -
之后,继续迭代。每轮选 PMI 最高的 pair 合并,直到词表达到目标大小。
-
另外,需要注意,具体的合并顺序取决于语料的统计分布。在不同的语料上,BPE 和 WordPiece 可能产生相同的合并顺序,也可能截然不同——差异在大规模语料上更明显。
三、编码:贪心最长匹配
3.1 和 BPE 编码的本质区别
训练策略的差异之外,WordPiece 和 BPE 在编码新文本时的策略也完全不同。
-
BPE 编码:按训练时的合并规则顺序逐条"重放"。先把文本拆成字节,然后依次应用规则 1、规则 2、规则 3…直到没有可应用的规则为止。编码时需要的是一张有序的合并规则表。
-
WordPiece 编码:完全不需要合并历史,而是用贪心最长匹配(Greedy Longest-Match-First) 从左到右扫描。编码时需要的只是一个词表集合(无需顺序)。
这是一个设计上的简化:BPE 的编码依赖合并规则的顺序(规则 1 必须先于规则 2 执行),而 WordPiece 的编码只需要知道"哪些子词在词表里",不关心它们是怎么被合并出来的。
(1) 为什么 WordPiece 不需要顺序
因为两者的编码算法根本不同。
-
BPE 编码是重放训练过程:从最细粒度(字节)出发,按训练时的合并顺序逐条尝试。如果先应用规则 3 再应用规则 1,结果可能完全不同——顺序就是一切。打个比方,BPE 像是按照菜谱一步步做菜,步骤顺序不能乱。
-
WordPiece 编码是查字典:拿到一个词,从左到右,每次在词表里找最长的匹配项。它不关心
token这个子词是第几轮合并产生的,只关心"词表里有没有token这个条目"。就像你查字典不需要知道这个词是什么时候被收录的,只需要知道它在不在字典里。 -
所以 BPE 训练后保存的是一张有序的合并规则表(顺序不能丢),WordPiece 训练后只需要保存词表集合(顺序无所谓)。
3.2 编码算法
-
WordPiece 对单个词的编码步骤:
- 从词的第一个字符开始,尝试匹配词表中最长的子词
- 如果当前子串不在词表中,缩短一个字符,再试
- 匹配成功后,移动到下一个位置,对剩余部分加
##前缀后重复 - 如果缩到单个字符仍不在词表中,返回
[UNK]
-
以
tokenization为例,假设词表中有token、##ization、##ize、##ing等子词:
3.3 和 BPE 编码的对比
用同一个词 tokenization,对比两种编码方式:
| BPE 编码 | WordPiece 编码 | |
|---|---|---|
| 策略 | 按合并规则顺序逐条重放 | 贪心最长匹配,从长到短尝试 |
| 需要的信息 | 合并规则表(有序) | 词表集合(无需顺序) |
| 过程 | 先拆成字节,逐步合并 | 先尝试最长,逐步缩短 |
| 子词标记 | 无特殊前缀 | 非词首子词加 ## |
| 结果 | ["token", "ization"] | ["token", "##ization"] |
| OOV 处理 | 退化为字节,永远不会 UNK | 可能返回 [UNK] |
最后一行值得注意。假设你要编码 "café",而词表中没有 é 也没有 ##é:
- BPE:
é的 UTF-8 编码是两个字节[195, 169],BPE 从字节出发,最坏退化为这两个单字节 token,虽然丑但不会丢信息 - WordPiece:贪心匹配走到
é时,从长到短尝试都找不到包含它的子词,直接放弃整个词,返回[UNK]。这样caf的信息也一起丢了
BPE 永远不会 OOV(因为任何字节值都在初始词表中),而 WordPiece 遇到词表外的字符就只能投降。实践中这种情况极少(BERT 的词表覆盖了绝大多数 Unicode 字符),但在处理生僻符号、特殊 emoji 或小语种文字时,这个差异就会显现。
四、WordPiece 在 BERT 中的角色
4.1 BERT 的词表
BERT 使用了一个
30
,
522
30,522
30,522大小的 WordPiece 词表(bert-base-uncased)。这个词表是在 BooksCorpus(约 8 亿词的未出版书籍)和英文 Wikipedia(约 25 亿词,去除了列表、表格和标题)上训练的,合计约 33 亿词的纯英文语料。
这意味着 BERT 的词表天然偏向英文。虽然 Wikipedia 中夹杂少量非英文字符(人名、地名等),但中文、日文等语言的子词合并次数极少,压缩率很低。这也是为什么后来 Google 又推出了多语言版本 bert-base-multilingual——在 104 种语言的 Wikipedia 上重新训练词表,让各语言都能获得合理的子词覆盖。
(1) 词表的具体组成
-
5 个特殊 token
token 作用 [PAD]填充短序列,使同一 batch 内所有序列等长 [UNK]代替词表中找不到的未知字符 [CLS]插在序列开头,其最终隐藏状态用作整句的分类向量 [SEP]分隔两个句子(如问答任务中的问题和段落),也标记序列结尾 [MASK]预训练时随机遮盖 token,让模型学习"完形填空" -
约 1,000 个无 ## 前缀的单字符(词首字符)
-
约 29,000 个子词(包括完整词和
##前缀的词内片段)
(2) BERT 的分词流程
- BasicTokenizer:按空格和标点拆分,转小写(uncased 版本),处理 Unicode
- WordPieceTokenizer:对每个词做贪心最长匹配
- 在序列首尾添加
[CLS]和[SEP]特殊 token
4.2 为什么 BERT 选了 WordPiece
BERT 诞生于 2018 年的 Google,而 WordPiece 早在 2016 年就在 Google 内部的机器翻译系统中使用了。选择 WordPiece 有历史和工程上的原因:
- 内部惯性:Google 内部已有成熟的 WordPiece 训练和编码工具
- PMI 的理论优势:基于互信息的合并在当时被认为比纯频率更合理
##前缀的便利:BERT 需要做掩码语言模型(MLM),能区分词首和词中位置有助于模型学习
但在 BERT 之后,大部分新模型都转向了 BPE。简单来说,BPE 的工程简洁性和字节级兜底能力在 LLM 时代更有优势。
五、总结
5.1 核心要点
- WordPiece 和 BPE 同属"自底向上合并"家族,框架相同,区别在于合并时的评分函数
- BPE 选频率最高的 pair,WordPiece 选 PMI 最高的 pair——前者偏爱高频组合,后者偏爱强关联组合
- 编码策略不同:BPE 重放合并规则,WordPiece 用贪心最长匹配。WordPiece 编码只需要词表集合,不需要知道合并顺序
##前缀区分词首和词中子词,让编码-解码可以无损往返- WordPiece 可能产生
[UNK],而 BPE 从字节出发永远不会 OOV
5.2 速查对比表
| 维度 | BPE | WordPiece |
|---|---|---|
| 合并依据 | 频率最高 | PMI 最高 |
| 编码方式 | 合并规则重放 | 贪心最长匹配 |
| 编码所需 | 有序合并规则表 | 词表集合 |
| 子词标记 | 无 | ## 前缀 |
| OOV 处理 | 退化为字节 | 返回 [UNK] |
| 代表模型 | GPT、LLaMA | BERT、DistilBERT |
WordPiece 在 BERT 时代是主流选择,理解它有助于你读懂 BERT 系列模型的分词逻辑。但如果你从零搭建新项目,当下的主流选择是 BPE。
332



被折叠的 条评论
为什么被折叠?



