Fine-tune a pretrained modelの翻訳です。
本書は抄訳であり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
事前学習済みモデルを用いるメリットは非常に大きなものです。計算コストやカーボンフットプリントを削減し、スクラッチからトレーニングすることなしに最先端のモデルを活用することができます。🤗 Transformesはさまざまなタスク向けに事前トレーニングされた数千のモデルへのアクセスを提供します。事前学習モデルを使用する際、ご自身のタスク固有のデータセットでトレーニングを行います。これは、非常にパワフルなトレーニングテクニックであるファインチューニングと呼ばれるものです。このチュートリアルでは、お好きなディープラーニングフレームワークを用いて事前学習済みモデルをファインチューンします。
- 🤗 Transformers Trainerによる事前学習済みモデルのファインチューン。
- Kerasを用いたTensorFlowにおける事前学習済みモデルのファインチューン。
- ネイティブPyTorchにおける事前学習済みモデルのファインチューン。
データセットの準備
事前学習済みモデルをファインチューンする前に、トレーニングためのデータセットをダウンロードして準備を行います。以前のチュートリアルでは、トレーニングのためのデータをどのように処理するのかを説明しているので、今がこのテストのためにそれらのスキルを活用する機会です!
Yelp Reviewsデータセットのロードからスタートします:
from datasets import load_dataset
dataset = load_dataset("yelp_review_full")
dataset["train"][100]
{'label': 0,
'text': 'My expectations for McDonalds are t rarely high. But for one to still fail so spectacularly...
すでにみなさんが知っている通り、テキストを処理し、すべての変動するシーケンス長をハンドリングするためのトークナイザが必要となります。一つのステップでデータセットを処理するには、データセット全体に前処理関数を適用するための🤗 Datasetsのmapメソッドを使用します:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
def tokenize_function(examples):
return tokenizer(examples["text"], padding="max_length", truncation=True)
tokenized_datasets = dataset.map(tokenize_function, batched=True)
必要であれば、処理時間を削減するためにファインチューンする全体のデータセットから小規模なサブセットを作成することができます。
small_train_dataset = tokenized_datasets["train"].shuffle(seed=42).select(range(1000))
small_eval_dataset = tokenized_datasets["test"].shuffle(seed=42).select(range(1000))
トレーニング
この時点から、使用したいフレームワークに対応するセクションに進んでください。好きなところにジャンプするために右側のリンクを使用することができます。特定のフレームワークのコンテンツを非表示にしたい場合には、フレームワークブロックの右上のボタンを使用してください!(原文の話です。)
PyTorch Trainerによるトレーニング
🤗 Transformersは🤗 Transformersモデルトレーイングに最適化されたTrainerクラスを提供しており、マニュアルで自分のトレーニングループを記述することなしに、より簡単にトレーニングをスタートできます。Trainer APIはさまざまなトレーニングオプションや、ロギング、勾配集積、混成精度のような機能をサポートしています。
モデルをロードし、期待するラベルの数を指定することからスタートします。Yelp Reviewのdataset cardからは、5つのラベルがあることがわかります:
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained("bert-base-cased", num_labels=5)
事前学習された重みのいくつかが使用されず、いくつかの重みがランダムに初期化された旨の警告が表示されます。心配しないでください、これは完全に正常です!BERTモデルの事前学習済みヘッドは除外され、ランダムに初期化された分類ヘッドで置き換えられます。ご自身のシーケンス分類タスクでこの新たなモデルヘッドをファインチューンし、事前学習済みモデルの知識を転送します。
トレーニングのハイパーパラメーター
次に、あなたがチューニングできるすべてのハイパーパラメーターと異なるトレーニングオプションを有効にするフラグを格納するTrainingArgumentsクラスを作成します。このチュートリアルでは、デフォルトのトレーニングハイパーパラメーターでスタートすることができますが、ご自身の最適の設定を見つけ出すために自由に実験しても構いません。
ご自身のトレーニングでチェックポイントを保存する場所を指定します:
from transformers import TrainingArguments
training_args = TrainingArguments(output_dir="test_trainer")
評価
Trainerはトレーニング期間において自動ではモデルのパフォーマンスを評価しません。メトリクスを計算、レポートする関数をTrainerに引き渡す必要があります。🤗 Evaluateライブラリは、evaluate.load関数でロードできるシンプルなaccuracy関数を提供します(詳細はquicktourをご覧ください):
import numpy as np
import evaluate
metric = evaluate.load("accuracy")
ご自身の予測精度を計算するためにmetric
のcompute
を呼び出します。compute
に予測結果を渡す前に、予測結果をlogitsに変換する必要があります(すべての🤗 Transformersモデルはlogitsを返却することを思い出してください):
def compute_metrics(eval_pred):
logits, labels = eval_pred
predictions = np.argmax(logits, axis=-1)
return metric.compute(predictions=predictions, references=labels)
ファインチューニング中に評価メトリクスを監視したいのであれば、それぞれのエポックの最後に評価メトリックをレポートするようにトレーニングの引数にevaluation_strategy
パラメーターを指定します:
from transformers import TrainingArguments, Trainer
training_args = TrainingArguments(output_dir="test_trainer", evaluation_strategy="epoch")
トレーナー
モデル、トレーニングの引数、トレーニング・テスとデータセット、評価関数を用いてTrainerオブジェクトを作成します:
trainer = Trainer(
model=model,
args=training_args,
train_dataset=small_train_dataset,
eval_dataset=small_eval_dataset,
compute_metrics=compute_metrics,
)
そして、train()を呼び出すことでモデルをファインチューンします:
trainer.train()
KerasによるTensorFlowモデルのトレーニング
TensorFlowとKeras APIで🤗 Transformersモデルをトレーニングすることもできます!
Kelasのデータロード
Keras APIで🤗 Transformersモデルをトレーニングする際には、ご自身のデータセットをKerasが理解できるフォーマットに変換する必要があります。データセットが小さい場合、すべてをNumPy配列に変換してKerasに渡すことができます。より複雑なことを行う前に最初にこれをトライしてみましょう。
最初にデータセットをロードします。GLUE benchmarkはシンプルな二値のテキスト分類タスクなので、これのCoLaデータセットを使用して、ここではトレーニングデータのみを使います。
from datasets import load_dataset
dataset = load_dataset("glue", "cola")
dataset = dataset["train"] # Just take the training split for now
次に、トークナイザーをロードし、データをNumPy配列としてトークナイズします。すでにラベルは0と1のリストなので、トークナイズなしに直接NumPy配列に変換できることに注意してください!
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
tokenized_data = tokenizer(dataset["sentence"], return_tensors="np", padding=True)
# Tokenizer returns a BatchEncoding, but we convert that to a dict for Keras
tokenized_data = dict(tokenized_data)
labels = np.array(dataset["label"]) # Label is already an array of 0 and 1
最後に、モデルをロードして、compileし、fitします:
from transformers import TFAutoModelForSequenceClassification
from tensorflow.keras.optimizers import Adam
# Load and compile our model
model = TFAutoModelForSequenceClassification.from_pretrained("bert-base-cased")
# Lower learning rates are often better for fine-tuning transformers
model.compile(optimizer=Adam(3e-5))
model.fit(tokenized_data, labels)
モデルをcompile()
する際にloss引数を渡すべきではありません!この引数が空の場合、Hugging Faceモデルは自分のタスクとモデルアーキテクチャに適切なlossを自動で選択します。必要であれば、自分でlossを指定することで常に上書きすることができます!
このアプローチは小規模なデータセットでは問題なく動作しますが、すぐに問題になることでしょう。なぜ?トークナイズされた配列とラベルはメモリーに完全にロードされる必要があり、NumPyは「でこぼこの」配列を取り扱えないため、データセット全体において最長のサンプルの長さにすべてのトークナイズされたサンプルをパディングしなくてはなりません。これは、配列をさらに大きなものとし、パディングされたすべてのトークンはトレーニングも遅くしてしまうことでしょう!
tf.data.Datasetとしてデータをロード
トレーニングのスローダウンを避けたいのであれば、代わりにtf.data.Dataset
としてデータをロードすることができます。必要であれば自分でtf.data
パイプラインを記述することができますが、これを行うための2つの便利なメソッドを提供しています:
- prepare_tf_dataset(): これは、多くの場合で推奨されるメソッドです。ご自身のモデルのメソッドなので、モデル入力としてどのカラムが使用できるのかをモデルが自動で判別するために調査を行うことができ、よりシンプルかつより性能のでるデータセットにするためにほかのカラムを排除することができます。
-
to_tf_dataset: このメソッドはより低レベルで、どの
columns
とlabel_cols
を含めるのかを正確に指定することで、データセットをどのように作成するのかを正確にコントロールしたい際には有用です。
prepare_tf_dataset()を使用する前に、以下のコードサンプルのように、自分のデータセットにカラムとしてトークナイザのアウトプットを追加する必要があります:
def tokenize_dataset(data):
# Keys of the returned dictionary will be added to the dataset as columns
return tokenizer(data["text"])
dataset = dataset.map(tokenize_dataset)
Hugging Faceデータセットはデフォルトでディスクに保存され、メモリー使用量を増大させないことを思い出してください!カラムが追加されると、データセットからバッチをストリーミングし、それぞれのバッチにパディングを追加することができるので、データセット全体にパディングするのと比べて、パディングするトークンの数を大きく削減することができます。
tf_dataset = model.prepare_tf_dataset(dataset["train"], batch_size=16, shuffle=True, tokenizer=tokenizer)
上のコードサンプルではprepare_tf_dataset
にトークナイザーを引き渡しているので、ロードされるバッチに適切にパディングされることに注意してください。ご自身のデータセットの全てのサンプルが同じ長さで、パディングが不要である場合には、この引数をスキップすることができます。サンプルをパディングする以上の複雑なことを行う必要がある場合には、サンプルのリストをバッチに変換し、任意の前処理を適用するために呼び出される関数を指定するためにcollate_fn
引数を活用することができます。このアプローチの実例を見るには、examplesやnotebooksをご覧ください。
tf.data.Dataset
を作成すると、上述の通りモデルのコンパイルとフィットを行うことができます。
model.compile(optimizer=Adam(3e-5))
model.fit(tf_dataset)
ネイティブPyTorchによるトレーニング
Trainerはトレーニングループのケアを行うので、一行のコードでモデルをファンチューンすることができます。自分でトレーニングループを記述したいユーザーは、ネイティブPyTorchで🤗 Transformersモデルをファインチューンすることもできます。
この際には、ある程度のメモリーを解放するために、ノートブックを再起動するか以下のコードを実行します:
del model
del trainer
torch.cuda.empty_cache()
次に、トレーニングのための準備としてtokenized_dataset
を手動で後処理します。
-
モデルは入力として生のテキストを受け付けないので
text
カラムを削除します:Pythontokenized_datasets = tokenized_datasets.remove_columns(["text"])
-
モデルは
labels
という引数を期待するので、label
カラムをlabels
に変更します:Pythontokenized_datasets = tokenized_datasets.rename_column("label", "labels")
-
リストではなくPyTorch tensorsを返却するようにデータセットのフォーマットを設定します:
Pythontokenized_datasets.set_format("torch")
ファインチューニングをスピードアップするために上述した通り、小規模なサブセットデータセットを作成します。
small_train_dataset = tokenized_datasets["train"].shuffle(seed=42).select(range(1000))
small_eval_dataset = tokenized_datasets["test"].shuffle(seed=42).select(range(1000))
DataLoader
データバッチにイテレーションを行えるように、トレーニングデータセットとテストデータセットのDataLoader
を作成します。
from torch.utils.data import DataLoader
train_dataloader = DataLoader(small_train_dataset, shuffle=True, batch_size=8)
eval_dataloader = DataLoader(small_eval_dataset, batch_size=8)
期待されるラベルの数を指定してモデルをロードします:
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained("bert-base-cased", num_labels=5)
オプティマイザと学習率スケジューラ
モデルをファインチューンするためにオプティマイザと学習率スケジューラを作成します。PyTorchのAdamWオプティマイザを使いましょう:
from torch.optim import AdamW
optimizer = AdamW(model.parameters(), lr=5e-5)
Trainerのデフォルト学習率スケジューラを作成します:
from transformers import get_scheduler
num_epochs = 3
num_training_steps = num_epochs * len(train_dataloader)
lr_scheduler = get_scheduler(
name="linear", optimizer=optimizer, num_warmup_steps=0, num_training_steps=num_training_steps
)
最後に、GPUにアクセスする際にはGPUを使うようにdevice
を指定します。そうでない場合、CPUでのトレーニングは数分ではなく数時間かかることがあります。
import torch
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
model.to(device)
手元にGPUがない場合には、ColaboratoryやSageMaker StudioLabのようなホスティングされているノートブックでクラウドGPUのフリーアクセスを活用しましょう。
すばらしい、トレーニングの準備ができました!🥳
トレーニングループ
トレーニングの進捗を追跡し続けるために、トレーニングステップの数のプログレスバーを追加するために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)
評価
Trainerに評価関数を追加するのと同じように、ご自身のトレーニングループを記述する際には同じことを行う必要があります。しかし、それぞれのエポックの最後にメトリックを計算、レポートする代わりに、今回はadd_batch
を用いて全てのバッチの積算値を計算し、一番最後にメトリックを計算します。
import evaluate
metric = evaluate.load("accuracy")
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()
その他のリソース
より多くのファインチューニングのサンプルに関しては、以下をご覧ください:
- 🤗 Transformers Examplesには、PyTorchとTensorFlowで一般的なNLPタスクをトレーニングするためのスクリプトが含まれています。
- 🤗 Transformers Notebooksには、PyTorchとTensorFlowで固有のタスクにモデルをファインチューニングする方法を記したさまざまなノートブックが含まれています。