自然语言处理使用的WordPiece分词算法详解
WordPiece 是一种广泛应用于自然语言处理(NLP)的子词分词算法,由Google 于 2016 在 BERT 模型中首次引入,旨在解决长尾词汇(如罕见词、复合词)的语义表示问题,同时平衡词表大小与语义覆盖率。现已成为 BERT、MPNet 等主流 Transformer 模型的核心分词技术。
WordPiece 广泛应用于现代基于 Transformer 的预训练语言模型,如 BERT、DistilBERT、Electra、XLNet 等。它的核心思想是将单词拆分成更小的、有意义的子单元(subword units)。
作为 Sentence Transformers 等现代模型的基础,WordPiece 在处理复杂语言结构和平衡计算效率方面展现出不可替代的价值,是当前 NLP 工程实践的基石技术。
核心设计思想
WordPiece 的核心目标:平衡词汇表大小与未登记词处理能力
- 解决词汇表外问题: 传统基于单词的分词方法会遇到训练集中未出现过的单词(OOV),导致模型无法处理。WordPiece 通过将未知单词拆分成已知的子词来解决这个问题。
- 平衡词表大小与粒度: 纯字符分词词表极小但丢失了单词内部的结构信息;纯单词分词词表巨大且稀疏。WordPiece 找到了一种折中,词表大小可控(通常在 10k 到 100k 之间),同时能有效表示常见单词和罕见/未知单词。
- 捕捉单词内部结构: 许多单词由词根、前缀、后缀组成(如 “unhappiness” -> “un”, “happi”, “ness”)。WordPiece 能学习到这些有语言学意义的片段。
1 | graph LR |
与传统方法的对比
| 分词方法 | 示例输入 | 输出 | 问题 |
|---|---|---|---|
| 单词级 | "unhappiness" |
["unhappiness"] |
词汇表爆炸(百万级) |
| 字符级 | "unhappiness" |
["u","n","h","a",...] |
语义丢失严重 |
| WordPiece | "unhappiness" |
["un", "##happiness"] |
理想平衡点 |
算法工作原理
WordPiece 基于一个简单的策略:贪婪地合并最频繁共现的字符对。它从一个基础字符集开始,逐步构建词表。
算法步骤
从训练阶段到构建词表:初始化 > 计算频率 > 合并高频 > 子词单元 > 最终词汇表
1 | flowchart TB |
初始化:
将所有文本拆分成最小单元(通常是字符,包括字母、数字、标点符号等)
如:
"low"→["l","o","w"]。定义目标词表大小
V(一个预设的超参数)。初始词表就是所有基础字符 + 一些特殊的标记(如
[CLS],[SEP],[UNK],[PAD],[MASK])。
计算频率:
将训练语料库中的所有单词拆分成字符序列(单词前后通常加上特殊边界符号,如
_或##前缀)。例如,单词 “word” 初始表示为
["w", "o", "r", "d"]。统计所有可能的相邻字符对在训练语料库中出现的频率。
选择并合并:
- 在所有相邻字符对中,选择出现频率最高的那一对(例如,
("e", "s")的频率非常高)。 - 将这对字符合并成一个新的子词单元(例如,
"es"),并将这个新单元加入到词表中。 - 在语料库中,所有出现这个字符对的地方都用这个新的子词单元替换(例如,单词 “words” 原本是
["w", "o", "r", "d", "s"],合并后变成["w", "o", "r", "ds"]或更可能是["word", "s"]如果 “word” 已经是一个子词了)。
- 在所有相邻字符对中,选择出现频率最高的那一对(例如,
重复迭代:
- 基于更新后的分词结果(包含新的子词单元),重新计算所有相邻单元对(现在可能是字符或子词)的频率。
- 再次选择频率最高的对进行合并,并将其加入词表。
- 重复这个过程,直到达到以下两个条件之一:
- 达到预设的目标词表大小
V。 - 在合并任何一对都无法显著提高语言模型的似然概率(这是 WordPiece 原始论文中提到的目标函数,但实践中频率作为代理也很常用且有效)。
- 达到预设的目标词表大小
关键点(训练阶段)
- 贪婪性: 每次只合并当前最优(频率最高)的一对,不考虑全局最优。虽然可能不是最优解,但计算高效,效果很好。
- 频率驱动: 合并决策主要基于共现频率。高频组合(如英语的
ing,ed,un,tion)会被优先合并成子词。 - 生成子词词表: 最终输出的是一个包含基础字符、常用单词、常用词缀(前缀、后缀、词根)和常用字符组合的子词集合。
分词阶段(应用阶段)
一旦训练好 WordPiece 词表,就可以用它来对新的文本进行分词:
- 初始化: 将待分词的句子拆分成单词(空格通常是基本分隔符)。
- 处理每个单词:
- 从单词的开头开始,尝试匹配词表中最长的可能子词。
- 如果找到了匹配的子词,将其作为一个 token 切分出来。
- 对于单词剩余的部分,重复步骤 2。
- 如果剩余部分不能匹配词表中的任何子词,但不是空字符串:
- 如果该部分包含未知字符(不在基础字符集中),通常用
[UNK]表示。 - 否则,将其拆分成尽可能长的已知子词序列(通常是字符级),并给除第一个子词外的其他子词添加特殊前缀(如
##)以表明它是单词的一部分而不是开头。
- 如果该部分包含未知字符(不在基础字符集中),通常用
- 添加特殊标记: 根据模型要求,在句子开头添加
[CLS],在句子结尾添加[SEP],在需要的地方添加[PAD]。
Python代码实现示例:
1 | """ |
Java代码实现示例:
1 | package com.example.demo.split; |
输出结果:[un, happ, [UNK], ##ness]
分词示例
分词示例
假设词表包含:["un", "##able", "##ing", "happ", "happy", "ness", "!"](以及基础字符和特殊标记)
- 单词 “unhappiness”:
- 最长匹配:
"un"(匹配) -> tokens:["un"] - 剩余部分:
"happiness" - 最长匹配:
"happ"(匹配) -> tokens:["un", "happ"] - 剩余部分:
"iness" - 最长匹配:
"##iness"不在词表。尝试更短:"##ine"不在,"##in"不在… 最终匹配单个字符失败(假设##i不在词表)。那么退而求其次:- 最长匹配剩余部分:
"ness"(匹配,但需加##表示非开头) -> tokens:["un", "happ", "##ness"] - 剩余部分
"i"-> 作为单独字符"##i"(如果##i在基础词表中) 或[UNK]。
- 最长匹配剩余部分:
- 更常见的实际情况是词表包含
"happiness"或"##iness"会被合并成更合理的片段。 - 理想情况下,可能会被分成
["un", "happi", "##ness"](如果"happi"在词表中)。
- 最长匹配:
- 单词 “jumping”:
- 最长匹配:
"jump"(假设在词表中) -> tokens:["jump"] - 剩余部分:
"ing" - 最长匹配:
"##ing"(匹配) -> tokens:["jump", "##ing"]
- 最长匹配:
- 单词 “apple”:
- 如果
"apple"在词表中,直接分成["apple"]。 - 否则,可能分成
["app", "##le"]或["a", "##pp", "##le"]等,取决于词表内容。
- 如果
- 单词 “😊” (表情符号):
- 如果该字符不在基础字符集和词表中,会被分成
[UNK]。
- 如果该字符不在基础字符集和词表中,会被分成
多语言处理示例
| 语言 | 输入文本 | WordPiece 输出 | 优势体现 |
|---|---|---|---|
| 英语 | "Transformer" |
["Transform", "##er"] |
保留词根语义 |
| 中文 | "自然语言处理" |
["自","然","语","言","处","理"] |
无需空格,按字拆分 |
| 德语 | "Hauptbahnhof" |
["Haupt", "##bahn", "##hof"] |
分解复合词(主火车站) |
| 日语 | "ディープラーニング" |
["ディープ","##ラーニング"] |
处理片假名复合词 |
优缺点分析
优点
- 语义泛化:子词组合可覆盖新词(如 “blogging” → “blog” + “##ging”)。
- 训练稳定性:通过语言模型优化,避免 BPE 的纯频率偏差。
缺点
- 计算复杂度高:合并阶段需遍历所有字符对,时间复杂度为 O(N2)。
- 依赖语言模型假设:可能过度依赖训练语料的统计特性,忽略语法结构。
优点
- 词汇表效率
- 词汇表高效压缩,规模仅为全词模型的 1/10~1/100,30k 词汇表可覆盖 95%+ 的常见文本(英语)
- 对比:单词级需 100k+ 才能达到同等覆盖率
- 未登记词处理
- 能分解未见过的单词(如
"tokenization"→["token","##ization"]) - 对比:单词级遇到新词直接返回
[UNK]
- 能分解未见过的单词(如
- 跨语言一致性
- 同一套算法处理所有语言
- 特别适合多语言模型(如 mBERT、XLM-R)
- 语义保留能力
- 子词单元常对应语素(如
"##ly"表副词,"##ness"表名词性)
- 子词单元常对应语素(如
缺点
前缀标记问题
"##"符号增加额外负载(约 5-10% token 增长)
非确定性切分
1
2
3# 相同词在不同位置拆分不同
"play" → ["play"] # 作为独立词
"playing" → ["play", "##ing"] # 作为复合词部分子词歧义
"pain"可能被拆为["pa","##in"]失去 “疼痛” 语义
分词方法对比
| 方法 | 合并依据 | 典型应用 | 代表模型 |
|---|---|---|---|
| WordPiece | 最大化语言模型似然 | BERT、ALBERT | 降低词表至 3 万级 |
| BPE | 最高频字符对 | GPT、T5 | 平衡词表与覆盖率 |
| Unigram | 最小化损失函数 | T5、mBART | 动态优化子词选择 |
实际应用场景
现代 NLP 模型依赖
| 模型家族 | 使用分词器 | 典型词汇表大小 |
|---|---|---|
| BERT | WordPiece | 30,522 |
| MPNet | WordPiece | 30,000 |
| ELECTRA | WordPiece | 35,000 |
部署优化技巧
1 | from transformers import AutoTokenizer |
自定义词汇表训练
1 | # 使用Hugging Face工具训练自定义WordPiece |
💡 历史演进与变体
- 原始 WordPiece (2016)
- 基于频率的贪婪合并
- BPE (Byte Pair Encoding)
- WordPiece 前身,未使用 ## 标记
- Unigram LM (2018)
- 基于概率模型的分词,被 T5 采用
- SentencePiece (2018)
- Google 改进版:直接处理原始字节,无需预分词
总结:为什么 WordPiece 主导 NLP
1 | pie |
。
以下是其核心原理、训练流程及实际应用的详细解析:
一、核心思想与目标
子词粒度优化
WordPiece 将单词拆分为语义连贯的子词单元(如 “unhappy” → “un” + “##happy”),而非传统的全词或字符级切分。其核心目标是:- 减少词表规模:避免覆盖所有罕见词,降低存储与计算成本。
- 支持 OOV(未登记词)处理:通过子词组合覆盖未知词汇(如 “huggingface” → “hugging” + “##face”)。
- 保留语义完整性:子词通常携带独立语义(如 “##ing” 表示进行时态)。
与 BPE 的区别
合并策略
:BPE 基于字符对的
频率统计
选择高频组合,而 WordPiece 基于
语言模型似然概率
选择合并对(最大化文本生成概率)
6
8
。
分词标记:BERT 使用
##前缀标识非首子词(如 “##ing”),而 BPE 无此标记。
二、训练流程(以 BERT 为例)
1. 初始化阶段
- 输入语料:大规模文本(如维基百科、书籍)。
- 预处理:将单词拆分为字符序列,并添加特殊标记(如
<w>表示词尾)。 - 初始词表:包含所有单字符及特殊符号(如
h,u,g,<w>)。
2. 构建初始语言模型
- 统计字符频率:计算每个字符及
<w>的出现次数。 - 训练 Unigram 模型:基于字符频率构建简单语言模型,计算每个字符的似然概率。
3. 迭代合并子词对
- 候选对生成:遍历语料库,统计所有相邻字符对的联合频率(如
h+u→hu)。 - 计算合并分数:
使用互信息公式评估合并对的增益:
1其中,P(merged) 为合并后的概率,P(unmerged) 为未合并时的概率。
选择分数最高的合并对(如##u+##g→##ug),将其加入词表。 - 更新语料库:将合并对替换为新子词,重新统计频率。
- 重复迭代:直至词表达到预设大小(如 BERT 的 30,522 个词元)。
4. 输出最终词表
- 包含内容:高频子词(如
##ing)、低频字符组合(如##z)、罕见字符(如ð)。
三、分词阶段(应用流程)
1. 贪心最长匹配算法
- 输入单词:如 “lowest”。
- 从左到右扫描:匹配词表中最长的子词(如
low→ 匹配成功)。 - 剩余部分递归处理:对剩余字符
est重复匹配(##e→##s→##t)。 - 最终输出:
[low, ##e, ##s, ##t](@ref)。
2. BERT 的特殊处理
- 前缀标记:非首子词添加
##(如##happiness),避免与完整词混淆。 - OOV 处理:若子词不在词表中,拆分为更小的已知子词(如
mug→m+##u+##g)。
四、实际应用案例
| 场景 | 示例 | 优势 |
|---|---|---|
| BERT 预训练 | 将 “unhappiness” 拆分为 ["un", "##happiness"],提升模型对否定语义的理解。 |
减少词表至 3 万级,降低显存占用。 |
| 机器翻译 | 德语复合词 “Donaudampfschifffahrtsgesellschaft” → ["Donau", "##dampf", "##schiff"]。 |
处理长词时保持语义连贯性。 |
| 中文分词增强 | 专有名词 “BACE1” → ["BA", "##CE", "##1"],解决传统分词工具的局限性。 |
提升低频术语的表示能力。 |
五、优缺点分析
六、
七、代码实现(简化版)
1 | from collections import defaultdict |
八、扩展与优化
- 动态词表:根据任务需求动态调整词表大小(如 Longformer 的扩展词表)。
- 多语言适配:为不同语言设计独立的子词规则(如 mBERT 支持 104 种语言)。
- 混合分词:结合 BPE 与 WordPiece,平衡效率与语义(如 XLM-R)。
通过合理应用 WordPiece,开发者能在模型性能与计算资源间取得平衡,尤其适合处理大规模多语言文本场景。
自然语言处理使用的WordPiece分词算法详解
http://blog.gxitsky.com/2026/02/26/AI-LangChain-023-Text-Splitter-WordPiece/

