BPE分词:相邻符号如何合并
BPE 分词器不是把每个词都存进词表,而是逐步合并训练文本中常见的相邻符号。训练阶段学习合并顺序,编码阶段复用这套顺序;分词长度因此取决于具体词表与预处理,不能只按汉字数或英文单词数估算。
先看一次合并
用虚构的小语料:cat 出现3次,car 出现2次。初始按字符拆开。相邻对 c+a 的加权计数为5,a+t 为3,a+r 为2。第一步选择 c+a,得到符号 ca;两个词分别变成 [ca,t] 与 [ca,r]。
原来每次 cat、car 都要三个符号,现在只需两个。这里的压缩是这个小例子的一轮结果,不能推成真实模型一定少多少 token。训练语料改变,合并优先级也可能改变。
词频必须进入计数
如果只把不同单词各看一次,c+a 的计数会变成2,忽略了它在语料中实际出现的次数。教学实现把“符号序列→出现次数”放进 Counter;统计相邻对时按词频累加。
当计数并列,需要固定处理方式才能复现。本例按符号对的字典序打破平局。它只是明确的教学约定,不声称等同于任何模型的训练工具。
训练规则与应用规则分开
Sennrich、Haddow、Birch 在 ACL 2016 论文中把子词单元用于机器翻译的稀有词处理。论文中的 BPE 思路提供这篇教程的算法背景;本文不介绍为新论文,也不移用作者实验成绩。
训练时需要语料来决定合并顺序。使用已经训练好的 tokenizer 时,不应重新根据每次请求的词频学习新规则;否则同一串文本在不同请求中可能变成不同 token。模型的词嵌入和输出词表已经绑定特定 token 编号。
实现真正的编码器,还需保存合并排名,并明确初始拆分、边界标记、特殊 token 与未知符号策略。本例只展示字符级的一轮训练合并,没有实现完整编码器或 byte-level BPE。
代码:统计并合并相邻对
from collections import Counter
def count_pairs(words):
counts = Counter()
for symbols, frequency in words.items():
for pair in zip(symbols, symbols[1:]):
counts[pair] += frequency
return counts
def merge_pair(words, pair):
result = Counter()
for symbols, frequency in words.items():
merged, i = [], 0
while i < len(symbols):
if tuple(symbols[i:i+2]) == pair:
merged.append(symbols[i] + symbols[i+1])
i += 2
else:
merged.append(symbols[i])
i += 1
result[tuple(merged)] += frequency
return result
words = Counter({tuple("cat"): 3, tuple("car"): 2})
counts = count_pairs(words)
assert counts[("c", "a")] == 5
assert counts[("a", "t")] == 3
best = min(counts, key=lambda pair: (-counts[pair], pair))
assert best == ("c", "a")
merged = merge_pair(words, best)
assert merged == Counter({("ca", "t"): 3, ("ca", "r"): 2})
assert merge_pair(Counter({tuple("aaa"): 1}), ("a", "a")) == Counter({("aa", "a"): 1})
assert count_pairs(Counter({("x",): 1})) == Counter()
assert count_pairs(Counter()) == Counter()
print(best, dict(merged))
代码已执行,验证了加权计数、第一轮合并、aaa 中相互重叠的 aa 按从左到右非重叠方式合并,以及单符号和空语料。输出为 (‘c’,’a’),随后得到 {(‘ca’,’t’):3,(‘ca’,’r’):2}。
一次统计与一次合并都扫描当前序列。设 L 为不同序列的总符号数、P 为不同相邻对数,统计约 O(L),选最佳对 O(P),合并 O(L),输出需额外空间。这是简化实现的成本;完整大语料训练需要不同的数据结构与工程优化。
上下文预算要用实际 tokenizer
API 中的 token 预算涉及最终编码结果,还可能包含消息包装与特殊 token。一个中文字符或英文单词,都没有与 token 一一对应的普遍保证。估算适合初筛,精确边界应使用目标模型匹配的 tokenizer 与接口计数规则。
对RAG 分块而言,先按字符切再想当然当成 token 切,会让上下文预算失准。对PPL 对比而言,换分词器也改变“每 token”的计量单位。
更大的词表可能减少部分文本的序列长度,但这不是免费收益:它也改变嵌入与输出层、覆盖方式和训练需求。本文没有对这些取舍做模型实验,只说明为何分词结果必须作为工程条件记录。
参考资料
本文于2026年10月13日补发,文章日期保留原计划北京时间。参考资料早于计划时段,示例为原创教学计算。
支持
如果这篇文章对你有帮助,欢迎支持本站。
二维码可点击放大。更多支持方式见支持页面。


