从零训练自定义 TokenizerHuggingFace Tokenizers 完整训练指南AI-Research-SKILLs【免费下载链接】AI-Research-SKILLsComprehensive open-source library of AI research and engineering skills for any AI model. Package the skills and your claude code/codex/gemini agent will be an AI research agent with full horsepower. Maintained by Orchestra Research.项目地址: https://gitcode.com/gh_mirrors/ai/AI-Research-SKILLsHuggingFace Tokenizers 是基于 Rust 内核的高性能分词库支持 BPE、WordPiece、Unigram 三种主流算法的自定义词表训练官方宣称可在 20 秒内完成 1GB 语料的切分。在 AI-Research-SKILLs 仓库中该技能位于 02-tokenization/huggingface-tokenizers/ 目录本指南以其中 references/training.md 为主体系统讲解从算法选型、数据准备、训练器配置、大规模语料处理到质量验证的完整闭环读完即可独立为 GPT、BERT、T5 等架构训练并落地自定义词表。为什么需要训练自定义 Tokenizer预训练模型自带的分词器是为其训练语料与任务形态设计的。当你切换到代码、医学、多语言等垂直领域时通用词表的覆盖率和压缩率都会显著下降表现为 OOVOut-Of-Vocabulary率升高、序列长度膨胀最终拖累下游任务的精度与训练吞吐。此时需要基于领域语料从头训练分词器。在 SKILL.md 的元数据中该技能被定位为面向研究与生产环境的快速分词方案依赖tokenizers、transformers、datasets三个核心库训练出的词表可以通过PreTrainedTokenizerFast无缝接入 transformers 生态供后续微调或推理使用。训练工作流总览从零训练一个自定义分词器遵循六步流程每一步解决一个独立问题选型根据模型架构选择分词算法数据准备整理训练语料文件、列表或迭代器初始化组装 Tokenizer 对象模型 归一化 预分词训练调用train或train_from_iterator学习词表后处理配置特殊 token 模板如[CLS] ... [SEP]保存导出 JSON 并转换为 transformers 格式下文将逐步展开并结合 algorithms.md、pipeline.md 中的原理细节做纵深说明。Step 1选择分词算法算法选型是训练的第一步直接决定词表形态与下游模型契合度。仓库文档给出了如下决策树GPT 风格模型自回归→ 选择BPEByte-Pair EncodingGPT-2/GPT-3、RoBERTa、BART 均属此类BERT 风格模型双向掩码→ 选择WordPieceBERT、DistilBERT、ELECTRA 属此类多语言 / 无词边界语言CJK 等→ 选择UnigramT5、ALBERT、XLNet经 SentencePiece属此类。三者的核心差异可以从 algorithms.md 中归纳算法训练方向合并/裁剪判据典型代表BPE自底向上迭代合并最频繁的相邻 token 对出现频率GPT-2、RoBERTa、BARTWordPiece自底向上合并但按freq(pair) / (freq(first) × freq(second))打分语义关联度稀有但共现紧密的组合优先BERT、DistilBERTUnigram自顶向下从全子串大词表出发逐步删掉对语料损失影响最小的 token概率似然损失增量ALBERT、T5、mBART从源码结构看tokenizers.models模块下的BPE、WordPiece、Unigram三个模型类分别封装了上述三种学习范式训练器则位于tokenizers.trainers两者一一对应。需要指出的是BPE 在合并顺序上存在依赖WordPiece 对未知词会直接退化为[UNK]若子词也无法匹配Unigram 训练成本最高且超参数更多这些 trade-off 都应纳入选型考量。Step 2准备训练数据训练数据有三种组织方式tokenizers的 API 对三种方式一视同仁方式一从文件列表训练files [train.txt, validation.txt]方式二从 Python 列表训练texts [ This is the first sentence., This is the second sentence., # ... more texts ]方式三从数据集迭代器训练推荐大规模场景from datasets import load_dataset dataset load_dataset(wikitext, wikitext-103-raw-v1, splittrain) def batch_iterator(batch_size1000): for i in range(0, len(dataset), batch_size): yield dataset[i:i batch_size][text]第三种方式配合下文 Step 4 的train_from_iterator使用可以做到语料边读边训避免一次性载入全部数据是处理 GB 级以上语料的推荐姿势。Step 3初始化 Tokenizer初始化过程本质上是组装 pipeline.md 中描述的完整管线归一化Normalization→ 预分词Pre-tokenization→ 模型Model→ 后处理Post-processing→ 解码Decoder。训练阶段至少要配置好模型与预分词器归一化器则视领域需求决定。BPE 示例GPT-2 风格ByteLevel 预分词保证全 Unicode 覆盖from tokenizers import Tokenizer from tokenizers.models import BPE from tokenizers.trainers import BpeTrainer from tokenizers.pre_tokenizers import ByteLevel from tokenizers.decoders import ByteLevel as ByteLevelDecoder tokenizer Tokenizer(BPE()) tokenizer.pre_tokenizer ByteLevel() tokenizer.decoder ByteLevelDecoder() trainer BpeTrainer( vocab_size50000, min_frequency2, special_tokens[|endoftext|, |padding|], show_progressTrue )ByteLevel 预分词将文本映射为 256 个字节组合因此任何 Unicode 字符含 emoji都不会产生未知 token最坏情况退化为字节级表示——这正是 GPT-2 系列的选择。decoder 必须与 pre_tokenizer 配套设置否则解码时字节标记无法正确还原为文本。WordPiece 示例BERT 风格from tokenizers.models import WordPiece from tokenizers.trainers import WordPieceTrainer from tokenizers.normalizers import BertNormalizer from tokenizers.pre_tokenizers import BertPreTokenizer tokenizer Tokenizer(WordPiece(unk_token[UNK])) tokenizer.normalizer BertNormalizer(lowercaseTrue) tokenizer.pre_tokenizer BertPreTokenizer() trainer WordPieceTrainer( vocab_size30522, min_frequency2, special_tokens[[UNK], [CLS], [SEP], [PAD], [MASK]], continuing_subword_prefix##, show_progressTrue )这里BertNormalizer一站式完成清理解除控制字符、中文字符两侧补空格、去重音、小写化BertPreTokenizer则在空白与标点上切分并保留 CJK 字符。continuing_subword_prefix##是 BERT 风格的续词标记例如unbelievable→[un, ##believ, ##able]解码器靠它重建完整单词。Unigram 示例SentencePiece 风格from tokenizers.models import Unigram from tokenizers.trainers import UnigramTrainer tokenizer Tokenizer(Unigram()) trainer UnigramTrainer( vocab_size8000, special_tokens[unk, s, /s, pad], unk_tokenunk, show_progressTrue )Unigram 是概率式分词对一个词存在多种合法切分推理时选择联合概率最高的一条底层用 Viterbi 动态规划在 O(n²) 内求最优路径而非指数级穷举。这也带来了一个独特能力——子词正则化训练后同一文本可以采样出不同切分相当于免费的数据增强相关讨论可参考 algorithms.md。Step 4执行训练训练入口有两个分别对应 Step 2 的数据组织方式# From files tokenizer.train(filesfiles, trainertrainer) # From iterator (recommended for large datasets) tokenizer.train_from_iterator( batch_iterator(), trainertrainer, lengthlen(dataset) # Optional, for progress bar )train_from_iterator是面向大数据量的推荐路径它边消费迭代器边统计词频无需将整个语料驻留内存。length参数仅用于渲染进度条可以省略。仓库文档给出了一份在 16 核 CPU、30k 词表条件下的实测训练耗时参考不同语料规模语料规模训练耗时10 MB15–30 秒100 MB1–3 分钟1 GB15–30 分钟10 GB2–4 小时SKILL.md 的快速上手部分补充了 100MB 约 1–2 分钟、1GB 约 10–20 分钟的区间两个数据相互印证可作为训练资源规划的起点。需要注意的是这些数字取决于语料形态与 CPU 核数应将其视为量级参考而非精确基准。Step 5添加后处理模板训练完成后需要配置post_processor让输出带上模型要求的特殊 token。TemplateProcessing用$A代表第一条序列、$B代表第二条序列通过special_tokens列表把模板中的 token 名映射到已学到的 ID用tokenizer.token_to_id()动态查询避免硬编码错误from tokenizers.processors import TemplateProcessing # BERT-style tokenizer.post_processor TemplateProcessing( single[CLS] $A [SEP], pair[CLS] $A [SEP] $B [SEP], special_tokens[ ([CLS], tokenizer.token_to_id([CLS])), ([SEP], tokenizer.token_to_id([SEP])), ], ) # GPT-2 style tokenizer.post_processor TemplateProcessing( single$A |endoftext|, special_tokens[ (|endoftext|, tokenizer.token_to_id(|endoftext|)), ], )除了TemplateProcessingpipeline.md 还介绍了RobertaProcessings ... /s形态支持add_prefix_space与trim_offsets和ByteLevelProcessing用于裁剪 ByteLevel 产生的Ġ前缀偏移。模板中的 token 必须出现在训练时的special_tokens列表中否则token_to_id会返回空值导致模板失效——这是特殊 token 未添加类问题的常见根因。Step 6保存与接入 transformers训练、后处理完毕的分词器有三种落地方案# Save to JSON tokenizer.save(my-tokenizer.json) # Save to directory (for transformers) tokenizer.save(my-tokenizer-dir/tokenizer.json) # Convert to transformers format from transformers import PreTrainedTokenizerFast transformers_tokenizer PreTrainedTokenizerFast( tokenizer_objecttokenizer, unk_token[UNK], pad_token[PAD], cls_token[CLS], sep_token[SEP], mask_token[MASK] ) transformers_tokenizer.save_pretrained(my-tokenizer-dir)转换后的save_pretrained会生成标准 transformers 目录tokenizer.jsontokenizer_config.jsonspecial_tokens_map.json之后即可用AutoTokenizer.from_pretrained加载并获得 padding、truncation、return_tensors、offset mapping 等全套 transformers 能力。细节可参考 integration.md其中强调了PreTrainedTokenizerFast也支持直接传tokenizer_filemy-tokenizer.json加载已保存的 JSON且接入后应通过model.resize_token_embeddings(len(tokenizer))同步模型嵌入层大小。Trainer 配置深度解析三个训练器的参数是训练效果的旋钮以下逐一展开。BpeTrainer 参数from tokenizers.trainers import BpeTrainer trainer BpeTrainer( vocab_size30000, # Target vocabulary size min_frequency2, # Minimum frequency for merges special_tokens[[UNK]], # Special tokens (added first) limit_alphabet1000, # Limit initial alphabet size initial_alphabet[], # Pre-defined initial characters show_progressTrue, # Show progress bar continuing_subword_prefix, # Prefix for continuing subwords end_of_word_suffix # Suffix for end of words )调参要点vocab_size英文单语建议从 30k 起步多语言建议 50k 起min_frequency大语料用 2–5小语料可降到 1低于阈值的 token 对不会被合并直接影响词表覆盖limit_alphabet对 CJK 等字符集庞大的非英文语料应调低用于裁剪初始字母表规模控制训练成本special_tokens会最先加入词表并占用 ID 0..ninitial_alphabet可注入预定义初始字符集。WordPieceTrainer 参数from tokenizers.trainers import WordPieceTrainer trainer WordPieceTrainer( vocab_size30522, # BERT uses 30,522 min_frequency2, special_tokens[[UNK], [CLS], [SEP], [PAD], [MASK]], limit_alphabet1000, continuing_subword_prefix##, # BERT-style prefix show_progressTrue )WordPiece 与 BPE 的差异在于合并判据见 algorithms.md 的score freq(pair) / (freq(first) × freq(second))因此min_frequency与limit_alphabet的作用方向一致但词表对低频但语义紧密的片段更友好。continuing_subword_prefix直接影响解码还原与模型对词边界的感知。UnigramTrainer 参数from tokenizers.trainers import UnigramTrainer trainer UnigramTrainer( vocab_size8000, # Typically smaller than BPE/WordPiece special_tokens[unk, s, /s], unk_tokenunk, max_piece_length16, # Maximum token length n_sub_iterations2, # EM algorithm iterations shrinking_factor0.75, # Vocabulary reduction rate show_progressTrue )Unigram 的四个独有参数背后是它的训练机制EM 算法 迭代裁剪max_piece_length候选 token 的最大字符长度限制搜索空间n_sub_iterations每次裁剪前重估概率的 EM 迭代次数shrinking_factor每轮从词表中裁剪的比例0.75 表示每轮移除约 25% 的低影响 token。由于 Unigram 词表存的是 (token, score) 概率对而非合并规则同等覆盖下词表可以做得更小示例中 8k这也是 algorithms.md 中Unigram 最小词表、良好覆盖结论的来源。大规模数据集训练内存高效训练流式迭代器对 Wikipedia 这类超大规模语料使用datasets的 streaming 模式配合迭代器内存占用可从全量载入的 10 GB 降至约 200 MBfrom datasets import load_dataset from tokenizers import Tokenizer from tokenizers.models import BPE from tokenizers.trainers import BpeTrainer # Load dataset dataset load_dataset(wikipedia, 20220301.en, splittrain, streamingTrue) # Create iterator (yields batches) def batch_iterator(batch_size1000): batch [] for sample in dataset: batch.append(sample[text]) if len(batch) batch_size: yield batch batch [] if batch: yield batch # Initialize tokenizer tokenizer Tokenizer(BPE()) trainer BpeTrainer(vocab_size50000, special_tokens[|endoftext|]) # Train (memory efficient - streams data) tokenizer.train_from_iterator( batch_iterator(), trainertrainer )这里的原理是BPE 训练本质上是词频统计 迭代合并二者都可以增量完成因此迭代器逐批喂入即可无需保留原始文本。多文件训练import glob # Find all training files files glob.glob(data/train/*.txt) print(fTraining on {len(files)} files) # Train on all files tokenizer.train(filesfiles, trainertrainer)train接受文件路径列表会按顺序流式读取适合按 shard 存放的语料。并行训练多进程分片from multiprocessing import Pool, cpu_count import os def train_shard(shard_files): Train tokenizer on a shard of files. tokenizer Tokenizer(BPE()) trainer BpeTrainer(vocab_size50000) tokenizer.train(filesshard_files, trainertrainer) return tokenizer.get_vocab() # Split files into shards num_shards cpu_count() file_shards [files[i::num_shards] for i in range(num_shards)] # Train shards in parallel with Pool(num_shards) as pool: vocab_shards pool.map(train_shard, file_shards) # Merge vocabularies (custom logic needed) # This is a simplified example - real implementation would merge intelligently final_vocab {} for vocab in vocab_shards: final_vocab.update(vocab)需要特别说明并行分片训练是一个简化示例。每个分片独立学到的合并规则无法通过简单字典合并得到等价词表——真正生产级的做法是分片统计词频后合并频次再对全量频次执行一次完整合并流程。文档也明确标注real implementation would merge intelligently因此该片段更适合作为理解并行化思路的起点而非可直接复制的生产代码。领域定制分词器代码分词器代码语料的特点是大小写敏感、空白与符号密集。配置要点是最小归一化 字节级预分词from tokenizers import Tokenizer from tokenizers.models import BPE from tokenizers.trainers import BpeTrainer from tokenizers.pre_tokenizers import ByteLevel from tokenizers.normalizers import Sequence, NFC # Code-optimized configuration tokenizer Tokenizer(BPE()) # Minimal normalization (preserve case, whitespace) tokenizer.normalizer NFC() # Only normalize Unicode # Byte-level pre-tokenization (handles all characters) tokenizer.pre_tokenizer ByteLevel() # Train on code corpus trainer BpeTrainer( vocab_size50000, special_tokens[|endoftext|, |pad|], min_frequency2 ) tokenizer.train(files[code_corpus.txt], trainertrainer)NFC()只做 Unicode 规范组合e 组合音符 →é不进行小写化或去重音从而保留代码中的标识符语义。医学/科学分词器医学文本含大量长复合词与特殊字符需要保留大小写与标点、提高合并门槛# Preserve case and special characters from tokenizers.normalizers import NFKC from tokenizers.pre_tokenizers import Whitespace, Punctuation, Sequence tokenizer Tokenizer(BPE()) # Minimal normalization tokenizer.normalizer NFKC() # Preserve medical terms tokenizer.pre_tokenizer Sequence([ Whitespace(), Punctuation(behaviorisolated) # Keep punctuation separate ]) trainer BpeTrainer( vocab_size50000, special_tokens[[UNK], [CLS], [SEP]], min_frequency3 # Higher threshold for rare medical terms ) tokenizer.train(files[pubmed_corpus.txt], trainertrainer)NFKC是兼容性组合归一化能把全角字符、连字fi→fi统一为规范形式适合术语密度高的科学文本min_frequency3则抑制罕见术语的过度碎片化。多语言分词器多语言场景的要点是归一化但不小写、字节级覆盖所有文字系统、词表放大、不限字母表# Handle multiple scripts from tokenizers.normalizers import NFKC, Lowercase, Sequence tokenizer Tokenizer(BPE()) # Normalize but dont lowercase (preserves script differences) tokenizer.normalizer NFKC() # Byte-level handles all Unicode from tokenizers.pre_tokenizers import ByteLevel tokenizer.pre_tokenizer ByteLevel() trainer BpeTrainer( vocab_size100000, # Larger vocab for multiple languages special_tokens[unk, s, /s], limit_alphabetNone # No limit (handles all scripts) ) # Train on multilingual corpus tokenizer.train(files[multilingual_corpus.txt], trainertrainer)注意limit_alphabetNone显式取消字母表上限因为多文字系统拉丁、西里尔、天城文、汉字等的字符并集远超单语场景。词表大小选择按任务推荐的词表规模任务推荐词表大小理由英文单语30,000 – 50,000覆盖均衡多语言50,000 – 250,000语言越多token 需求越大代码30,000 – 50,000与英文接近领域专属10,000 – 30,000更小、更聚焦的词表字符级任务1,000 – 5,000仅字符 子词词表规模的影响小词表10k优点训练快、模型小、内存占用低缺点每句 token 数更多OOV 处理更差。中等词表30k–50k优点均衡的性价比业界标准选择缺点基本没有推荐作为默认起点。大词表100k优点每句 token 更少、OOV 覆盖率更好缺点训练更慢、嵌入表更大模型参数量与内存随之上升。实证对比测试与其凭经验猜测不如直接做一次多词表扫描用每 token 平均字符数衡量压缩率# Train multiple tokenizers with different vocab sizes vocab_sizes [10000, 30000, 50000, 100000] for vocab_size in vocab_sizes: tokenizer Tokenizer(BPE()) trainer BpeTrainer(vocab_sizevocab_size) tokenizer.train(files[sample.txt], trainertrainer) # Evaluate on test set test_text Test sentence for evaluation... tokens tokenizer.encode(test_text).ids print(fVocab: {vocab_size:6d} | Tokens: {len(tokens):3d} | Avg: {len(test_text)/len(tokens):.2f} chars/token) # Example output: # Vocab: 10000 | Tokens: 12 | Avg: 2.33 chars/token # Vocab: 30000 | Tokens: 8 | Avg: 3.50 chars/token # Vocab: 50000 | Tokens: 7 | Avg: 4.00 chars/token # Vocab: 100000 | Tokens: 6 | Avg: 4.67 chars/token从示例输出可以清楚看到压缩率随词表增大而改善的收益递减趋势——这正是先扫描、再定档策略的价值。测试分词器质量训练完成不等于质量合格仓库文档给出了三个可量化的测试维度。覆盖率测试未知 token 率# Test on held-out data test_corpus load_dataset(wikitext, wikitext-103-raw-v1, splittest) total_tokens 0 unk_tokens 0 unk_id tokenizer.token_to_id([UNK]) for text in test_corpus[text]: if text.strip(): encoding tokenizer.encode(text) total_tokens len(encoding.ids) unk_tokens encoding.ids.count(unk_id) unk_rate unk_tokens / total_tokens print(fUnknown token rate: {unk_rate:.2%}) # Good quality: 1% unknown tokens # Acceptable: 1-5% # Poor: 5%判定标准1% 优秀、1–5% 可接受、5% 较差。注意该测试只对配置了unk_token的模型有意义ByteLevel BPE 因为没有未知 token 概念天然不会产生[UNK]。压缩率测试每 token 字符数# Measure tokenization efficiency import numpy as np token_lengths [] for text in test_corpus[text][:1000]: if text.strip(): encoding tokenizer.encode(text) chars_per_token len(text) / len(encoding.ids) token_lengths.append(chars_per_token) avg_chars_per_token np.mean(token_lengths) print(fAverage characters per token: {avg_chars_per_token:.2f}) # Good: 4-6 chars/token (English) # Acceptable: 3-4 chars/token # Poor: 3 chars/token (under-compression)英文参考值4–6 字符/token 良好3–4 可接受3 欠压缩。语义测试人工抽查切分# Manually inspect tokenization of common words/phrases test_phrases [ tokenization, machine learning, artificial intelligence, preprocessing, hello world ] for phrase in test_phrases: tokens tokenizer.encode(phrase).tokens print(f{phrase:25s} → {tokens}) # Good tokenization: # tokenization → [token, ization] # machine learning → [machine, learning] # artificial intelligence → [artificial, intelligence]优秀的切分应当是词根完整、形态学合理如tokenization而非碎片化的逐字符切分。故障排查问题一训练过慢解决方案减小词表大小提高min_frequency用limit_alphabet缩小初始字母表先在子集上试训。# Fast training configuration trainer BpeTrainer( vocab_size20000, # Smaller vocab min_frequency5, # Higher threshold limit_alphabet500, # Limit alphabet show_progressTrue )问题二未知 token 率过高解决方案增大词表降低min_frequency检查归一化是否过激如不必要的去重音/小写化丢失了领域字符。# Better coverage configuration trainer BpeTrainer( vocab_size50000, # Larger vocab min_frequency1, # Lower threshold )问题三切分质量差解决方案核对归一化是否符合使用场景检查预分词是否正确切分确保训练语料具有代表性尝试更换算法BPE ↔ WordPiece ↔ Unigram。定位此类问题的最快方式是把管线逐级拆开观察中间产物# Debug tokenization pipeline text Sample text to debug # Check normalization normalized tokenizer.normalizer.normalize_str(text) print(fNormalized: {normalized}) # Check pre-tokenization pre_tokens tokenizer.pre_tokenizer.pre_tokenize_str(text) print(fPre-tokens: {pre_tokens}) # Check final tokenization tokens tokenizer.encode(text).tokens print(fTokens: {tokens})algorithms.md 中还补充了另两类典型问题子词切分过于细碎running→ 逐字符时应增大词表、延长训练或降低min_frequency切分不一致running→[run,ning]但runner→ 逐字符时应检查归一化与预分词器的确定性。最佳实践清单仓库文档在结尾给出了七条经过实战检验的最佳实践使用有代表性的训练数据——语料必须匹配目标领域领域不匹配是覆盖率差的头号原因从标准配置起步——BERT WordPiece 或 GPT-2 BPE 作为基线再逐步调整在留出集上测试——量化未知 token 率与压缩率而非只看训练集表现迭代词表大小——按 30k / 50k / 100k 梯度扫描用数据说话随模型一起保存分词器——确保可复现性避免推理时词表与模型不匹配为分词器做版本管理——跟踪词表变更方便回滚与对比文档化特殊 token——特殊 token 的 ID 顺序直接影响模型训练与解码必须记录在案。与之呼应integration.md 进一步建议接入 transformers 后始终使用 fast tokenizerRust 内核5–10× 加速、完整 offset mapping、用datasets.map(batchedTrue)批量切分、对重复输入启用lru_cache、新增特殊 token 后调用resize_token_embeddings同步嵌入层。仓库内延伸阅读本指南对应技能位于仓库的 02-tokenization/huggingface-tokenizers/ 目录围绕训练主题还有四份可深入的材料SKILL.md技能总览含快速上手示例、性能定位与算法速览references/algorithms.mdBPE / WordPiece / Unigram 的算法原理、逐步示例与对比基准references/pipeline.md归一化、预分词、后处理、解码器各组件的完整参考references/integration.mdAutoTokenizer/PreTrainedTokenizerFast接入、特殊 token 管理、padding/truncation 与对齐追踪。结合本指南的训练流程你可以走通选型 → 数据 → 训练 → 后处理 → 保存 → 接入 transformers → 质量验证的完整链路为任意架构和领域构建生产级自定义词表。【免费下载链接】AI-Research-SKILLsComprehensive open-source library of AI research and engineering skills for any AI model. Package the skills and your claude code/codex/gemini agent will be an AI research agent with full horsepower. Maintained by Orchestra Research.项目地址: https://gitcode.com/gh_mirrors/ai/AI-Research-SKILLs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
