LLM 課程文件
一個完整的訓練迴圈
並獲得增強的文件體驗
開始使用
一個完整的訓練迴圈
現在我們將看到如何在不使用 Trainer
類的情況下實現與上一節相同的結果,我們將使用現代 PyTorch 最佳實踐從頭開始實現一個訓練迴圈。同樣,我們假設您已經完成了第 2 節中的資料處理。這裡有一個簡短的總結,涵蓋了您需要的所有內容
🏗️ 從頭開始訓練:本節建立在之前內容的基礎上。有關 PyTorch 訓練迴圈和最佳實踐的全面指導,請查閱 🤗 Transformers 訓練文件 和 自定義訓練手冊。
from datasets import load_dataset
from transformers import AutoTokenizer, DataCollatorWithPadding
raw_datasets = load_dataset("glue", "mrpc")
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
def tokenize_function(example):
return tokenizer(example["sentence1"], example["sentence2"], truncation=True)
tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
準備訓練
在實際編寫訓練迴圈之前,我們需要定義一些物件。首先是我們用於迭代批次的 dataloaders。但在定義這些 dataloaders 之前,我們需要對 tokenized_datasets
進行一些後處理,以處理 Trainer
為我們自動完成的一些事情。具體來說,我們需要
- 刪除與模型不期望的值對應的列(如
sentence1
和sentence2
列)。 - 將列
label
重新命名為labels
(因為模型期望引數名為labels
)。 - 設定資料集的格式,使其返回 PyTorch 張量而不是列表。
我們的 tokenized_datasets
為每個步驟都提供了一個方法
tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"])
tokenized_datasets = tokenized_datasets.rename_column("label", "labels")
tokenized_datasets.set_format("torch")
tokenized_datasets["train"].column_names
然後我們可以檢查結果是否只包含模型接受的列
["attention_mask", "input_ids", "labels", "token_type_ids"]
完成此操作後,我們可以輕鬆定義 dataloaders
from torch.utils.data import DataLoader
train_dataloader = DataLoader(
tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator
)
eval_dataloader = DataLoader(
tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator
)
為了快速檢查資料處理中是否存在錯誤,我們可以像這樣檢查一個批次
for batch in train_dataloader:
break
{k: v.shape for k, v in batch.items()}
{'attention_mask': torch.Size([8, 65]),
'input_ids': torch.Size([8, 65]),
'labels': torch.Size([8]),
'token_type_ids': torch.Size([8, 65])}
請注意,實際形狀可能略有不同,因為我們為訓練資料載入器設定了 shuffle=True
,並且我們在批次內部填充到最大長度。
現在資料預處理已經完全完成(對於任何機器學習從業者來說,這是一個令人滿意但難以捉摸的目標),讓我們轉向模型。我們像上一節中一樣例項化它
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
為了確保訓練期間一切順利,我們將批次傳遞給這個模型
outputs = model(**batch)
print(outputs.loss, outputs.logits.shape)
tensor(0.5441, grad_fn=<NllLossBackward>) torch.Size([8, 2])
當提供 labels
時,所有 🤗 Transformers 模型都會返回損失,我們還會得到 logits(我們的批次中每個輸入有兩個,因此是大小為 8 x 2 的張量)。
我們幾乎可以編寫訓練迴圈了!我們只缺少兩樣東西:一個最佳化器和一個學習率排程器。由於我們正在嘗試手動複製 Trainer
的行為,因此我們將使用相同的預設值。Trainer
使用的最佳化器是 AdamW
,它與 Adam 相同,但在權重衰減正則化方面有所不同(請參閱 Ilya Loshchilov 和 Frank Hutter 的“解耦權重衰減正則化”)
from torch.optim import AdamW
optimizer = AdamW(model.parameters(), lr=5e-5)
💡 現代最佳化技巧:為了獲得更好的效能,您可以嘗試
- 帶有權重衰減的 AdamW:
AdamW(model.parameters(), lr=5e-5, weight_decay=0.01)
- 8 位 Adam:使用
bitsandbytes
進行記憶體高效最佳化 - 不同的學習率:對於大型模型,較低的學習率(1e-5 到 3e-5)通常效果更好
🚀 最佳化資源:在 🤗 Transformers 最佳化指南中瞭解更多關於最佳化器和訓練策略的資訊。
最後,預設使用的學習率排程器只是從最大值(5e-5)到 0 的線性衰減。為了正確定義它,我們需要知道我們將執行的訓練步數,這是我們想要執行的 epoch 數乘以訓練批次數量(也就是我們訓練資料載入器的長度)。Trainer
預設使用三個 epoch,所以我們將遵循這個
from transformers import get_scheduler
num_epochs = 3
num_training_steps = num_epochs * len(train_dataloader)
lr_scheduler = get_scheduler(
"linear",
optimizer=optimizer,
num_warmup_steps=0,
num_training_steps=num_training_steps,
)
print(num_training_steps)
1377
訓練迴圈
最後一件事:如果我們可以訪問 GPU,我們將使用它(在 CPU 上,訓練可能需要幾個小時而不是幾分鐘)。為此,我們定義一個 device
,我們將把模型和批次放在上面
import torch
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(device)
device
device(type='cuda')
我們現在可以開始訓練了!為了大致瞭解訓練何時完成,我們使用 tqdm
庫在訓練步數上新增一個進度條
from tqdm.auto import tqdm
progress_bar = tqdm(range(num_training_steps))
model.train()
for epoch in range(num_epochs):
for batch in train_dataloader:
batch = {k: v.to(device) for k, v in batch.items()}
outputs = model(**batch)
loss = outputs.loss
loss.backward()
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
progress_bar.update(1)
💡 現代訓練最佳化:為了使您的訓練迴圈更高效,請考慮
- 梯度裁剪:在
optimizer.step()
之前新增torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
- 混合精度:使用
torch.cuda.amp.autocast()
和GradScaler
進行更快的訓練 - 梯度累積:累積多個批次的梯度以模擬更大的批次大小
- 檢查點:定期儲存模型檢查點,以便在中斷時恢復訓練
🔧 實施指南:有關這些最佳化的詳細示例,請參閱 🤗 Transformers 高效訓練指南 和 最佳化器範圍。
您可以看到訓練迴圈的核心與導論中的訓練迴圈非常相似。我們沒有要求任何報告,因此此訓練迴圈不會告訴我們模型表現如何。我們需要為此新增一個評估迴圈。
評估迴圈
和前面一樣,我們將使用 🤗 Evaluate 庫提供的指標。我們已經看過 metric.compute()
方法,但實際上,指標可以在我們進行預測迴圈時使用 add_batch()
方法為我們累積批次。一旦我們累積了所有批次,我們就可以使用 metric.compute()
獲得最終結果。以下是如何在評估迴圈中實現所有這些:
📊 評估最佳實踐:有關更復雜的評估策略和指標,請瀏覽 🤗 Evaluate 文件 和 綜合評估手冊。
import evaluate
metric = evaluate.load("glue", "mrpc")
model.eval()
for batch in eval_dataloader:
batch = {k: v.to(device) for k, v in batch.items()}
with torch.no_grad():
outputs = model(**batch)
logits = outputs.logits
predictions = torch.argmax(logits, dim=-1)
metric.add_batch(predictions=predictions, references=batch["labels"])
metric.compute()
{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535}
同樣,由於模型頭部初始化和資料混洗的隨機性,您的結果會略有不同,但它們應該在相同的範圍內。
✏️ 試一試! 修改之前的訓練迴圈,在 SST-2 資料集上微調您的模型。
使用 🤗 Accelerate 加速您的訓練迴圈
我們之前定義的訓練迴圈在單個 CPU 或 GPU 上執行良好。但是,使用 🤗 Accelerate 庫,只需稍作調整,我們就可以在多個 GPU 或 TPU 上啟用分散式訓練。🤗 Accelerate 自動處理分散式訓練、混合精度和裝置放置的複雜性。從建立訓練和驗證資料載入器開始,我們的手動訓練迴圈如下所示:
⚡ Accelerate 深入探討:在 🤗 Accelerate 文件 中瞭解有關分散式訓練、混合精度和硬體最佳化的所有資訊,並在 transformers 文件 中探索實際示例。
from accelerate import Accelerator
from torch.optim import AdamW
from transformers import AutoModelForSequenceClassification, get_scheduler
accelerator = Accelerator()
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
optimizer = AdamW(model.parameters(), lr=3e-5)
train_dl, eval_dl, model, optimizer = accelerator.prepare(
train_dataloader, eval_dataloader, model, optimizer
)
num_epochs = 3
num_training_steps = num_epochs * len(train_dl)
lr_scheduler = get_scheduler(
"linear",
optimizer=optimizer,
num_warmup_steps=0,
num_training_steps=num_training_steps,
)
progress_bar = tqdm(range(num_training_steps))
model.train()
for epoch in range(num_epochs):
for batch in train_dl:
outputs = model(**batch)
loss = outputs.loss
accelerator.backward(loss)
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
progress_bar.update(1)
要新增的第一行是 import 行。第二行例項化一個 Accelerator
物件,該物件將檢視環境並初始化適當的分散式設定。🤗 Accelerate 會為您處理裝置放置,因此您可以刪除將模型放置在裝置上的行(或者,如果您願意,可以將它們更改為使用 accelerator.device
而不是 device
)。
然後,大部分工作在將資料載入器、模型和最佳化器傳送到 accelerator.prepare()
的那一行中完成。這將把這些物件包裝在適當的容器中,以確保您的分散式訓練按預期工作。需要進行的其餘更改是刪除將批次放置在 device
上的行(同樣,如果您想保留此行,只需將其更改為使用 accelerator.device
),並將 loss.backward()
替換為 accelerator.backward(loss)
。
如果您想複製貼上並嘗試一下,這就是使用 🤗 Accelerate 的完整訓練迴圈:
from accelerate import Accelerator
from torch.optim import AdamW
from transformers import AutoModelForSequenceClassification, get_scheduler
accelerator = Accelerator()
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
optimizer = AdamW(model.parameters(), lr=3e-5)
train_dl, eval_dl, model, optimizer = accelerator.prepare(
train_dataloader, eval_dataloader, model, optimizer
)
num_epochs = 3
num_training_steps = num_epochs * len(train_dl)
lr_scheduler = get_scheduler(
"linear",
optimizer=optimizer,
num_warmup_steps=0,
num_training_steps=num_training_steps,
)
progress_bar = tqdm(range(num_training_steps))
model.train()
for epoch in range(num_epochs):
for batch in train_dl:
outputs = model(**batch)
loss = outputs.loss
accelerator.backward(loss)
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
progress_bar.update(1)
將其放入 `train.py` 指令碼中,該指令碼就可以在任何分散式設定下執行。要在您的分散式設定中嘗試,請執行以下命令:
accelerate config
這會提示您回答幾個問題,並將您的答案轉儲到此命令使用的配置檔案中
accelerate launch train.py
這將啟動分散式訓練。
如果你想在 Notebook 中嘗試此操作(例如,在 Colab 上使用 TPU 進行測試),只需將程式碼貼上到 training_function()
中,並在最後一個單元格中執行
from accelerate import notebook_launcher
notebook_launcher(training_function)
您可以在 🤗 Accelerate 儲存庫中找到更多示例。
🌐 分散式訓練:有關多 GPU 和多節點訓練的全面介紹,請查閱 🤗 Transformers 分散式訓練指南 和 擴充套件訓練手冊。
下一步和最佳實踐
現在您已經瞭解瞭如何從頭開始實現訓練,以下是生產使用的一些額外考慮事項
模型評估:始終在多個指標上評估您的模型,而不僅僅是準確性。使用 🤗 Evaluate 庫進行全面的評估。
超引數調優:考慮使用 Optuna 或 Ray Tune 等庫進行系統的超引數最佳化。
模型監控:在整個訓練過程中跟蹤訓練指標、學習曲線和驗證效能。
模型共享:訓練完成後,在 Hugging Face Hub 上共享您的模型,以便社群可以使用。
效率:對於大型模型,請考慮梯度檢查點、引數高效微調(LoRA、AdaLoRA)或量化方法等技術。
我們對使用自定義訓練迴圈進行微調的深入探討到此結束。您在這裡學到的技能將在您需要完全控制訓練過程或想要實現超出 Trainer
API 範圍的自定義訓練邏輯時派上用場。
本節測驗
測試您對自定義訓練迴圈和高階訓練技術的理解
1. Adam 和 AdamW 最佳化器之間的主要區別是什麼?
2. 在訓練迴圈中,操作的正確順序是什麼?
3. 🤗 Accelerate 庫主要有什麼作用?
4. 為什麼我們在訓練迴圈中將批次移動到裝置上?
5. 在評估之前,model.eval() 的作用是什麼?
6. torch.no_grad() 在評估期間的目的是什麼?
7. 在您的訓練迴圈中使用 🤗 Accelerate 後會有哪些變化?
💡 要點:
- 手動訓練迴圈提供完全控制,但需要理解正確的順序:前向 → 反向 → 最佳化器步進 → 排程器步進 → 梯度清零
- 帶有權重衰減的 AdamW 是推薦用於 Transformer 模型的最佳化器
- 在評估期間始終使用
model.eval()
和torch.no_grad()
以獲得正確的行為和效率 - 🤗 Accelerate 透過最少的程式碼更改即可實現分散式訓練
- 裝置管理(將張量移動到 GPU/CPU)對於 PyTorch 操作至關重要
- 混合精度、梯度累積和梯度裁剪等現代技術可以顯著提高訓練效率