SentencePiece 子词分词完全指南:语言无关的 BPE / Unigram 分词器实战
【免费下载链接】AI-Research-SKILLsComprehensive open-source library of AI research and engineering skills for any AI model. Package the skills and your claude code/codex/gemini agent will be an AI research agent with full horsepower. Maintained by Orchestra Research.项目地址: https://gitcode.com/gh_mirrors/ai/AI-Research-SKILLs
SentencePiece 是 Google 开源的语言无关(language-independent)子词分词器,它将文本视为原始 Unicode 序列直接处理,无需任何语言专属的预分词规则,因此天然适合多语言模型、CJK(中文/日文/韩文)场景以及需要可复现分词结果的科研与工程任务。在本仓库的 AI Research Skills 体系中,它被定位为「子词分词(Subword tokenization)」任务的指定技能(参见 技能路由表)。读完本文,你将掌握 SentencePiece 的安装、训练、编码/解码全流程,理解 BPE 与 Unigram 两种核心算法及其适用场景,并能在 T5、ALBERT、XLNet、mBART 等主流模型生态中直接落地使用。
何时使用 SentencePiece
优先选择 SentencePiece 的场景
- 构建多语言模型:无需任何语言专属规则,同一套流程处理所有语言;
- 处理 CJK 语言(中文、日文、韩文):这些语言没有显式词边界,SentencePiece 直接在原始文本上切分;
- 需要可复现的分词结果:词表(vocabulary)确定性生成,同一语料产出完全一致的模型;
- 希望在原始文本上直接训练:不需要预分词(pre-tokenization)环节;
- 追求轻量部署:加载后的模型仅占用约 6MB 内存,分词吞吐约 5 万句/秒。
性能概览
| 指标 | 数值 |
|---|---|
| 分词速度 | 约 50,000 句/秒 |
| 模型加载内存 | 约 6MB |
| 支持语言 | 全部(语言无关) |
何时改用替代方案
- HuggingFace Tokenizers:训练更快、灵活性更高(Rust 核心实现,可追踪 token 与原文字符的对齐关系),详见 02-tokenization/huggingface-tokenizers/SKILL.md;
- tiktoken:OpenAI GPT-3.5/4 系列模型使用;
- BERT WordPiece:偏英语中心的任务。
在仓库的 Tokenization 分类下,两个技能是互补关系:HuggingFace Tokenizers 负责「高性能与自定义分词器训练」,SentencePiece 负责「子词分词」(见 技能路由表)。
快速开始
安装
# Python pip install sentencepiece # C++(需要 CMake) git clone https://github.com/google/sentencepiece.git cd sentencepiece mkdir build && cd build cmake .. && make -j $(nproc) sudo make installPython 环境安装后即可使用spm.SentencePieceTrainer(训练)与spm.SentencePieceProcessor(推理)两个核心类。
训练模型
# 命令行(BPE,8000 词表) spm_train --input=data.txt --model_prefix=m --vocab_size=8000 --model_type=bpe # Python API import sentencepiece as spm spm.SentencePieceTrainer.train( input='data.txt', model_prefix='m', vocab_size=8000, model_type='bpe' )训练耗时参考:约 100MB 语料 1~2 分钟。
编码与解码
import sentencepiece as spm # 加载模型 sp = spm.SentencePieceProcessor(model_file='m.model') # 编码为子词片段 pieces = sp.encode('This is a test', out_type=str) print(pieces) # ['▁This', '▁is', '▁a', '▁test'] # 编码为 token ID ids = sp.encode('This is a test', out_type=int) print(ids) # [284, 47, 11, 1243] # 解码还原 text = sp.decode(ids) print(text) # "This is a test"注意out_type参数决定编码结果的形态:str返回可读的子词片段,int返回词表索引 ID。decode接受 ID 列表并还原原始文本。
语言无关的设计核心:▁ 元符号
SentencePiece 最关键的机制是将空白符作为普通字符处理,训练前先把空白替换为元符号▁(U+2581),解码时再还原为空格。这样分词器无需预先按空格切词,从根源上绕开了「语言是否有词边界」的问题。
text = "Hello world" pieces = sp.encode(text, out_type=str) print(pieces) # ['▁Hello', '▁world'] # 解码保留空格 decoded = sp.decode_pieces(pieces) print(decoded) # "Hello world"核心原则:把文本当作原始 Unicode 处理,空白 =▁(元符号)。这也是它在 CJK 场景表现优异的原因——中文句子不需要先分词,直接切出有意义的子词单元。
Tokenization 算法:BPE 与 Unigram
SentencePiece 支持四种model_type:unigram(默认)、bpe、char、word。其中 BPE 与 Unigram 是最常用的两种,详细原理见 references/algorithms.md。
BPE(Byte-Pair Encoding)
算法流程:
- 以字符集初始化词表;
- 统计相邻 token 对的共现频率;
- 合并出现频率最高的对;
- 重复直到达到目标词表大小。
示例(语料low:5, lower:2, newest:6, widest:3):
- 第 1 轮:最高频对为
e+s(9 次),合并为es; - 第 2 轮:最高频为
es+t(9 次),合并为est; - 结果:
newest→new|est,widest→wid|est。
spm.SentencePieceTrainer.train( input='data.txt', model_prefix='bpe_model', vocab_size=16000, model_type='bpe' )优势:算法简单、训练快、压缩比好。劣势:分词结果是确定性的(无法采样),可能意外拆散常见词。代表模型:mBART(facebook/mbart-large-50,25 万词表)。
Unigram(默认推荐)
算法流程:
- 从超大种子词表(覆盖所有子串)出发;
- 计算每个 token 的概率;
- 逐步剔除对整体损失影响最小的 token;
- 重复直到达到目标词表大小。
概率分词示例(对 "lowest"):
Option 1: ['low', 'est'] P = 0.02 × 0.03 = 0.0006 ← 概率最高,被选中 Option 2: ['l', 'o', 'w', 'est'] P = 0.01 × 0.015 × 0.01 × 0.03 = 0.000000045spm.SentencePieceTrainer.train( input='data.txt', model_prefix='unigram_model', vocab_size=8000, model_type='unigram' )优势:概率式分词(支持采样),对形态丰富的语言效果更好,天然支持子词正则化。劣势:训练较慢、算法更复杂。代表模型:T5、ALBERT、XLNet。
两种算法对比
| 特性 | BPE | Unigram |
|---|---|---|
| 训练速度 | 快 | 慢 |
| 分词方式 | 确定性 | 概率式 |
| 支持采样 | 否 | 是 |
| 常见词表大小 | 16k–32k | 8k–32k |
| 代表模型 | mBART | T5, ALBERT, XLNet |
训练配置详解
核心参数
spm.SentencePieceTrainer.train( # 必填 input='corpus.txt', # 输入语料 model_prefix='output', # 输出前缀 vocab_size=8000, # 目标词表大小 # 算法 model_type='unigram', # 'unigram'、'bpe'、'char'、'word' # 覆盖率 character_coverage=0.9995, # 多数语言 0.9995,CJK 用 1.0 # 归一化 normalization_rule_name='nmt_nfkc', # 'nmt_nfkc'、'nfkc'、'identity' # 性能 num_threads=16, # 训练线程数 input_sentence_size=10000000 # 最大加载句子数 )特殊 token 配置
spm.SentencePieceTrainer.train( input='corpus.txt', model_prefix='m', vocab_size=32000, # 控制符号(模型控制用特殊 token) control_symbols=['<s>', '</s>', '<pad>'], # 用户自定义符号(永不切分) user_defined_symbols=['[MASK]', '[SEP]', '[CLS]'], # 特殊 token 的 piece 形式 unk_piece='<unk>', bos_piece='<s>', eos_piece='</s>', pad_piece='<pad>', # 特殊 token 的 ID unk_id=0, bos_id=1, eos_id=2, pad_id=3 )高级选项
spm.SentencePieceTrainer.train( input='corpus.txt', model_prefix='m', vocab_size=32000, # 字节回退(处理未知字符,如 emoji) byte_fallback=True, # 数字处理 split_digits=True, # 数字逐位切分 # 脚本切分 split_by_unicode_script=True, # 按 Unicode 脚本切分 split_by_whitespace=True, # 按空白切分 # 长度约束 max_sentencepiece_length=16, # 单个 token 最大长度 # 低频词处理 min_frequency=2, # token 最低出现频次 # 训练规模 input_sentence_size=10000000, # 最大句子数 shuffle_input_sentence=True, # 打乱训练数据 # 种子词表 seed_sentencepiece_size=1000000 # 种子词表大小 )各参数的详细说明与调优建议可参考 references/training.md。
从 Python 迭代器训练(大数据集)
import sentencepiece as spm from datasets import load_dataset # 加载数据集 dataset = load_dataset('wikitext', 'wikitext-103-raw-v1', split='train') # 创建迭代器 def corpus_iterator(): for example in dataset: if example['text'].strip(): yield example['text'] # 从迭代器训练 spm.SentencePieceTrainer.train( sentence_iterator=corpus_iterator(), model_prefix='wiki', vocab_size=32000, model_type='unigram' )train结束后会输出两个文件:m.model(二进制模型)与m.vocab(文本词表),分别用于推理加载和词表检查。
字符覆盖率(Character Coverage)
character_coverage决定训练时覆盖语料中字符的比例,未覆盖的字符将落入未知 token。注意:它只作用于训练时字符级采样,而非切分限制。
| 语言类型 | 建议覆盖率 | 原因 |
|---|---|---|
| 英语 | 0.9995 | 覆盖 a-z、A-Z、标点及常见重音即可 |
| CJK(中文) | 1.0 | 汉字体系必须全部覆盖 |
| 多语言 | 0.9995–1.0 | 在覆盖率与词表大小间取平衡 |
# 中文语料:全覆盖 spm.SentencePieceTrainer.train( input='zh_corpus.txt', character_coverage=1.0 )词表大小选择
| 任务 | 词表大小 | 依据 |
|---|---|---|
| 英语单语 | 16k–32k | 标准配置 |
| 多语言 | 32k–250k | 语言越多词表越大 |
| CJK | 32k–100k | 汉字字符基数大 |
| 代码 | 16k–32k | 与英语类似 |
归一化规则(Normalization)
归一化在训练与推理前对文本做统一化处理,直接影响 token 的稳定性:
nmt_nfkc(推荐):NFKC Unicode 归一化 + 空白处理,适合绝大多数任务;identity:不做任何归一化,原样保留输入,适用于代码、大小写敏感任务;nfkc:标准 NFKC 归一化,强度低于nmt_nfkc。
normalization_rule_name='identity' # 代码 / 大小写敏感场景编码选项:子词正则化(Subword Regularization)
Unigram 模型支持在编码时对同一文本采样多种合法切分,从而对训练数据做隐式增强:
sp = spm.SentencePieceProcessor(model_file='m.model') # 采样不同切分 for _ in range(3): pieces = sp.encode('tokenization', out_type=str, enable_sampling=True, alpha=0.1) print(pieces) # 输出(每次不同): # ['▁token', 'ization'] # ['▁tok', 'en', 'ization'] # ['▁token', 'iz', 'ation']alpha参数(正则化强度):
0.0:确定性分词(不采样);0.1:轻微扰动(推荐平衡点);0.5:高扰动;1.0:最大扰动。
收益:① 鲁棒性——模型学到多种合法切分;② 数据增强——训练样本更多样;③ 泛化——减少对特定切分的过拟合。代表应用:mT5、XLM-RoBERTa。实践上推荐训练时开启采样、推理时关闭(确定性模式),以获得一致输出。
# 训练循环中的用法 for batch in dataloader: tokens = sp.encode(batch['text'], enable_sampling=True, alpha=0.1) # 训练模型...NBest 编码:获取多种切分候选
sp = spm.SentencePieceProcessor(model_file='m.model') # 获取 top-5 切分 nbest = sp.nbest_encode('tokenization', nbest_size=5, out_type=str) for pieces, score in nbest: print(f"{pieces} (log prob: {score:.4f})") # 输出: # ['▁token', 'ization'] (log prob: -2.34) # ['▁tok', 'en', 'ization'] (log prob: -2.41) # ['▁token', 'iz', 'ation'] (log prob: -2.57)应用场景:① 集成分词——对多种切分结果取平均;② 不确定性估计——观察分数方差;③ 调试——理解分词器行为。
常见模式
T5 风格训练
T5 使用 Unigram + 100 个<extra_id_*>哨兵 token 作为去噪目标:
spm.SentencePieceTrainer.train( input='c4_corpus.txt', model_prefix='t5', vocab_size=32000, model_type='unigram', user_defined_symbols=[f'<extra_id_{i}>' for i in range(100)], unk_id=2, eos_id=1, pad_id=0 )与 transformers 集成
from transformers import T5Tokenizer # T5 内部使用 SentencePiece tokenizer = T5Tokenizer.from_pretrained('t5-base') inputs = tokenizer('translate English to French: Hello', return_tensors='pt')性能基准
训练速度
| 语料 | BPE (16k) | Unigram (8k) |
|---|---|---|
| 100 MB | 1–2 分钟 | 3–4 分钟 |
| 1 GB | 10–15 分钟 | 30–40 分钟 |
分词速度
- SentencePiece:约 50,000 句/秒;
- HF Tokenizers:约 200,000 句/秒(约 4 倍)。
若吞吐成为瓶颈,可参考仓库中 HuggingFace Tokenizers 技能(Rust 核心,1GB 语料 <20 秒)。
性能优化实践
多线程训练
spm.SentencePieceTrainer.train( input='large_corpus.txt', num_threads=32 # 使用全部核心 )加速比:16+ 核心下约 4–8 倍。
超大语料采样
# 大于 10GB 语料:采样 1000 万句 spm.SentencePieceTrainer.train( input='huge_corpus.txt', input_sentence_size=10000000, shuffle_input_sentence=True )超大规模语料(>10GB)
spm.SentencePieceTrainer.train( input='massive_corpus.txt', train_extremely_large_corpus=True, # 为 >10GB 语料启用 input_sentence_size=100000000 )支持的主流模型
| 模型 | 词表 | 算法 |
|---|---|---|
T5 系列(t5-base、t5-large) | 32k | Unigram |
ALBERT(albert-base-v2) | 30k | Unigram |
XLNet(xlnet-base-cased) | 32k | Unigram |
mBART(facebook/mbart-large-50) | 250k | BPE |
最佳实践清单
- 多数任务优先 Unigram:对多语言更友好;
- CJK 语料设
character_coverage=1.0:保证汉字全覆盖; - 使用
nmt_nfkc归一化:通用场景表现稳定; - 为特殊 token 添加
user_defined_symbols:如 BERT 风格[CLS]/[SEP]; - 开启
byte_fallback:增强对 emoji 与生僻字的鲁棒性; - 词表从 32000 起步:多数任务的良好默认值;
- 开启多线程训练:显著缩短训练时间;
- 多语言场景选 Unigram、追求速度选 BPE:按需求权衡;
- 训练开启子词正则化(alpha=0.1)、推理用确定性模式:兼顾鲁棒与一致。
深入阅读
- SentencePiece 训练指南——完整的训练参数、语料准备与性能调优;
- Tokenization 算法详解——BPE 与 Unigram 算法对比及子词正则化原理;
- 技能路由表——了解该技能在自动研究流程中的定位与触发场景;
- HuggingFace Tokenizers 技能——需要更高吞吐或对齐追踪时的互补方案。
版本说明:以上用法基于 sentencepiece 0.2.0+ 与 transformers 最新稳定版的 Python API;命令行工具spm_train/spm_spm_encode参数与 Python API 一一对应,可放心混用。
【免费下载链接】AI-Research-SKILLsComprehensive open-source library of AI research and engineering skills for any AI model. Package the skills and your claude code/codex/gemini agent will be an AI research agent with full horsepower. Maintained by Orchestra Research.项目地址: https://gitcode.com/gh_mirrors/ai/AI-Research-SKILLs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考