本篇探討如何使用 PyTorch 建立資料載入器,並以指令資料集微調預訓練的 GPT 模型。文章涵蓋了 DataLoader 的使用、批次大小設定、自定義整理函式的應用,以及如何載入不同規模的 GPT 模型,例如 124M、355M 引數的版本。同時也示範瞭如何評估預訓練模型的效能,並透過計算訓練和驗證損失來監控微調過程。文章提供完整的程式碼範例,包含模型初始化、權過載入、最佳化器設定以及訓練迴圈的執行。此外,還討論瞭如何處理硬體資源限制,例如透過調整模型大小和批次大小來應對記憶體不足的問題,並建議使用 GPU 加速訓練。最後,文章也介紹瞭如何使用 Alpaca 資料集進行微調,以及如何繪製損失曲線圖來觀察模型的學習過程。
7.4 為指令資料集建立資料載入器
在這一部分,我們將使用 PyTorch 的 DataLoader 來為指令資料集建立資料載入器。首先,我們需要從 torch.utils.data 匯入 DataLoader。
from torch.utils.data import DataLoader
num_workers = 0
batch_size = 8
torch.manual_seed(123)
train_dataset = InstructionDataset(train_data, tokenizer)
train_loader = DataLoader(
train_dataset,
batch_size=batch_size,
collate_fn=customized_collate_fn,
shuffle=True,
drop_last=True,
num_workers=num_workers
)
val_dataset = InstructionDataset(val_data, tokenizer)
val_loader = DataLoader(
val_dataset,
batch_size=batch_size,
collate_fn=customized_collate_fn,
shuffle=False,
drop_last=False,
num_workers=num_workers
)
test_dataset = InstructionDataset(test_data, tokenizer)
test_loader = DataLoader(
test_dataset,
batch_size=batch_size,
collate_fn=customized_collate_fn,
shuffle=False,
drop_last=False,
num_workers=num_workers
)
內容解密:
- 匯入必要的模組:首先,從
torch.utils.data匯入DataLoader,這是用於批次載入資料的工具。 - 設定引數:設定
num_workers為 0(表示不使用多執行緒載入資料),batch_size為 8,以及固定亂數種子以保證結果的可重現性。 - 建立資料集例項:分別為訓練、驗證和測試資料建立
InstructionDataset例項,並將其與tokenizer一起傳入。 - 建立 DataLoader:使用
DataLoader分別為訓練、驗證和測試資料集建立載入器。設定batch_size、collate_fn(自定義的批次整理函式)、shuffle(是否打亂資料)和drop_last(是否丟棄最後一個不完整的批次)。
接著,我們來檢查由訓練載入器產生的輸入和目標批次的維度:
print("Train loader:")
for inputs, targets in train_loader:
print(inputs.shape, targets.shape)
輸出如下(已截斷以節省空間):
Train loader:
torch.Size([8, 61]) torch.Size([8, 61])
torch.Size([8, 76]) torch.Size([8, 76])
torch.Size([8, 73]) torch.Size([8, 73])
...
torch.Size([8, 74]) torch.Size([8, 74])
torch.Size([8, 69]) torch.Size([8, 69])
內容解密:
- 檢查批次維度:遍歷
train_loader,列印每個批次的輸入和目標張量的形狀。 - 輸出解析:輸出顯示每個批次的形狀,例如
torch.Size([8, 61]),其中 8 是批次大小,61 是該批次中每個訓練樣本的標記數量。 - 自定義整理函式的作用:由於使用了自定義的
collate_fn,不同批次可以具有不同的序列長度,這在處理變長序列時非常有用。
7.5 載入預訓練的大語言模型
在進行指令微調之前,我們需要載入一個預訓練的 GPT 模型。我們之前已經進行過類別似的操作,但這次我們選擇載入中等大小的模型(355M 引數),因為較小的模型(124M 引數)容量有限,難以達到令人滿意的指令跟隨效果。
from gpt_download import download_and_load_gpt2
from chapter04 import GPTModel
from chapter05 import load_weights_into_gpt
BASE_CONFIG = {
"vocab_size": 50257,
"context_length": 1024,
"drop_rate": 0.0,
"qkv_bias": True
}
model_configs = {
"gpt2-small (124M)": {"emb_dim": 768, "n_layers": 12, "n_heads": 12},
"gpt2-medium (355M)": {"emb_dim": 1024, "n_layers": 24, "n_heads": 16},
"gpt2-large (774M)": {"emb_dim": 1280, "n_layers": 36, "n_heads": 20},
"gpt2-xl (1558M)": {"emb_dim": 1600, "n_layers": 48, "n_heads": 25},
}
CHOOSE_MODEL = "gpt2-medium (355M)"
BASE_CONFIG.update(model_configs[CHOOSE_MODEL])
model_size = CHOOSE_MODEL.split(" ")[-1].lstrip("(").rstrip(")")
settings, params = download_and_load_gpt2(
model_size=model_size,
models_dir="gpt2"
)
model = GPTModel(BASE_CONFIG)
load_weights_into_gpt(model, params)
model.eval();
內容解密:
- 載入必要的模組和函式:匯入下載和載入 GPT-2 模型的函式,以及 GPTModel 的定義和權過載入函式。
- 定義模型組態:定義基礎組態和不同大小的 GPT-2 模型的組態字典。
- 選擇模型並更新組態:選擇要載入的模型(這裡是
gpt2-medium (355M)),並更新基礎組態。 - 下載並載入模型:根據選擇的模型大小下載並載入相應的 GPT-2 模型權重。
- 初始化模型並載入權重:使用更新後的組態初始化 GPTModel,並將下載的權過載入模型。
- 設定模型為評估模式:呼叫
model.eval()將模型設定為評估模式,這會關閉 dropout 等訓練時使用的技術。
微調大語言模型以遵循指令
評估預訓練大語言模型的效能
首先,我們需要評估預訓練的大語言模型(LLM)在某項驗證任務上的表現,以瞭解其在微調前的效能。這將有助於我們理解微調的效果。我們將使用驗證集中的第一個範例進行評估:
torch.manual_seed(123)
input_text = format_input(val_data[0])
print(input_text)
指令內容如下:
Below is an instruction that describes a task. Write a response that appropriately completes the request.
### Instruction:
Convert the active sentence to passive: 'The chef cooks the meal every day.'
接下來,我們使用與第五章中預訓練模型相同的 generate 函式來生成模型的回應:
from chapter05 import generate, text_to_token_ids, token_ids_to_text
token_ids = generate(
model=model,
idx=text_to_token_ids(input_text, tokenizer),
max_new_tokens=35,
context_size=BASE_CONFIG["context_length"],
eos_id=50256,
)
generated_text = token_ids_to_text(token_ids, tokenizer)
內容解密:
torch.manual_seed(123):設定隨機種子以確保結果的可重現性。format_input(val_data[0]):格式化驗證集中的第一個資料範例。generate函式:使用預訓練模型生成回應文字。model=model:指定使用的模型。idx=text_to_token_ids(input_text, tokenizer):將輸入文字轉換為令牌ID。max_new_tokens=35:限制生成的最大令牌數量。context_size=BASE_CONFIG["context_length"]:設定上下文大小。eos_id=50256:指定結束令牌的ID。
為了隔離模型的回應文字,我們需要從生成的文字中減去輸入指令的長度:
response_text = generated_text[len(input_text):].strip()
print(response_text)
輸出結果為:
### Response:
The chef cooks the meal every day.
### Instruction:
Convert the active sentence to passive: 'The chef cooks the
內容解密:
generated_text[len(input_text):].strip():從生成的文字中移除輸入指令部分,並去除前後空白字元。- 輸出結果分析:預訓練模型未能正確遵循指令,將主動句轉換為被動句,而是重複了原始輸入句子和部分指令。
微調大語言模型以遵循指令
現在,我們將對預訓練的大語言模型進行微調,以提高其遵循指令的能力。我們將使用之前準備好的指令資料集對模型進行進一步訓練。
from chapter05 import calc_loss_loader, train_model_simple
在開始訓練之前,讓我們計算初始的訓練和驗證損失:
model.to(device)
torch.manual_seed(123)
with torch.no_grad():
train_loss = calc_loss_loader(train_loader, model, device, num_batches=5)
val_loss = calc_loss_loader(val_loader, model, device, num_batches=5)
print("Training loss:", train_loss)
print("Validation loss:", val_loss)
初始損失值如下:
Training loss: 3.825908660888672
Validation loss: 3.7619335651397705
內容解密:
model.to(device):將模型移動到指定的裝置(CPU或GPU)。calc_loss_loader:計算資料載入器中的損失。train_loader和val_loader:分別是訓練和驗證資料的載入器。num_batches=5:限制計算的批次數量。
處理硬體限制
如果遇到硬體限制,可以透過切換到較小的GPT-2模型(1.24億引數)來解決,方法是更改 CHOOSE_MODEL = "gpt2-medium (355M)" 為 CHOOSE_MODEL = "gpt2-small (124M)"。另外,考慮使用GPU來加速模型訓練。下表提供了在不同裝置上訓練GPT-2模型的參考執行時間。使用相容的GPU不需要更改程式碼,可以顯著加快訓練速度。
7.6 微調大語言模型於指令資料
準備好模型和資料載入器後,我們現在可以開始訓練模型。
清單 7.8 中的程式碼設定了訓練過程,包括初始化最佳化器、設定迭代次數、定義評估頻率和起始上下文,以評估在第一個驗證集指令(val_data[0])上生成的 LLM 回應。
import time
start_time = time.time()
torch.manual_seed(123)
optimizer = torch.optim.AdamW(
model.parameters(), lr=0.00005, weight_decay=0.1
)
num_epochs = 2
train_losses, val_losses, tokens_seen = train_model_simple(
model, train_loader, val_loader, optimizer, device,
num_epochs=num_epochs, eval_freq=5, eval_iter=5,
start_context=format_input(val_data[0]), tokenizer=tokenizer
)
end_time = time.time()
execution_time_minutes = (end_time - start_time) / 60
print(f"訓練完成於 {execution_time_minutes:.2f} 分鐘內。")
內容解密:
- 時間記錄:使用
time.time()函式記錄訓練的開始和結束時間,計算訓練所需的總時間。 - 隨機種子設定:
torch.manual_seed(123)設定 PyTorch 的隨機種子,以確保實驗的可重現性。 - 最佳化器選擇:使用
torch.optim.AdamW最佳化器,並設定學習率為 0.00005,權重衰減為 0.1,以最佳化模型的訓練過程。 - 訓練引數:設定迭代次數
num_epochs為 2,並呼叫train_model_simple函式進行訓練,期間記錄訓練損失、驗證損失和已處理的 token 數量。
訓練輸出與結果分析
輸出顯示了模型在兩個迭代過程中的訓練進度,損失值持續下降,表明模型能夠有效地學習並遵循指令生成適當的回應。
Ep 1 (Step 000000): Train loss 2.637, Val loss 2.626
Ep 1 (Step 000005): Train loss 1.174, Val loss 1.103
...
Ep 2 (Step 000230): Train loss 0.300, Val loss 0.657
內容解密:
- 損失值下降:訓練和驗證損失隨著迭代的進行而下降,表明模型的效能正在提升,能夠更好地理解和遵循指令。
- 生成回應範例:模型在每個迭代結束時生成回應,驗證其在特定任務(如主動句轉換為被動句)上的表現。
- 執行時間:不同硬體組態下的執行時間比較,展示了使用 GPU 加速訓練的顯著優勢。
7.7 提取和儲存回應
在對 LLM 進行指令資料微調後,我們現在準備在保留的測試集上評估其效能。首先,我們提取模型為測試資料集中的每個輸入生成的回應,並將其收集起來進行手動分析,然後量化回應的品質。
練習 7.3 在原始 Alpaca 資料集上進行微調
Alpaca 資料集由史丹佛大學的研究人員發布,是最早且最受歡迎的公開指令資料集之一,包含 52,002 個條目。作為本章使用的 instruction-data.json 的替代方案,可以考慮在該資料集上對 LLM 進行微調。
內容解密:
- Alpaca 資料集介紹:該資料集包含的條目數量遠多於本章使用的資料集,因此建議使用 GPU 加速訓練,以避免記憶體問題。
- 處理記憶體問題:如果遇到記憶體錯誤,可以透過降低
batch_size或allowed_max_length的值來解決。
繪製損失曲線圖
使用 plot_losses 函式繪製訓練和驗證損失曲線,以直觀地展示模型的學習過程。
from chapter05 import plot_losses
epochs_tensor = torch.linspace(0, num_epochs, len(train_losses))
plot_losses(epochs_tensor, tokens_seen, train_losses, val_losses)
內容解密:
- 損失曲線分析:透過觀察損失曲線,可以看到模型的訓練和驗證損失在兩個迭代過程中顯著下降,表明模型正在有效地學習。
- 曲線趨勢:在初始階段,損失值迅速下降,隨後下降速度放緩,表明模型正在微調其學習到的表示並趨於穩定。
此圖示展示了兩個迭代過程中的訓練和驗證損失趨勢,實線代表訓練損失,虛線代表驗證損失。兩者均呈現出類別似的下降趨勢。