第一章:初识 Tokenizers 库
1.1 什么是 HuggingFace Tokenizers
| 概念名称 | 说明 | 注意事项 |
|---|
| HuggingFace Tokenizers | 一个独立的、高性能的分词库,专为自然语言处理任务设计,提供快速、灵活的文本到模型输入的转换能力。底层使用 Rust 实现,Python 接口调用,速度快于纯 Python 分词器。 | 不要与 transformers 库混淆,它是独立库,但常与其配合使用。 |
| 核心目标 | 将原始文本转换为模型可接受的数字表示(如 token IDs、attention mask、token type ids 等)。 | 分词是 NLP 模型训练和推理的第一步,直接影响模型性能。 |
| 主要特性 | 支持 BPE、WordPiece、Unigram、SentencePiece 等主流子词算法;支持并行处理;可训练自定义分词器;支持特殊标记管理。 | 所有操作均不可逆(无法完全还原原始空格和标点),需注意预处理一致性。 |
| 概念名称 | 说明 | 注意事项 |
|---|
| tokenizers 库 | 独立的分词引擎,负责高效执行分词逻辑,提供 Tokenizer 类及其组件(Normalizer, Model 等)。 | 可单独使用,不依赖 transformers。 |
| transformers 库 | 高层模型库,包含预训练模型(BERT、GPT 等)、训练接口和 tokenizer 包装类。 | 使用 PreTrainedTokenizer 和 PreTrainedTokenizerFast 包装 tokenizers.Tokenizer 实例。 |
| 关系总结 | transformers 中的 AutoTokenizer.from_pretrained() 若加载的是 Fast 版本,则内部使用 tokenizers 库;否则使用慢速 Python 实现。 | 推荐使用 Fast Tokenizer(基于 tokenizers)以获得更好性能。 |
| TokenizerFast 类 | transformers 提供的包装类,将 tokenizers.Tokenizer 集成进来,提供统一 API。 | 支持 .encode(), .batch_encode_plus() 等方法,行为与慢速 tokenizer 一致。 |
1.3 安装与环境配置
| 操作步骤 | 操作细节 | 注意事项 |
|---|
| 安装 tokenizers 库 | 在终端运行:pip install tokenizers | 建议在虚拟环境中安装,避免依赖冲突。 |
| 验证安装 | 在 Python 中执行:from tokenizers import Tokenizer,若无报错则安装成功。 | print(Tokenizer) 可确认导入成功。 |
| 安装 transformers(可选) | 如需与模型集成,运行:pip install transformers | 大部分场景需要同时安装 transformers。 |
| 查看版本 | 运行:python -c "from tokenizers import version; print(version)" | 建议使用最新稳定版以获得最佳功能支持。 |
| 兼容性要求 | Python 3.7+,操作系统不限(Linux/macOS/Windows 均支持) | Windows 用户无需额外配置,pip 安装即用。 |
1.4 快速入门:一个完整的分词示例
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
Tokenizer.from_pretrained | Tokenizer.from_pretrained(model_name) | 从 HuggingFace Hub 加载预训练分词器 | from tokenizers import Tokenizer
tok = Tokenizer.from_pretrained("bert-base-uncased") | model_name 需为支持的模型标识符,如 "gpt2", "roberta-base" |
encode | tok.encode(text) → Encoding 对象 | 对单个文本进行编码,返回包含 token IDs 等信息的对象 | encoding = tok.encode("Hello, world!")
print(encoding.ids) # [101, 7592, 1010, 2088, 102] | 返回对象支持 .tokens(), .offsets(), .attention_mask() 等方法 |
encode_batch | tok.encode_batch(list_of_texts) | 批量编码多个文本,效率更高 | texts = ["Hi there!", "How are you?"]
encodings = tok.encode_batch(texts) | 推荐用于数据集预处理,充分利用并行能力 |
get_vocab | tok.get_vocab() → dict | 获取当前词汇表映射(token → id) | vocab = tok.get_vocab()
print(len(vocab)) # 如 30522 | 词汇表大小取决于模型类型 |
decode | tok.decode(token_ids, skip_special_tokens=True) | 将 token IDs 转回原始文本 | decoded = tok.decode([7592, 1010, 2088])
# "hello, world" | 设置 skip_special_tokens=True 可去除 [CLS], [SEP] 等标记 |
示例完整流程:
from tokenizers import Tokenizer
# 加载预训练分词器
tok = Tokenizer.from_pretrained("bert-base-uncased")
# 编码文本
encoding = tok.encode("Transformers are great!")
# 输出结果
print("Tokens:", encoding.tokens)
print("IDs:", encoding.ids)
print("Decoded:", tok.decode(encoding.ids, skip_special_tokens=True))
输出示例:
Tokens: ['[CLS]', 'transformers', 'are', 'great', '!', '[SEP]']
IDs: [101, 19081, 2024, 2307, 999, 102]
Decoded: transformers are great!
第二章:核心组件与工作流程
2.1 分词流程总览(PreTokenizer → Tokenizer → PostProcessor)
| 概念名称 | 说明 | 注意事项 |
|---|
| 整体流程 | 文本输入 → Normalizer → PreTokenizer → Model (BPE/WordPiece等) → PostProcessor → Encoder → 输出 IDs/Attention Mask 等 | 各组件可独立配置,组合灵活。 |
| Normalizer | 对原始文本进行标准化处理,如小写化、Unicode 规范化、去除重音等。 | 应在 PreTokenizer 之前执行,确保输入一致性。 |
| PreTokenizer | 将文本按规则切分为子单元(如按空格、标点),为后续子词分割做准备。 | 切分方式影响最终 tokenization 结果,需根据语言选择。 |
| Model | 核心分词模型(如 BPE),将 PreTokenizer 输出的词进一步拆分为子词单元。 | 模型决定词汇生成方式和最终 token 表示。 |
| PostProcessor | 在模型输出后添加特殊结构,如 BERT 的 [CLS] + A + [SEP] + B + [SEP]。 | 用于构造特定任务的输入格式,如句子对分类。 |
| Encoder | 将处理后的 token 序列编码为模型所需格式(IDs, type_ids, attention_mask)。 | 最终输出可直接送入模型 forward 函数。 |
2.2 Normalizer:文本预处理
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
Normalizer | from tokenizers.normalizers import Normalizer | 抽象基类,定义标准化接口 | 不可直接实例化 | 需使用具体实现类 |
Lowercase | Lowercase() | 将所有字符转为小写 | from tokenizers.normalizers import Lowercase
norm = Lowercase() | 常用于不区分大小写的模型(如 bert-base-uncased) |
NFD / NFKD / NFC / NFKC | NFD(), NFKD(), NFC(), NFKC() | Unicode 标准化形式(分解/组合) | from tokenizers.normalizers import NFD
norm = NFD() | 推荐使用 NFC 保证字符一致性 |
Strip | Strip(left=True, right=True) | 去除字符串首尾空白 | Strip(left=True, right=False) # 仅去左空格 | 默认左右都去 |
StripAccents | StripAccents() | 去除重音符号(如 é → e) | from tokenizers.normalizers import StripAccents
norm = StripAccents() | 对多语言文本有用 |
Replace | Replace(pattern, content) | 正则替换(pattern 为字符串或 Regex 对象) | Replace("http://", "http ") | 可用于 URL、邮箱等预处理 |
Sequence | Sequence([norm1, norm2]) | 组合多个 Normalizer 顺序执行 | Sequence([Lowercase(), StripAccents()]) | 执行顺序从左到右 |
2.3 PreTokenizer:分词前切分
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
PreTokenizer | from tokenizers.pre_tokenizers import PreTokenizer | 抽象基类 | 不可直接实例化 | 需使用具体实现 |
Whitespace | Whitespace() | 按空白字符(空格、制表符等)切分 | from tokenizers.pre_tokenizers import Whitespace
pre_tok = Whitespace() | 最常见方式 |
Punctuation | Punctuation(behavior="isolated") | 将标点符号单独切出 | Punctuation(behavior="removed") # 可选 removed/isolated | behavior 控制标点处理方式 |
Digits | Digits(individual_digits=True) | 数字是否逐位切分 | Digits(individual_digits=False) # 保持数字整体 | 中文场景常设为 True |
Metaspace | Metaspace(replacement="_", add_prefix_space=True) | 用特殊字符替换空格(用于 BPE) | Metaspace(replacement="▁") | 常见于 SentencePiece 风格 |
ByteLevel | ByteLevel(add_prefix_space=True) | 按字节级别切分(用于 GPT-2、RoBERTa) | from tokenizers.pre_tokenizers import ByteLevel
pre_tok = ByteLevel() | 支持 emoji 和罕见字符 |
Sequence | Sequence([pre1, pre2]) | 组合多个 PreTokenizer | Sequence([Whitespace(), Punctuation()]) | 顺序执行,前一个输出为后一个输入 |
2.4 Model:核心分词模型(BPE、WordPiece、Unigram、SentencePiece)
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
BPE | BPE(vocab, merges, unk_token=None, end_of_word_suffix=None) | 构建 BPE 模型 | from tokenizers.models import BPE
model = BPE(vocab, merges) | vocab 和 merges 通常由训练生成 |
WordPiece | WordPiece(vocab, unk_token="[UNK]", max_input_chars_per_word=100) | 构建 WordPiece 模型(BERT 使用) | from tokenizers.models import WordPiece
model = WordPiece(vocab) | max_input_chars_per_word 防止过长词 OOM |
Unigram | Unigram(pieces, unk_id=0) | 构建 Unigram 模型(SentencePiece 使用) | from tokenizers.models import Unigram
model = Unigram(pieces) | pieces 为 (token, score) 列表 |
SentencePieceBP | SentencePieceBP(model_file, vocab_size, reverse=False, add_prefix_space=False) | 从 .model 文件加载 SentencePiece BPE | SentencePieceBP("sp.model", vocab_size=32000) | 支持直接集成 SP 模型 |
SentencePieceUnigram | SentencePieceUnigram(model_file, vocab_size) | 从 .model 文件加载 SentencePiece Unigram | SentencePieceUnigram("sp.model") | 常用于多语言模型 |
save | model.save(directory, file_prefix=None) | 保存模型到磁盘 | model.save("./my_model", "bpe") | 生成 vocab.json 和 merges.txt |
get_vocab | model.get_vocab(with_added_tokens=True) → dict | 获取词汇表 | vocab = model.get_vocab() | 返回 token → id 映射 |
2.5 PostProcessor:后处理(如添加 [CLS]、[SEP])
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
PostProcessor | from tokenizers.processors import PostProcessor | 抽象基类 | 不可直接实例化 | 需使用具体实现 |
TemplateProcessing | TemplateProcessing(template, special_tokens) | 使用模板定义输出结构 | TemplateProcessing("$A", special_tokens=[("", 1), ("", 2)]) | $A 表示第一句,$B 第二句 |
BertProcessing | BertProcessing(sep, cls) | 专为 BERT 设计:[CLS] A [SEP] B [SEP] | BertProcessing(("[SEP]", 102), ("[CLS]", 101)) | 第一个参数是 (sep_token, sep_id) 元组 |
RobertaProcessing | RobertaProcessing(sep, cls, add_prefix_space=True) | 专为 RoBERTa 设计 | RobertaProcessing(("", 0), ("", 2)) | 注意空格处理 |
Single | Single(special_token, special_token_id) | 单句后处理,仅添加首尾标记 | Single("[CLS]", 101) | 用于单句分类任务 |
concat | processor1.concat(processor2) | 连接两个 PostProcessor | proc_a.concat(proc_b) | 构建复杂结构 |
2.6 Encoder:编码输出(Token IDs, Attention Mask 等)
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
encode | tokenizer.encode(text) → Encoding | 编码单个文本 | encoding = tokenizer.encode("Hello world") | 返回 Encoding 对象 |
encode_batch | tokenizer.encode_batch(list_of_texts) | 批量编码 | encodings = tokenizer.encode_batch(["t1", "t2"]) | 性能优于循环单条编码 |
Encoding.ids | encoding.ids → List[int] | 获取 token IDs | input_ids = encoding.ids | 可直接作为模型输入 |
Encoding.tokens | encoding.tokens → List[str] | 获取 token 字符串列表 | tokens = encoding.tokens | 用于调试和可视化 |
Encoding.attention_mask | encoding.attention_mask → List[int] | 获取 attention mask | mask = encoding.attention_mask | 1 表示有效 token,0 表示 padding |
Encoding.type_ids | encoding.type_ids → List[int] | 获取 token type ids(句子 A/B) | type_ids = encoding.type_ids | 用于句子对任务 |
Encoding.offsets | encoding.offsets → List[Tuple[int, int]] | 获取每个 token 在原文中的字符位置 | start, end = encoding.offsets[0] | 用于 NER、QA 等任务 |
Encoding.overflowing | encoding.overflowing → List[Encoding] | 获取截断后的溢出部分 | if encoding.overflowing: ... | 配合 truncation 使用 |
Encoding.special_tokens_mask | encoding.special_tokens_mask → List[int] | 标记哪些是特殊 token([CLS], [SEP]) | mask = encoding.special_tokens_mask | 用于 loss 计算时忽略特殊 token |
第三章:分词模型详解
3.1 BPE(Byte-Pair Encoding)模型原理与实现
| 概念名称 | 说明 | 注意事项 |
|---|
| 基本原理 | 从字符级开始,迭代合并出现频率最高的相邻符号对,逐步构建子词单元。初始词汇为字符集,通过 merges 文件记录合并规则。 | 合并规则是贪心的,不考虑全局最优。 |
| 训练过程 | 1. 统计词频;2. 初始化词汇为字符;3. 重复:找最高频相邻对 → 合并 → 更新词频 → 加入 merges;4. 直到达到 vocab_size 或合并次数上限。 | 需要设置 vocab_size 和 min_frequency 控制词汇表大小。 |
| 未知词处理 | 将未登录词按字符或子词切分,使用已知 merges 规则分解。 | 可能产生大量子词片段,影响语义完整性。 |
| merges 文件 | 记录所有合并操作的有序列表,每行两个符号表示一次合并。 | 是 BPE 模型的核心组成部分,必须与 vocab 配合使用。 |
| vocab 文件 | 初始词汇表,包含所有基础字符及高频词,通常还包括特殊标记。 | 常见格式为 JSON,键为 token,值为索引。 |
add_prefix_space | 是否在词前添加空格作为切分依据(如 GPT-2) | 设置为 True 可更好处理词边界,避免歧义。 |
3.2 WordPiece 模型原理与实现
| 概念名称 | 说明 | 注意事项 |
|---|
| 基本原理 | 基于最大似然估计选择要合并的子词对,目标是最大化训练数据的似然。每次选择能使语言模型概率增益最大的 pair 进行合并。 | 比 BPE 更注重统计意义,但训练更复杂。 |
unk_token | 用于表示词汇表外的词,默认为 [UNK] | 所有 OOV 词统一替换为此标记,损失语义信息。 |
max_input_chars_per_word | 单个词最大字符数限制(默认 100) | 防止过长单词导致内存溢出或性能下降。 |
| 分词策略 | 采用贪心最长匹配:从左到右尽可能匹配最长的子词。 | 可能无法覆盖所有可能的子词组合。 |
| 特殊标记处理 | 支持 [CLS], [SEP], [MASK] 等 BERT 特有标记 | 必须在词汇表中预定义并分配 ID。 |
| 词汇表构建 | 初始为字符集,逐步扩展,最终包含常见词和子词。 | 通常比 BPE 生成更多完整词 token。 |
3.3 Unigram 模型原理与实现
| 概念名称 | 说明 | 注意事项 |
|---|
| 基本原理 | 假设每个 token 独立出现,基于概率模型评分。训练时从大词汇表开始,逐步移除低概率 token,保留高概率子词。 | 不是合并过程,而是”淘汰”过程。 |
| pieces 文件 | 包含 (token, log_probability) 的列表,概率越高越可能被选中。 | 概率为负数,绝对值越小越好。 |
| 分词方式 | 对同一词可能有多种切分方式,选择总概率最高的路径。 | 支持多路径解码,灵活性高。 |
| lattice 解码 | 构建所有可能的子词路径图,使用 Viterbi 算法找最优路径。 | 计算量较大,但结果更优。 |
| dropout | 在训练中随机丢弃某些子词路径,增强鲁棒性 | 可防止过拟合,提高泛化能力。 |
| 词汇缩减 | 通过 loss 值控制最终 vocab_size,自动裁剪低频 token | 不需要显式合并规则,配置更简单。 |
3.4 SentencePiece 模型(BPE/Unigram)集成
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
SentencePieceBP | SentencePieceBP(model_file) | 加载 SentencePiece 的 BPE 模型文件 | from tokenizers.models import SentencePieceBP
model = SentencePieceBP("sp.model") | model_file 为 .model 扩展名 |
SentencePieceUnigram | SentencePieceUnigram(model_file) | 加载 SentencePiece 的 Unigram 模型文件 | from tokenizers.models import SentencePieceUnigram
model = SentencePieceUnigram("sp.model") | 支持直接读取 SP 训练结果 |
add_prefix_space | SentencePieceBP(..., add_prefix_space=True) | 是否在词前加空格 | 常用于与 GPT 类模型兼容 | 影响分词边界判断 |
vocab_size | SentencePieceBP(..., vocab_size=32000) | 指定词汇表大小(用于验证) | 可用于调试和一致性检查 | 实际大小由模型文件决定 |
save | model.save("dir/") | 保存为 tokenizers 原生格式 | model.save("./converted") | 生成 vocab.json 和 config.json |
get_vocab | model.get_vocab() | 获取词汇表映射 | vocab = model.get_vocab() | 返回字典形式便于查看 |
3.5 模型选择与性能对比
| 模型类型 | 优点 | 缺点 | 适用场景 | 注意事项 |
|---|
| BPE | 实现简单、速度快、社区支持好、适合英文 | 对低频词处理较差,可能过度分割 | GPT、RoBERTa、大多数英文模型 | 推荐搭配 ByteLevel PreTokenizer |
| WordPiece | BERT 官方方案,平衡完整词与子词 | 训练复杂,依赖最大似然估计 | BERT 及其变体 | 中文常直接以字为单位 |
| Unigram | 分词灵活,支持多路径,适合多语言 | 计算开销大,训练时间长 | 日文、韩文、XLM-R 等 | 支持更好的未知词处理 |
| SentencePiece | 无需空格分词,天然支持多语言 | 默认移除空格,可能影响可读性 | 多语言模型、无空格语言 | 可设置 add_dummy_prefix=False 保留空格 |
| 性能对比(速度) | BPE ≈ WordPiece > Unigram | Unigram 解码较慢 | 高吞吐场景优先选 BPE | 批量处理可缓解差异 |
| 性能对比(OOV率) | Unigram < BPE ≈ WordPiece | 具体取决于训练数据 | 领域迁移任务推荐 Unigram | 良好训练数据是关键 |
| 中文处理建议 | WordPiece(以字切分)或 BPE(字节级) | 避免按词切分导致 OOV | 中文 BERT、MacBERT | 可结合中文分词工具做 PreTokenizer |
第四章:高级特性与自定义配置
4.1 自定义词汇表(Vocabulary)构建
| 操作步骤 | 操作细节 | 注意事项 |
|---|
| 准备训练语料 | 收集领域相关文本,保存为纯文本文件(.txt)或按行分隔的 JSON/CSV | 数据质量决定词汇表效果,需清洗噪声 |
| 初始化词汇表 | 可指定初始字符集或从已有词表加载 | 建议包含基础字母、数字、标点及常用符号 |
设置 vocab_size | 在训练时指定目标词汇表大小(如 30000) | 过大会增加模型参数,过小会提高 OOV 率 |
设置 min_frequency | 设定词频阈值,低于此值的词不加入词汇表 | 防止低频词膨胀词汇表 |
| 保留特殊标记 | 在构建时预留 [PAD], [UNK], [CLS] 等 token 的 ID 位置 | 必须在训练前定义,避免冲突 |
使用 BPE.train() | 调用 tokenizer.train(files, vocab_size=...) 开始训练 | 支持多文件输入,自动处理大文件 |
| 验证词汇表 | 使用 tokenizer.get_vocab() 检查生成的 token 分布 | 查看是否包含预期的领域术语 |
4.2 特殊标记(Special Tokens)管理([PAD], [UNK], [CLS], [SEP], [MASK])
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
set_padding | tokenizer.enable_padding(pad_token="[PAD]", pad_token_id=0) | 配置填充参数 | tokenizer.enable_padding("[PAD]", 0) | 必须启用后 encode 才有 attention_mask |
set_truncation | tokenizer.enable_truncation(max_length=512) | 配置截断长度 | tokenizer.enable_truncation(512) | 超长文本将被截断 |
add_special_tokens | tokenizer.add_special_tokens([{"id": 0, "content": "[PAD]"}, ...]) | 添加多个特殊标记 | tokenizer.add_special_tokens(["[PAD]", "[UNK]"]) | 可批量添加 |
token_to_id | tokenizer.token_to_id("[CLS]") → int | 获取特殊标记的 ID | cls_id = tokenizer.token_to_id("[CLS]") | 用于构建输入结构 |
id_to_token | tokenizer.id_to_token(101) → str | 根据 ID 获取 token 字符串 | sep_token = tokenizer.id_to_token(102) | 用于调试和解码 |
special_tokens | tokenizer.special_tokens → List[str] | 获取所有特殊标记列表 | tokens = tokenizer.special_tokens | 包括 [PAD], [UNK], [CLS] 等 |
unk_token | tokenizer.unk_token = "[UNK]" | 设置或获取未知标记 | print(tokenizer.unk_token) | 必须存在于词汇表中 |
pad_token | tokenizer.pad_token = "[PAD]" | 设置填充标记 | tokenizer.pad_token = "[PAD]" | 影响 padding 行为 |
4.3 添加新标记(add_tokens, add_special_tokens)
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
add_tokens | tokenizer.add_tokens(["new_word", "domain_term"]) | 添加普通词汇(不更新特殊标记) | n_added = tokenizer.add_tokens(["covid", "vaccine"]) | 返回实际添加数量 |
add_special_tokens | tokenizer.add_special_tokens(["[NEW]", "[SEP2]"]) | 添加特殊标记(会更新 special_tokens) | tokenizer.add_special_tokens(["[NEW]"]) | 特殊标记参与模型结构构建 |
add_tokens(对象形式) | tokenizer.add_tokens([{"content": "token", "single_word": True}]) | 控制是否允许子词切分 | add_tokens([{"content": "USA", "single_word": True}]) | single_word=True 表示禁止拆分 |
resize_token_embeddings | 需在 transformers 中调用 model.resize_token_embeddings(len(tokenizer)) | 同步更新模型嵌入层大小 | model.resize_token_embeddings(len(tokenizer)) | 添加 token 后必须调用 |
get_vocab_size | tokenizer.get_vocab_size(with_added_tokens=True) | 获取包含新增 token 的总大小 | size = tokenizer.get_vocab_size() | 用于验证添加结果 |
4.4 处理未知词(Unknown Tokens)策略
| 策略名称 | 说明 | 注意事项 |
|---|
[UNK] 替换 | 所有未登录词统一替换为 [UNK] 标记 | 简单但损失语义,影响下游任务 |
| 子词分解 | 使用 BPE/WordPiece 规则将未知词拆分为子词 | 如 “unhappiness” → “un” + “happiness” |
| 字符级回退 | 当子词也无法表示时,逐字符切分 | 常见于中文或形态丰富语言 |
| 词汇表扩展 | 在训练时加入领域术语,减少 OOV | 需重新训练或增量学习 |
| dropout 机制 | 训练中随机 masking 某些子词,提升鲁棒性 | 类似数据增强 |
| 未知词检测 | 通过 special_tokens_mask 或 token 是否为 [UNK] 判断 | 用于后处理或日志记录 |
4.5 截断(Truncation)与填充(Padding)配置
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
enable_truncation | tokenizer.enable_truncation(max_length=512, direction="right", stride=0) | 启用截断 | tokenizer.enable_truncation(512) | 默认从右侧截断 |
enable_padding | tokenizer.enable_padding(length=None, pad_token="[PAD]", pad_token_id=0, pad_to_multiple_of=None) | 启用填充 | tokenizer.enable_padding(pad_to_multiple_of=8) | 提高 GPU 利用率 |
max_length | enable_truncation(max_length=512) | 最大长度限制 | 不可超过模型最大上下文 | 如 BERT 为 512 |
direction | direction="left" | "right" | "only_first" | "only_second" | 截断方向 | 处理问答任务时可用 "only_first" | 控制保留哪部分信息 |
stride | stride=64 | 滑动窗口重叠长度 | 用于长文本分块 | 结合 overflowing 使用 |
length | enable_padding(length=64) | 固定填充到指定长度 | 否则按 batch 最大长度对齐 | 可控但可能浪费 |
pad_to_multiple_of | pad_to_multiple_of=8 | 填充到指定倍数 | 优化 Tensor Core 计算 | 推荐设置为 8 的倍数 |
4.6 编码缓存与性能优化
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
| 编码批处理 | encode_batch(texts) | 一次性处理多个文本 | encodings = tokenizer.encode_batch(texts) | 比循环调用快数倍 |
| 预分配内存 | 使用固定 max_length + padding | 减少动态分配开销 | 启用 padding 并指定 length | 适合固定长度任务 |
| 禁用不需要的字段 | 如无需 offsets 可忽略 | 减少输出体积 | 只使用 ids 和 attention_mask | 特别在推理时有效 |
| 使用 Fast Tokenizer | 基于 tokenizers 库而非 Python 实现 | 显著提升速度 | 确保使用 tokenizer_fast | transformers 中默认优先加载 |
| 内存映射(mmap) | 处理超大语料训练时使用 | 避免加载全部数据到内存 | 内部自动启用 | 适用于 >10GB 文本 |
| 缓存机制 | 手动缓存 encode 结果到磁盘(如 pickle) | 避免重复编码 | cache[md5(text)] = encoding | 适用于固定数据集 |
| 并行处理 | 利用多核 CPU 进行批处理 | Rust 后端自动支持 | encode_batch 天然并行 | 不需额外配置 |
第五章:训练自己的分词器
5.1 准备训练语料数据
| 操作步骤 | 操作细节 | 注意事项 |
|---|
| 数据收集 | 收集与目标领域相关的文本数据(如新闻、论文、对话记录) | 数据越贴近下游任务,分词效果越好 |
| 格式统一 | 将所有文本转换为 UTF-8 编码的纯文本格式 | 避免乱码和不可见字符干扰 |
| 清洗噪声 | 去除无关 HTML 标签、特殊控制符、广告文本等 | 可使用正则表达式或专用清洗工具 |
| 分行存储 | 每个句子或文档占一行(适用于 .txt 文件) | Tokenizer.train() 默认按行读取 |
| 多文件组织 | 将大语料拆分为多个小文件(如 part_001.txt, part_002.txt) | 提高加载效率,避免单文件过大 |
| 语言一致性 | 确保语料主要为单一语言或明确标注多语言 | 混合语言需特别处理分词策略 |
| 数据量建议 | 至少 1GB 文本以获得稳定词汇表,更大更佳 | 小数据集易过拟合或覆盖不足 |
5.2 使用 Tokenizer.train() 方法训练 BPE/WordPiece
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
Tokenizer.train | tokenizer.train(files, trainer) | 开始训练分词器 | tokenizer.train(["data.txt"], trainer) | files 支持字符串列表或单个字符串 |
BPETrainer | BPETrainer(special_tokens=["[UNK]", "[CLS]"]) | 配置 BPE 训练器 | from tokenizers.trainers import BPETrainer
trainer = BPETrainer(special_tokens=["[UNK]"]) | 必须指定 special_tokens |
WordPieceTrainer | WordPieceTrainer(vocab_size=30000, special_tokens=["[UNK]"]) | 配置 WordPiece 训练器 | from tokenizers.trainers import WordPieceTrainer
trainer = WordPieceTrainer(vocab_size=30000) | 不需要 merges 文件 |
| 设置模型类型 | tokenizer.model = BPE() 或 WordPiece() | 先绑定空模型再训练 | tokenizer.model = BPE()
tokenizer.train(files, trainer) | 模型类型决定训练方式 |
| 训练过程监控 | 无内置进度条,可通过日志查看 | 大语料可能耗时数分钟至数小时 | 建议在后台运行 | |
| 返回值 | 无显式返回,结果更新 tokenizer 实例内部状态 | 训练完成后 tokenizer 即可使用 | — | |
5.3 训练参数详解(vocab_size, min_frequency, special_tokens 等)
| 参数名称 | 语法位置 | 说明 | 示例值 | 注意事项 |
|---|
vocab_size | BPETrainer(..., vocab_size=30000) | 目标词汇表大小 | 30000, 50000 | 过大会增加模型负担,过小提高 OOV |
min_frequency | BPETrainer(..., min_frequency=2) | 仅频率 ≥ 此值的 token 参与合并 | 2, 5 | 过滤低频噪声词 |
special_tokens | BPETrainer(special_tokens=["[PAD]", "[UNK]"]) | 定义特殊标记列表 | ["[CLS]", "[SEP]", "[MASK]"] | 必须包含 [UNK],否则报错 |
limit_alphabet | BPETrainer(limit_alphabet=1000) | 限制初始字符集大小 | 1000 | 防止 Unicode 膨胀 |
initial_alphabet | BPETrainer(initial_alphabet=set("abc")) | 自定义初始字符集 | set(string.ascii_letters) | 扩展支持 emoji 或符号 |
show_progress | BPETrainer(show_progress=True) | 是否显示训练进度 | True / False | 调试时启用 |
continuing_subword_prefix | BPETrainer(continuing_subword_prefix="##") | 子词前缀(WordPiece 特有) | "##" | 表示该子词是前一个词的延续 |
end_of_word_suffix | BPETrainer(end_of_word_suffix="") | 词尾标记 | "</w>" | BPE 中用于区分词边界 |
5.4 保存与加载自定义分词器
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
save | tokenizer.save("path/to/tokenizer.json") | 保存整个分词器配置到单个文件 | tokenizer.save("my_tokenizer.json") | 推荐格式,包含所有组件 |
save_model | tokenizer.save_model("directory/", "prefix") | 仅保存模型部分(vocab + merges) | tokenizer.save_model("./", "bpe") | 生成 vocab.json 和 merges.txt |
from_file | Tokenizer.from_file("tokenizer.json") | 从文件加载完整分词器 | tok = Tokenizer.from_file("my_tok.json") | 恢复训练后的状态 |
from_pretrained | Tokenizer.from_pretrained("local/path") | 加载本地预训练分词器 | tok = Tokenizer.from_pretrained("./") | 目录需包含 config.json |
| 保存目录结构 | save_model 生成两个文件:vocab.json 和 merges.txt | 分离模型与配置 | 可手动编辑 vocab.json | 不要修改 merges.txt |
| 加载兼容性 | 从 JSON 加载可保留 Normalizer、PreTokenizer 等完整流程 | 完整恢复训练配置 | 推荐使用 save() | — |
5.5 从文件训练(Text files, JSON, CSV)
| 文件类型 | 操作方式 | 代码示例 | 注意事项 |
|---|
| 纯文本 (.txt) | 每行一个文本片段 | files = ["corpus.txt"]
tokenizer.train(files, trainer) | 最简单,推荐首选 |
| JSON 文件 | 指定字段名提取文本 | 需先解析 JSON,提取字段写入临时 txt | 不支持直接传 JSON 路径 |
| CSV 文件 | 指定列名提取文本 | import pandas as pd
df = pd.read_csv("data.csv")
df["text"].to_csv("tmp.txt", index=False, header=False) | 注意引号和转义字符 |
| 多个文件 | 传入文件路径列表 | files = [f"part_{i}.txt" for i in range(10)] | 自动按顺序读取 |
| 压缩文件 | 需先解压 | gunzip data.txt.gz | 不支持直接读取 .gz |
| 大文件流式处理 | 库内部自动按行读取,无需全加载内存 | 支持 >10GB 文件 | 确保磁盘 I/O 性能 |
| 字段选择(JSON/CSV) | 使用外部脚本提取目标字段 | with open("data.json") as f: texts = [json.loads(line)["content"] for line in f] | 确保字段存在且非空 |
6.1 将 tokenizers.Tokenizer 包装为 PreTrainedTokenizerFast
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
PreTrainedTokenizerFast | PreTrainedTokenizerFast(tokenizer_object=...) | 包装原生 tokenizer 为 Transformers 兼容对象 | from transformers import PreTrainedTokenizerFast
fast_tok = PreTrainedTokenizerFast(tokenizer=tokenizer) | 必须传入已训练的 tokenizers.Tokenizer 实例 |
from_file | PreTrainedTokenizerFast.from_pretrained("path/to/tokenizer.json") | 从保存的 JSON 文件加载 | tok = PreTrainedTokenizerFast.from_pretrained("./my_tokenizer.json") | 路径指向 .json 文件或包含它的目录 |
from_pretrained | PreTrainedTokenizerFast.from_pretrained("local_dir/") | 加载本地保存的分词器 | tok = PreTrainedTokenizerFast.from_pretrained("./") | 目录需有 tokenizer.json 或 Hugging Face 标准结构 |
| 添加特殊标记映射 | fast_tok.add_special_tokens(...) | 同步特殊标记到 Transformers 接口 | fast_tok.add_special_tokens({"pad_token": "[PAD]"}) | 确保模型配置能识别 |
| 绑定模型类型 | PreTrainedTokenizerFast(..., model_max_length=512, padding_side="right") | 设置模型相关参数 | fast_tok = PreTrainedTokenizerFast(tokenizer=tokenizer, model_max_length=512) | 影响 truncation 和 padding 行为 |
| 验证包装结果 | fast_tok.encode("Hello world") | 测试是否正常工作 | print(fast_tok.encode("test")) | 输出应为 list of int |
| 与模型配合 | model = AutoModelForCausalLM.from_pretrained("config_only", vocab_size=len(fast_tok)) | 使用自定义 vocab_size 初始化模型 | 模型词汇表大小必须匹配分词器 | 否则 embedding 维度不匹配 |
6.2 在模型训练中使用自定义分词器
| 操作步骤 | 操作细节 | 注意事项 |
|---|
| 准备数据集 | 将原始文本通过自定义分词器编码为 input_ids, attention_mask 等 | 建议使用 Dataset.map() 批量处理 |
| 编码函数定义 | 定义 def tokenize(examples): return tokenizer(examples["text"], truncation=True, padding=True) | 传入列表字段 |
| 动态填充 | 使用 DataCollatorWithPadding 或自定义 collator | 自动对齐 batch 内序列长度 |
| 模型初始化 | 使用 AutoModel 或从配置初始化,确保 vocab_size 匹配 | model = AutoModel.from_config(config) |
| 调整嵌入层 | 若添加了新 token,调用 model.resize_token_embeddings(len(tokenizer)) | 必须在训练前执行 |
| 训练循环 | 将 encoded 数据送入模型训练 | 可使用 Trainer 或自定义 loop |
| 验证解码 | 在评估时使用 tokenizer.decode() 查看生成文本 | skip_special_tokens=True 更清晰 |
6.3 保存与共享分词器配置
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
save_pretrained | tokenizer.save_pretrained("save_directory") | 保存为 Transformers 标准格式 | fast_tok.save_pretrained("./my_custom_tok/") | 生成 config.json, tokenizer.json, special_tokens_map.json 等 |
push_to_hub | tokenizer.push_to_hub("your-username/repo-name") | 推送到 Hugging Face Hub | from huggingface_hub import login
login(); fast_tok.push_to_hub("my-tok-uncased") | 需先登录 huggingface-cli login |
| 保存文件列表 | config.json, tokenizer.json, special_tokens_map.json, tokenizer_config.json | 完整保存所有配置 | 可手动编辑后重载 | 不要删除任何文件 |
| 从 Hub 加载 | AutoTokenizer.from_pretrained("your-username/repo-name") | 下载并加载共享分词器 | tok = AutoTokenizer.from_pretrained("nlpjungle/my-bio-tok") | 支持私有仓库(需权限) |
| 版本控制 | 推送时自动支持 Git 版本管理 | 可回退到历史版本 | 建议添加 README.md 说明用途 | |
| 共享建议 | 在模型卡片中说明训练语料、vocab_size、特殊标记等信息 | 提高可复用性 | 可附带使用示例代码 | |
6.4 处理多语言与子词共享
| 概念名称 | 说明 | 注意事项 |
|---|
| 多语言训练语料 | 混合多种语言文本进行训练(如 Wikipedia 多语言 dump) | 建议按比例采样,避免主导语言垄断 |
| 字节级 BPE(Byte-Level BPE) | 将字符转为字节序列再应用 BPE | GPT-2、RoBERTa 使用 |
| 统一词汇表 | 所有语言共享同一词汇表,促进跨语言迁移 | XLM-R、mBERT 采用此策略 |
| 子词重用 | 常见子结构(如 “ing”, “tion”)在不同语言中复用 | 提高低资源语言表现 |
| 语言标记(lang token) | 在输入前添加语言 ID 标记(如 <en>, <zh>) | 用于多语言模型控制生成 |
| 分词一致性 | 确保不同语言的标点、空格处理一致 | 使用统一 Normalizer(如 NFD + StripAccents) |
| 评估 OOV 率 | 按语言分别统计未登录词比例 | 发现特定语言问题 |
| 中文处理策略 | 可结合字级分词或加入中文词典作为初始词汇 | 避免过度拆分为单字 |
第七章:实战案例
7.1 中文文本分词器训练(基于 BPE)
| 操作步骤 | 操作细节 | 注意事项 |
|---|
| 准备中文语料 | 收集中文维基百科、新闻、小说等文本,每行一个句子 | 推荐使用 WikiZh 或 CLUE 数据集 |
| 预处理文本 | 可选:进行简体化、去除特殊符号、规范化标点 | 使用 opencc 转换繁体→简体:import opencc; cc = opencc.OpenCC('t2s'); text = cc.convert(text) |
| 初始化 Tokenizer | 使用 BPE 模型并配置 Normalizer 处理中文字符 | from tokenizers import Tokenizer, normalizers
from tokenizers.normalizers import NFD, StripAccents
tokenizer = Tokenizer(BPE())
tokenizer.normalizer = normalizers.Sequence([NFD(), StripAccents()]) |
| 配置 PreTokenizer | 使用 Whitespace() 按空格切分(适用于已分词文本)或 CharDelimiterSplit('\n') | tokenizer.pre_tokenizer = Whitespace() |
| 定义 Trainer | 设置 vocab_size=20000,包含中文常用特殊标记 | from tokenizers.trainers import BPETrainer
trainer = BPETrainer(special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"], vocab_size=20000, min_frequency=2) |
| 训练分词器 | 调用 tokenizer.train(files=["zh_corpus.txt"], trainer=trainer) | 支持大文件流式读取 |
| 测试分词效果 | encode("自然语言处理很有趣") → ['自', '然', '语', '言', '处', '理', '很', '有', '趣'] | 若未出现预期子词,检查语料频率 |
| 保存分词器 | tokenizer.save("zh-bpe-tokenizer.json") | 后续可用于 Transformers 集成 |
7.2 构建领域专用分词器(医学、法律文本)
| 领域类型 | 医学文本 | 法律文本 | 对比说明 |
|---|
| 语料来源 | PubMed 文摘、临床记录、医学教科书 | 判决书、法律法规、合同模板 | 医学术语高度专业,法律文本结构严谨 |
| 特殊词汇 | ”myocardial infarction”, “COVID-19”, “hemoglobin" | "plaintiff”, “jurisdiction”, “indemnification” | 建议提前构建领域词典 |
| 分词策略 | 使用 BPE 并在 initial_alphabet 中加入希腊字母、化学符号 | 使用 WordPiece,避免将长法律术语过度拆分 | 医学更适合子词,法律可倾向完整词 |
| 训练参数 | vocab_size=50000, min_frequency=1(低频术语重要) | vocab_size=30000, special_tokens=["[EVIDENCE]", "[VERDICT]"] | 医学需更大词汇表 |
| 添加领域标记 | add_special_tokens(["[DIAGNOSIS]", "[TREATMENT]"]) | add_special_tokens(["[CLAUSE]", "[PARTY_A]"]) | 增强模型对结构的理解 |
| Normalizer 配置 | 保留大小写(如 DNA vs dna),不 strip accents | 统一小写,标准化引号(” → “) | 医学术语区分大小写 |
| 验证方法 | 检查是否能正确分割 “atrial fibrillation” → [“atrial”, “fibrillation”] | 测试 “Section 5.2.1” 是否不被拆分 | 可编写单元测试脚本 |
| 应用优势 | 显著降低 OOV 率,提升命名实体识别(NER)性能 | 提高法律文档分类和信息抽取准确率 | 领域适配带来 5–15% 性能提升 |
7.3 处理长文本的滑动窗口编码
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
enable_truncation | tokenizer.enable_truncation(max_length=512, stride=64, strategy="longest_first") | 启用带滑动窗口的截断 | tokenizer.enable_truncation(512, stride=64) | stride 表示重叠长度 |
stride | 参数 in enable_truncation | 控制相邻 chunk 的重叠 token 数 | stride=64 表示每块重复前 64 个 token | 防止关键信息被切断 |
Encoding.overflowing | encoding.overflowing → List[Encoding] | 获取所有溢出的 chunks | for overflow in encoding.overflowing: print(overflow.ids) | 主 encoding 是第一块 |
| 手动分块 | 结合 offsets 定位原始文本位置 | 用于问答任务中答案定位 | 需记录每个 token 的 char offset | |
| 示例流程 | 对 1000 字符文本,max_len=8, stride=2 → 分为多个 8-token chunk,重叠 2 个 | total_len = 1000
chunk_size = 512
stride = 128
encodings = tokenizer.encode_batch([text[i:i+chunk_size+stride] for i in range(0, total_len, chunk_size)]) | 实际使用 encode 自动处理 | |
| 注意事项 | 最大支持长度仍受限于模型(如 BERT 为 512);过多 chunks 影响推理速度;QA 任务中需确保答案落在某个 chunk 内 | 建议设置 return_overflowing_tokens=True | 在 Transformers 中使用 TruncationStrategy.ONLY_FIRST 等策略 | |
7.4 批量编码与数据集预处理
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
encode_batch | tokenizer.encode_batch(text_list) | 高效批量编码 | texts = ["sentence1", "sentence2", ...]
encodings = tokenizer.encode_batch(texts) | 比循环调用快 5–10 倍 |
| 启用 Padding | tokenizer.enable_padding(pad_token_id=0) | 统一对齐序列长度 | tokenizer.enable_padding()
encodings = tokenizer.encode_batch(texts) | 自动生成 attention_mask |
| 启用 Truncation | tokenizer.enable_truncation(max_length=512) | 防止超长输入 | 必须在 encode 前调用 | 否则报错 |
| 与 Datasets 库集成 | dataset.map(tokenize_function, batched=True) | 直接处理 Hugging Face Dataset | def tokenize(examples): return tokenizer(examples["text"], truncation=True, padding=True)
encoded_ds = ds.map(tokenize, batched=True) | 推荐方式,支持缓存 |
| 返回 Tensor 类型 | tokenizer(..., return_tensors="pt") | 直接返回 PyTorch 张量 | outputs = tokenizer(texts, return_tensors="pt", padding=True) | 可直接送入模型 |
| 性能优化技巧 | 使用 batched=True、预分配内存、固定长度 padding | 处理百万级样本时显著提速 | 可结合 num_proc 多进程 | |
| 输出字段 | input_ids, attention_mask, token_type_ids(如有) | 标准模型输入格式 | model(**outputs) | 注意字段名称一致性 |
| 缓存机制 | map() 自动缓存到磁盘(arrow 文件) | 避免重复预处理 | 第二次运行极快 | 删除 cache_files 可强制重算 |
第八章:调试与性能分析
8.1 查看分词结果与解码还原
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
encode / encode_batch | tokenizer.encode("text") → Encoding | 获取编码对象 | encoding = tokenizer.encode("Hello world!") | 返回 Encoding 对象,包含丰富信息 |
encoding.tokens | encoding.tokens → List[str] | 查看分词后的 token 列表 | print(encoding.tokens) # ['Hello', 'world', '!'] | 最直接的调试方式 |
encoding.ids | encoding.ids → List[int] | 查看对应的 token ID 序列 | input_ids = encoding.ids | 模型实际输入 |
decode | tokenizer.decode(ids, skip_special_tokens=True) | 将 IDs 还原为原始文本 | text = tokenizer.decode([101, 7592, 2088, 102]) | skip_special_tokens 可隐藏 [CLS], [SEP] |
decode_batch | tokenizer.decode_batch(list_of_ids) | 批量解码 | texts = tokenizer.decode_batch([[...], [...]]) | 高效查看多个样本 |
encoding.offsets | encoding.offsets → List[Tuple[int, int]] | 查看每个 token 在原文中的字符位置 | start, end = encoding.offsets[0] | 用于 NER、QA 定位任务 |
encoding.words | encoding.words → List[int or None] | 标记 token 属于第几个输入”词”(word) | words = encoding.words | 多个 token 可能属于同一个 word |
| 特殊标记可视化 | 结合 special_tokens_mask 区分普通 token 与特殊标记 | mask = encoding.special_tokens_mask
print([(t, m) for t, m in zip(encoding.tokens, mask)]) | 便于理解后处理效果 | |
8.2 分析词汇表覆盖度
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
get_vocab | tokenizer.get_vocab() → Dict[str, int] | 获取完整词汇表映射 | vocab = tokenizer.get_vocab()
print(f"Vocab size: {len(vocab)}") | 返回字典形式,键为 token,值为 ID |
token_to_id / id_to_token | tokenizer.token_to_id("token"), tokenizer.id_to_token(100) | 单个查询 | if tokenizer.token_to_id("covid") is not None: ... | 快速验证特定词是否存在 |
| 覆盖率计算 | 自定义函数统计语料中可被完全表示的词比例 | def coverage(text): tokens = tokenizer.encode(text).tokens words = text.split() covered = sum(1 for w in words if any(w in t for t in tokens)) return covered / len(words) | 简化版,实际需更精细对齐 | |
| 子词分析 | 检查长词是否被合理切分 | tokenizer.encode("unhappiness") → ['un', 'happiness'] | 理想情况是语义完整子词 | |
| 高频词检查 | 统计训练语料词频,对比是否进入词汇表 | 使用 collections.Counter + 分词器验证 | 发现遗漏的重要领域术语 | |
| 低频词分布 | 分析 vocab 中频率低于阈值的 token 数量 | [t for t, i in vocab.items() if freq[t] < 5] | 判断 min_frequency 设置是否合理 | |
| Unicode 覆盖 | 测试 emoji、罕见字符是否可表示 | tokenizer.encode("👍🌍你好") | 验证多语言支持能力 | |
8.3 监控 OOV(Out-of-Vocabulary)率
| 方法名 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|
unk_token | tokenizer.unk_token → str | 获取未知标记字符串 | unk = tokenizer.unk_token # "[UNK]" | 确保其存在且正确设置 |
统计 [UNK] 出现次数 | 在 encode 后统计 ids 中对应 [UNK] ID 的数量 | unk_id = tokenizer.token_to_id("[UNK]")
unk_count = sum(1 for i in encoding.ids if i == unk_id) | 基础 OOV 检测方法 | |
| OOV 率计算 | ([UNK] 出现次数 / 总 token 数) × 100% | total_tokens = len(encoding.ids)
oov_rate = (unk_count / total_tokens) * 100 | 评估分词器泛化能力 | |
| 按文本粒度统计 | 对整个数据集计算平均 OOV 率 | oov_rates = [compute_oov(t) for t in test_texts]
avg_oov = sum(oov_rates) / len(oov_rates) | 更具代表性 | |
| 领域外测试集 | 使用未参与训练的领域文本测试 OOV 率 | 如医学分词器在法律文本上测试 | 检验迁移能力 | |
| 改进策略 | 添加领域词到训练语料、降低 min_frequency、扩大 vocab_size | retrain with new config | 根据 OOV 分析结果优化 | |
| 注意事项 | 中文因以字切分,OOV 率天然较低;英文专有名词易成 OOV;过度降低 OOV 可能导致词汇表膨胀 | 平衡模型大小与性能 | | |
8.4 内存与速度优化技巧
| 优化方向 | 技巧说明 | 实现方式 | 效果 | 注意事项 |
|---|
| 使用 Fast Tokenizer | 基于 Rust 的 tokenizers 库比 Python 实现快 5–10 倍 | 确保使用 AutoTokenizer.from_pretrained(..., use_fast=True) | 显著提升 encode/decode 速度 | Transformers 默认优先加载 fast 版本 |
| 批量处理 | 批量编码比逐条处理高效得多 | 使用 encode_batch() 或 dataset.map(batched=True) | 提升 3–8 倍吞吐量 | 减少 Python 循环开销 |
| 启用缓存 | Hugging Face Datasets 自动缓存预处理结果 | 第二次运行跳过计算 | 适合固定数据集 | 缓存文件可能占用磁盘空间 |
| 减少输出字段 | 仅请求需要的字段(如只用 input_ids 和 attention_mask) | 不访问 offsets、overflowing 等 | 减少内存占用 | 特别在推理时有效 |
| 固定长度 Padding | 使用 pad_to_multiple_of=8 或指定 length | tokenizer.enable_padding(length=512) | 提高 GPU 利用率,利于 Tensor Core | 避免动态形状 |
| 内存映射(mmap) | 处理超大语料时不全加载入内存 | tokenizers 库内部自动支持大文件流式读取 | 支持 >10GB 文本训练 | 确保磁盘 I/O 性能 |
| 模型轻量化 | 减小 vocab_size 或使用更简单模型(BPE vs Unigram) | 训练时设置 vocab_size=10000 而非 50000 | 减少 embedding 层参数 | 权衡 OOV 率 |
| 多进程处理 | 利用 CPU 多核并行预处理 | dataset.map(..., num_proc=4) | 加速大规模数据集处理 | 进程数不宜超过 CPU 核心数 |
| 禁用不必要的组件 | 如无需 truncation/overflow,则不启用 | 避免调用 enable_truncation | 减少计算路径 | 保持 pipeline 简洁 |