使用Huggingface训练自定义分词器,推理时出现异常分词问题
问题:自定义训练的BertWordPieceTokenizer拆分高频词汇
我使用Huggingface的tokenizers库从零训练了BertWordPieceTokenizer,训练过程无报错,但在推理阶段,像“awesome”“terrible”这类出现在训练数据中的高频词汇,会被拆分为多个子词(例如aw、##es、##ome),且vocab文件中没有完整的这些词汇。我原本认为训练数据中存在的词汇应该会被分词器完整识别。
训练代码
from tokenizers import BertWordPieceTokenizer files = ["data.txt"] # 每行一条文本 tokenizer = BertWordPieceTokenizer(lowercase=True) tokenizer.train(files=files, vocab_size=3000, min_frequency=2, special_tokens=["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]) tokenizer.save_model("my_tokenizer")
推理代码
from transformers import BertTokenizer tokenizer = BertTokenizer.from_pretrained("my_tokenizer") text = "this movie was awesome and I loved the acting" tokens = tokenizer.tokenize(text) print(tokens)
输出结果:
['this', 'movie', 'was', 'aw', '##es', '##ome', 'and', 'i', 'loved', 'the', 'acting']
已尝试的操作
- 将vocab_size提升至10k,问题依旧
- 降低min_frequency至1
- 关闭lowercase设置
- 检查vocab.txt,仍未找到预期的完整词汇
原因分析与解决思路
原因:BertWordPiece的训练逻辑并非直接收录高频完整词
BertWordPieceTokenizer采用基于统计的子词合并算法,核心目标是用最少的子词覆盖最多的文本内容,而非单纯保留出现次数多的完整词。其训练流程大致为:
- 初始化词汇表为所有单个字符
- 迭代合并出现频率最高的子词对,直到词汇量达到设定的
vocab_size - 优先选择能减少整体分词数量的子词组合,而非优先保留完整词
你的训练数据量过小(示例仅4行),哪怕目标词汇出现次数达标,算法也无法感知到保留完整词的收益——拆分后的子词可能能覆盖更多其他文本片段,因此被优先保留。
解决思路
- 大幅增加训练数据量:这是最有效的解决方式。只有当训练数据足够大,完整词的出现频率高到让算法认为保留它比拆分为子词更能提升分词效率时,才会被收录进词汇表。
- 调整训练参数:
- 设置
limit_alphabet参数(例如limit_alphabet=60),限制初始字符集的大小,迫使算法更快合并出更长的子词/完整词 - 确认
min_frequency的统计逻辑:如果目标词汇在初始字符拆分阶段被分散统计,可能需要调整参数,但核心还是数据量问题
- 设置
- 手动添加目标词汇:如果需要强制保留特定词汇,可以在训练后的
vocab.txt末尾添加这些词(注意调整vocab_size预留位置),再重新加载分词器。
内容的提问来源于stack exchange,提问作者RobGG3938




