並獲得增強型文件體驗
開始
完整訓練
現在我們將看到如何在不使用Trainer 類的情況下實現與上一節相同的結果。同樣,我們假設您已完成第 2 節中的資料處理。以下是對您將需要的所有內容的簡要概述
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)準備訓練
在實際編寫訓練迴圈之前,我們需要定義一些物件。第一個是我們將用來迭代批次的 dataloader。但在我們可以定義這些 dataloader 之前,我們需要對我們的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"]現在,我們可以輕鬆地定義我們的 dataloader
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])}請注意,由於我們為訓練 dataloader 設定了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])所有 🤗 Transformers 模型在提供labels 時將返回損失,我們還將獲得 logits(每個輸入在我們的批次中都有兩個,因此張量的尺寸為 8 x 2)。
我們幾乎準備好編寫訓練迴圈了!我們只缺少兩樣東西:最佳化器和學習率排程器。由於我們正在嘗試手動複製Trainer 所做的操作,因此我們將使用相同的預設值。Trainer 使用的最佳化器是AdamW,它與 Adam 相同,但有一個針對權重衰減正則化的調整(請參見Ilya Loshchilov 和 Frank Hutter 的“解耦權重衰減正則化”)
from transformers import AdamW
optimizer = AdamW(model.parameters(), lr=5e-5)最後,預設情況下使用的學習率排程器只是一個從最大值(5e-5)線性衰減到 0 的排程器。為了正確定義它,我們需要知道我們將採取的訓練步驟數,即我們想要執行的 epoch 數乘以訓練批次數(即訓練 dataloader 的長度)。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(如果我們有權訪問 GPU)。(在 CPU 上,訓練可能需要幾個小時,而不是幾分鐘)。為此,我們定義一個device,我們將模型和批次放到它上面
import torch
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(device)
devicedevice(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)您可以看到訓練迴圈的核心與介紹中的迴圈非常相似。我們沒有要求任何報告,因此這個訓練迴圈不會告訴我們任何關於模型表現的資訊。我們需要新增一個評估迴圈來做到這一點。
評估迴圈
與之前一樣,我們將使用 🤗 Evaluate 庫提供的指標。我們已經看到了metric.compute() 方法,但指標實際上可以為我們累加批次,因為我們使用add_batch() 方法遍歷預測迴圈。在我們累加完所有批次後,我們可以使用metric.compute() 獲取最終結果。以下是在評估迴圈中實現所有這些內容的方法
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 上啟用分散式訓練。從建立訓練和驗證 dataloader 開始,以下是手動訓練迴圈的樣子
from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
optimizer = AdamW(model.parameters(), lr=3e-5)
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(device)
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,
)
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)以下是更改內容
+ from accelerate import Accelerator
from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler
+ accelerator = Accelerator()
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
optimizer = AdamW(model.parameters(), lr=3e-5)
- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
- model.to(device)
+ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare(
+ train_dataloader, eval_dataloader, model, optimizer
+ )
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
)
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()
+ accelerator.backward(loss)
optimizer.step()
lr_scheduler.step()
optimizer.zero_grad()
progress_bar.update(1)要新增的第一行是匯入行。第二行例項化一個Accelerator物件,它將檢視環境並初始化適當的分散式設定。🤗 Accelerate 會為您處理裝置放置,因此您可以刪除將模型放在裝置上的程式碼行(或者,如果您願意,可以更改它們以使用accelerator.device而不是device)。
然後,主要工作量是在將 dataloaders、模型和最佳化器傳送到accelerator.prepare() 的那一行中完成的。這將用適當的容器包裝這些物件,以確保您的分散式訓練按預期進行。要做的其餘更改是刪除將批處理放在device上的程式碼行(同樣,如果您想保留它,只需將其更改為使用accelerator.device)並將loss.backward()替換為accelerator.backward(loss)。
padding="max_length"和max_length引數將您的樣本填充到固定長度。如果您想複製貼上它來試用,以下是使用🤗 Accelerate 的完整訓練迴圈:
from accelerate import Accelerator
from transformers import AdamW, 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這將啟動分散式訓練。
如果您想在筆記本(例如在 Colab 上使用 TPU 測試它)中試用它,只需將程式碼貼上到training_function()中,然後執行最後一個單元格:
from accelerate import notebook_launcher
notebook_launcher(training_function)您可以在🤗 Accelerate 程式碼庫中找到更多示例。