大模型詞表注入(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)