自然语言处理使用的WordPiece分词算法详解
WordPiece 是一种广泛应用于自然语言处理(NLP)的子词分词算法,由Google 于 2016 在 BERT 模型中首次引入,旨在解决长尾词汇(如罕见词、复合词)的语义表示问题,同时平衡词表大小与语义覆盖率。现已成为 BERT、MPNet 等主流 Transformer 模型的核心分词技术。
WordPiece 广泛应用于现代基于 Transformer 的预训练语言模型,如 BERT、DistilBERT、Electra、XLNet 等。它的核心思想是将单词拆分成更小的、有意义的子单元(subword units)。
作为 Sentence Transformers 等现代模型的基础,WordPiece 在处理复杂语言结构和平衡计算效率方面展现出不可替代的价值,是当前 NLP 工程实践的基石技术。
术语解释:
似然:统计学的专业述语,英文是Likelihood,指可能性、顺眼程度、合理性。在已知数据的情况下,衡量某个原因或参数的合理性(有多靠谱)。是统计学中用于从数据反推模型参数的核心概念
核心思想
WordPiece 基于一种贪心的高频符号合并策略,其本质是一种数据压缩或词表学习算法。
它的底层逻辑是:两个子词如果经常一起出现,那么将它们合并成一个新的子词,可以帮助减少文本序列的长度,同时更好地捕捉语言的规律。
WordPiece 的核心目标:平衡词汇表大小与未登记词处理能力
- 解决词汇表外问题: 传统基于单词的分词方法会遇到训练集中未出现过的单词(OOV),导致模型无法处理。WordPiece 通过将未知单词拆分成已知的子词来解决这个问题。
- 平衡词表大小与粒度: 纯字符分词词表极小但丢失了单词内部的结构信息;纯单词分词词表巨大且稀疏。WordPiece 找到了一种折中,词表大小可控(通常在 10k 到 100k 之间),同时能有效表示常见单词和罕见/未知单词。
- 捕捉单词内部结构: 许多单词由词根、前缀、后缀组成(如 “unhappiness” -> “un”, “happi”, “ness”)。WordPiece 能学习到这些有语言学意义的片段。
与传统方法的对比
| 分词方法 | 示例输入 | 输出 | 问题 |
|---|---|---|---|
| 单词级 | "unhappiness" |
["unhappiness"] |
词汇表爆炸(百万级) |
| 字符级 | "unhappiness" |
["u","n","h","a",...] |
语义丢失严重 |
| WordPiece | "unhappiness" |
["un", "##happiness"] |
理想平衡点 |
算法工作原理
WordPiece 基于一个简单的策略:贪婪地合并最频繁共现的字符对。它从一个基础字符集开始,逐步构建词表。
算法步骤
从训练阶段到构建词表:初始化 > 计算频率 > 合并高频 > 子词单元 > 最终词汇表
flowchart TB
subgraph 训练流程
A[原始语料] --> B[初始化词汇表
(所有字符+常见词)]
B --> C[统计所有相邻子词对频率]
C --> D{合并最高频子词对?}
D -->|是| E[创建新子词单元]
E --> C
D -->|达到目标大小| F[最终词汇表]
end
第一步:初始化词表
算法的起点是一个极其微小的词表,通常包含:
- 将所有文本拆分成最小单元(通常是字符,包括字母、中文汉字、数字、标点符号等)。
- 定义目标词表大小
V(一个预设的超参数)。 - 初始词表就是所有基础字符 + 一些特殊的标记(如
[CLS],[SEP],[UNK],[PAD],[MASK])。 - 必要的标点符号。
此时,任何一个单词都可以被拆分成单个字符来表示(例如, “playing” 被表示为 p l a y i n g)。
第二步:预分词
在进行子词合并之前,通常会用简单的空格将文本分割成单词(对于英文等语言)。这样做的目的是将合并的范围限制在单词内部,防止算法跨单词边界学习到无意义的连接(例如将两个单词连在一起)。最终的分词结果会由这些单词内部的子词拼接而成。
第三步:选择与合并
这是一个循环过程,直到词表大小达到预设的目标值为止:
- 统计相邻对:遍历整个语料库,统计当前词表状态下,所有相邻的两个子词共同出现的次数。
- 计算合并得分:这是 WordPiece 与 BPE 最大的不同点。
- BPE 单纯统计相邻对出现的频率,频率最高的直接合并。
- WordPiece 不只看频率,它计算的是这两个子词组合在一起后,对整体语言模型似然度的提升程度。通常,它会计算一个 互信息 或 似然增益 的分数。公式大致思想是:比较两个子词作为整体出现的概率,除以它们各自独立出现的概率。
- 如果
p(ab)远大于p(a) * p(b)(即a和b在一起比它们随机相遇的概率大得多),说明这两个字符具有强烈的绑定关系,应该合并。 - 这个机制能避免合并那些虽然常见但独立性很强的高频符号(比如
e和s在英语中可能只是因为单词复数而经常相遇,但 WordPiece 通过概率增益评估,可能会更倾向于合并i n g这种语义块)。
- 如果
- 合并:选择得分最高的相邻对,将其合并成一个新的子词单元,并加入词表。
- 重新切分:语料库中的所有单词都会根据更新后的词表重新进行最长匹配切分,然后回到第一步,进行下一轮统计。
第四步:停止条件
当词表大小达到预设的阈值(例如 BERT 使用 3 万左右),或者合并带来的增益低于某个阈值时,训练停止。
最终输出的是一个包含基础字符、常用单词、常用词缀(前缀、后缀、词根)和常用字符组合的子词集合。
应用阶段
一旦训练好 WordPiece 词表,就可以用它来对新的文本进行分词。在对新文本进行分词时,它采用最长匹配优先(Maximum Matching)或前向最大匹配的策略:
从左到右扫描一个单词。
从该单词的开头开始,在词表中查找尽可能长的子词。
如果找到了,就在此处切分,然后从切分点的下一个字符开始,继续重复这个过程。
如果遇到词表中没有的单个字符,则使用特殊的
[UNK]标记代替。初始化: 将待分词的句子拆分成单词(空格通常是基本分隔符)。
处理每个单词:
- 从左到右扫描一个单词。
- 从单词的开头开始,尝试匹配词表中最长的可能子词。
- 如果找到了匹配的子词,将其作为一个 token 切分出来。
- 对于单词剩余的部分,继续重复第二步。
- 如果剩余部分不能匹配词表中的任何子词,但不是空字符串:
- 如果该部分包含未知字符(不在基础字符集中),通常用
[UNK]表示。 - 否则,将其拆分成尽可能长的已知子词序列(通常是字符级),并给除第一个子词外的其他子词添加特殊前缀(如
##)以表明它是单词的一部分而不是开头。
- 如果该部分包含未知字符(不在基础字符集中),通常用
添加特殊标记: 根据模型要求,在句子开头添加
[CLS],在句子结尾添加[SEP],在需要的地方添加[PAD]。
多语言处理示例
| 语言 | 输入文本 | WordPiece 输出 | 优势体现 |
|---|---|---|---|
| 英语 | "Transformer" |
["Transform", "##er"] |
保留词根语义 |
| 中文 | "自然语言处理" |
["自","然","语","言","处","理"] |
无需空格,按字拆分 |
| 德语 | "Hauptbahnhof" |
["Haupt", "##bahn", "##hof"] |
分解复合词(主火车站) |
| 日语 | "ディープラーニング" |
["ディープ","##ラーニング"] |
处理片假名复合词 |
分词过程举例
假设词表中已有 play, ing,以及单个字母:
- 输入词:
playing - 分词过程:
- 看
playing整个词是否在词表?不在。 - 看
playin是否在词表?不在。 - 看
play是否在词表?在。 -> 切出play - 剩下
ing是否在词表?在。 -> 切出ing
- 看
- 结果:
['play', 'ing']
Python实现示例
1 | """ |
Java实现示例
1 | package com.example.demo.split; |
输出结果:[un, happ, [UNK], ##ness]
WordPiece与BPE的区别
| 特性 | Byte Pair Encoding (BPE) | WordPiece |
|---|---|---|
| 合并依据 | 最高频次。哪个相邻对出现的次数最多,就合并哪个。 | 似然增益/互信息。计算合并后能最大化训练数据似然度的相邻对。 |
| 数学直觉 | 统计频率。 | 概率比。衡量两个子词的绑定强度是否超过偶然。 |
| 代表模型 | GPT 系列,RoBERTa | BERT,DistilBERT |
优点与缺点
优点
- 处理未登录词:任何新词都可以被拆分成已知的子词,分解未见过的单词(如
"tokenization"→["token","##ization"]),避免了[UNK]泛滥。 - 平衡词表大小:比纯字符效率高,比纯单词词表小且覆盖广。词汇表高效压缩,规模仅为全词模型的 1/10~1/100,30k 词汇表可覆盖 95%+ 的常见文本(英语),单词级需 100k+ 才能达到同等覆盖率。
- 学习词根词缀:子词往往能对应有意义的语言单元(如
est,ing,pre)。 - 训练稳定性:通过语言模型优化,避免 BPE 的纯频率偏差。
- 跨语言一致性:同一套算法处理所有语言,特别适合多语言模型(如 mBERT、XLM-R)。
缺点
训练复杂度较高:计算概率比相对单纯数数更复杂,合并阶段需遍历所有字符对,时间复杂度为 O(N2)。
依赖语言模型假设:可能过度依赖训练语料的统计特性,忽略语法结构。空格分词在中文、日文等语言中不适用,需要额外的预处理。
前缀标记问题:
##符号增加额外负载(约 5-10% token 增长)子词歧义:
"pain"可能被拆为["pa","##in"]失去 “疼痛” 语义,对于这种情况模型(如 BERT)具备一定的纠错能力,但仍需要额外的学习成本来纠错这个语义,这在某些细粒度的情感分析或语义理解任务中,可能会对性能造成一定影响。这是WordPiece 的一个核心困境:为了追求覆盖率和词表效率,可能会牺牲语义的完整性。
分词方法对比
| 方法 | 合并依据 | 典型应用 | 代表模型 |
|---|---|---|---|
| WordPiece | 最大化语言模型似然 | BERT、ALBERT | 降低词表至 3 万级 |
| BPE | 最高频字符对 | GPT、T5 | 平衡词表与覆盖率 |
| Unigram | 最小化损失函数 | T5、mBART | 动态优化子词选择 |
分词过程示例
假设词表包含:["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]。
- 如果该字符不在基础字符集和词表中,会被分成
实际应用场景
现代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

自然语言处理使用的WordPiece分词算法详解
http://blog.gxitsky.com/2026/03/04/AI-LangChain-023-TextSpliter-WordPiece/

