大模型词表注入(Vocabulary Injection)
大模型词表注入(Vocabulary Injection)是指在预训练语言模型(如GPT、LLAMA等)的基础上,动态扩展其词表(Vocabulary)的技术,以适应特定任务或领域的需求。
一、词表注入的「原理」
- 词表结构与嵌入层调整
- 大模型的词表通常由子词(subword)或词片(token)组成(如BPE、WordPiece算法生成),每个词对应一个嵌入向量(Embedding Vector)。
- 注入新词时,需要扩展模型的嵌入矩阵(Embedding Matrix),新增词对应的向量,并调整模型输入层(Embedding Layer)和输出层(如LM Head)的维度。
- 新词向量的初始化
- 直接随机初始化新词的嵌入向量可能导致训练不稳定。常见策略是:
- 复用相似词向量:例如,将新词"LLM"初始化为"language"和"model"的平均向量。
- 对齐预训练语义空间:通过外部词向量(如Word2Vec)映射到模型的嵌入空间。
- 直接随机初始化新词的嵌入向量可能导致训练不稳定。常见策略是:
- 参数适配与微调
- 注入新词后,通常需要在小规模领域数据上对模型进行微调(Fine-tuning),使新词的嵌入向量与原有参数协同工作。
- 某些方法(如[《Extending Pre-trained Models with Domain-Specific Vocabulary》](https://arxiv.org/abs/2104.08646))会冻结部分参数,仅训练新词相关部分以减少计算量。
二、词表注入的「原因」
- 解决未登录词(OOV)问题
- 预训练模型的词表固定,无法覆盖领域专有名词(如医学术语“EGFR”)、新造词(如网络流行语“栓Q”)或多语言词汇。
- 提升领域任务性能
- 在特定领域(法律、医疗、金融)中,直接使用原始词表可能导致文本被过度切分为子词,丢失语义信息。注入领域词表可保留关键术语的完整性。
- 多语言扩展需求
- 为支持新语言,需注入该语言的词汇(如中文字符、俄文字母),同时调整模型处理多语言的能力。
- 避免全模型重训练
- 从头预训练大模型成本极高,词表注入允许在原有模型基础上低成本扩展,节省计算资源和时间。
三、技术挑战与解决方案
- 嵌入空间对齐
- 问题:新词向量可能破坏原有语义空间的一致性。
- 方案:使用对比学习(Contrastive Learning)或跨词注意力(Cross-token Attention)对齐新旧词向量。
- 模型结构限制
- 问题:Transformer的参数量与词表大小相关,盲目扩展词表会显著增加模型体积。
- 方案:动态词表(Dynamic Vocabulary)、参数共享(如ALBERT的跨层参数共享)。
- 训练数据偏差
- 问题:注入新词后,若微调数据不足,模型可能过拟合或遗忘原有知识。
- 方案:渐进式训练(Progressive Training)或知识蒸馏(Knowledge Distillation)。
四、典型应用场景
- 领域适配
- 例如,向BERT注入法律术语后,在合同解析任务中表现更佳。
- 多语言模型扩展
- 如为英文训练的GPT-2注入中文词表,支持中英混合生成。
- 实时更新
- 快速响应新事件(如疫情术语“奥密克戎”)或网络热词。
五、代码及微调实验
有两种方法:1.词表注入;2.词表训练–>添加词表
- 导入原始大模型及tokenier
tokenizer = AutoTokenizer.from_pretrained(model_path,trust_remote_code=True,use_fast=False if model_arch == 'llama' else True
)model = AutoModel.from_pretrained(model_path,trust_remote_code=True,torch_dtype=torch_dtype
)
- 词表注入
all_words = []
with open(file, 'r', encoding='utf-8') as f:lines = f.readlines()
words = [line.strip() for line in lines]
all_words.extend(words)
tokenizer.add_tokens(all_words)
tokenizer.save_pretrained(save_path)
- 词表训练
使用sentencepiece==4.1.0 训练词表
sp.SentencePieceTrainer.train(# 只支持 txt 和 tsv 格式input=corpus,# 保存的模型前缀名model_prefix='bpe_expand',# 词表大小vocab_size=vocab_size,# 指定模型的字符覆盖率, 中文日文等推荐为 0.9995, 其余可以尝试 1.0character_coverage=character_coverage,# 分词算法model_type='bpe',# 是否将数字划分为单个 token, 在 llama 中是这么做的split_digits=True if model_arch == 'llama' else False,# 指定在遇到未知或很少的字符时将其分解为 UTF-8 字节, 开启后等效于 bbpebyte_fallback=True,# 指定输入句子的最大长度,以字节为单位max_sentence_length=max_sentence_length
参数说明:
参数 | 重要性 | 推荐场景 |
---|---|---|
input | 必填 | 所有场景 |
model_prefix | 必填 | 所有场景 |
model_type | 高 | 根据需求选择 unigram 或 bpe |
vocab_size | 高 | 通常设为 32000 、50000 或更高 |
character_coverage | 中 | 多语言数据设为 0.9995 ,单语言数据默认 1.0 |
byte_fallback | 中 | 处理未知字符时设为 True |
max_sentence_length | 中 | 处理长文本时需调大(如 16384 ) |
split_digits | 中 | 需区分数字时设为 True |
-
添加词表
词表训练之后需要添加词表,并保存:
#1.加载bpe model sp_bpe = sp.SentencePieceProcessor() sp_bpe.load(bpe_model) #2.处理词汇 raw_vocab = [sp_bpe.id_to_piece(id) for id in range(sp_bpe.get_piece_size())] clean_vocab = list(set(filter(is_chinese, raw_vocab))) #添加词汇并保存zz tokenizer.add_tokens(clean_vocab) tokenizer.save_pretrained(save_path)
-
维度更新
经过词表注入或者词表训练,两种方法其中之一之后,更新模型的维度参数:
model.resize_token_embeddings(new_length)#new_length 为新词表的词汇量
model.save_pretrained(save_path)