Huggingface Tokenizers
為研究和生產環境優化的快速分詞器。基於 Rust 的實現可在 <20 秒內對 1GB 數據進行分詞。支持 BPE、WordPiece 和 Unigram 算法。訓練自定義詞彙表、跟蹤對齊信息、處理填充/截斷。與 transformers 無縫集成。當您需要高性能分詞或自定義分詞器訓練時使用。
技能元數據
| 來源 | 可選 — 使用 hermes skills install official/mlops/huggingface-tokenizers 安裝 |
| 路徑 | optional-skills/mlops/huggingface-tokenizers |
| 版本 | 1.0.0 |
| 作者 | Orchestra Research |
| 許可證 | MIT |
| 依賴項 | tokenizers, transformers, datasets |
| 標籤 | Tokenization, HuggingFace, BPE, WordPiece, Unigram, Fast Tokenization, Rust, Custom Tokenizer, Alignment Tracking, Production |
參考:完整 SKILL.md
以下是 Hermes 在觸發此技能時加載的完整技能定義。這是技能激活時代理看到的指令。
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 用於 GPT 模型的 BPE 分詞器
- transformers AutoTokenizer:僅加載預訓練模型(內部使用此庫)
快速開始
安裝
# Install tokenizers
pip install tokenizers
# With transformers integration
pip install tokenizers transformers
加載預訓練分詞器
from tokenizers import Tokenizer
# Load from HuggingFace Hub
tokenizer = Tokenizer.from_pretrained("bert-base-uncased")
# Encode text
output = tokenizer.encode("Hello, how are you?")
print(output.tokens) # ['hello', ',', 'how', 'are', 'you', '?']
print(output.ids) # [7592, 1010, 2129, 2024, 2017, 1029]
# Decode back
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
# Initialize tokenizer with BPE model
tokenizer = Tokenizer(BPE(unk_token="[UNK]"))
tokenizer.pre_tokenizer = Whitespace()
# Configure trainer
trainer = BpeTrainer(
vocab_size=30000,
special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"],
min_frequency=2
)
# Train on files
files = ["train.txt", "validation.txt"]
tokenizer.train(files, trainer)
# Save
tokenizer.save("my-tokenizer.json")
訓練時間:100MB 語料庫約需 1-2 分鐘,1GB 語料庫約需 10-20 分鐘
帶填充的批量編碼
# Enable padding
tokenizer.enable_padding(pad_id=3, pad_token="[PAD]")
# Encode batch
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 (Byte-Pair Encoding, 字節對編碼)
工作原理:
- 從字符級詞彙表開始
- 查找最頻繁的字符對
- 合併為新 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
工作原理:
- 從字符詞彙表開始
- 對合並對評分:
frequency(pair) / (frequency(first) × frequency(second)) - 合併得分最高的對
- 重複直到達到詞彙表大小
使用者: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)
優勢:
- 概率性(找到最可能的分詞方式)
- 適用於沒有詞邊界的語言
- 處理多樣的語言上下文
權衡:
- 訓練計算成本高
- 需要調整的超參數更多
分詞管道
完整管道:Normalization(標準化) → Pre-tokenization(預分詞) → Model(模型) → Post-processing(後處理)
Normalization(標準化)
清理和標準化文本:
from tokenizers.normalizers import NFD, StripAccents, Lowercase, Sequence
tokenizer.normalizer = Sequence([
NFD(), # Unicode normalization (decompose)
Lowercase(), # Convert to lowercase
StripAccents() # Remove accents
])
# Input: "Héllo WORLD"
# After normalization: "hello world"
常用標準化器:
NFD,NFC,NFKD,NFKC- Unicode 標準化形式Lowercase()- 轉換為小寫StripAccents()- 去除重音符號(é → e)Strip()- 去除空白字符Replace(pattern, content)- 正則表達式替換
Pre-tokenization(預分詞)
將文本分割為類似單詞的單元:
from tokenizers.pre_tokenizers import Whitespace, Punctuation, Sequence, ByteLevel
# Split on whitespace and punctuation
tokenizer.pre_tokenizer = Sequence([
Whitespace(),
Punctuation()
])
# Input: "Hello, world!"
# After pre-tokenization: ["Hello", ",", "world", "!"]
常用預分詞器:
Whitespace()- 按空格、製表符、換行符分割ByteLevel()- GPT-2 風格的字節級分割Punctuation()- 隔離標點符號Digits(individual_digits=True)- 單獨分割數字Metaspace()- 用 ▁ 替換空格(SentencePiece 風格)
Post-processing(後處理)
為模型輸入添加特殊 token:
from tokenizers.processors import TemplateProcessing
# BERT-style: [CLS] sentence [SEP]
tokenizer.post_processor = TemplateProcessing(
single="[CLS] $A [SEP]",
pair="[CLS] $A [SEP] $B [SEP]",
special_tokens=[
("[CLS]", 1),
("[SEP]", 2),
],
)
常用模式:
# GPT-2: sentence <|endoftext|>
TemplateProcessing(
single="$A <|endoftext|>",
special_tokens=[("<|endoftext|>", 50256)]
)
# RoBERTa: <s> sentence </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!")
# Get token offsets
for token, offset in zip(output.tokens, output.offsets):
start, end = offset
print(f"{token:10} → [{start:2}, {end:2}): {text[start:end]!r}")
# Output:
# hello → [ 0, 5): 'Hello'
# , → [ 5, 6): ','
# world → [ 7, 12): 'world'
# ! → [12, 13): '!'
使用場景:
- 命名實體識別(將預測結果映射回文本)
- 問答系統(提取答案片段)
- Token 分類(將標籤對齊到原始位置)
與 transformers 集成
使用 AutoTokenizer 加載
from transformers import AutoTokenizer
# AutoTokenizer automatically uses fast tokenizers
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
# Check if using fast tokenizer
print(tokenizer.is_fast) # True
# Access underlying tokenizers.Tokenizer
fast_tokenizer = tokenizer.backend_tokenizer
print(type(fast_tokenizer)) # <class 'tokenizers.Tokenizer'>
將自定義 tokenizer 轉換為 transformers 格式
from tokenizers import Tokenizer
from transformers import PreTrainedTokenizerFast
# Train custom tokenizer
tokenizer = Tokenizer(BPE())
# ... train tokenizer ...
tokenizer.save("my-tokenizer.json")
# Wrap for transformers
transformers_tokenizer = PreTrainedTokenizerFast(
tokenizer_file="my-tokenizer.json",
unk_token="[UNK]",
pad_token="[PAD]",
cls_token="[CLS]",
sep_token="[SEP]",
mask_token="[MASK]"
)
# Use like any transformers tokenizer
outputs = transformers_tokenizer(
"Hello world",
padding=True,
truncation=True,
max_length=512,
return_tensors="pt"
)
常見模式
從迭代器訓練(大型數據集)
from datasets import load_dataset
# Load dataset
dataset = load_dataset("wikitext", "wikitext-103-raw-v1", split="train")
# Create batch iterator
def batch_iterator(batch_size=1000):
for i in range(0, len(dataset), batch_size):
yield dataset[i:i + batch_size]["text"]
# Train tokenizer
tokenizer.train_from_iterator(
batch_iterator(),
trainer=trainer,
length=len(dataset) # For progress bar
)
性能:處理 1GB 數據約需 10-20 分鐘
啟用截斷和填充
# Enable truncation
tokenizer.enable_truncation(max_length=512)
# Enable padding
tokenizer.enable_padding(
pad_id=tokenizer.token_to_id("[PAD]"),
pad_token="[PAD]",
length=512 # Fixed length, or None for batch max
)
# Encode with both
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
# Load tokenizer
tokenizer = Tokenizer.from_file("tokenizer.json")
def encode_batch(texts):
return tokenizer.encode_batch(texts)
# Process large corpus in parallel
with Pool(8) as pool:
# Split corpus into chunks
chunk_size = 1000
chunks = [corpus[i:i+chunk_size] for i in range(0, len(corpus), chunk_size)]
# Encode in parallel
results = pool.map(encode_batch, chunks)
加速比:8 核 CPU 下提升 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,在英文維基百科上測試
Tokenization 速度
| 實現方式 | 1 GB 語料庫 | 吞吐量 |
|---|---|---|
| 純 Python | ~20 分鐘 | ~50 MB/min |
| HF Tokenizers | ~15 秒 | ~4 GB/min |
| 加速比 | 80× | 80× |
測試條件:英文文本,平均句子長度 20 個單詞
內存使用
| 任務 | 內存 |
|---|---|
| 加載 tokenizer | ~10 MB |
| 訓練 BPE (30k 詞表) | ~200 MB |
| 編碼 100 萬條句子 | ~500 MB |
支持的模型
可通過 from_pretrained() 獲取的預訓練 tokenizer:
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
參考資料
- 訓練指南 - 訓練自定義 tokenizer、配置訓練器、處理大型數據集
- 算法深入解析 - 詳細解釋 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)