是一種訓練技術,用於調整預訓練的大型語言模型,更好地適應特定任務或領域的數據。預訓練模型是在海量通用文本數據上訓練的,具備廣泛的語言知識。然而,這些通用模型在特定、小眾或專業領域的任務上可能表現不佳。微調就是透過在特定任務的數據集上進行額外訓練,讓模型「專精化」的過程
為什麼微調LLM很重要?
提升特定任務性能
:預訓練模型可能在通用任務上表現出色,但在特定領域(法律、醫療、金融)或特定任務(情感分析、問答)上,其性能可能不如經過微調的模型。微調能顯著提高模型在這些特定場景下的準確性和相關性適應領域知識
:透過微調,模型可以學習特定領域的術語、語氣、上下文和知識,使其輸出更符合該領域的專業要求降低提示工程的複雜度
:對於某些任務,單靠提示工程可能難以達到理想效果。微調可以讓模型直接內化任務的邏輯和模式,減少對複雜提示的依賴應對數據分佈變化
:當應用場景的數據分佈與預訓練數據有顯著差異時,微調能幫助模型適應新的數據特徵節省資源和時間
:相對於從頭訓練一個大型模型,微調所需的數據量和計算資源要少得多,時間成本也更低微調常見策略
全量微調 (Full Fine-tuning)
方法
:更新模型中的所有或大部分參數優點
:通常能達到最佳性能,模型可以完全適應新數據缺點
:需要大量的計算資源(GPU記憶體和算力)和較多的標註數據,訓練時間較長,每個微調任務都需要儲存一份完整的模型權重參數高效微調 (Parameter-Efficient Fine-tuning - PEFT)
方法
:只更新模型中一小部分參數,或者引入少量新的可訓練參數,同時保持大部分預訓練參數凍結不變優點
:顯著減少了計算資源和儲存需求,加快了訓練速度,有效降低了過擬合風險LoRA (Low-Rank Adaptation of Large Language Models)
這是目前非常流行且高效的技術。它在原始模型的權重矩陣旁邊增加一對低秩矩陣(A 和 B),只訓練這些新增的矩陣,而原始模型權重保持凍結。在推理時,可以將這些小矩陣的乘積加回到原始權重上
Prefix-tuning
在模型輸入序列前添加一組可訓練的「前綴」向量
Prompt-tuning
類似 Prefix-tuning,但只添加在輸入序列的最開頭,且長度通常更短
Adapter-tuning
在預訓練模型的每一層中插入小型神經網路模塊(Adapter),只訓練這些模塊
數據準備
收集和整理特定任務的數據集,並將其格式化為模型訓練所需的輸入-輸出對。數據質量對微調結果至關重要
模型選擇
選擇一個適合您任務和資源的預訓練LLM
分詞化(Tokenization)
使用預訓練模型對應的分詞器將文本數據轉換為模型能理解的數字ID
訓練
使用準備好的數據集,在預選的微調策略下訓練模型。這涉及損失函數的計算和優化器的更新
評估
在獨立的驗證集或測試集上評估微調後模型的性能,通常使用與任務相關的評估指標
部署
將微調後的模型部署到實際應用中
使用 Hugging Face peft 庫進行 LoRA 微調
以下程式碼範例展示如何使用 Hugging Face 的 transformers 和 peft (Parameter-Efficient Fine-tuning) 庫對一個小型 LLM (gpt2) 進行 LoRA 微調
首先,已安裝相關庫
pip install transformers datasets peft accelerate
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer
from datasets import load_dataset
from peft import LoraConfig, get_peft_model, TaskType
# --- 1. 載入預訓練模型和分詞器 ---
model_name = "gpt2" # 選擇一個小型模型進行示範
try:
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)
print(f"成功載入模型:{model_name}")
except Exception as e:
print(f"載入模型或分詞器時發生錯誤:{e}")
print("請檢查您的網路連接或嘗試其他模型名稱。")
exit()
# 設定 tokenizer 的 padding token,避免警告
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
# 設置模型的 pad_token_id,確保模型在生成時能正確處理 padding
model.config.pad_token_id = tokenizer.pad_token_id
# --- 2. 準備數據集 ---
# 這裡使用一個簡單的範例數據集。實際應用中會是您的任務數據
# 我們將使用一個極小的數據集來加速範例運行
# 假設我們想讓 GPT2 學會生成一些帶有特定風格的短語
data = {
"text": [
"A wizard's spell is a wondrous thing.",
"The enchanted forest whispered ancient secrets.",
"Dragons soar high above the misty mountains.",
"In the realm of magic, possibilities are endless.",
"A knight's quest for glory began at dawn.",
"The ancient tome held forgotten lore.",
"Elves danced under the moonlight in the glen."
]
}
# 使用 Hugging Face datasets 庫創建數據集
from datasets import Dataset
raw_dataset = Dataset.from_dict(data)
def preprocess_function(examples):
"""
數據預處理函數:將文本分詞並添加 labels
對於因果語言模型 (Causal LM),通常將輸入作為其自身的標籤
"""
tokenized_inputs = tokenizer(examples["text"], truncation=True, max_length=128)
tokenized_inputs["labels"] = tokenized_inputs["input_ids"].copy()
return tokenized_inputs
# 對數據集進行分詞和預處理
tokenized_dataset = raw_dataset.map(
preprocess_function,
batched=True,
remove_columns=["text"] # 移除原始文本列
)
# 為了簡化,我們只用 train 分割,實際應用中應有 train/eval 分割
train_dataset = tokenized_dataset
print(f"\n數據集範例 (tokenized_dataset[0]):\n{train_dataset[0]}")
print(f"解碼後範例: {tokenizer.decode(train_dataset[0]['input_ids'])}")
# --- 3. 配置 LoRA ---
# r: LoRA 的秩,值越大表達能力越強,但參數也越多
# lora_alpha: LoRA 適配的縮放因子
# target_modules: 指定哪些模塊要應用 LoRA (通常是 attention 相關的線性層)
# lora_dropout: LoRA 層的 dropout 比率
# bias: 是否訓練偏置參數
# task_type: 任務類型,對於語言模型是 CAUSAL_LM
lora_config = LoraConfig(
r=8,
lora_alpha=16,
target_modules=["c_attn", "c_proj", "c_fc"], # GPT2 的 attention/MLP 層
lora_dropout=0.05,
bias="none",
task_type=TaskType.CAUSAL_LM,
)
# 將 LoRA 配置應用到模型
model = get_peft_model(model, lora_config)
model.print_trainable_parameters() # 打印可訓練參數的數量
# --- 4. 配置訓練參數 ---
training_args = TrainingArguments(
output_dir="./lora_fine_tuned_gpt2", # 模型輸出目錄
num_train_epochs=5, # 訓練輪數,小數據集可以多跑幾輪
per_device_train_batch_size=2, # 每個設備的訓練批次大小
gradient_accumulation_steps=1, # 梯度累積步數
warmup_steps=50, # 預熱步數
weight_decay=0.01, # 權重衰減
logging_dir="./logs", # 日誌目錄
logging_steps=10, # 每隔多少步記錄一次日誌
learning_rate=2e-4, # 學習率
fp16=True, # 啟用混合精度訓練 (如果您的 GPU 支持)
report_to="none" # 不報告到任何外部平台
)
# --- 5. 創建 Trainer 並開始訓練 ---
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
tokenizer=tokenizer, # 傳入 tokenizer 給 Trainer
)
print("\n開始 LoRA 微調...")
trainer.train()
print("微調完成!")
# --- 6. 保存微調後的模型 ---
# LoRA 只保存新增的小權重,而不是整個模型
lora_output_dir = "./lora_fine_tuned_gpt2_weights"
model.save_pretrained(lora_output_dir)
tokenizer.save_pretrained(lora_output_dir) # 也保存分詞器以備加載時使用
print(f"微調後的 LoRA 權重已保存到:{lora_output_dir}")
# --- 7. 加載微調後的模型並進行推理測試 ---
print("\n--- 載入微調後的模型並測試生成 ---")
from peft import PeftModel, PeftConfig
# 首先載入原始預訓練模型
base_model = AutoModelForCausalLM.from_pretrained(model_name)
base_tokenizer = AutoTokenizer.from_pretrained(model_name)
if base_tokenizer.pad_token is None:
base_tokenizer.pad_token = base_tokenizer.eos_token
# 加載 LoRA 配置和權重
lora_config_loaded = PeftConfig.from_pretrained(lora_output_dir)
peft_model_loaded = PeftModel.from_pretrained(base_model, lora_output_dir)
peft_model_loaded.eval() # 設置為評估模式
# 測試生成
prompt_test = "A wizard's" # 預期模型能接續生成類似訓練數據的風格
input_ids_test = base_tokenizer.encode(prompt_test, return_tensors='pt')
with torch.no_grad():
output_test = peft_model_loaded.generate(
input_ids_test,
max_length=50,
num_beams=5,
no_repeat_ngram_size=2,
early_stopping=True,
do_sample=True,
temperature=0.7,
top_p=0.95,
top_k=50
)
generated_text_test = base_tokenizer.decode(output_test[0], skip_special_tokens=True)
print(f"提示詞: {prompt_test}")
print(f"微調後模型生成結果:\n{generated_text_test}")
# 對比未微調模型的生成結果 (可選)
print("\n--- 對比未微調模型生成結果 ---")
with torch.no_grad():
original_output = model.base_model.generate( # 使用原始的 GPT2 模型生成 (如果它還在記憶體中)
input_ids_test,
max_length=50,
num_beams=5,
no_repeat_ngram_size=2,
early_stopping=True,
do_sample=True,
temperature=0.7,
top_p=0.95,
top_k=50
)
original_text = base_tokenizer.decode(original_output[0], skip_special_tokens=True)
print(f"原始模型生成結果:\n{original_text}")
載入模型與分詞器
:首先載入一個預訓練的 gpt2 模型及其分詞器數據準備
:創建一個小型模擬數據集,並使用 preprocess_function 函數將文本數據轉換為模型可訓練的輸入 input_ids 和 labelsLoRA配置
:創建 LoraConfig 對象,指定 r (秩)、lora_alpha (縮放因子) 和 target_modules (應用 LoRA 的模型層)。TaskType.CAUSAL_LM 表示我們正在進行因果語言建模任務應用LoRA
:使用 get_peft_model(model, lora_config) 將 LoRA 適配器整合到原始模型中。此時,只有 LoRA 模塊的參數是可訓練的,原始模型的大部分參數都被凍結訓練配置
:使用 Hugging Face TrainingArguments 設定訓練過程的各種參數,如訓練輪數、批次大小、學習率等等訓練模型
:創建 Trainer 對象,並呼叫 trainer.train() 方法開始微調保存模型
:微調完成後,使用 model.save_pretrained() 保存 LoRA 權重。注意: 這裡只保存了 LoRA 引入的少量可訓練參數,而不是整個模型加載與測試
:展示如何加載原始的預訓練模型,然後再通過 PeftModel.from_pretrained() 加載並整合之前保存的 LoRA 權重,最後進行文本生成測試,以觀察微調後模型的行為變化這個範例展示了 LoRA 微調的基本流程,能幫助理解如何在實踐中利用 PEFT 技術對 LLM 進行高效的微調。在實際應用中,會用到更大的數據集和更具體的任務來訓練模型