Tokenization实战:从BPE到Hugging Face应用 | 吴恩达2025最新课程笔记
摘要: 本文深入讲解大语言模型的第一步------分词(Tokenization),从经典的Byte Pair Encoding(BPE)算法到WordPiece、SentencePiece等主流方案,详解GPT、BERT、LLaMA等模型的分词策略。通过Hugging Face Tokenizers库的实战代码,帮助读者掌握分词器训练、词表构建、特殊token处理等工程实践,理解分词对模型性能的深远影响。
关键词: Tokenization、BPE算法、WordPiece、SentencePiece、Hugging Face Tokenizers、词表构建、子词分词、GPT分词器、BERT分词器、吴恩达课程笔记、NLP预处理
一、为什么分词如此重要?
1.1 分词是LLM的"眼睛"
用户输入:
我爱学习AI
分词器
Tokenizer
Token序列:
101, 2769, 4263, 2110, 749, AI, 102
大语言模型
Transformer
模型输出
核心作用:
- 将文本转换为模型可处理的数字序列
- 决定模型的词汇量和泛化能力
- 影响推理速度和显存占用
1.2 分词的三大挑战
解决方案
挑战
📝 OOV问题
Out-of-Vocabulary
未登录词无法处理
🌍 多语言支持
不同语言差异巨大
中文无空格分隔
⚖️ 粒度平衡
词级太粗,字符级太细
需要折中方案
子词分词
Subword Tokenization
BPE/WordPiece/Unigram
1.3 主流分词方案对比
| 方案 | 代表模型 | 优点 | 缺点 | 词表大小 |
|---|---|---|---|---|
| 词级分词 | Word2Vec | 语义完整 | OOV严重 | 10万+ |
| 字符级 | CharRNN | 无OOV | 序列过长 | <100 |
| BPE | GPT系列 | 平衡,通用 | 无语言先验 | 5万 |
| WordPiece | BERT | 语言感知 | 需要预分词 | 3万 |
| Unigram | T5, mBART | 概率模型 | 计算复杂 | 3.2万 |
| SentencePiece | LLaMA, XLM | 端到端 | 空格特殊处理 | 3.2万 |
二、Byte Pair Encoding(BPE)详解
2.1 BPE算法原理
核心思想:从字符开始,迭代合并出现频率最高的相邻token对。
初始: 字符级分词
l o w e r
统计频次:
er出现最多
合并: low er
再统计:
er_出现最多
合并: low er_
继续统计:
low出现最多
最终: low er_
2.2 BPE训练流程
Step 1: 统计词频
python
corpus = [
"lower",
"lowest",
"newer",
"wider"
]
# 字符级分词 + 词频统计
vocab = {
"l o w e r </w>": 5, # </w>表示词尾
"l o w e s t </w>": 2,
"n e w e r </w>": 6,
"w i d e r </w>": 3
}
Step 2: 迭代合并
Vocab Counter Corpus Vocab Counter Corpus 迭代1 迭代2 迭代N 统计相邻token对 ("e", "r"): 16次 最高频 合并为"er" l o w er </w> 统计相邻token对 ("er", "</w>"): 16次 合并为"er_" 重复直到达到目标词表大小
Python实现:
python
from collections import Counter
import re
def get_stats(vocab):
"""统计相邻token对的频次"""
pairs = Counter()
for word, freq in vocab.items():
symbols = word.split()
for i in range(len(symbols) - 1):
pairs[(symbols[i], symbols[i+1])] += freq
return pairs
def merge_vocab(pair, vocab):
"""合并最高频的token对"""
new_vocab = {}
bigram = ' '.join(pair)
replacement = ''.join(pair)
for word in vocab:
new_word = word.replace(bigram, replacement)
new_vocab[new_word] = vocab[word]
return new_vocab
def train_bpe(corpus, num_merges):
"""
训练BPE分词器
参数:
corpus: 文本列表
num_merges: 合并操作次数
返回:
merge_ops: 合并操作序列
"""
# 1. 初始化词表(字符级)
vocab = {}
for text in corpus:
words = text.split()
for word in words:
word_with_boundary = ' '.join(list(word)) + ' </w>'
vocab[word_with_boundary] = vocab.get(word_with_boundary, 0) + 1
merge_ops = []
# 2. 迭代合并
for i in range(num_merges):
pairs = get_stats(vocab)
if not pairs:
break
# 选择频次最高的pair
best_pair = max(pairs, key=pairs.get)
vocab = merge_vocab(best_pair, vocab)
merge_ops.append(best_pair)
print(f"Merge {i+1}: {best_pair} (freq: {pairs[best_pair]})")
return merge_ops, vocab
# 示例
corpus = [
"low low low low low",
"lower lower lower",
"newest newest newest newest newest newest",
"wider wider wider"
]
merge_ops, final_vocab = train_bpe(corpus, num_merges=10)
print("\n最终词表:")
for word, freq in final_vocab.items():
print(f"{word}: {freq}")
输出示例:
Merge 1: ('e', 's') (freq: 6)
Merge 2: ('es', 't') (freq: 6)
Merge 3: ('est', '</w>') (freq: 6)
Merge 4: ('l', 'o') (freq: 8)
Merge 5: ('lo', 'w') (freq: 8)
...
最终词表:
low </w>: 5
lower </w>: 3
newest </w>: 6
wider </w>: 3
2.3 BPE编码(Encoding)
使用训练好的merge_ops对新文本分词:
python
def bpe_encode(text, merge_ops):
"""
使用BPE分词
参数:
text: 输入文本
merge_ops: 训练好的合并操作序列
"""
# 1. 字符级初始化
word = list(text) + ['</w>']
# 2. 应用merge操作
for pair in merge_ops:
i = 0
while i < len(word) - 1:
if (word[i], word[i+1]) == pair:
word[i:i+2] = [''.join(pair)]
else:
i += 1
return word
# 示例
text = "lowest"
tokens = bpe_encode(text, merge_ops)
print(f"'{text}' → {tokens}")
# 输出: 'lowest' → ['low', 'est', '</w>']
2.4 BPE的优势
应用
优点
✅ 无OOV问题
任何词都能分解为子词
✅ 数据驱动
无需语言学知识
✅ 压缩效率高
常用词整体保留
✅ 多语言通用
对语言无感知
GPT系列
RoBERTa
BART
三、WordPiece:BERT的分词方案
3.1 WordPiece vs BPE
核心区别:合并策略不同
| 特性 | BPE | WordPiece |
|---|---|---|
| 合并依据 | 频次最高 | 似然增益最大 |
| 公式 | max count(A,B) | max P(AB) / (P(A)×P(B)) |
| 语言感知 | 无 | 有(似然偏好完整词) |
| 前缀标记 | 无 | ##表示非词首 |
似然增益公式 :
Score(A,B)=P(AB)P(A)×P(B) \text{Score}(A, B) = \frac{P(AB)}{P(A) \times P(B)} Score(A,B)=P(A)×P(B)P(AB)
3.2 WordPiece特殊标记
python
# BERT的特殊token
special_tokens = {
"[PAD]": 0, # 填充
"[UNK]": 100, # 未知词
"[CLS]": 101, # 句子开始
"[SEP]": 102, # 句子分隔
"[MASK]": 103 # 掩码(用于MLM任务)
}
# 示例分词
text = "playing"
tokens = ["play", "##ing"] # ##表示非词首
playing
play
##ing
unhappiness
un
##happiness
3.3 Hugging Face实现WordPiece
python
from tokenizers import Tokenizer
from tokenizers.models import WordPiece
from tokenizers.trainers import WordPieceTrainer
from tokenizers.pre_tokenizers import Whitespace
# 1. 创建WordPiece tokenizer
tokenizer = Tokenizer(WordPiece(unk_token="[UNK]"))
tokenizer.pre_tokenizer = Whitespace() # 空格预分词
# 2. 定义trainer
trainer = WordPieceTrainer(
vocab_size=30000,
special_tokens=["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"],
min_frequency=2
)
# 3. 训练
files = ["train.txt"]
tokenizer.train(files, trainer)
# 4. 保存
tokenizer.save("wordpiece-tokenizer.json")
# 5. 使用
output = tokenizer.encode("I love playing football")
print(f"Tokens: {output.tokens}")
print(f"IDs: {output.ids}")
# 输出示例:
# Tokens: ['I', 'love', 'play', '##ing', 'football']
# IDs: [256, 891, 1234, 5678, 9012]
四、SentencePiece:端到端的无损分词
4.1 SentencePiece的特点
核心创新:将空格视为普通字符,直接从原始文本训练。
SentencePiece
WordPiece/BPE
问题
优势
原始文本
预分词
(按空格)
子词分词
原始文本
直接训练
空格=_字符
信息丢失
无法还原
无损可逆
示例:
python
# 传统方案
"Hello World" → ["Hello", "World"] → ["Hel", "##lo", "World"]
# 问题:无法区分"HelloWorld"和"Hello World"
# SentencePiece
"Hello World" → ["▁Hello", "▁World"] # ▁代表空格
# 解码:"▁Hello▁World" → "Hello World" (完美还原)
4.2 SentencePiece训练与使用
python
import sentencepiece as spm
# 1. 训练SentencePiece模型
spm.SentencePieceTrainer.train(
input='train.txt',
model_prefix='sp_model',
vocab_size=32000,
character_coverage=0.9995, # 字符覆盖率
model_type='unigram', # 可选: unigram/bpe/char/word
pad_id=0,
unk_id=1,
bos_id=2,
eos_id=3,
user_defined_symbols=['<|endoftext|>'] # 自定义token
)
# 2. 加载模型
sp = spm.SentencePieceProcessor()
sp.load('sp_model.model')
# 3. 编码
text = "I love NLP!"
tokens = sp.encode_as_pieces(text)
ids = sp.encode_as_ids(text)
print(f"Text: {text}")
print(f"Tokens: {tokens}")
print(f"IDs: {ids}")
# 输出示例:
# Text: I love NLP!
# Tokens: ['▁I', '▁love', '▁N', 'LP', '!']
# IDs: [123, 456, 789, 1011, 12]
# 4. 解码(无损还原)
decoded = sp.decode_ids(ids)
print(f"Decoded: {decoded}")
# Decoded: I love NLP! (完全一致)
4.3 LLaMA的SentencePiece配置
python
# LLaMA使用的SentencePiece参数
{
"vocab_size": 32000,
"model_type": "BPE", # LLaMA用BPE而非Unigram
"character_coverage": 1.0,
"byte_fallback": True, # 使用byte级回退处理未知字符
"split_by_unicode_script": True,
"split_by_number": True,
"split_by_whitespace": True,
"remove_extra_whitespaces": False,
"normalization_rule_name": "identity" # 不做规范化
}
五、不同模型的分词策略对比
5.1 GPT系列(BPE)
python
from transformers import GPT2Tokenizer
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
text = "Hello, I'm a language model."
tokens = tokenizer.tokenize(text)
ids = tokenizer.encode(text)
print(f"Tokens: {tokens}")
# ['Hello', ',', 'ĠI', "'m", 'Ġa', 'Ġlanguage', 'Ġmodel', '.']
# 注意:Ġ表示空格
print(f"IDs: {ids}")
# [15496, 11, 314, 1101, 257, 3303, 2746, 13]
# 词表大小
print(f"Vocab size: {tokenizer.vocab_size}")
# 50257
GPT分词特点:
Hello, world!
BPE分词
Hello
,
Ġworld
!
5.2 BERT系列(WordPiece)
python
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
text = "Hello, I'm learning NLP."
tokens = tokenizer.tokenize(text)
ids = tokenizer.encode(text, add_special_tokens=True)
print(f"Tokens: {tokens}")
# ['hello', ',', 'i', "'", 'm', 'learning', 'nl', '##p', '.']
print(f"IDs: {ids}")
# [101, 7592, 1010, 1045, 1005, 1049, 4083, 17953, 2361, 1012, 102]
# 101=[CLS], 102=[SEP]
# 词表大小
print(f"Vocab size: {tokenizer.vocab_size}")
# 30522
BERT vs GPT分词:
| 特性 | GPT | BERT |
|---|---|---|
| 大小写 | 保留 | 小写化(uncased版本) |
| 空格标记 | Ġ(前置) | 无 |
| 子词标记 | 无 | ##(后缀) |
| 特殊token | <|endoftext|> | [CLS], [SEP], [MASK] |
5.3 LLaMA系列(SentencePiece BPE)
python
from transformers import LlamaTokenizer
tokenizer = LlamaTokenizer.from_pretrained('meta-llama/Llama-2-7b-hf')
text = "Hello! I'm learning AI."
tokens = tokenizer.tokenize(text)
ids = tokenizer.encode(text)
print(f"Tokens: {tokens}")
# ['▁Hello', '!', '▁I', "'", 'm', '▁learning', '▁AI', '.']
print(f"IDs: {ids}")
# [1, 15043, 29991, 306, 29915, 29885, 6509, 319, 29902, 29889]
# 1 = <s>(BOS token)
# 词表大小
print(f"Vocab size: {tokenizer.vocab_size}")
# 32000
LLaMA特点:
- ✅ 无损可逆(SentencePiece)
- ✅ 多语言友好
- ✅ Byte-level fallback处理未知字符
六、分词对模型性能的影响
6.1 词表大小的权衡
词表过大 >100K
❌ 参数膨胀
显存占用高
训练困难
适中 30K-50K
✅ 平衡
效率与性能兼顾
词表过小 <10K
❌ 序列过长
计算量增加
上下文受限
实验数据:
| 词表大小 | 平均序列长度 | 参数量增加 | Perplexity |
|---|---|---|---|
| 8K | 1.8x | - | 18.5 |
| 16K | 1.5x | +1.2% | 17.2 |
| 32K | 1.0x | +2.4% | 16.8 (最优) |
| 64K | 0.7x | +4.8% | 17.1 |
| 128K | 0.5x | +9.6% | 17.6 |
6.2 中文分词的挑战
问题:中文没有天然的分隔符
python
# 英文
"I love AI" → ["I", "love", "AI"] # 空格天然分隔
# 中文
"我爱AI" → ???
# 方案1: 字符级 ["我", "爱", "A", "I"] # 序列过长
# 方案2: 词级 ["我", "爱", "AI"] # 需要分词工具
# 方案3: 子词级 ["我", "爱", "AI"] # SentencePiece最佳
对比实验:
python
from transformers import BertTokenizer, GPT2Tokenizer
text_cn = "我爱学习人工智能技术"
# BERT(WordPiece,有中文词表)
bert_tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
bert_tokens = bert_tokenizer.tokenize(text_cn)
print(f"BERT: {bert_tokens}")
# ['我', '爱', '学', '习', '人', '工', '智', '能', '技', '术'] # 字符级
# GPT-2(BPE,无中文优化)
gpt2_tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
gpt2_tokens = gpt2_tokenizer.tokenize(text_cn)
print(f"GPT-2: {gpt2_tokens}")
# 乱码或大量未知token!
# LLaMA(SentencePiece,多语言)
llama_tokenizer = LlamaTokenizer.from_pretrained('meta-llama/Llama-2-7b-hf')
llama_tokens = llama_tokenizer.tokenize(text_cn)
print(f"LLaMA: {llama_tokens}")
# ['▁我', '爱', '学习', '人工智能', '技术'] # 子词级,更合理
6.3 分词效率对比
文本压缩效率
英文: 'Hello world'
中文: '你好世界'
GPT: 2 tokens
(Hello, Ġworld)
BERT: 2 tokens
(hello, world)
GPT-2: 8+ tokens
(低效)
BERT中文: 4 tokens
(你,好,世,界)
LLaMA: 2-3 tokens
(你好,世界)
七、Hugging Face Tokenizers完整实战
7.1 从零训练一个BPE Tokenizer
python
from tokenizers import Tokenizer
from tokenizers.models import BPE
from tokenizers.trainers import BpeTrainer
from tokenizers.pre_tokenizers import Whitespace
from tokenizers.processors import TemplateProcessing
# 1. 初始化BPE模型
tokenizer = Tokenizer(BPE(unk_token="<unk>"))
# 2. 预分词器(按空格)
tokenizer.pre_tokenizer = Whitespace()
# 3. 定义训练器
trainer = BpeTrainer(
vocab_size=30000,
min_frequency=2,
special_tokens=[
"<pad>",
"<unk>",
"<s>", # 句子开始
"</s>", # 句子结束
"<mask>"
],
show_progress=True
)
# 4. 训练(可以从文件或迭代器)
files = ["data/train.txt"]
tokenizer.train(files, trainer)
# 或从迭代器训练
# def batch_iterator():
# for i in range(1000):
# yield f"This is sentence {i}"
# tokenizer.train_from_iterator(batch_iterator(), trainer)
# 5. 后处理:添加特殊token
tokenizer.post_processor = TemplateProcessing(
single="<s> $A </s>",
pair="<s> $A </s> </s> $B </s>",
special_tokens=[
("<s>", tokenizer.token_to_id("<s>")),
("</s>", tokenizer.token_to_id("</s>")),
],
)
# 6. 保存
tokenizer.save("my_bpe_tokenizer.json")
# 7. 使用
text = "Hello world! This is a test."
output = tokenizer.encode(text)
print(f"Original: {text}")
print(f"Tokens: {output.tokens}")
print(f"IDs: {output.ids}")
print(f"Decoded: {tokenizer.decode(output.ids)}")
7.2 自定义Normalizer和PreTokenizer
python
from tokenizers import normalizers, pre_tokenizers
from tokenizers.normalizers import NFD, StripAccents, Lowercase
from tokenizers.pre_tokenizers import ByteLevel
# 1. 组合多个normalizer
tokenizer.normalizer = normalizers.Sequence([
NFD(), # Unicode规范化
StripAccents(), # 去除重音符号
Lowercase() # 小写化
])
# 2. Byte-level预分词(GPT-2风格)
tokenizer.pre_tokenizer = ByteLevel(add_prefix_space=True)
# 测试
text = "Café résumé"
normalized = tokenizer.normalizer.normalize_str(text)
print(f"Normalized: {normalized}")
# Normalized: cafe resume
7.3 批量编码和高效处理
python
from tokenizers import Tokenizer
tokenizer = Tokenizer.from_file("my_bpe_tokenizer.json")
# 1. 批量编码
texts = [
"Hello world!",
"How are you?",
"I'm learning NLP."
]
# 单线程
outputs = tokenizer.encode_batch(texts)
for i, output in enumerate(outputs):
print(f"Text {i}: {output.tokens}")
# 2. 多线程加速
tokenizer.enable_padding(pad_id=0, pad_token="<pad>")
tokenizer.enable_truncation(max_length=512)
outputs = tokenizer.encode_batch(texts, is_pretokenized=False)
# 3. 转换为PyTorch/TensorFlow格式
input_ids = [output.ids for output in outputs]
attention_mask = [output.attention_mask for output in outputs]
import torch
input_ids = torch.tensor(input_ids)
attention_mask = torch.tensor(attention_mask)
print(f"Input IDs shape: {input_ids.shape}")
print(f"Attention mask shape: {attention_mask.shape}")
八、分词器的性能分析与优化
8.1 分词速度对比
python
import time
from transformers import AutoTokenizer
models = [
"bert-base-uncased",
"gpt2",
"meta-llama/Llama-2-7b-hf",
"google/flan-t5-base"
]
text = "This is a test sentence for tokenization speed comparison." * 100
for model_name in models:
tokenizer = AutoTokenizer.from_pretrained(model_name)
start = time.time()
for _ in range(100):
tokens = tokenizer.encode(text)
end = time.time()
print(f"{model_name}: {(end-start)/100*1000:.2f} ms/iteration")
# 输出示例:
# bert-base-uncased: 0.52 ms/iteration
# gpt2: 0.48 ms/iteration
# meta-llama/Llama-2-7b-hf: 0.61 ms/iteration
# google/flan-t5-base: 0.55 ms/iteration
8.2 内存占用分析
python
import sys
tokenizer = AutoTokenizer.from_pretrained("gpt2")
# 1. 词表大小
vocab_size = tokenizer.vocab_size
print(f"Vocab size: {vocab_size}")
# 2. 估算内存占用
# 假设每个token ID是int32(4 bytes)
embedding_memory = vocab_size * 768 * 4 / 1024 / 1024 # 768是embedding维度
print(f"Embedding layer: {embedding_memory:.2f} MB")
# LM Head(输出层)
lm_head_memory = vocab_size * 768 * 4 / 1024 / 1024
print(f"LM Head: {lm_head_memory:.2f} MB")
print(f"Total: {(embedding_memory + lm_head_memory):.2f} MB")
# 输出示例(GPT-2):
# Vocab size: 50257
# Embedding layer: 147.07 MB
# LM Head: 147.07 MB
# Total: 294.14 MB (占模型总参数的20%+)
8.3 分词质量评估
python
def evaluate_tokenizer(tokenizer, test_texts):
"""
评估分词器质量
"""
total_tokens = 0
total_chars = 0
oov_count = 0
for text in test_texts:
tokens = tokenizer.tokenize(text)
total_tokens += len(tokens)
total_chars += len(text)
# 统计UNK token
oov_count += sum(1 for t in tokens if t in ['<unk>', '[UNK]', 'Ġ<unk>'])
# 1. 压缩率(越小越好)
compression_ratio = total_tokens / total_chars
# 2. OOV率(越低越好)
oov_rate = oov_count / total_tokens
print(f"压缩率: {compression_ratio:.3f}")
print(f"OOV率: {oov_rate:.3%}")
print(f"平均token长度: {total_chars/total_tokens:.2f} chars/token")
return {
'compression_ratio': compression_ratio,
'oov_rate': oov_rate
}
# 测试
test_texts = [
"The quick brown fox jumps over the lazy dog.",
"I love machine learning and natural language processing!",
"This is a test of the tokenizer's performance."
]
tokenizer_gpt = AutoTokenizer.from_pretrained("gpt2")
tokenizer_bert = AutoTokenizer.from_pretrained("bert-base-uncased")
print("GPT-2:")
evaluate_tokenizer(tokenizer_gpt, test_texts)
print("\nBERT:")
evaluate_tokenizer(tokenizer_bert, test_texts)
九、常见问题与最佳实践
9.1 特殊token的处理
python
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("gpt2")
# 1. 查看特殊token
print(f"BOS token: {tokenizer.bos_token} (ID: {tokenizer.bos_token_id})")
print(f"EOS token: {tokenizer.eos_token} (ID: {tokenizer.eos_token_id})")
print(f"PAD token: {tokenizer.pad_token} (ID: {tokenizer.pad_token_id})")
print(f"UNK token: {tokenizer.unk_token} (ID: {tokenizer.unk_token_id})")
# 2. 添加新的特殊token
new_tokens = ["<|system|>", "<|user|>", "<|assistant|>"]
tokenizer.add_special_tokens({'additional_special_tokens': new_tokens})
print(f"New vocab size: {len(tokenizer)}")
# 3. 使用特殊token
text = "<|system|> You are a helpful assistant. <|user|> Hello!"
tokens = tokenizer.tokenize(text)
print(f"Tokens: {tokens}")
# 4. 重要:模型embedding需要resize
# model.resize_token_embeddings(len(tokenizer))
9.2 处理长文本
python
# 方案1: Truncation(截断)
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
long_text = "This is a very long text..." * 1000
encoded = tokenizer(
long_text,
max_length=512,
truncation=True, # 超过512截断
padding='max_length',
return_tensors='pt'
)
print(f"Input shape: {encoded['input_ids'].shape}") # torch.Size([1, 512])
# 方案2: Sliding Window(滑动窗口)
def encode_with_sliding_window(text, tokenizer, max_length=512, stride=256):
"""
滑动窗口编码
"""
tokens = tokenizer.tokenize(text)
chunks = []
for i in range(0, len(tokens), stride):
chunk = tokens[i:i+max_length]
chunk_ids = tokenizer.convert_tokens_to_ids(chunk)
chunks.append(chunk_ids)
if i + max_length >= len(tokens):
break
return chunks
chunks = encode_with_sliding_window(long_text, tokenizer)
print(f"分成 {len(chunks)} 个chunks")
9.3 多语言分词
python
# 使用XLM-RoBERTa(100+语言)
from transformers import XLMRobertaTokenizer
tokenizer = XLMRobertaTokenizer.from_pretrained('xlm-roberta-base')
texts = {
'en': "Hello world!",
'zh': "你好世界!",
'ja': "こんにちは世界!",
'ar': "مرحبا بالعالم!",
'ru': "Привет мир!"
}
for lang, text in texts.items():
tokens = tokenizer.tokenize(text)
print(f"{lang}: {tokens}")
# 输出:
# en: ['▁Hello', '▁world', '!']
# zh: ['▁你好', '世界', '!']
# ja: ['▁', 'こんにちは', '世界', '!']
# ar: ['▁مرحبا', '▁بالعالم', '!']
# ru: ['▁Привет', '▁мир', '!']
十、总结与最佳实践
10.1 分词器选择指南
英文为主
多语言
中文为主
生成
理解
字符级
子词级
你的需求?
语言?
English
Multi
Chinese
模型类型?
GPT系列
BPE
BERT系列
WordPiece
XLM-RoBERTa
SentencePiece
粒度?
BERT中文
字符级
LLaMA
SentencePiece
10.2 关键要点总结
| 问题 | 解决方案 | 最佳实践 |
|---|---|---|
| OOV | 使用子词分词(BPE/WordPiece) | 词表30K-50K |
| 多语言 | SentencePiece + byte-level fallback | XLM-R, mT5 |
| 长文本 | Sliding window + sparse attention | Longformer |
| 中文 | SentencePiece或字符级 | LLaMA, BERT中文 |
| 效率 | 预计算+缓存 | Hugging Face Fast Tokenizers |
10.3 未来趋势
未来方向
当前主流
BPE
GPT系列
WordPiece
BERT系列
SentencePiece
LLaMA系列
Token-free模型
直接处理bytes
动态词表
根据任务调整
多模态分词
统一文本/图像/音频
研究前沿:
- Byte-level模型: ByT5, CANINE (无需分词)
- 多模态统一: CLIP, Flamingo (统一tokenizer)
- 可学习分词: 端到端训练分词器
十一、练习与资源
练习题
1. 手动实现BPE
python
# 补全BPE编码函数
def bpe_encode(text, merge_ops):
# TODO: 实现BPE分词
pass
2. 对比不同tokenizer
python
# 对比GPT vs BERT vs LLaMA在同一文本上的分词结果
text = "I'm learning about transformers and tokenization!"
# TODO: 分析压缩率、token数量、OOV等
3. 训练自定义tokenizer
python
# 在自己的数据集上训练BPE tokenizer
# TODO: 使用Hugging Face Tokenizers库