Transformerの仕組みを体系的に理解したい 実践編

OpenAIやAnthropic ,Googleなどの巨大IT企業が新しい大規模LLMモデルを開発し続けているので、一般的な用途ではファインチューニングの必要性はあまり感じられなくなりました。LLM自体がすでに一般人以上の知識を獲得しています。

また、RAGを使うことによって、特定知識の獲得のためのファインチューニングの必要性も無くなってきているのかもしれません。

それでも、実際にファインチューニングをしてみることで、Transformerに対する理解を深めることができると考えて試してみます。

BERTを使った感情分類

事前学習された日本語のBERTモデルを使って、感情分類(テキスト分類)ができるようにしてみます。テキストを入力すると、そのテキストがポジティブかネガティブかを判断し、出力するものです。

基本のBERTには分類結果を出力するという構造がありませんので、BERTの構造を少し変える必要があります。

また、学習用のデータセットが必要になります。具体的には、テキストと判定結果の組み合わせ用意しておく必要があります。

実装コード

まず最初に、実装コードを記述します。

以下のコマンドを実行して必要なライブラリをインストールします。

pip install transformers datasets torch fugashi ipadic
  • transformers: Hugging FaceのBERTや他のモデルを扱うライブラリ
  • datasets: データセットを簡単に取得・処理するライブラリ
  • fugashi & ipadic: 日本語テキストのトークン化をサポートするためのツール
トレーニング

以下は、事前学習済みの日本語BERTモデルをトレーニングして感情分類を実現するコードです。
学習にはGoogleColab ProのGPU (A100)を使いました。約15分ほどかかりました。

from transformers import BertTokenizer, BertForSequenceClassification, Trainer, TrainingArguments
from datasets import load_dataset
import torch
import os

# 1. WRIME感情分析データセットの読み込み
dataset = load_dataset("llm-book/wrime-sentiment")

# データの構造を確認
print(dataset)

# 2. トークナイザの準備(日本語BERTを指定)
model_name = "cl-tohoku/bert-base-japanese"
tokenizer = BertTokenizer.from_pretrained(model_name)

# トークン化処理を定義
def tokenize_function(examples):
    return tokenizer(examples["sentence"], padding="max_length", truncation=True)

# データセットをトークナイズ
tokenized_datasets = dataset.map(tokenize_function, batched=True)

# PyTorch形式に変換
tokenized_datasets.set_format("torch", columns=["input_ids", "attention_mask", "label"])

# 訓練用、検証用、テスト用データを取得
train_dataset = tokenized_datasets["train"]
eval_dataset = tokenized_datasets["validation"]
test_dataset = tokenized_datasets["test"]

# 3. モデルの準備(日本語BERTを感情分類タスク用に変換)
model = BertForSequenceClassification.from_pretrained(model_name, num_labels=2)

# 4. GPUが利用可能か確認
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
print(f"Using device: {device}")
model.to(device)  # モデルをGPUに移動

# 5. W&Bを無効化(報告先をTensorBoardに設定)
os.environ["WANDB_MODE"] = "disabled"  # W&Bを完全に無効化

# 6. トレーニング設定(TensorBoard用のログディレクトリを指定)
training_args = TrainingArguments(
    output_dir="./model_output",      # モデルの出力先
    num_train_epochs=3,              # エポック数
    per_device_train_batch_size=16,  # GPUを使用したトレーニング用バッチサイズ
    per_device_eval_batch_size=16,   # GPUを使用した評価用バッチサイズ
    evaluation_strategy="epoch",     # 各エポック終了時に評価
    logging_dir="./logs",            # TensorBoard用のログ保存先
    logging_steps=10,                # ログ出力間隔
    save_strategy="epoch",           # モデル保存のタイミング
    report_to="tensorboard",         # TensorBoardにログを出力
    fp16=True                        # Mixed Precision(半精度浮動小数点数)を有効化
)

# 7. トレーナーの定義
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
)

# 8. モデルのファインチューニング
trainer.train()

# 9. モデルの評価
results = trainer.evaluate(test_dataset)
print(results)

# 学習済みのモデルの保存
trainer.save_model("./saved_model")
tokenizer.save_pretrained("./saved_model")

# 学習経過の確認
!pip install tensorboard

%load_ext tensorboard
%tensorboard --logdir ./logs

推論

保存しておいたモデルを使って、判定を行うコードです。

# 学習済みモデルの利用

from transformers import BertTokenizer, BertForSequenceClassification
import torch

# 保存したモデルとトークナイザをロード
model_dir = "./saved_model"  # モデルを保存したディレクトリ
tokenizer = BertTokenizer.from_pretrained(model_dir)
model = BertForSequenceClassification.from_pretrained(model_dir)

# モデルを評価モードに設定
model.eval()

# GPUが利用可能ならGPUを使用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

# 判定したいテキスト
texts = [
    "この映画はとても面白かった!また見たい。",
    "最悪の体験だった。二度と行きたくない。",
    "普通の内容だったけど、少し退屈だった。",
    "ひどい映画だ",
    "おすすめです"
]

# テキストをトークン化してモデルに入力できる形式に変換
inputs = tokenizer(texts, padding=True, truncation=True, return_tensors="pt")

# GPUに移動
inputs = {key: val.to(device) for key, val in inputs.items()}

# モデルで予測
with torch.no_grad():
    outputs = model(**inputs)
    predictions = torch.argmax(outputs.logits, dim=-1)

# 判定結果を表示
labels = ["ポジティブ", "ネガティブ"]
for text, prediction in zip(texts, predictions):
    print(f"テキスト: {text}")
    print(f"判定: {labels[prediction]}")
    print()

実行結果

テキスト: この映画はとても面白かった!また見たい。
判定: ポジティブ

テキスト: 最悪の体験だった。二度と行きたくない。
判定: ネガティブ

テキスト: 普通の内容だったけど、少し退屈だった。
判定: ネガティブ

テキスト: ひどい映画だ
判定: ネガティブ

テキスト: おすすめです
判定: ポジティブ

実装の説明

モデルの準備

model_name = "cl-tohoku/bert-base-japanese"
tokenizer = BertTokenizer.from_pretrained(model_name)

model = BertForSequenceClassification.from_pretrained(model_name, num_labels=2)

ベースとなるモデルには、"cl-tohoku/bert-base-japanese"を使っています。これは日本語で事前学習済みのBERTモデルです。

BertForSequenceClassificationを使って、テキスト分類ができるようにします。

BertForSequenceClassificationは、Hugging FaceのTransformersライブラリで提供されるモデルクラスの一つで、テキスト分類タスク向けに設計されたBERTモデルです。このモデルは、事前学習済みのBERTを基盤としており、その上に分類タスク専用の線形ヘッド(全結合層)を追加することで、入力テキストを特定のクラスラベルに分類できるように拡張されています。

具体的には、テキスト入力(例えば、文章や文書)を与えると、それをクラスラベル(例えば、感情分析なら「ポジティブ」「ネガティブ」、トピック分類なら「スポーツ」「政治」「テクノロジー」など)に分類します。

今回は、「ポジティブ」「ネガティブ」の二値分類なので、num_labels=2 としています。


データセットの準備

日本語の学習用データとして、llm-book/wrime-sentimentを使わせていただきました。

日本語の感情分析データセット WRIME を、ポジティブ/ネガティブの二値分類のタスクに加工したデータセットです。 GitHub リポジトリ ids-cv/wrime で公開されているデータセットを利用しています。 Avg. Readers_Sentiment の値が0より大きいものをポジティブ、0より小さいものをネガティブとラベル付をしています。

https://huggingface.co/datasets/llm-book/wrime-sentiment

このデータセットには、train,validation,testの3つのデータセットが含まれています。

ClassLabel(names=['positive', 'negative', 'neutral'], id=None)
DatasetDict({
    train: Dataset({
        features: ['sentence', 'label'],
        num_rows: 30000
    })
    validation: Dataset({
        features: ['sentence', 'label'],
        num_rows: 2500
    })
    test: Dataset({
        features: ['sentence', 'label'],
        num_rows: 2500
    })
})

load_datasetを使って読み込みます。

dataset = load_dataset("llm-book/wrime-sentiment")

datasetの構造を見てみます。

print(dataset)

DatasetDict({
    train: Dataset({
        features: ['sentence', 'label', 'user_id', 'datetime'],
        num_rows: 20149
    })
    validation: Dataset({
        features: ['sentence', 'label', 'user_id', 'datetime'],
        num_rows: 1608
    })
    test: Dataset({
        features: ['sentence', 'label', 'user_id', 'datetime'],
        num_rows: 1781
    })
})

トークナイズ後は、以下のようになっています。

print(tokenized_datasets)

DatasetDict({
    train: Dataset({
        features: ['sentence', 'label', 'user_id', 'datetime', 'input_ids', 'token_type_ids', 'attention_mask'],
        num_rows: 20149
    })
    validation: Dataset({
        features: ['sentence', 'label', 'user_id', 'datetime', 'input_ids', 'token_type_ids', 'attention_mask'],
        num_rows: 1608
    })
    test: Dataset({
        features: ['sentence', 'label', 'user_id', 'datetime', 'input_ids', 'token_type_ids', 'attention_mask'],
        num_rows: 1781
    })
})

tokenized_datasetsにPytorchに入力する形に出力形式を指定します。

tokenized_datasets.set_format("torch", columns=["input_ids", "attention_mask", "label"])

これにより、データセットのうち、指定したカラム(input_idsattention_masklabel)だけをモデルのトレーニングや推論に使用できるようにします。

input_ids:トークナイザーによって変換されたトークンID(数値リスト)。

attention_mask:パディングされたトークンを無視するためのマスク。

label:各サンプルのラベル

最後に、トレーニング用のオプションを設定して、トレーニングを実施します。

# 6. トレーニング設定(TensorBoard用のログディレクトリを指定)
training_args = TrainingArguments(
    output_dir="./model_output",      # モデルの出力先
    num_train_epochs=3,              # エポック数
    per_device_train_batch_size=16,  # GPUを使用したトレーニング用バッチサイズ
    per_device_eval_batch_size=16,   # GPUを使用した評価用バッチサイズ
    evaluation_strategy="epoch",     # 各エポック終了時に評価
    logging_dir="./logs",            # TensorBoard用のログ保存先
    logging_steps=10,                # ログ出力間隔
    save_strategy="epoch",           # モデル保存のタイミング
    report_to="tensorboard",         # TensorBoardにログを出力
    fp16=True                        # Mixed Precision(半精度浮動小数点数)を有効化
)

# 7. トレーナーの定義
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
)

# 8. モデルのファインチューニング
trainer.train()


TrainingArgumentsで指定できるオプションの一覧表を記します。詳細は公式のドキュメント等を確認してください。

https://huggingface.co/docs/transformers/v4.49.0/en/main_classes/trainer#transformers.TrainingArguments

1. 出力関連のオプション

トレーニングの成果物(モデルやログなど)の保存先や頻度に関する設定。

オプション説明デフォルト値
output_dirモデルの保存先ディレクトリ。必須
overwrite_output_diroutput_dirを上書きするかどうか。False
save_strategyモデル保存のタイミング("no", "epoch", "steps")。"steps"
save_stepsモデルを保存するステップ数(save_strategy="steps"の場合に適用)。500
save_total_limit保存するチェックポイントの最大数(古いものを削除)。無制限
logging_dirTensorBoardログの保存先ディレクトリ。"runs"
logging_strategyログ出力のタイミング("no", "epoch", "steps")。"steps"
logging_stepsログを出力するステップ数(logging_strategy="steps"の場合に適用)。500

2. トレーニングの基本設定

トレーニングのエポック数やバッチサイズ、デバイス設定に関するオプション。

オプション説明デフォルト値
num_train_epochsトレーニングのエポック数。3
per_device_train_batch_size1つのデバイス(GPU/CPU)あたりのトレーニングバッチサイズ。8
per_device_eval_batch_size1つのデバイス(GPU/CPU)あたりの評価バッチサイズ。8
gradient_accumulation_steps勾配を更新する前にバッチをいくつ積み重ねるか(バッチサイズの仮想的な拡張)。1
eval_accumulation_steps評価時にメモリ使用量を削減するためにバッチを積み重ねるステップ数。None
device使用するデバイス(CPUまたはGPU)。自動的に設定される。自動検出

3. 学習率スケジューリングと最適化

学習率やスケジューリング、重み減衰(L2正則化)などの設定。

オプション説明デフォルト値
learning_rate初期学習率。5e-5
warmup_steps学習率のウォームアップ(徐々に増加)に使用するステップ数。0
weight_decay重み減衰(L2正則化)の係数。0.0
lr_scheduler_type学習率スケジューラーのタイプ(例: "linear", "cosine", "constant")。"linear"
adam_beta1AdamWオプティマイザーのβ1パラメータ。0.9
adam_beta2AdamWオプティマイザーのβ2パラメータ。0.999
adam_epsilonAdamWオプティマイザーのイプシロン値(数値安定性のための小さい値)。1e-8
max_grad_norm勾配クリッピングの上限値。1.0

4. 評価と検証

トレーニング中のモデル性能を評価するタイミングや方法を設定。

オプション説明デフォルト値
evaluation_strategy評価を行うタイミング("no", "steps", "epoch")。"no"
eval_steps評価を行うステップ数(evaluation_strategy="steps"の場合に適用)。500
load_best_model_at_endトレーニング終了時に最良のモデルをロードするかどうか。False
metric_for_best_model最良モデルを決定するための評価指標(例: "accuracy", "loss")。None
greater_is_bettermetric_for_best_modelが高い方が良いか低い方が良いかを指定。None

5. データ並列化と分散トレーニング

複数GPUやTPUを利用した分散トレーニングに関する設定。

オプション説明デフォルト値
dataloader_num_workersデータローダーに使用するスレッド数(データ読み込みの並列化)。0
fp16半精度(Mixed Precision)トレーニングを有効化するかどうか(GPUのみ)。False
fp16_opt_levelApexを使用する際の精度オプション("O0", "O1", "O2", "O3")。"O1"
sharded_ddpSharded DDPを有効化するオプション(例: "simple")。None
deepspeedDeepSpeed設定ファイルのパス。None

6. その他の便利なオプション

トレーニングに関するその他の設定。

オプション説明デフォルト値
seed乱数シード(再現性を確保するため)。42
disable_tqdmトレーニング中の進行状況バー(TQDM)を無効化するかどうか。False
report_toログを出力するサービス(例: "tensorboard", "wandb", "none")。"none"
remove_unused_columnsモデルに不要なカラムを自動的に削除するかどうか。True
push_to_hubトレーニング後にモデルをHugging Face Hubにアップロードするかどうか。False