並獲得增強型文件體驗
開始
除錯訓練管道
您已經編寫了一個漂亮的指令碼,用於在給定任務上訓練或微調模型,並遵循了來自 第七章 的建議。 但是,當您啟動命令 trainer.train() 時,發生了可怕的事情:您遇到了錯誤 😱! 或者更糟糕的是,一切似乎都很好,訓練執行沒有錯誤,但結果模型很糟糕。 在本節中,我們將向您展示如何除錯這類問題。
除錯訓練管道
當您在 trainer.train() 中遇到錯誤時,問題在於它可能來自多個來源,因為 Trainer 通常會將很多東西整合在一起。 它將資料集轉換為資料載入器,因此問題可能是您的資料集中存在問題,或者在嘗試將資料集的元素一起批處理時出現了一些問題。 然後它獲取一批資料並將其饋送到模型,因此問題可能出在模型程式碼中。 之後,它計算梯度並執行最佳化步驟,因此問題也可能出在您的最佳化器中。 即使訓練一切順利,如果您的指標存在問題,在評估期間仍然可能出現問題。
除錯 trainer.train() 中出現的錯誤的最佳方法是手動遍歷整個管道,以檢視問題出在哪裡。 然後,錯誤通常很容易解決。
為了演示這一點,我們將使用以下指令碼(嘗試)在 MNLI 資料集 上微調 DistilBERT 模型
from datasets import load_dataset
import evaluate
from transformers import (
AutoTokenizer,
AutoModelForSequenceClassification,
TrainingArguments,
Trainer,
)
raw_datasets = load_dataset("glue", "mnli")
model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
def preprocess_function(examples):
return tokenizer(examples["premise"], examples["hypothesis"], truncation=True)
tokenized_datasets = raw_datasets.map(preprocess_function, batched=True)
model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint)
args = TrainingArguments(
f"distilbert-finetuned-mnli",
evaluation_strategy="epoch",
save_strategy="epoch",
learning_rate=2e-5,
num_train_epochs=3,
weight_decay=0.01,
)
metric = evaluate.load("glue", "mnli")
def compute_metrics(eval_pred):
predictions, labels = eval_pred
return metric.compute(predictions=predictions, references=labels)
trainer = Trainer(
model,
args,
train_dataset=raw_datasets["train"],
eval_dataset=raw_datasets["validation_matched"],
compute_metrics=compute_metrics,
)
trainer.train()如果您嘗試執行它,您將遇到一個相當神秘的錯誤
'ValueError: You have to specify either input_ids or inputs_embeds'檢查您的資料
這不用說,但如果您的資料已損壞,Trainer 將無法形成批次,更不用說訓練您的模型了。 因此,首先,您需要檢視訓練集中包含的內容。
為了避免花費無數小時試圖修復不是錯誤來源的東西,我們建議您使用 trainer.train_dataset 進行檢查,而不是使用其他任何東西。 因此,讓我們在這裡執行此操作
trainer.train_dataset[0]{'hypothesis': 'Product and geography are what make cream skimming work. ',
'idx': 0,
'label': 1,
'premise': 'Conceptually cream skimming has two basic dimensions - product and geography.'}您注意到有什麼問題嗎? 這與關於缺少 input_ids 的錯誤訊息相結合,應該讓您意識到這些是文字,而不是模型可以理解的數字。 在這裡,原始錯誤非常具有誤導性,因為 Trainer 會自動刪除與模型簽名不匹配的列(即模型期望的引數)。 這意味著在這裡,除了標籤以外的所有內容都被丟棄了。 因此,在建立批次並將其傳送到模型時沒有問題,而模型又抱怨它沒有收到正確的輸入。
為什麼資料沒有被處理? 我們確實在資料集上使用了 Dataset.map() 方法,以便在每個樣本上應用分詞器。 但是,如果您仔細檢視程式碼,您會發現我們在將訓練集和評估集傳遞給 Trainer 時犯了一個錯誤。 在這裡,我們使用的是 raw_datasets 🤦,而不是使用 tokenized_datasets。 因此,讓我們修復它!
from datasets import load_dataset
import evaluate
from transformers import (
AutoTokenizer,
AutoModelForSequenceClassification,
TrainingArguments,
Trainer,
)
raw_datasets = load_dataset("glue", "mnli")
model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
def preprocess_function(examples):
return tokenizer(examples["premise"], examples["hypothesis"], truncation=True)
tokenized_datasets = raw_datasets.map(preprocess_function, batched=True)
model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint)
args = TrainingArguments(
f"distilbert-finetuned-mnli",
evaluation_strategy="epoch",
save_strategy="epoch",
learning_rate=2e-5,
num_train_epochs=3,
weight_decay=0.01,
)
metric = evaluate.load("glue", "mnli")
def compute_metrics(eval_pred):
predictions, labels = eval_pred
return metric.compute(predictions=predictions, references=labels)
trainer = Trainer(
model,
args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["validation_matched"],
compute_metrics=compute_metrics,
)
trainer.train()這段新程式碼現在將產生一個不同的錯誤(進展!)
'ValueError: expected sequence of length 43 at dim 1 (got 37)'查看回溯,我們可以看到錯誤發生在資料整理步驟中
~/git/transformers/src/transformers/data/data_collator.py in torch_default_data_collator(features)
105 batch[k] = torch.stack([f[k] for f in features])
106 else:
--> 107 batch[k] = torch.tensor([f[k] for f in features])
108
109 return batch因此,我們應該轉移到那裡。 但是,在我們這樣做之前,讓我們完成對資料的檢查,以確保它 100% 正確。
在除錯訓練會話時,您始終應該做的一件事是檢視模型的解碼輸入。 我們無法直接理解我們饋送給它的數字,因此我們應該檢視這些數字代表什麼。 例如,在計算機視覺中,這意味著檢視傳遞的畫素的解碼圖片,在語音中,這意味著收聽解碼的音訊樣本,而對於我們這裡提供的 NLP 示例,則意味著使用我們的分詞器來解碼輸入
tokenizer.decode(trainer.train_dataset[0]["input_ids"])'[CLS] conceptually cream skimming has two basic dimensions - product and geography. [SEP] product and geography are what make cream skimming work. [SEP]'因此,這似乎是正確的。 您應該對輸入中的所有鍵執行此操作
trainer.train_dataset[0].keys()dict_keys(['attention_mask', 'hypothesis', 'idx', 'input_ids', 'label', 'premise'])請注意,與模型接受的輸入不對應的鍵將被自動丟棄,因此在這裡我們將只保留 input_ids、attention_mask 和 label(它將被重新命名為 labels)。 要仔細檢查模型簽名,您可以列印模型的類,然後檢查其文件
type(trainer.model)transformers.models.distilbert.modeling_distilbert.DistilBertForSequenceClassification
因此,在我們的示例中,我們可以檢查 此頁面 上接受的引數。 Trainer 還將記錄它丟棄的列。
我們已經透過解碼它們來檢查輸入 ID 是否正確。 下一個是 attention_mask
trainer.train_dataset[0]["attention_mask"][1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]由於我們在預處理中沒有應用填充,因此這似乎是完全自然的。 為了確保注意力掩碼沒有問題,讓我們檢查它是否與我們的輸入 ID 的長度相同
len(trainer.train_dataset[0]["attention_mask"]) == len(
trainer.train_dataset[0]["input_ids"]
)True很好! 最後,讓我們檢查我們的標籤
trainer.train_dataset[0]["label"]1與輸入 ID 一樣,這只是一個本身沒有意義的數字。 正如我們之前所見,整數和標籤名稱之間的對映儲存在資料集相應特徵的 names 屬性中
trainer.train_dataset.features["label"].names['entailment', 'neutral', 'contradiction']因此 1 表示 neutral,這意味著我們上面看到的兩個句子沒有矛盾,第一個句子也不意味著第二個句子。 這似乎是正確的!
我們這裡沒有令牌型別 ID,因為 DistilBERT 不需要它們; 如果您的模型中有一些,您還應該確保它們正確匹配第一個和第二個句子在輸入中的位置。
✏️ 輪到你了! 檢查訓練資料集的第二個元素是否一切正常。
我們這裡只對訓練集進行了檢查,但您當然應該以相同的方式仔細檢查驗證集和測試集。
既然我們知道我們的資料集看起來不錯,那麼是時候檢查訓練管道的下一步了。
從資料集到資料載入器
訓練管道中可能出錯的下一個地方是當Trainer嘗試從訓練集或驗證集中形成批次時。一旦確定Trainer的資料集是正確的,就可以嘗試透過執行以下程式碼手動形成一個批次(將train替換為eval以用於驗證資料載入器)
for batch in trainer.get_train_dataloader():
break此程式碼建立訓練資料載入器,然後遍歷它,在第一次迭代時停止。如果程式碼執行沒有錯誤,則擁有可以檢查的第一個訓練批次;如果程式碼出錯,則可以確定問題出在資料載入器中,就像這裡的情況一樣
~/git/transformers/src/transformers/data/data_collator.py in torch_default_data_collator(features)
105 batch[k] = torch.stack([f[k] for f in features])
106 else:
--> 107 batch[k] = torch.tensor([f[k] for f in features])
108
109 return batch
ValueError: expected sequence of length 45 at dim 1 (got 76)檢查回溯的最後一幀應該足以提供線索,但讓我們進一步深入。在批次建立過程中,大多數問題都是由於將示例合併到單個批次中而引起的,因此,如果有疑問,首先要檢查的是DataLoader正在使用的collate_fn
data_collator = trainer.get_train_dataloader().collate_fn data_collator
<function transformers.data.data_collator.default_data_collator(features: List[InputDataClass], return_tensors='pt') -> Dict[str, Any]>所以這是default_data_collator,但這並不是我們想要的情況。我們需要將示例填充到批次中最長的句子,這是透過DataCollatorWithPadding合併器完成的。此資料合併器應該由Trainer預設使用,那麼為什麼它沒有在這裡使用呢?
答案是我們沒有將tokenizer傳遞給Trainer,因此它無法建立我們想要的DataCollatorWithPadding。實際上,不應該猶豫明確傳遞想要使用的資料合併器,以確保避免此類錯誤。讓我們修改程式碼以完全這樣做
from datasets import load_dataset
import evaluate
from transformers import (
AutoTokenizer,
AutoModelForSequenceClassification,
DataCollatorWithPadding,
TrainingArguments,
Trainer,
)
raw_datasets = load_dataset("glue", "mnli")
model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
def preprocess_function(examples):
return tokenizer(examples["premise"], examples["hypothesis"], truncation=True)
tokenized_datasets = raw_datasets.map(preprocess_function, batched=True)
model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint)
args = TrainingArguments(
f"distilbert-finetuned-mnli",
evaluation_strategy="epoch",
save_strategy="epoch",
learning_rate=2e-5,
num_train_epochs=3,
weight_decay=0.01,
)
metric = evaluate.load("glue", "mnli")
def compute_metrics(eval_pred):
predictions, labels = eval_pred
return metric.compute(predictions=predictions, references=labels)
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
trainer = Trainer(
model,
args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["validation_matched"],
compute_metrics=compute_metrics,
data_collator=data_collator,
tokenizer=tokenizer,
)
trainer.train()好訊息是,我們不再遇到之前的錯誤,這絕對是進步。壞訊息是,我們反而遇到了臭名昭著的 CUDA 錯誤
RuntimeError: CUDA error: CUBLAS_STATUS_ALLOC_FAILED when calling `cublasCreate(handle)`
這很糟糕,因為 CUDA 錯誤通常很難除錯。我們將在下一分鐘看到如何解決這個問題,但首先讓我們完成對批次建立的分析。
如果確定資料合併器是正確的,則應該嘗試將其應用於資料集的幾個樣本
data_collator = trainer.get_train_dataloader().collate_fn
batch = data_collator([trainer.train_dataset[i] for i in range(4)])此程式碼將失敗,因為train_dataset包含字串列,Trainer通常會刪除這些列。可以手動刪除它們,或者如果想要完全複製Trainer在幕後所做的事情,可以呼叫私有Trainer._remove_unused_columns()方法來執行此操作
data_collator = trainer.get_train_dataloader().collate_fn
actual_train_set = trainer._remove_unused_columns(trainer.train_dataset)
batch = data_collator([actual_train_set[i] for i in range(4)])如果錯誤仍然存在,則應該能夠手動除錯資料合併器內部發生的情況。
既然我們已經除錯了批次建立過程,現在該將一個批次傳遞給模型了!
透過模型
應該能夠透過執行以下命令獲取一個批次
for batch in trainer.get_train_dataloader():
break如果在筆記本中執行此程式碼,可能會遇到與之前類似的 CUDA 錯誤,在這種情況下,需要重新啟動筆記本並重新執行最後一段程式碼,但沒有trainer.train()行。這是 CUDA 錯誤第二令人討厭的地方:它們會不可挽回地破壞核心。最令人討厭的是它們很難除錯。
為什麼?這與 GPU 的工作方式有關。它們在並行執行大量操作方面非常高效,但缺點是,當其中一條指令導致錯誤時,無法立即知道。只有當程式呼叫對 GPU 上多個程序的同步時,它才會意識到出現問題,因此錯誤實際上是在與建立錯誤無關的地方引發的。例如,如果檢視之前的回溯,錯誤是在反向傳播過程中引發的,但我們將在下一分鐘看到,它實際上源於正向傳播中的某些內容。
那麼如何除錯這些錯誤呢?答案很簡單:不除錯。除非 CUDA 錯誤是記憶體不足錯誤(這意味著 GPU 中沒有足夠的記憶體),否則應該始終回到 CPU 進行除錯。
為了在我們的案例中做到這一點,我們只需要將模型放回 CPU 並對我們的批次進行呼叫——由DataLoader返回的批次尚未移動到 GPU
outputs = trainer.model.cpu()(**batch)
~/.pyenv/versions/3.7.9/envs/base/lib/python3.7/site-packages/torch/nn/functional.py in nll_loss(input, target, weight, size_average, ignore_index, reduce, reduction)
2386 )
2387 if dim == 2:
-> 2388 ret = torch._C._nn.nll_loss(input, target, weight, _Reduction.get_enum(reduction), ignore_index)
2389 elif dim == 4:
2390 ret = torch._C._nn.nll_loss2d(input, target, weight, _Reduction.get_enum(reduction), ignore_index)
IndexError: Target 2 is out of bounds.所以,情況越來越清楚。現在,我們不是遇到 CUDA 錯誤,而是遇到損失計算中的IndexError(因此與前面提到的反向傳播無關)。更確切地說,我們可以看到是目標 2 導致了錯誤,因此現在是檢查模型標籤數量的好時機
trainer.model.config.num_labels
2使用兩個標籤,只允許 0 和 1 作為目標,但根據收到的錯誤訊息,卻得到了一個 2。得到 2 實際上很正常:如果還記得之前提取的標籤名稱,有三個,所以資料集中有索引 0、1 和 2。問題是我們沒有將此告訴模型,模型應該使用三個標籤建立。所以讓我們修正一下!
from datasets import load_dataset
import evaluate
from transformers import (
AutoTokenizer,
AutoModelForSequenceClassification,
DataCollatorWithPadding,
TrainingArguments,
Trainer,
)
raw_datasets = load_dataset("glue", "mnli")
model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
def preprocess_function(examples):
return tokenizer(examples["premise"], examples["hypothesis"], truncation=True)
tokenized_datasets = raw_datasets.map(preprocess_function, batched=True)
model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=3)
args = TrainingArguments(
f"distilbert-finetuned-mnli",
evaluation_strategy="epoch",
save_strategy="epoch",
learning_rate=2e-5,
num_train_epochs=3,
weight_decay=0.01,
)
metric = evaluate.load("glue", "mnli")
def compute_metrics(eval_pred):
predictions, labels = eval_pred
return metric.compute(predictions=predictions, references=labels)
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
trainer = Trainer(
model,
args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["validation_matched"],
compute_metrics=compute_metrics,
data_collator=data_collator,
tokenizer=tokenizer,
)我們還沒有包括trainer.train()行,以便花時間檢查一切看起來是否正常。如果請求一個批次並將其傳遞給模型,現在它就可以正常執行,沒有任何錯誤!
for batch in trainer.get_train_dataloader():
break
outputs = trainer.model.cpu()(**batch)下一步是回到 GPU,並檢查一切是否仍然正常執行
import torch
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
batch = {k: v.to(device) for k, v in batch.items()}
outputs = trainer.model.to(device)(**batch)如果仍然遇到錯誤,請確保重新啟動筆記本,只執行指令碼的最新版本。
執行一步最佳化
現在我們知道可以構建實際透過模型的批次,我們已經準備好進行訓練管道的下一步:計算梯度並執行一步最佳化。
第一步只是對損失呼叫backward()方法
loss = outputs.loss loss.backward()
在這個階段遇到錯誤的情況很少見,但如果遇到錯誤,請確保回到 CPU 以獲取有用的錯誤訊息。
要執行最佳化步驟,只需要建立optimizer並呼叫它的step()方法
trainer.create_optimizer() trainer.optimizer.step()
同樣,如果在Trainer中使用預設最佳化器,則在這個階段不應該遇到錯誤,但如果具有自定義最佳化器,則這裡可能存在一些需要除錯的問題。不要忘記,如果在這個階段遇到奇怪的 CUDA 錯誤,請回到 CPU。
處理 CUDA 記憶體不足錯誤
每當遇到以RuntimeError: CUDA out of memory開頭的錯誤訊息時,就表示 GPU 記憶體不足。這與程式碼本身無關,它可能會出現在完美執行的指令碼中。此錯誤表示試圖在 GPU 的內部記憶體中放入太多內容,導致出現錯誤。與其他 CUDA 錯誤一樣,需要重新啟動核心才能回到可以再次執行訓練的位置。
要解決這個問題,只需要減少 GPU 空間的使用量——這通常說起來容易做起來難。首先,確保沒有同時在 GPU 上使用兩個模型(當然,除非問題需要這樣做)。然後,可能需要縮小批次大小,因為它會直接影響模型所有中間輸出及其梯度的大小。如果問題仍然存在,請考慮使用模型的較小版本。
在課程的下一部分,我們將介紹更多高階技術,這些技術可以幫助您減少記憶體佔用並讓您微調更大的模型。
評估模型
現在我們已經解決了程式碼中的所有問題,一切都很完美,訓練應該順利執行,對吧?沒有那麼快!如果執行trainer.train()命令,最初一切都會看起來很好,但過一段時間後,會遇到以下情況
# This will take a long time and error out, so you shouldn't run this cell
trainer.train()TypeError: only size-1 arrays can be converted to Python scalars會發現此錯誤出現在評估階段,所以這是最後需要除錯的地方。
可以獨立於訓練執行Trainer的評估迴圈,如下所示
trainer.evaluate()
TypeError: only size-1 arrays can be converted to Python scalars💡 應該始終確保可以在啟動trainer.train()之前執行trainer.evaluate(),以避免在遇到錯誤之前浪費大量的計算資源。
在嘗試除錯評估迴圈中的問題之前,您應該首先確保您已經查看了資料,能夠正確地形成一個批次,並且可以在其上執行您的模型。我們已經完成了所有這些步驟,因此以下程式碼可以執行而不會出錯
for batch in trainer.get_eval_dataloader():
break
batch = {k: v.to(device) for k, v in batch.items()}
with torch.no_grad():
outputs = trainer.model(**batch)錯誤出現在評估階段的最後,如果我們查看回溯,我們會看到這個
~/git/datasets/src/datasets/metric.py in add_batch(self, predictions, references)
431 """
432 batch = {"predictions": predictions, "references": references}
--> 433 batch = self.info.features.encode_batch(batch)
434 if self.writer is None:
435 self._init_writer()這告訴我們錯誤起源於datasets/metric.py模組——所以這是一個與我們的compute_metrics()函式有關的問題。它接受一個包含 logits 和標籤的元組作為 NumPy 陣列,所以讓我們嘗試向它提供這些資料
predictions = outputs.logits.cpu().numpy()
labels = batch["labels"].cpu().numpy()
compute_metrics((predictions, labels))TypeError: only size-1 arrays can be converted to Python scalars我們得到了相同的錯誤,所以問題肯定在於那個函式。如果我們回顧它的程式碼,我們會看到它只是將predictions和labels轉發給metric.compute()。那麼,那個方法有問題嗎?其實沒有。讓我們快速看一下形狀
predictions.shape, labels.shape
((8, 3), (8,))我們的預測仍然是 logits,而不是實際的預測,這就是為什麼指標會返回這個(有點模糊)錯誤。修復很簡單;我們只需要在compute_metrics()函式中新增一個 argmax
import numpy as np
def compute_metrics(eval_pred):
predictions, labels = eval_pred
predictions = np.argmax(predictions, axis=1)
return metric.compute(predictions=predictions, references=labels)
compute_metrics((predictions, labels)){'accuracy': 0.625}現在我們的錯誤修復了!這是最後一個錯誤,所以我們的指令碼現在將正確地訓練模型。
作為參考,以下是被完全修復的指令碼
import numpy as np
from datasets import load_dataset
import evaluate
from transformers import (
AutoTokenizer,
AutoModelForSequenceClassification,
DataCollatorWithPadding,
TrainingArguments,
Trainer,
)
raw_datasets = load_dataset("glue", "mnli")
model_checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
def preprocess_function(examples):
return tokenizer(examples["premise"], examples["hypothesis"], truncation=True)
tokenized_datasets = raw_datasets.map(preprocess_function, batched=True)
model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=3)
args = TrainingArguments(
f"distilbert-finetuned-mnli",
evaluation_strategy="epoch",
save_strategy="epoch",
learning_rate=2e-5,
num_train_epochs=3,
weight_decay=0.01,
)
metric = evaluate.load("glue", "mnli")
def compute_metrics(eval_pred):
predictions, labels = eval_pred
predictions = np.argmax(predictions, axis=1)
return metric.compute(predictions=predictions, references=labels)
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
trainer = Trainer(
model,
args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["validation_matched"],
compute_metrics=compute_metrics,
data_collator=data_collator,
tokenizer=tokenizer,
)
trainer.train()在這種情況下,沒有更多的問題,我們的指令碼將微調一個應該給出合理結果的模型。但是,當訓練在沒有任何錯誤的情況下進行,並且訓練的模型表現得非常糟糕時,我們該怎麼辦?這是機器學習中最難的部分,我們將向您展示一些可以幫助您的技術。
💡 如果您使用的是手動訓練迴圈,則相同的步驟適用於除錯您的訓練管道,但更容易將它們分開。但是,請確保您沒有在錯誤的位置忘記了model.eval()或model.train(),或者每個步驟的zero_grad()!
除錯訓練過程中的靜默錯誤
我們如何除錯一個沒有錯誤但沒有獲得良好結果的訓練?我們將在這裡為您提供一些提示,但請注意,這種型別的除錯是機器學習中最難的部分,沒有神奇的答案。
再次檢查您的資料!
您的模型只有在能夠從您的資料中學習到某些東西時才能學到東西。如果有錯誤破壞了資料或標籤是隨機分配的,那麼您很可能不會得到任何在您的資料集上訓練的模型。因此,總是從仔細檢查您的解碼輸入和標籤開始,並問問自己以下問題
- 解碼後的資料是否可以理解?
- 您同意標籤嗎?
- 是否存在一個比其他標籤更常見的標籤?
- 如果模型預測一個隨機答案/總是同一個答案,那麼損失/指標應該是什麼?
⚠️ 如果您正在進行分散式訓練,請在每個程序中列印資料集的樣本,並仔細檢查您是否得到相同的結果。一個常見的錯誤是,在資料建立中存在一些隨機性,使得每個程序擁有資料集的不同版本。
在檢視完您的資料之後,再檢視模型的一些預測結果並對其進行解碼。如果模型總是預測相同的結果,可能是因為您的資料集偏向於一個類別(對於分類問題);諸如過取樣稀有類別之類的技術可能會有所幫助。
如果在初始模型上得到的損失/指標與您對隨機預測的期望損失/指標有很大不同,請仔細檢查您的損失或指標的計算方式,因為那裡可能存在錯誤。如果您使用的是幾個最後要加起來的損失,請確保它們具有相同的尺度。
當您確定您的資料是完美的時,您可以透過一個簡單的測試來檢視模型是否能夠在您的資料上進行訓練。
在單個批次上過擬合您的模型
過擬合通常是我們訓練時想要避免的東西,因為它意味著模型沒有學會識別我們想要它識別的通用特徵,而是僅僅記住了訓練樣本。但是,嘗試反覆在單個批次上訓練您的模型是一個很好的測試,可以檢查您所構建的問題是否可以透過您試圖訓練的模型解決。它還有助於您檢視初始學習率是否過高。
在您定義了Trainer之後,這樣做非常容易;只需獲取一批訓練資料,然後執行一個小的手動訓練迴圈,只使用該批次進行大約 20 步訓練
for batch in trainer.get_train_dataloader():
break
batch = {k: v.to(device) for k, v in batch.items()}
trainer.create_optimizer()
for _ in range(20):
outputs = trainer.model(**batch)
loss = outputs.loss
loss.backward()
trainer.optimizer.step()
trainer.optimizer.zero_grad()💡 如果您的訓練資料不平衡,請確保構建一批包含所有標籤的訓練資料。
生成的模型應該對同一個batch具有接近完美的訓練結果。讓我們計算一下結果預測的指標
with torch.no_grad():
outputs = trainer.model(**batch)
preds = outputs.logits
labels = batch["labels"]
compute_metrics((preds.cpu().numpy(), labels.cpu().numpy())){'accuracy': 1.0}100% 的準確率,這是一個過擬合的很好的例子(這意味著,如果您在任何其他句子上嘗試您的模型,它很可能給您錯誤的答案)!
如果您無法讓您的模型獲得這樣的完美結果,則意味著您構建問題的方式或您的資料存在問題,因此您應該修復它。只有當您能夠透過過擬合測試時,才能確定您的模型實際上能夠學習到一些東西。
⚠️ 您將不得不在此測試後重新建立您的模型和Trainer,因為得到的模型可能無法恢復並從您的完整資料集上學習到有用的東西。
在您獲得第一個基線之前不要調整任何引數
超引數調整總是被強調為機器學習中最難的部分,但這只是幫助您在指標上稍微提升一下的最後一步。大多數情況下,Trainer的預設超引數會很好地為您提供良好的結果,因此,在您獲得一個在資料集上超過基線的模型之前,不要進行耗時且成本高昂的超引數搜尋。
當您獲得了一個足夠好的模型時,您可以開始進行一些調整。不要嘗試用不同的超引數啟動一千次執行,而是比較幾個用不同的超引數值進行的執行,以瞭解哪個超引數對結果的影響最大。
如果您正在調整模型本身,請保持簡單,不要嘗試任何您無法合理解釋的東西。始終確保您回到過擬合測試,以驗證您的更改是否產生了任何意外後果。
尋求幫助
希望您在本節中找到了一些對解決問題有幫助的建議,但如果沒有,請記住您始終可以在論壇上向社群尋求幫助。
以下是一些可能對您有所幫助的其他資源
- “可重複性作為工程最佳實踐的載體” by Joel Grus
- “除錯神經網路的清單” by Cecelia Shao
- “如何對機器學習程式碼進行單元測試” by Chase Roberts
- “訓練神經網路的配方” by Andrej Karpathy
當然,您在訓練神經網路時遇到的並非所有問題都是您自己的過錯!如果您在🤗 Transformers或🤗 Datasets庫中遇到了看起來不對勁的東西,您可能遇到了錯誤。您應該告訴我們所有關於它的資訊,在下一節中我們將解釋如何做到這一點。