什么是 Tokenization?
Tokenization(分词)是将原始文本转换为模型可处理的token序列的过程。在深度学习中,模型无法直接处理原始文本,需要将文本分解为更小的单元(tokens),并将其映射为数值表示。
输入文本: "机器学习很有趣"
字符级: ["机", "器", "学", "习", "很", "有", "趣"]
词级: ["机器学习", "很", "有趣"]
子词级: ["机器", "学习", "很", "有趣"]
Tokenization 的重要性
- 词汇表大小控制:平衡表达能力和计算效率
- 未知词处理:处理训练时未见过的词汇
- 语义保持:保留词汇的语义信息
- 多语言支持:统一处理不同语言的文本
传统分词方法的局限性
基于空格的分词
优势
- 简单直观,易于实现
- 保留完整词汇语义
- 适用于英语等语言
劣势
- 词汇表过大,内存占用高
- 无法处理未知词(OOV)
- 不适用于中文等语言
- 无法处理形态变化
字符级分词
优势
- 词汇表小,无OOV问题
- 适用于所有语言
- 能处理拼写错误
劣势
- 序列长度大大增加
- 丢失词汇级别的语义
- 计算成本高
BPE (Byte Pair Encoding) 算法
BPE 是一种数据压缩技术,后来被应用于自然语言处理。它通过迭代地合并最频繁出现的字符对来构建词汇表。
BPE 算法步骤
训练阶段:
- 将训练语料分解为字符序列
- 统计所有相邻字符对的频率
- 合并频率最高的字符对
- 重复步骤2-3,直到达到预设的词汇表大小
BPE 实例演示
初始词汇: ["low", "lower", "newest", "widest"]
字符序列: ["l o w ", "l o w e r ", "n e w e s t ", "w i d e s t "]
迭代1: 最频繁对 "e s" → "es"
["l o w ", "l o w e r ", "n e w es t ", "w i d es t "]
迭代2: 最频繁对 "es t" → "est"
["l o w ", "l o w e r ", "n e w est ", "w i d est "]
迭代3: 最频繁对 "l o" → "lo"
["lo w ", "lo w e r ", "n e w est ", "w i d est "]
字符序列: ["l o w ", "l o w e r ", "n e w e s t ", "w i d e s t "]
迭代1: 最频繁对 "e s" → "es"
["l o w ", "l o w e r ", "n e w es t ", "w i d es t "]
迭代2: 最频繁对 "es t" → "est"
["l o w ", "l o w e r ", "n e w est ", "w i d est "]
迭代3: 最频繁对 "l o" → "lo"
["lo w ", "lo w e r ", "n e w est ", "w i d est "]
BPE 算法训练实现
def train_bpe(corpus, num_merges):
# 初始化词汇表为字符级别
vocab = get_character_vocab(corpus)
for i in range(num_merges):
# 统计字符对频率
pairs = get_pair_frequencies(corpus)
if not pairs:
break
# 找到频率最高的字符对
best_pair = max(pairs, key=pairs.get)
# 合并字符对
corpus = merge_pair(corpus, best_pair)
vocab.add(''.join(best_pair))
return vocab
WordPiece 算法
WordPiece 是Google开发的子词算法,被用于BERT等模型。与BPE类似,但使用不同的合并策略。
WordPiece 与 BPE 的区别
| 特性 | BPE | WordPiece |
|---|---|---|
| 合并策略 | 基于频率 | 基于似然度提升 |
| 评分函数 | count(A,B) | count(A,B) / (count(A) × count(B)) |
| 子词标记 | @@标记 | ##标记 |
| 应用模型 | GPT系列 | BERT系列 |
WordPiece 合并准则
似然度提升公式:
对于字符对 (A, B),合并后的得分为:
$$\text{Score}(A, B) = \frac{\text{freq}(AB)}{\text{freq}(A) \times \text{freq}(B)}$$
WordPiece 示例:
输入:playing
输出:play ##ing
输入:unaffable
输出:un ##aff ##able
输入:playing
输出:play ##ing
输入:unaffable
输出:un ##aff ##able
SentencePiece 算法
SentencePiece 是Google开发的无监督文本分词器,它将文本视为Unicode字符序列,不依赖于语言特定的预处理。
SentencePiece 的核心特点
- 语言无关:统一处理所有语言,包括没有明显词边界的语言
- 可逆性:分词结果可以无损地还原为原始文本
- 空格处理:将空格视为特殊字符 ▁ 进行处理
- 端到端:从原始文本直接到子词,无需预分词
SentencePiece 处理流程
原始文本: "Hello world"
Unicode normalization: "Hello world"
空格替换: "Hello▁world"
子词分割: ["Hell", "o", "▁wor", "ld"]
还原: "Hello world"
Unicode normalization: "Hello world"
空格替换: "Hello▁world"
子词分割: ["Hell", "o", "▁wor", "ld"]
还原: "Hello world"
训练步骤:
- 将文本标准化为Unicode NFC格式
- 将空格替换为特殊符号 ▁
- 应用BPE或Unigram算法学习子词
- 生成词汇表和分词模型
Unigram Language Model
Unigram语言模型是另一种子词分割算法,与BPE采用自底向上的方法不同,它采用自顶向下的方法。
Unigram 算法原理
目标函数:最大化语料库的对数似然
$$\mathcal{L} = \sum_{i=1}^{|D|} \log P(X_i)$$
其中 $P(X_i) = \sum_{x \in S(X_i)} \prod_{j=1}^{|x|} p(x_j)$
算法步骤:
- 初始化一个足够大的候选词汇表
- 使用EM算法优化Unigram语言模型
- 计算每个子词的损失值
- 删除损失最小的子词(保留一定比例)
- 重复步骤2-4直到达到目标词汇表大小
算法对比与选择
| 算法 | 训练复杂度 | 分词速度 | 语言支持 | 代表模型 |
|---|---|---|---|---|
| BPE | 低 | 快 | 需要预处理 | GPT, GPT-2 |
| WordPiece | 中等 | 快 | 需要预处理 | BERT, DistilBERT |
| SentencePiece | 中等 | 快 | 语言无关 | T5, XLNet |
| Unigram | 高 | 中等 | 语言无关 | ALBERT, T5 |
选择建议
- 英语为主:BPE 或 WordPiece
- 多语言:SentencePiece + Unigram
- 计算资源有限:BPE
- 追求最佳性能:SentencePiece + Unigram
实际应用中的考虑因素
词汇表大小设计
- 小词汇表 (8K-16K):计算高效,但可能增加序列长度
- 中等词汇表 (32K-64K):平衡性能和效率的常见选择
- 大词汇表 (100K+):更好的语言覆盖,但内存开销大
特殊token处理
常见特殊token:
[CLS] - 分类任务的开始标记
[SEP] - 句子分隔符
[PAD] - 填充标记
[UNK] - 未知词标记
[MASK] - 掩码语言模型的掩码标记
[CLS] - 分类任务的开始标记
[SEP] - 句子分隔符
[PAD] - 填充标记
[UNK] - 未知词标记
[MASK] - 掩码语言模型的掩码标记
性能优化技巧
- 预计算:缓存常见词的分词结果
- 并行处理:批量处理多个文本
- 内存优化:使用trie树存储词汇表
- 增量训练:支持词汇表的动态更新
🔧 交互式演示
点击按钮体验不同的分词算法: