使用自定义HF Tokenizers分词器后,如何构建适配的预训练模型?
核心结论
更换自定义Tokenizer后,必须修改模型的嵌入层并重新训练,直接使用原预训练模型会完全失效。
为什么必须修改?
- 词汇表映射不匹配:原
fnlp/bart-base-chinese的分词器和你的自定义Tokenizer的Token-ID对应关系完全不同。模型的嵌入层权重是基于原词汇表训练的,新Tokenizer输出的Token ID在原模型中要么没有对应的嵌入向量,要么对应错误的语义,模型根本无法理解输入。 - 嵌入层维度不兼容:如果自定义Tokenizer的词汇表大小和原模型的不一致,加载模型时会直接报错——因为模型嵌入层的
num_embeddings参数固定为原词汇表大小,新词汇表大小不匹配就无法兼容。
适配自定义Tokenizer的具体步骤
1. 加载自定义Tokenizer和原模型
from transformers import BartForConditionalGeneration # 加载自定义Tokenizer tokenizer = MyCustomTokenizerFast.from_pretrained('myusername/mytokenizer') # 加载原预训练模型 checkpoint = 'fnlp/bart-base-chinese' model = BartForConditionalGeneration.from_pretrained(checkpoint, output_attentions=True, output_hidden_states=True)
2. 修改模型的嵌入层
Bart是Seq2Seq模型,需要同时修改编码器、解码器的嵌入层,以及最后的语言模型头:
import torch # 获取自定义词汇表大小 new_vocab_size = tokenizer.vocab_size # 获取原嵌入层的维度 embedding_dim = model.model.encoder.embed_tokens.embedding_dim # 替换编码器嵌入层 model.model.encoder.embed_tokens = torch.nn.Embedding(new_vocab_size, embedding_dim) # 替换解码器嵌入层 model.model.decoder.embed_tokens = torch.nn.Embedding(new_vocab_size, embedding_dim) # 替换语言模型头(如果存在) if hasattr(model, 'lm_head'): model.lm_head = torch.nn.Linear(embedding_dim, new_vocab_size, bias=False)
3. 初始化嵌入层权重(可选但推荐)
如果自定义Tokenizer和原分词器有部分重合的Token(比如中文汉字),可以用原模型的权重初始化这些Token的嵌入,其余随机初始化,保留原模型的部分知识:
# 获取原分词器的词汇表 from transformers import BertTokenizer original_tokenizer = BertTokenizer.from_pretrained(checkpoint) # 遍历自定义词汇表,匹配重合Token for token, idx in tokenizer.get_vocab().items(): if token in original_tokenizer.get_vocab(): original_idx = original_tokenizer.get_vocab()[token] # 复制原嵌入权重 model.model.encoder.embed_tokens.weight.data[idx] = model.model.encoder.embed_tokens.weight.data[original_idx].clone() model.model.decoder.embed_tokens.weight.data[idx] = model.model.decoder.embed_tokens.weight.data[original_idx].clone()
4. 重新训练模型
- 优先选择继续预训练:用和训练Tokenizer类似的大规模中文语料,让模型在新词汇表上学习嵌入表示,这能让模型更好地适配新Tokenizer。
- 如果数据量有限,也可以直接在任务数据集上微调,但效果可能不如继续预训练稳定。
内容的提问来源于stack exchange,提问作者Raptor
相关产品推荐
相关产品推荐

