Tokenization:文本分词技术详解
--- title: "Tokenization:文本分词技术详解" description: "深入理解BPE、WordPiece、SentencePiece等分词算法及其在LLM中的应用" tags: ["Tokenization", "BPE", "分词", "NLP"] category: "llm" icon: "🧠"
Tokenization:文本分词技术详解
为什么需要Tokenization
大语言模型无法直接处理原始文本,需要将文本转换为数字表示。Tokenization(分词)就是将文本切分成更小单元(token)的过程。
不同的分词策略会显著影响模型的性能和效率:
# 不同分词器对同一文本的处理差异
text = "人工智能正在改变世界"
# 字符级分词
char_tokens = list(text) # ['人', '工', '智', '能', '正', '在', '改', '变', '世', '界']
# 词级分词(假设)
word_tokens = ['人工智能', '正在', '改变', '世界']
# 子词级分词(BPE)
# 可能是: ['人工', '智能', '正在', '改变', '世界']
主流分词算法
1. BPE(Byte Pair Encoding)
BPE 是目前最流行的分词算法,GPT 系列和许多其他模型都使用它。
算法原理
- 初始化:将文本拆分为单个字符
- 统计:计算相邻字符对的频率
- 合并:将最频繁的字符对合并为新token
- 重复:直到达到预设的词汇表大小
def simple_bpe(text, num_merges=10):
"""简化的BPE算法演示"""
# 初始化为字符级
tokens = list(text)
vocab = set(tokens)
for i in range(num_merges):
# 统计相邻对频率
pairs = {}
for j in range(len(tokens) - 1):
pair = (tokens[j], tokens[j + 1])
pairs[pair] = pairs.get(pair, 0) + 1
if not pairs:
break
# 找到最频繁的对
best_pair = max(pairs, key=pairs.get)
new_token = best_pair[0] + best_pair[1]
# 合并
new_tokens = []
j = 0
while j < len(tokens):
if j < len(tokens) - 1 and (tokens[j], tokens[j+1]) == best_pair:
new_tokens.append(new_token)
j += 2
else:
new_tokens.append(tokens[j])
j += 1
tokens = new_tokens
vocab.add(new_token)
print(f"合并 {i+1}: {best_pair} -> {new_token}")
return tokens, vocab
# 示例
text = "aabbaaabba"
tokens, vocab = simple_bpe(text, num_merges=3)
print(f"最终tokens: {tokens}")
print(f"词汇表: {vocab}")
2. WordPiece
WordPiece 是 BERT 使用的分词算法,与 BPE 类似,但使用不同的合并策略。
# WordPiece的合并策略
# BPE: 选择频率最高的对
# WordPiece: 选择使语言模型似然最大化的对
# 使用HuggingFace的WordPiece分词器
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
text = "自然语言处理是人工智能的重要分支"
tokens = tokenizer.tokenize(text)
print(tokens)
# ['自', '然', '语', '言', '处', '理', '是', '人', '工', '智', '能', '的', '重', '要', '分', '支']
3. SentencePiece
SentencePiece 是一个独立于语言的分词工具,特别适合处理中文、日文等无空格分隔的语言。
import sentencepiece as spm
# 训练SentencePiece模型
# spm.SentencePieceTrainer.train(
# input='corpus.txt',
# model_prefix='my_tokenizer',
# vocab_size=32000,
# model_type='bpe'
# )
# 加载并使用
sp = spm.SentencePieceProcessor(model_file='my_tokenizer.model')
tokens = sp.encode("你好世界", out_type=str)
print(tokens) # ['▁你好', '世界']
实际使用示例
使用HuggingFace Tokenizers
from transformers import AutoTokenizer
# 加载不同模型的分词器
models = {
'GPT-2': 'gpt2',
'BERT': 'bert-base-uncased',
'T5': 't5-small',
'LLaMA': 'meta-llama/Llama-2-7b-hf'
}
text = "Large language models are transforming AI"
for name, model_name in models.items():
try:
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokens = tokenizer.tokenize(text)
print(f"{name}: {len(tokens)} tokens")
print(f" Tokens: {tokens[:10]}...")
except:
print(f"{name}: 无法加载")
分词与反分词
from transformers import GPT2Tokenizer
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
# 编码
text = "Hello, how are you?"
encoded = tokenizer(text)
print(f"Token IDs: {encoded['input_ids']}")
# [15496, 11, 703, 389, 345, 30]
# 解码
decoded = tokenizer.decode(encoded['input_ids'])
print(f"Decoded: {decoded}")
# "Hello, how are you?"
特殊Token处理
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
text = "Hello, [MASK] are you?"
# 添加特殊token
inputs = tokenizer(
text,
padding='max_length',
max_length=20,
truncation=True,
return_tensors='pt'
)
print(f"Input IDs: {inputs['input_ids']}")
print(f"Attention Mask: {inputs['attention_mask']}")
print(f"Token Type IDs: {inputs['token_type_ids']}")
中文分词的挑战
中文分词有其特殊性,因为中文没有天然的空格分隔:
# 中文分词的不同粒度
text = "北京大学计算机科学技术研究所"
# 字符级
char_tokens = list(text)
# 基于词典的分词
import jieba
word_tokens = list(jieba.cut(text))
# ['北京大学', '计算机', '科学', '技术', '研究所']
# BPE分词(可能的结果)
# ['北京', '大学', '计算', '机科', '学技', '术研', '究所']
词汇表大小的选择
词汇表大小是重要的超参数,需要在以下因素间权衡:
# 词汇表大小的影响
vocab_sizes = [32000, 50000, 100000]
for vocab_size in vocab_sizes:
# 假设平均每个token覆盖的字符数
avg_chars_per_token = 3.5 # 中文大约2-4个字符
# 编码1000字符需要的token数
tokens_needed = 1000 / avg_chars_per_token
# 模型嵌入层参数量
embedding_params = vocab_size * 4096 # 假设hidden_size=4096
print(f"Vocab {vocab_size}: ~{tokens_needed:.0f} tokens/1000chars, "
f"embedding params: {embedding_params/1e6:.1f}M")
优化分词效率
# 批量分词优化
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained('gpt2')
texts = ["Hello world"] * 1000
# 批量编码比逐条编码快得多
import time
start = time.time()
batch_encoded = tokenizer(texts, padding=True, truncation=True, max_length=512)
batch_time = time.time() - start
print(f"批量编码1000条: {batch_time:.3f}秒")
print(f"平均速度: {len(texts)/batch_time:.0f}条/秒")
总结
Tokenization 是大语言模型处理文本的第一步,不同的分词算法会显著影响模型的性能。理解 BPE、WordPiece、SentencePiece 等算法的原理,对于选择合适的分词器和优化模型表现至关重要。在实际应用中,通常直接使用预训练模型配套的分词器即可。