BPE 和 WordPiece 都是"搭积木":从最小的字符或字节出发,一块块往上垒,直到词表够大。
Unigram 恰好相反,它是"剪枝"——从一棵枝繁叶茂的候选树开始,一轮轮剪掉对整体贡献最小的枝条,直到只留下最有价值的那些。

这个方向上的反转不只是形式上的不同,它带来了一个 BPE 和 WordPiece 都不具备的能力:同一段文本可以有多种合法的切分方式,每种切分都有明确的概率。这让 Unigram 成为三种子词算法中理论框架最自洽的一个。
如果你用过 T5、ALBERT 或 XLNet,你已经在用 Unigram 了。这篇文章带你搞清楚它的原理、实现和独特优势。
一、核心思想:从大到小
1.1 方向反转
-
先回顾一下 BPE 和 WordPiece 的思路:
- BPE:初始词表 = 256 个字节 → 每轮合并频率最高的 pair → 词表逐步增大
- WordPiece:初始词表 = 基础字符集 → 每轮合并 PMI 最高的 pair → 词表逐步增大
可以看到,两者都是自底向上的构建过程。
-
Unigram 走了相反的路:
初始词表 = 一个巨大的候选集 → 每轮删掉对似然损失最小的子词 → 词表逐步缩小
初始候选词表通常是目标大小的 2~10 倍,包含所有单字符加上从语料中提取的高频子串。然后一轮一轮地"修剪",每轮裁掉 10%~20% 的子词,直到词表缩小到目标大小。

可以看到,左右两条路径方向相反,但终点相同:都是达到目标词表大小。BPE/WordPiece 每轮只加 1 个子词,需要成千上万轮迭代;Unigram 每轮批量裁掉 10%~20%,收敛快得多。更关键的区别在于"凭什么决定加谁/删谁":BPE 看频率,WordPiece 看 PMI,而 Unigram 看的是删掉某个子词后,整个语料的似然损失有多大—这是一个全局的评估标准,也正是它理论上更自洽的根源。
1.2 为什么要反过来
一个自然的问题:好端端的为什么要反过来?自底向上不是挺好的吗?
反向裁剪有一个核心优势:它天然维护了一个概率模型。在 Unigram 的框架中,词表中每个子词都有一个明确的概率值,任何一种切分方式的概率就是各子词概率的乘积。这意味着:
- 编码时,可以用动态规划找到全局最优切分(而非 BPE 的贪心重放或 WordPiece 的贪心最长匹配)
- 训练时,有一个清晰的优化目标:最大化语料的似然
- 裁剪时,有一个明确的标准:删掉对似然影响最小的子词
整个框架在概率论上是自洽的。而 BPE 和 WordPiece 本质上是启发式算法——它们没有一个全局的优化目标,只是每一步做局部最优的选择。
1.3 "Unigram"这个名字
如果你学过 NLP 基础,“Unigram"这个词大概率是在 N-gram 语言模型里第一次见到的。N-gram 是一种经典的语言模型,用来预测"下一个词是什么”。
-
常见的N-gram策略:
- Unigram(一元):假设每个词独立出现,P(w1,w2,...,wn)=∏P(wi)P(w_1, w_2, ..., w_n) = \prod P(w_i)P(w1,w2,...,wn)=∏P(wi)
- Bigram(二元):假设每个词只依赖前一个词
- Trigram(三元):假设每个词依赖前两个词
-
但 N-gram 和分词器是两个不同层次的东西
-
分词器解决"怎么切":把文本切成什么样的 token
-
N-gram 解决"切完之后怎么建模":用 token 序列来预测概率。
它们是上下游关系:先 tokenize,再用 N-gram 建模。
-
Unigram 分词算法假设子词之间完全独立。也就是说每个单词的概率与其前面的任何单词的概率都无关,它只取决于该词在训练文本中所有词出现的频率比例。因此,训练模型实际上就是计算训练文本中所有一元词的这些频率比例。
Unigram 分词的巧妙之处在于,它把语言模型的思想反过来用到了分词这一步。用一个 unigram(一元)语言模型来决定"怎么切"。具体来说,它假设子词之间完全独立,这就是概率论中事件独立性的直接应用:独立事件的联合概率等于各自概率的乘积,即 P(A∩B)=P(A)×P(B)P(A \cap B) = P(A) \times P(B)P(A∩B)=P(A)×P(B)。所以一个切分的概率就是各子词概率的乘积,不考虑子词之间的顺序关系。这个假设虽然简单(甚至有些粗糙),但在实践中效果很好,而且让计算变得非常高效。
所以"Unigram 分词"这个名字不只是借了个术语,它是真的把一元语言模型的概率框架下沉到了分词阶段,用概率来指导切分。
二、训练过程详解
2.1 整体流程
Unigram 的训练用的是 EM 算法(Expectation-Maximization),交替执行两个步骤:
-
E 步:在当前词表下,估计每个子词的概率
-
M 步:评估每个子词的"重要性",删掉最不重要的那些
重复直到词表缩小到目标大小。
先看个全貌:训练从一个超大候选词表出发,E 步估概率、M 步裁词表,循环往复直到词表缩到目标大小。下面逐步展开每个环节。

2.2 初始化:构造候选词表
训练的第一步是构造一个足够大的初始候选词表。
-
常见做法:
- 收集语料中所有单字符(保证任何文本都能被表示)
- 提取所有出现过的子串(长度不超过某个上限,比如 16)
- 按频率取 top-N 作为初始候选
-
或者更实用的做法:
先用 BPE 训练一个大词表,把 BPE 产生的所有子词作为 Unigram 的初始候选。SentencePiece 库的实现就用了这种方式。
-
初始词表大小通常是目标的 2~10 倍。比如目标 32,000 个子词,初始候选可能有 100,000~300,000 个。
2.3 E 步:估计子词概率
假设当前词表是固定的。对语料中的每个词,用 Viterbi 算法找到最优切分,然后统计每个子词在所有最优切分中的出现次数。
子词 xix_ixi 的概率为:
P(xi)=count(xi)∑jcount(xj)
P(x_i) = \frac{\text{count}(x_i)}{\sum_j \text{count}(x_j)}
P(xi)=∑jcount(xj)count(xi)
其中,xix_ixi 是词表中的某个子词,count(xi)\text{count}(x_i)count(xi) 是它在所有最优切分中出现的总次数,分母 ∑jcount(xj)\sum_j \text{count}(x_j)∑jcount(xj) 是所有子词出现次数的总和。
直觉上很好理解:一个子词被用得越多,说明它在当前词表下越"有用",分到的概率就越大。这其实就是最朴素的最大似然估计(MLE),用频率来近似概率。
2.4 M 步:裁剪词表
这是 Unigram 训练的核心。我们需要回答:如果从词表中去掉某个子词,语料的整体似然会下降多少?
语料的似然定义为:
L=∑s∈corpuslogP(x∗∣s)
\mathcal{L} = \sum_{s \in \text{corpus}} \log P(\mathbf{x}^* | s)
L=s∈corpus∑logP(x∗∣s)
从里往外拆这个公式:x∗\mathbf{x}^*x∗ 是句子 sss 在当前词表下的最优切分(概率最大的切法),P(x∗)P(\mathbf{x}^*)P(x∗) 是这种切分的概率(按 Unigram 假设就是各子词概率的乘积)。加 log\loglog 是因为概率连乘容易数值下溢,取对数后乘积变成求和,计算更稳定。最外层 ∑s∈corpus\sum_{s \in \text{corpus}}∑s∈corpus 对语料中所有句子求和,因为我们要评估的是词表对整个语料的表现,不是单句。
所以 L\mathcal{L}L 的含义就是:用当前词表去切整个语料,每句话都取最优切法,把所有概率(取对数后)加起来。这个值越大,说明当前词表越好。
有了这个"词表好不好"的度量,裁剪的思路就很自然了。对词表中的每个子词 xix_ixi,算一下去掉它后似然下降多少:
ΔL(xi)=L−L∖xi
\Delta\mathcal{L}(x_i) = \mathcal{L} - \mathcal{L}_{\setminus x_i}
ΔL(xi)=L−L∖xi
其中 L∖xi\mathcal{L}_{\setminus x_i}L∖xi 是去掉 xix_ixi 后,重新对语料做最优切分得到的似然。ΔL(xi)\Delta\mathcal{L}(x_i)ΔL(xi) 越小,说明去掉 xix_ixi 对整体影响越小,这个子词越"不重要"。
然后按 ΔL\Delta\mathcal{L}ΔL 从小到大排序,删掉排在前面的 10%~20% 的子词。但有一条铁律:单字符子词永远不删——它们是兜底的,保证任何文本都能被编码。
2.5 训练示例
用一个具体例子走一遍。假设语料只有两个词:
| Corpus | 出现次数 |
|---|---|
| “low” | 5 |
| “lower” | 3 |
(1)初始化: 构造候选词表(包含所有子串):
| 长度 | 候选子词 | 个数 |
|---|---|---|
| 单字符 | l, o, w, e, r | 5 |
| 双字符 | lo, ow, we, er | 4 |
| 三字符 | low, owe, wer | 3 |
| 四字符 | lowe, ower | 2 |
| 五字符 | lower | 1 |
去重后大约 13 个候选子词。假设目标词表大小为 8。
(2) 第一轮E步:估计子词概率
用当前词表对每个词做最优切分,统计子词概率。
初始时每个子词还没有概率,先用均匀分布:每个子词概率 P=1/15P = 1/15P=1/15。
然后用 Viterbi 找最优切分。因为概率都一样,Viterbi 会倾向于用尽量少的子词来切(fewer tokens = fewer probabilities multiplied = higher total probability)。所以:
"low" → ["low"] (1 个 token,概率 = 1/15)
"lower" → ["lower"] (1 个 token,概率 = 1/15)
为什么不切成
["l", "o", "w"]?因为 3 个 token 的概率是 (1/15)3(1/15)^3(1/15)3,远小于 1 个 token 的 1/151/151/15。
之后统计每个子词的出现次数,计算概率 P(xi)=count(xi)/∑count(xj)P(x_i) = \text{count}(x_i) / \sum \text{count}(x_j)P(xi)=count(xi)/∑count(xj)。
| 子词 | 出现次数 | 概率 |
|---|---|---|
| low | 5 | 5/8 = 0.625 |
| lower | 3 | 3/8 = 0.375 |
| 其余 13 个 | 0 | 0 |
(3) 第一轮M 步:评估每个子词的重要性。
-
首先,先来计算当前的语料似然:
L=∑logP(x∗) \mathcal{L} = \sum \log P(\mathbf{x}^*) L=∑logP(x∗)
代入可得:
L=5×log(0.625)+3×log(0.375)=5×(−0.47)+3×(−0.98)=−5.29 \mathcal{L} = 5 \times \log(0.625) + 3 \times \log(0.375) = 5 \times (-0.47) + 3 \times (-0.98) = -5.29 L=5×log(0.625)+3×log(0.375)=5×(−0.47)+3×(−0.98)=−5.29 -
然后逐个评估。
-
先看那些概率为 0、当前切分没用到的子词(如
ower、lo、lowe等):去掉它们后,最优切分不变,似然不变,所以 ΔL=0\Delta\mathcal{L} = 0ΔL=0。这些子词是裁剪的首选目标。
-
再看关键子词:
- 去掉
lower:"lower"无法用一个 token 表示,退化为["low", "er"]。重新算概率和似然,ΔL\Delta\mathcal{L}ΔL 会有明显下降 - 去掉
low:"low"退化为["l", "o", "w"],"lower"也受影响。ΔL\Delta\mathcal{L}ΔL 下降更大 - 去掉单字符
l、o等:铁律单字符永远不删
- 去掉
-
-
按 ΔL\Delta\mathcal{L}ΔL 从小到大排序,删掉损失最小的(这一轮主要是那些出现次数为 0 的子词)。假设裁掉 20%(3 个),删掉
ower、lowe、owe,词表从 15 缩到 12。
之后,按照(2)-(3)继续迭代: 新一轮 E 步用缩小后的词表重新跑 Viterbi、重新估概率,M 步再裁一批……循环往复,直到词表缩到目标大小 8。
三、编码:Viterbi 最优切分
3.1 所有切分都是候选
这是 Unigram 和 BPE/WordPiece 最根本的区别。
BPE 对 tokenization 只有一种确定的编码结果:按合并规则顺序重放。WordPiece 也是类似,贪心最长匹配只有一条路径。但 Unigram 认为所有可能的切分方式都是候选,每种都有概率:
"tokenization" 的可能切分:
["token", "ization"] → P = P(token) × P(ization)
["token", "iz", "ation"] → P = P(token) × P(iz) × P(ation)
["to", "ken", "ization"] → P = P(to) × P(ken) × P(ization)
["t", "o", "k", "e", "n", "i", "z", "a", "t", "i", "o", "n"]
→ P = P(t) × P(o) × ... × P(n)
...(还有很多)
编码时选概率最大的那个。但关键是:次优切分也是有意义的,这一点在后面的 Subword Regularization 中会派上大用场。
3.2 概率计算
给定一个词 sss 和一种切分 x=(x1,x2,...,xn)\mathbf{x} = (x_1, x_2, ..., x_n)x=(x1,x2,...,xn),切分概率为:
P(x)=∏i=1nP(xi)
P(\mathbf{x}) = \prod_{i=1}^{n} P(x_i)
P(x)=i=1∏nP(xi)
这就是 Unigram(一元模型)假设的体现,子词之间完全独立,联合概率等于各子词概率的乘积。
编码时要找概率最大的切分:
x∗=argmaxxP(x)=argmaxx∑i=1nlogP(xi)
\mathbf{x}^* = \arg\max_{\mathbf{x}} P(\mathbf{x}) = \arg\max_{\mathbf{x}} \sum_{i=1}^{n} \log P(x_i)
x∗=argxmaxP(x)=argxmaxi=1∑nlogP(xi)
取对数后,乘积变成加法。问题就变成了:在所有合法切分中,找到对数概率之和最大的那个。这是一个标准的最短路径(最长路径)问题,Viterbi 动态规划可以高效求解。
3.3 Viterbi 动态规划
以 "lower" 为例,假设词表和对数概率为:
"l": -3.5, "o": -3.6, "w": -4.5, "e": -3.2, "r": -4.0
"lo": -2.8, "ow": -3.0, "we": -3.1, "er": -2.5, "low": -1.8
"owe": -2.6, "wer": -2.7, "lower": -1.2, "lowe": -2.0
-
动态规划的核心思路:
从左到右扫描每个位置,对于每个位置,往回看所有能到达它的子词,选得分最高的那条路径。
每一步的得分=前一个位置的最优得分+当前子词的对数概率 \text{每一步的得分} = \text{前一个位置的最优得分} + \text{当前子词的对数概率} 每一步的得分=前一个位置的最优得分+当前子词的对数概率 -
位置 0(起点): 还没开始切,得分为 0。
score[0]=0.0 \text{score}[0] = 0.0 score[0]=0.0 -
位置 1(覆盖 “l”): 只有一种方式到达——从位置 0 出发,用子词
"l":
score[1]=score[0]+logP("l")=0.0+(−3.5)=−3.5 \text{score}[1] = \text{score}[0] + \log P(\text{"l"}) = 0.0 + (-3.5) = -3.5 score[1]=score[0]+logP("l")=0.0+(−3.5)=−3.5 -
位置 2(覆盖 “lo”): 有两种方式到达:
从哪来 用什么子词 计算 得分 位置 1 “o” −3.5+(−3.6)-3.5 + (-3.6)−3.5+(−3.6) -7.1 位置 0 “lo” 0.0+(−2.8)0.0 + (-2.8)0.0+(−2.8) -2.8 选最大的:score[2]=−2.8\text{score}[2] = -2.8score[2]=−2.8(来自
"lo") -
位置 3(覆盖 “low”): 有三种方式到达:
从哪来 用什么子词 计算 得分 位置 2 “w” −2.8+(−4.5)-2.8 + (-4.5)−2.8+(−4.5) -7.3 位置 1 “ow” −3.5+(−3.0)-3.5 + (-3.0)−3.5+(−3.0) -6.5 位置 0 “low” 0.0+(−1.8)0.0 + (-1.8)0.0+(−1.8) -1.8 选最大的:score[3]=−1.8\text{score}[3] = -1.8score[3]=−1.8(来自
"low") -
位置 4(覆盖 “lowe”): 列举主要候选:
从哪来 用什么子词 计算 得分 位置 3 “e” −1.8+(−3.2)-1.8 + (-3.2)−1.8+(−3.2) -5.0 位置 2 “we” −2.8+(−3.1)-2.8 + (-3.1)−2.8+(−3.1) -5.9 位置 1 “owe” −3.5+(−2.6)-3.5 + (-2.6)−3.5+(−2.6) -6.1 位置 0 “lowe” 0.0+(−2.0)0.0 + (-2.0)0.0+(−2.0) -2.0 选最大的:score[4]=−2.0\text{score}[4] = -2.0score[4]=−2.0(来自
"lowe") -
位置 5(覆盖 “lower”): 列举主要候选:
从哪来 用什么子词 计算 得分 位置 4 “r” −2.0+(−4.0)-2.0 + (-4.0)−2.0+(−4.0) -6.0 位置 3 “er” −1.8+(−2.5)-1.8 + (-2.5)−1.8+(−2.5) -4.3 位置 2 “wer” −2.8+(−2.7)-2.8 + (-2.7)−2.8+(−2.7) -5.5 位置 0 “lower” 0.0+(−1.2)0.0 + (-1.2)0.0+(−1.2) -1.2 选择最大的:score[5]=−1.2\text{score}[5] = -1.2score[5]=−1.2(来自
"lower") -
最优切分:回溯路径,位置 5 直接来自位置 0,所以整个词不切,保留
["lower"]。此时,对数概率 -1.2。如果词表中没有"lower",次优路径是位置 5 来自位置 3、位置 3 来自位置 0,即["low", "er"],对数概率 −1.8+(−2.5)=−4.3-1.8 + (-2.5) = -4.3−1.8+(−2.5)=−4.3。
四、三种编码策略的对比
| BPE | WordPiece | Unigram | |
|---|---|---|---|
| 策略 | 合并规则重放 | 贪心最长匹配 | Viterbi 最优切分 |
| 搜索方式 | 逐条应用规则 | 从左到右,每次选最长 | 动态规划,全局搜索 |
| 最优性 | 局部(取决于规则顺序) | 局部(贪心不保证全局最优) | 全局最优 |
| 需要的信息 | 有序合并规则表 | 词表集合 | 词表 + 概率字典 |
| 确定性 | 确定 | 确定 | 可确定可随机 |
最后一行是 Unigram 最独特的地方,编码可以是随机的。这是因为 Unigram 维护了完整的概率分布,所以编码时有两种选择:
- 确定性:用 Viterbi 找概率最大的切分,每次结果一样
- 随机:按概率分布随机采样一种切分,同一段文本每次可能切出不同结果
而BPE 和 WordPiece 做不到随机。它们的编码规则是固定的(合并规则重放 /贪心最长匹配),同一段文本只有唯一的切法。Unigram 因为有概率模型,所有切分方式都有概率,才能做到"随机采样"。
后面,就引出了 Subword Regularization。
五、Subword Regularization:独特的数据增强
5.1 一种输入,多种切分
BPE 和 WordPiece 的编码是确定性的:同一段文本,每次编码结果完全一样。但 Unigram 维护了完整的概率分布,除了最优切分,其他切分也有概率。
比如 "unigram" 可能有这些切分:
["uni", "gram"] → 概率 0.42
["u", "ni", "gram"] → 概率 0.31
["uni", "g", "ram"] → 概率 0.18
["u", "n", "i", "gram"] → 概率 0.05
...
Kudo(2018)提出的 Subword Regularization 利用了这一特性:训练下游模型时(比如翻译模型),不总是用概率最大的切分,而是按概率分布随机采样一种切分方式。
5.2 为什么有效
同一句话在不同 epoch 中可能被切成不同的子词序列:
Epoch 1: "unigram" → ["uni", "gram"]
Epoch 2: "unigram" → ["u", "ni", "gram"]
Epoch 3: "unigram" → ["uni", "gram"]
Epoch 4: "unigram" → ["uni", "g", "ram"]
这相当于一种数据增强,模型看到了同一语义内容的不同 token 表示。效果类似于图像中的随机裁剪、翻转:让模型不依赖某种特定的"切法",而是学到更鲁棒的表示。
Kudo 的实验表明,Subword Regularization 在低资源翻译任务上能带来显著提升(1~2 BLEU)。在数据量大的场景下提升较小,但也不会伤害性能。
六、Unigram 在实际模型中的应用
6.1 SentencePiece
前面讲的都是 Unigram 的算法原理,但实际使用时你不需要自己实现它。Google 开源的 SentencePiece 是目前最主流的子词分词工具库,可以理解为"分词器的工厂":你给它语料,它帮你训练出一个分词器。
SentencePiece 解决了一个传统分词工具的痛点:语言无关性。传统的 BPE/WordPiece 实现通常需要先按空格把文本切成词,再对每个词做子词切分。这对英文没问题,但中文、日文这类没有空格分隔的语言就麻烦了。SentencePiece 的做法是把空格也当作普通字符(用 ▁ 替换),直接在原始文本上训练,不需要预先分词,对所有语言一视同仁。
SentencePiece 同时支持 BPE 和 Unigram 两种模式,Unigram 算法的提出者 Kudo 本人就是 SentencePiece 的作者:
| 模型 | SentencePiece 模式 |
|---|---|
| T5 | Unigram |
| ALBERT | Unigram |
| XLNet | Unigram |
| LLaMA | BPE |
| Mistral | BPE |
6.2 为什么 T5 选了 Unigram
T5(Text-to-Text Transfer Transformer)选择 Unigram 有几个原因:
- 多语言支持:T5 的 mC4 数据集覆盖 101 种语言,Unigram + SentencePiece 的语言无关性正好适合
- Subword Regularization:T5 在训练时利用了随机采样切分,增强了模型的鲁棒性
- 概率框架:Unigram 的全局最优切分在 seq2seq 任务中表现稳定
6.3 为什么 LLM 时代 BPE 更流行
尽管 Unigram 理论上更优美,但在当下的 LLM 赛道上,BPE 是绝对主流。
- 工程简洁性:BPE 的合并规则就是一个有序列表,Unigram 需要维护概率字典 + EM 迭代 + Viterbi
- 确定性编码:LLM 推理需要确定性,Subword Regularization 在推理时反而是负担
- 字节级兜底:BPE 从字节出发永远不会 OOV,Unigram 需要单字符兜底(实践中差别不大)
- 生态惯性:GPT 系列奠定了 BPE 的主导地位
七、总结
7.1 核心要点
- Unigram 是唯一的"自顶向下"子词算法——从大词表开始裁剪,而非从小词表开始合并
- 训练用 EM 算法:E 步估计子词概率,M 步裁掉对似然损失最小的子词
- 编码用 Viterbi 动态规划,在所有可能的切分中找全局最优,而非贪心的局部最优
- 独有的 Subword Regularization:同一文本可以有多种切分,训练时随机采样相当于数据增强
- “Unigram”= 一元模型:假设子词独立,切分概率 = 各子词概率的乘积
7.2 三种算法速查对比
| 维度 | BPE | WordPiece | Unigram |
|---|---|---|---|
| 训练方向 | 自底向上(合并) | 自底向上(合并) | 自顶向下(裁剪) |
| 选择标准 | 频率最高的 pair | PMI 最高的 pair | 删除后损失最小的子词 |
| 编码策略 | 合并规则重放 | 贪心最长匹配 | Viterbi 最优切分 |
| 编码确定性 | 确定 | 确定 | 可确定可随机 |
| 理论框架 | 启发式 | 启发式 | 概率模型(EM + Viterbi) |
| 代表模型 | GPT、LLaMA | BERT | T5、ALBERT、XLNet |
Unigram 是三种子词算法中理论最自洽的一个。它用概率模型统一了训练(EM)、编码(Viterbi)和数据增强(随机采样),整个框架在数学上一气呵成。但在工程实践中,"够好且简单"的 BPE 赢得了 LLM 时代的主流地位——这也许是软件工程中"简单胜于完美"的又一个例证。
157



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



