Hugging Face NLP Course - 3. FINE-TUNING A PRETRAINED MODEL
概要
の要点纏め。
Processing the data
AutoModelForSequenceClassificationを少ないデータでファインチューニングする例。
※データが少ないのであまり意味はない。
import torch
from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification
# Same as before
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = [
"I've been waiting for a HuggingFace course my whole life.",
"This course is amazing!",
]
batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt")
# This is new
batch["labels"] = torch.tensor([1, 1])
optimizer = AdamW(model.parameters())
loss = model(**batch).loss
loss.backward()
optimizer.step()
以下のチュートリアルでは
MRPC (Microsoft Research Paraphrase Corpus) dataset
を使って訓練する。
このデータセットは5,801組の文からなり、両文が言い換えであるかどうか(つまり、両文が同じ意味であるかどうか)を示すラベルが付いている。
Loading a dataset from the Hub
huggingfaceのデータセット一覧
MRPCデータセットはGLUEベンチマークを構成する10個のデータセットのうちの1つ。
読み込み方
from datasets import load_dataset
raw_datasets = load_dataset("glue", "mrpc")
raw_datasets
DatasetDict({
train: Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 3668
})
validation: Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 408
})
test: Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 1725
})
})
データセットも以下にキャッシュされる。
HF_HOMEで変更可能。
~/.cache/huggingface/datasets
データへのアクセス方法
raw_train_dataset = raw_datasets["train"]
raw_train_dataset[0]
{'idx': 0,
'label': 1,
'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .',
'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'}
labelの対応などのデータ構造を見る
raw_train_dataset.features
{'sentence1': Value(dtype='string', id=None),
'sentence2': Value(dtype='string', id=None),
'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None),
'idx': Value(dtype='int32', id=None)}
Preprocessing a dataset
単純にセンテンス1と2をトーカナイズする方法
from transformers import AutoTokenizer
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"])
tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"])
実際は以下のように2つの文を結合してトーカナイズする必要がある
inputs = tokenizer("This is the first sentence.", "This is the second one.")
inputs
{
'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102],
'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
}
token_type_idsでどちらのセンテンスか表現されている。
デコードしてみる
tokenizer.convert_ids_to_tokens(inputs["input_ids"])
['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]']
token_type_idsがわかるように揃えてみる
['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]']
[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1]
注意する点
トークン化された入力にtoken_type_idsがあるとは限らない。
(たとえば、DistilBERTモデルを使用した場合、token_type_idsは返されない)
同じチェックポイントを使用していれば、トークン化された入力とモデルの期待している入力が一致するので問題ない。
一括で学習データセットを前処理する方法
tokenized_dataset = tokenizer(
raw_datasets["train"]["sentence1"],
raw_datasets["train"]["sentence2"],
padding=True,
truncation=True,
)
しかしこの方法ではメモリを多く消費する。
ちなみに初期化した段階のデータはApache Arrowという形式でディスクに保存されている。
必要に応じてトーカナイズするためにDataset.map()を使用する。
def tokenize_function(example):
return tokenizer(example["sentence1"], example["sentence2"], truncation=True)
tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
tokenized_datasets
DatasetDict({
train: Dataset({
features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
num_rows: 3668
})
validation: Dataset({
features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
num_rows: 408
})
test: Dataset({
features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
num_rows: 1725
})
})
batched=True
とすることで各特徴量が以下の様にリスト化されて渡される。
tokenizerはリストに対しても動作するのでうまくいく。
example {'sentence1': ['Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', "Yucaipa owned Dominick 's before selling the chain to Safeway in 1998 for $ 2.5 billion .",...
map()関数にnum_procオプションを渡すことでマルチプロセスで実行することができる。
※ただしここではTokenizersライブラリがすでにマルチスレッドに対応しているのであまり効果がない。
Dynamic padding
バッチ内のサンプルをまとめる関数をcollate functionという。
デフォルトはサンプルをPyTorchテンソルに変換して(要素がリスト、タプル、辞書の場合は再帰的に)連結する関数。
パディングはバッチ単位で行う必要があるのでcollate functionで行うことが多い。
DataCollatorWithPaddingを使用すれば実現できる。
from transformers import DataCollatorWithPadding
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
1バッチに相当するデータをつくる。
文字列のカラムはテンソルにできないので削除する必要がある。
samples = tokenized_datasets["train"][:8]
samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]}
[len(x) for x in samples["input_ids"]]
[50, 59, 47, 67, 59, 50, 62, 32]
このバッチでは、バッチ内の最大長である67の長さにパディングする必要がある。
データの最大長を見てみる。
batch = data_collator(samples)
{k: v.shape for k, v in batch.items()}
{'attention_mask': torch.Size([8, 67]),
'input_ids': torch.Size([8, 67]),
'token_type_ids': torch.Size([8, 67]),
'labels': torch.Size([8])}
print(batch)
結果
{'input_ids': tensor([[ 101, 2572, 3217, 5831, 5496, 2010, 2567, 1010, 3183, 2002,
2170, 1000, 1996, 7409, 1000, 1010, 1997, 9969, 4487, 23809,
3436, 2010, 3350, 1012, 102, 7727, 2000, 2032, 2004, 2069,
1000, 1996, 7409, 1000, 1010, 2572, 3217, 5831, 5496, 2010,
2567, 1997, 9969, 4487, 23809, 3436, 2010, 3350, 1012, 102,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0],
[ 101, 9805, 3540, 11514, 2050, 3079, 11282, 2243, 1005, 1055,
2077, 4855, 1996, 4677, 2000, 3647, 4576, 1999, 2687, 2005,
1002, 1016, 1012, 1019, 4551, 1012, 102, 9805, 3540, 11514,
2050, 4149, 11282, 2243, 1005, 1055, 1999, 2786, 2005, 1002,
6353, 2509, 2454, 1998, 2853, 2009, 2000, 3647, 4576, 2005,
1002, 1015, 1012, 1022, 4551, 1999, 2687, 1012, 102, 0,
0, 0, 0, 0, 0, 0, 0],
[ 101, 2027, 2018, 2405, 2019, 15147, 2006, 1996, 4274, 2006,
2238, 2184, 1010, 5378, 1996, 6636, 2005, 5096, 1010, 2002,
2794, 1012, 102, 2006, 2238, 2184, 1010, 1996, 2911, 1005,
1055, 5608, 2018, 2405, 2019, 15147, 2006, 1996, 4274, 1010,
5378, 1996, 14792, 2005, 5096, 1012, 102, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0],
[ 101, 2105, 6021, 19481, 13938, 2102, 1010, 21628, 6661, 2020,
2039, 2539, 16653, 1010, 2030, 1018, 1012, 1018, 1003, 1010,
2012, 1037, 1002, 1018, 1012, 5179, 1010, 2383, 3041, 2275,
1037, 2501, 2152, 1997, 1037, 1002, 1018, 1012, 5401, 1012,
102, 21628, 6661, 5598, 2322, 16653, 1010, 2030, 1018, 1012,
1020, 1003, 1010, 2000, 2275, 1037, 2501, 5494, 2152, 2012,
1037, 1002, 1018, 1012, 5401, 1012, 102],
[ 101, 1996, 4518, 3123, 1002, 1016, 1012, 2340, 1010, 2030,
2055, 2340, 3867, 1010, 2000, 2485, 5958, 2012, 1002, 2538,
1012, 4868, 2006, 1996, 2047, 2259, 4518, 3863, 1012, 102,
18720, 1004, 1041, 13058, 1012, 6661, 5598, 1002, 1015, 1012,
6191, 2030, 1022, 3867, 2000, 1002, 2538, 1012, 6021, 2006,
1996, 2047, 2259, 4518, 3863, 2006, 5958, 1012, 102, 0,
0, 0, 0, 0, 0, 0, 0],
[ 101, 6599, 1999, 1996, 2034, 4284, 1997, 1996, 2095, 3333,
2321, 3867, 2013, 1996, 2168, 2558, 1037, 2095, 3041, 1012,
102, 2007, 1996, 9446, 5689, 2058, 5954, 1005, 1055, 2194,
1010, 6599, 1996, 2034, 4284, 1997, 1996, 2095, 3333, 2321,
3867, 2013, 1996, 2168, 2558, 1037, 2095, 3041, 1012, 102,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0],
[ 101, 1996, 17235, 2850, 4160, 2018, 1037, 4882, 5114, 1997,
2459, 1012, 2676, 1010, 2030, 1015, 1012, 1016, 3867, 1010,
5494, 2012, 1015, 1010, 19611, 1012, 2321, 2006, 5958, 1012,
102, 1996, 6627, 1011, 17958, 17235, 2850, 4160, 12490, 1012,
11814, 2594, 24356, 2382, 1012, 4805, 2685, 1010, 2030, 1016,
1012, 5840, 3867, 1010, 2000, 1015, 1010, 19611, 1012, 2321,
1012, 102, 0, 0, 0, 0, 0],
[ 101, 1996, 4966, 1011, 10507, 2050, 2059, 12068, 2000, 1996,
2110, 4259, 2457, 1012, 102, 1996, 4966, 10507, 2050, 12068,
2008, 3247, 2000, 1996, 1057, 1012, 1055, 1012, 4259, 2457,
1012, 102, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 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, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 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, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 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, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[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, 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, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[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, 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, 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, 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, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[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, 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, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[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, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), 'labels': tensor([1, 0, 1, 0, 1, 1, 0, 1])}
Fine-tuning a model with the Trainer API
現状のコード
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)
Training
TrainingArgumentsオブジェクトを準備する。
"test-trainer"はモデルを保存するディレクトリ。
それ以外はデフォルト
from transformers import TrainingArguments
training_args = TrainingArguments("test-trainer")
AutoModelForSequenceClassificationモデルを準備する。
この時BERTが文のペアの分類について事前学習されていないため、事前学習済みモデルのヘッドが破棄され、代わりにシーケンス分類に適した新しいヘッドが追加される。
新しいヘッドにはランダムな重みが設定される。
※ワーニングが出る
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
trainerオブジェクトの定義
from transformers import Trainer
trainer = Trainer(
model,
training_args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["validation"],
data_collator=data_collator,
tokenizer=tokenizer,
)
学習の実行
trainer.train()
ただしこのままだとモデルの評価が行われないのでうまく行っているかわからない。
Evaluation
評価関数を定義していく
訓練中モデルから予測を得る方法。
predictions = trainer.predict(tokenized_datasets["validation"])
print(predictions.predictions.shape, predictions.label_ids.shape)
(408, 2) (408,)
predictの戻り値には以下のフィールドが存在する。
predictions: 予測値。各要素のlogits
label_ids: 正解ラベル
metrics: 損失や実行時間のメトリクスを含む、compute_metrics()関数をtrainerに渡した場合、その結果も含まれる。
predictionsから正解ラベルを取得するには最大の要素を取る
import numpy as np
preds = np.argmax(predictions.predictions, axis=-1)
evaluateライブラリを使ってMRPCデータセットに関連するメトリクスを取得する。
metricにはcompute()メソッドが生えている。
import evaluate
metric = evaluate.load("glue", "mrpc")
metric.compute(predictions=preds, references=predictions.label_ids)
{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542}
最終的なcompute_metricsメソッド
def compute_metrics(eval_preds):
metric = evaluate.load("glue", "mrpc")
logits, labels = eval_preds
predictions = np.argmax(logits, axis=-1)
return metric.compute(predictions=predictions, references=labels)
trainerオブジェクトに渡す。
training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch")
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
trainer = Trainer(
model,
training_args,
train_dataset=tokenized_datasets["train"],
eval_dataset=tokenized_datasets["validation"],
data_collator=data_collator,
tokenizer=tokenizer,
compute_metrics=compute_metrics,
)
再度学習。
trainer.train()
A full training
trainerオブジェクトを使わない訓練方法。
現状のコード。
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)
Prepare for training
最初にtokenized_datasetsの形式をモデルが期待するpytorchの行列に合わせる必要がある。
不要なフィールドの削除や、フィールド名の調整を行う。
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"]
torchの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])}
モデルのインスタンス化
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])
オプティマイザの定義
from transformers import AdamW
optimizer = AdamW(model.parameters(), lr=5e-5)
学習率スケジューラの定義
num_training_steps: 総ステップ数
線形に減衰させる。
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
The training loop
GPU環境ならGPUに乗せる
import torch
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(device)
device
device(type='cuda')
トレーニングループ部分
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)
The evaluation loop
評価指標を取得するコード。
add_batchして蓄積した結果を最後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}
Supercharge your training loop with 🤗 Accelerate
Accelerateライブラリを使うことでマルチGPU,TPUに対応することができる。
未対応のコードがこちら。
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)
Cloud TPUsで動かす場合はtokenizerの引数で、max_lengthにパディングにしたほうがパフォーマンスが良いらしい。
Acceleratorを使った完全版
train.py
に以下を記載すると良い。
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)
分散環境をセットアップする。
いくつかプロンプトで質問され、configuration fileが作成されるらしい。
accelerate config
トレーニング開始。
accelerate launch train.py
notebookで実行する場合
from accelerate import notebook_launcher
notebook_launcher(training_function)
Discussion