名称:huggingface-tokenizers 描述:为研究和生产优化的快速分词器。基于Rust的实现,可在<20秒内对1GB文本进行分词。支持BPE、WordPiece和Unigram算法。训练自定义词汇表,跟踪对齐,处理填充/截断。与transformers无缝集成。当您需要高性能分词或自定义分词器训练时使用。 版本:1.0.0 作者:Orchestra Research 许可证:MIT 标签:[分词, HuggingFace, BPE, WordPiece, Unigram, 快速分词, Rust, 自定义分词器, 对齐跟踪, 生产] 依赖项:[tokenizers, transformers, datasets]
HuggingFace Tokenizers - 用于NLP的快速分词
快速、生产就绪的分词器,具有Rust性能和Python易用性。
何时使用HuggingFace Tokenizers
使用HuggingFace Tokenizers当:
- 需要极快的分词(每GB文本<20秒)
- 从头开始训练自定义分词器
- 需要对齐跟踪(token → 原始文本位置)
- 构建生产NLP流水线
- 需要高效地对大型语料库进行分词
性能:
- 速度:在CPU上对1GB文本进行分词<20秒
- 实现:基于Rust核心,带有Python/Node.js绑定
- 效率:比纯Python实现快10-100倍
使用替代方案:
- SentencePiece:语言无关,用于T5/ALBERT
- tiktoken:OpenAI的BPE分词器,用于GPT模型
- transformers AutoTokenizer:仅加载预训练(内部使用此库)
快速开始
安装
# 安装tokenizers
pip install tokenizers
# 与transformers集成
pip install tokenizers transformers
加载预训练分词器
from tokenizers import Tokenizer
# 从HuggingFace Hub加载
tokenizer = Tokenizer.from_pretrained("bert-base-uncased")
# 编码文本
output = tokenizer.encode("Hello, how are you?")
print(output.tokens) # ['hello', ',', 'how', 'are', 'you', '?']
print(output.ids) # [7592, 1010, 2129, 2024, 2017, 1029]
# 解码回文本
text = tokenizer.decode(output.ids)
print(text) # "hello, how are you?"
训练自定义BPE分词器
from tokenizers import Tokenizer
from tokenizers.models import BPE
from tokenizers.trainers import BpeTrainer
from tokenizers.pre_tokenizers import Whitespace
# 使用BPE模型初始化分词器
tokenizer = Tokenizer(BPE(unk_token="[UNK]"))
tokenizer.pre_tokenizer = Whitespace()
# 配置训练器
trainer = BpeTrainer(
vocab_size=30000,
special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"],
min_frequency=2
)
# 在文件上训练
files = ["train.txt", "validation.txt"]
tokenizer.train(files, trainer)
# 保存
tokenizer.save("my-tokenizer.json")
训练时间:约1-2分钟用于100MB语料库,约10-20分钟用于1GB
批编码与填充
# 启用填充
tokenizer.enable_padding(pad_id=3, pad_token="[PAD]")
# 编码批次
texts = ["Hello world", "This is a longer sentence"]
encodings = tokenizer.encode_batch(texts)
for encoding in encodings:
print(encoding.ids)
# [101, 7592, 2088, 102, 3, 3, 3]
# [101, 2023, 2003, 1037, 2936, 6251, 102]
分词算法
BPE(字节对编码)
工作原理:
- 从字符级词汇表开始
- 找到最频繁的字符对
- 合并为新token,添加到词汇表
- 重复直到达到词汇表大小
用于: GPT-2, GPT-3, RoBERTa, BART, DeBERTa
from tokenizers import Tokenizer
from tokenizers.models import BPE
from tokenizers.trainers import BpeTrainer
from tokenizers.pre_tokenizers import ByteLevel
tokenizer = Tokenizer(BPE(unk_token="<|endoftext|>"))
tokenizer.pre_tokenizer = ByteLevel()
trainer = BpeTrainer(
vocab_size=50257,
special_tokens=["<|endoftext|>"],
min_frequency=2
)
tokenizer.train(files=["data.txt"], trainer=trainer)
优势:
- 处理OOV词效果好(分解为子词)
- 词汇表大小灵活
- 适合形态丰富的语言
权衡:
- 分词依赖于合并顺序
- 可能意外拆分常见词
WordPiece
工作原理:
- 从字符词汇表开始
- 评分合并对:
频率(对) / (频率(第一个) × 频率(第二个)) - 合并最高评分对
- 重复直到达到词汇表大小
用于: BERT, DistilBERT, MobileBERT
from tokenizers import Tokenizer
from tokenizers.models import WordPiece
from tokenizers.trainers import WordPieceTrainer
from tokenizers.pre_tokenizers import Whitespace
from tokenizers.normalizers import BertNormalizer
tokenizer = Tokenizer(WordPiece(unk_token="[UNK]"))
tokenizer.normalizer = BertNormalizer(lowercase=True)
tokenizer.pre_tokenizer = Whitespace()
trainer = WordPieceTrainer(
vocab_size=30522,
special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"],
continuing_subword_prefix="##"
)
tokenizer.train(files=["corpus.txt"], trainer=trainer)
优势:
- 优先有意义合并(高评分 = 语义相关)
- 在BERT中成功使用(最先进的结果)
权衡:
- 未知词如果没有子词匹配变为
[UNK] - 保存词汇表,而非合并规则(文件较大)
Unigram
工作原理:
- 从大词汇表开始(所有子字符串)
- 计算当前词汇表对语料库的损失
- 移除对损失影响最小的token
- 重复直到达到词汇表大小
用于: ALBERT, T5, mBART, XLNet(通过SentencePiece)
from tokenizers import Tokenizer
from tokenizers.models import Unigram
from tokenizers.trainers import UnigramTrainer
tokenizer = Tokenizer(Unigram())
trainer = UnigramTrainer(
vocab_size=8000,
special_tokens=["<unk>", "<s>", "</s>"],
unk_token="<unk>"
)
tokenizer.train(files=["data.txt"], trainer=trainer)
优势:
- 概率性(找到最可能的分词)
- 适合没有词边界的语言
- 处理多样化的语言上下文
权衡:
- 训练计算成本高
- 更多超参数需要调整
分词流水线
完整流水线:归一化 → 预分词 → 模型 → 后处理
归一化
清理和标准化文本:
from tokenizers.normalizers import NFD, StripAccents, Lowercase, Sequence
tokenizer.normalizer = Sequence([
NFD(), # Unicode归一化(分解)
Lowercase(), # 转换为小写
StripAccents() # 移除重音
])
# 输入:"Héllo WORLD"
# 归一化后:"hello world"
常见归一化器:
NFD,NFC,NFKD,NFKC- Unicode归一化形式Lowercase()- 转换为小写StripAccents()- 移除重音(é → e)Strip()- 移除空白Replace(pattern, content)- 正则替换
预分词
将文本拆分为词样单元:
from tokenizers.pre_tokenizers import Whitespace, Punctuation, Sequence, ByteLevel
# 按空白和标点拆分
tokenizer.pre_tokenizer = Sequence([
Whitespace(),
Punctuation()
])
# 输入:"Hello, world!"
# 预分词后:["Hello", ",", "world", "!"]
常见预分词器:
Whitespace()- 按空格、制表符、换行拆分ByteLevel()- GPT-2风格字节级拆分Punctuation()- 隔离标点Digits(individual_digits=True)- 按数字单独拆分Metaspace()- 用▁替换空格(SentencePiece风格)
后处理
为模型输入添加特殊token:
from tokenizers.processors import TemplateProcessing
# BERT风格:[CLS] 句子 [SEP]
tokenizer.post_processor = TemplateProcessing(
single="[CLS] $A [SEP]",
pair="[CLS] $A [SEP] $B [SEP]",
special_tokens=[
("[CLS]", 1),
("[SEP]", 2),
],
)
常见模式:
# GPT-2:句子 <|endoftext|>
TemplateProcessing(
single="$A <|endoftext|>",
special_tokens=[("<|endoftext|>", 50256)]
)
# RoBERTa:<s> 句子 </s>
TemplateProcessing(
single="<s> $A </s>",
pair="<s> $A </s> </s> $B </s>",
special_tokens=[("<s>", 0), ("</s>", 2)]
)
对齐跟踪
跟踪token在原始文本中的位置:
output = tokenizer.encode("Hello, world!")
# 获取token偏移
for token, offset in zip(output.tokens, output.offsets):
start, end = offset
print(f"{token:10} → [{start:2}, {end:2}): {text[start:end]!r}")
# 输出:
# hello → [ 0, 5): 'Hello'
# , → [ 5, 6): ','
# world → [ 7, 12): 'world'
# ! → [12, 13): '!'
用例:
- 命名实体识别(将预测映射回文本)
- 问答(提取答案跨度)
- token分类(将标签对齐到原始位置)
与transformers集成
使用AutoTokenizer加载
from transformers import AutoTokenizer
# AutoTokenizer自动使用快速分词器
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
# 检查是否使用快速分词器
print(tokenizer.is_fast) # True
# 访问底层tokenizers.Tokenizer
fast_tokenizer = tokenizer.backend_tokenizer
print(type(fast_tokenizer)) # <class 'tokenizers.Tokenizer'>
将自定义分词器转换为transformers
from tokenizers import Tokenizer
from transformers import PreTrainedTokenizerFast
# 训练自定义分词器
tokenizer = Tokenizer(BPE())
# ... 训练分词器 ...
tokenizer.save("my-tokenizer.json")
# 包装用于transformers
transformers_tokenizer = PreTrainedTokenizerFast(
tokenizer_file="my-tokenizer.json",
unk_token="[UNK]",
pad_token="[PAD]",
cls_token="[CLS]",
sep_token="[SEP]",
mask_token="[MASK]"
)
# 像任何transformers分词器一样使用
outputs = transformers_tokenizer(
"Hello world",
padding=True,
truncation=True,
max_length=512,
return_tensors="pt"
)
常见模式
从迭代器训练(大型数据集)
from datasets import load_dataset
# 加载数据集
dataset = load_dataset("wikitext", "wikitext-103-raw-v1", split="train")
# 创建批次迭代器
def batch_iterator(batch_size=1000):
for i in range(0, len(dataset), batch_size):
yield dataset[i:i + batch_size]["text"]
# 训练分词器
tokenizer.train_from_iterator(
batch_iterator(),
trainer=trainer,
length=len(dataset) # 用于进度条
)
性能:处理1GB约10-20分钟
启用截断和填充
# 启用截断
tokenizer.enable_truncation(max_length=512)
# 启用填充
tokenizer.enable_padding(
pad_id=tokenizer.token_to_id("[PAD]"),
pad_token="[PAD]",
length=512 # 固定长度,或None用于批次最大长度
)
# 编码两者
output = tokenizer.encode("This is a long sentence that will be truncated...")
print(len(output.ids)) # 512
多进程
from tokenizers import Tokenizer
from multiprocessing import Pool
# 加载分词器
tokenizer = Tokenizer.from_file("tokenizer.json")
def encode_batch(texts):
return tokenizer.encode_batch(texts)
# 并行处理大型语料库
with Pool(8) as pool:
# 将语料库拆分为块
chunk_size = 1000
chunks = [corpus[i:i+chunk_size] for i in range(0, len(corpus), chunk_size)]
# 并行编码
results = pool.map(encode_batch, chunks)
加速:使用8核时5-8倍
性能基准
训练速度
| 语料库大小 | BPE (30k词汇表) | WordPiece (30k) | Unigram (8k) |
|---|---|---|---|
| 10 MB | 15 秒 | 18 秒 | 25 秒 |
| 100 MB | 1.5 分钟 | 2 分钟 | 4 分钟 |
| 1 GB | 15 分钟 | 20 分钟 | 40 分钟 |
硬件:16核CPU,在英文维基百科上测试
分词速度
| 实现 | 1 GB语料库 | 吞吐量 |
|---|---|---|
| 纯Python | ~20分钟 | ~50 MB/min |
| HF Tokenizers | ~15秒 | ~4 GB/min |
| 加速 | 80× | 80× |
测试:英文文本,平均句子长度20词
内存使用
| 任务 | 内存 |
|---|---|
| 加载分词器 | ~10 MB |
| 训练BPE (30k词汇表) | ~200 MB |
| 编码100万句子 | ~500 MB |
支持模型
通过from_pretrained()可用的预训练分词器:
BERT系列:
bert-base-uncased,bert-large-caseddistilbert-base-uncasedroberta-base,roberta-large
GPT系列:
gpt2,gpt2-medium,gpt2-largedistilgpt2
T5系列:
t5-small,t5-base,t5-largegoogle/flan-t5-xxl
其他:
facebook/bart-base,facebook/mbart-large-cc25albert-base-v2,albert-xlarge-v2xlm-roberta-base,xlm-roberta-large
浏览所有:https://huggingface.co/models?library=tokenizers
参考文献
- 训练指南 - 训练自定义分词器,配置训练器,处理大型数据集
- 算法深度解析 - BPE、WordPiece、Unigram详细解释
- 流水线组件 - 归一化器、预分词器、后处理器、解码器
- Transformers集成 - AutoTokenizer、PreTrainedTokenizerFast、特殊token
资源
- 文档:https://huggingface.co/docs/tokenizers
- GitHub:https://github.com/huggingface/tokenizers ⭐ 9,000+
- 版本:0.20.0+
- 课程:https://huggingface.co/learn/nlp-course/chapter6/1
- 论文:BPE (Sennrich et al., 2016), WordPiece (Schuster & Nakajima, 2012)