共计 1574 个字符,预计需要花费 4 分钟才能阅读完成。
核心概念:Token 在 NLP 中的作用
Token 是 NLP 中的基本处理单位,它将文本分割成模型可处理的离散单元。常见的 Token 类型包括:

- 单词级 Token:以空格或标点分隔的完整单词(如 ”hello”)
- 子词级 Token:解决未登录词问题(如 ”unhappiness” 拆分为 ”un”, “happiness”)
- 字符级 Token:单个字符作为最小单元(适合形态丰富的语言)
痛点分析:Token 化常见问题
- 长文本处理:当序列超过模型最大长度限制时(如 BERT 的 512),需要截断或分块
- 特殊字符处理:URL、emoji、混合语言文本的编码一致性难题
- 词汇表外 (OOV) 问题:未登录词导致的信息丢失
- 多义性处理:同一单词在不同语境下的拆分策略(如 ”bank” 作为金融机构或河岸)
技术方案对比
Byte Pair Encoding (BPE)
- 优点:动态生成词汇表,有效平衡词表大小与 OOV 率
- 缺点:可能产生不直观的子词组合(如 ”est”+”ly”=”estly”)
WordPiece
- 优点:基于概率合并,更适合处理前缀 / 后缀(BERT 采用)
- 缺点:需要预训练语言模型统计概率
Unigram Language Model
- 优点:支持概率采样生成多种分词方案
- 缺点:实现复杂度较高
代码示例:BPE 实现
from collections import defaultdict
import re
def get_stats(vocab):
"""统计相邻符号对频次"""
pairs = defaultdict(int)
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_in):
"""合并最高频符号对"""
vocab_out = {}
bigram = re.escape(' '.join(pair))
p = re.compile(r'(?<!\S)' + bigram + r'(?!\S)')
for word in vocab_in:
w_out = p.sub(''.join(pair), word)
vocab_out[w_out] = vocab_in[word]
return vocab_out
# 使用示例
vocab = {"l o w </w>": 5, "l o w e r </w>": 2}
for _ in range(3):
pairs = get_stats(vocab)
best = max(pairs, key=pairs.get)
vocab = merge_vocab(best, vocab)
print(vocab) # 输出合并后的词汇表
性能考量
- 计算效率:子词算法相比字符级减少序列长度,但增加预处理开销
- 内存占用:大词表(如 50k+)会显著增加 embedding 层参数
- 质量影响:
- 过细粒度导致语义碎片化(如拆分为无意义子词)
- 过粗粒度导致 OOV 问题加剧
优化策略:
- 动态长度采样:训练时随机截断不同长度文本
- 词汇表裁剪:根据领域数据调整词表大小
- 混合精度:使用 FP16 存储 embedding 矩阵
避坑指南
- 生产环境经验:
- 始终保留原始文本和 token 映射关系
- 对用户生成内容 (UGC) 做极端 case 测试(如连续空格、特殊符号)
-
监控 OOV 率变化,定期更新词表
-
常见错误:
- 忘记添加 [CLS]/[SEP] 等特殊 token
- 不同语言混合处理时未指定正确分词器
- 未考虑 token 化对 position embedding 的影响
结语
在实际项目中,token 化策略需要结合具体任务和数据特性进行选择。对于中文场景,建议优先测试 WordPiece;处理多语言内容时,可尝试 SentencePiece 的统一切分方案。记住:好的 token 化应该让模型更容易学习文本特征,而不是增加理解难度。
正文完
