STEP 17:BERTのファインチューニング

🚀 STEP 17: BERTのファインチューニング

Hugging Face Transformersで実践!事前学習済みBERTを活用したNLPタスク実装

📋 このステップで学ぶこと

  • Hugging Face Transformersライブラリの基本と利点
  • 事前学習済みBERTモデルの読み込み方法
  • トークナイザーの仕組みと各パラメータの意味
  • IMDBデータセットの準備とトークン化
  • Trainer APIを使ったファインチューニング
  • モデルの評価、保存、推論の実装
  • 日本語BERTの使い方

練習問題: 5問

⚠️ 実行環境について

このステップのコードはGoogle Colab(GPU必須)で実行してください。
「ランタイム」→「ランタイムのタイプを変更」→「GPU」を選択

🤗 1. Hugging Face Transformersとは

Hugging Face Transformersは、BERTやGPTなどの事前学習済みモデルを 簡単に使えるようにしたPythonライブラリです。 このライブラリのおかげで、数行のコードでBERTを使ったアプリケーションが作れます。

1-1. なぜHugging Faceを使うのか

【従来の方法(とても大変!)】 BERTを使いたい場合、以前は: 1. 論文を読んでアーキテクチャを理解 → 数日かかる 2. PyTorchでモデルを実装 → 数百行のコード、数日〜1週間 3. Googleから重みファイルをダウンロード → ファイル形式の変換が必要 4. 重みをモデルにロード → 次元の確認など面倒 5. トークナイザーを実装 → WordPieceの実装、語彙ファイルの読み込み 6. 前処理コードを書く → [CLS]、[SEP]の追加、パディングなど → 合計で数週間かかることも… 【Hugging Faceを使う方法(とても簡単!)】 BERTを使いたい場合: 1. pip install transformers 2. tokenizer = BertTokenizer.from_pretrained(‘bert-base-uncased’) model = BertForSequenceClassification.from_pretrained(‘bert-base-uncased’) 3. すぐ使える! → たった3行、数分で完了! 【Hugging Faceの主要機能】 ■ Transformersライブラリ ・BERT、GPT、T5など数万のモデルを提供 ・統一されたAPI(使い方が同じ) ・PyTorchとTensorFlow両対応 ■ Hugging Face Hub ・モデル共有プラットフォーム ・40万以上のモデルが無料で公開 ・世界中の研究者がモデルを共有 ■ Datasetsライブラリ ・IMDb、SQuADなど数千のデータセット ・1行でダウンロード可能

1-2. ライブラリのインストール

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

※モバイルでは横スクロールできます

# ======================================== # 必要なライブラリのインストール # ======================================== # transformersライブラリをインストール # transformers: BERTなどの事前学習モデルを使うためのライブラリ # ==4.35.0: バージョンを固定(互換性のため) !pip install transformers==4.35.0 # datasetsライブラリをインストール # datasets: IMDbなどのデータセットを簡単に読み込むためのライブラリ !pip install datasets==2.14.0 # accelerateライブラリをインストール # accelerate: 訓練を高速化するライブラリ(Trainerが内部で使用) !pip install accelerate==0.24.0 # scikit-learnをインストール # scikit-learn: 評価指標(精度、F1スコアなど)を計算するライブラリ !pip install scikit-learn

1-3. GPU環境の確認

BERTのファインチューニングにはGPUが必要です。 以下のコードでGPUが使えるか確認しましょう。

# ======================================== # GPU環境の確認 # ======================================== # PyTorchをインポート # torch: ディープラーニングのフレームワーク import torch # PyTorchのバージョンを表示 # なぜ確認: バージョンによって機能が異なるため print(f”PyTorch version: {torch.__version__}”) # CUDAが利用可能かチェック # CUDA: NVIDIAのGPU計算用ライブラリ # True = GPUが使える、False = 使えない print(f”CUDA available: {torch.cuda.is_available()}”) # GPUが使える場合の詳細情報 if torch.cuda.is_available(): # GPU名を取得 # get_device_name(0): 0番目のGPUの名前 print(f”GPU: {torch.cuda.get_device_name(0)}”) # GPUメモリ容量をGB単位で表示 # total_memory: 総メモリ量(バイト単位) # / 1e9: バイト → ギガバイトに変換 memory_gb = torch.cuda.get_device_properties(0).total_memory / 1e9 print(f”GPU Memory: {memory_gb:.2f} GB”) else: # GPUが使えない場合の警告 print(“⚠️ GPUが利用できません”) print(“ランタイム → ランタイムのタイプを変更 → GPU を選択してください”)

実行結果(GPUが利用可能な場合):

PyTorch version: 2.1.0+cu118 CUDA available: True GPU: Tesla T4 GPU Memory: 15.00 GB
⚠️ GPUが利用できない場合

Google Colabで「ランタイム」→「ランタイムのタイプを変更」→「GPU」を選択してください。 無料枠ではTesla T4(15GB)が使えます。BERT-Baseなら十分動作します。

📦 2. モデルとトークナイザーの読み込み

Hugging Faceでは、モデルトークナイザーの2つを読み込みます。 この2つはセットで使います。

2-1. モデルとトークナイザーの役割

【処理の流れを理解する】 入力テキスト: “I love this movie!” ↓ ① トークナイザー(文字列 → 数値への変換) [トークン化] “I love this movie!” ↓ [“i”, “love”, “this”, “movie”, “!”] [ID変換] [“i”, “love”, “this”, “movie”, “!”] ↓ [1045, 2293, 2023, 3185, 999] [特殊トークン追加] [1045, 2293, 2023, 3185, 999] ↓ [101, 1045, 2293, 2023, 3185, 999, 102] ↑CLS ↑SEP ↓ ② BERTモデル(数値 → ベクトルへの変換) 各トークンを768次元のベクトルに変換 [101, 1045, …] → [[0.1, 0.2, …], [0.3, -0.1, …], …] ↓ ③ 分類層(ベクトル → ラベルへの変換) [CLS]トークンのベクトル(768次元) ↓ 全結合層(768次元 → 2次元) ↓ Softmax ↓ [Negative: 0.05, Positive: 0.95] ↓ 予測: Positive 【なぜトークナイザーが必要?】 コンピュータは「文字列」を直接理解できません。 数値に変換する必要があります。 BERTの語彙(ボキャブラリー): ・約30,000語を記憶している ・各単語にユニークなIDを割り当て ・例: “love” → ID 2293 ・例: “movie” → ID 3185 ・未知語は「サブワード」に分割

2-2. トークナイザーの読み込み

まず、BERTのトークナイザーを読み込みます。

# ======================================== # トークナイザーの読み込み # ======================================== # BertTokenizerクラスをインポート # BertTokenizer: BERT専用のトークナイザー from transformers import BertTokenizer # 使用するモデルの名前を変数に格納 # bert-base-uncased の意味: # – bert: モデルの種類 # – base: 12層、110Mパラメータ(largeは24層、340M) # – uncased: 大文字小文字を区別しない(”Love”も”love”も同じ) model_name = ‘bert-base-uncased’ # トークナイザーを読み込み # from_pretrained: 事前学習済みのトークナイザーをダウンロード # 初回実行時: 自動でダウンロード(約200MB) # 2回目以降: キャッシュから読み込み(高速) tokenizer = BertTokenizer.from_pretrained(model_name) # トークナイザーの情報を表示 print(f”Tokenizer loaded: {model_name}”) print(f”Vocabulary size: {tokenizer.vocab_size}”)

実行結果:

Tokenizer loaded: bert-base-uncased Vocabulary size: 30522

語彙サイズは30,522語です。BERTはこの語彙内の単語をIDに変換します。

2-3. トークン化の流れを理解する

トークナイザーが何をしているか、ステップごとに確認しましょう。

ステップ1: テキストをトークンに分割

# ======================================== # ステップ1: トークン化(文字列 → トークンのリスト) # ======================================== # サンプルテキスト text = “I love natural language processing!” # tokenize(): テキストをトークン(単語/サブワード)のリストに分割 # なぜ使う: テキストを小さな単位に分解するため tokens = tokenizer.tokenize(text) print(“Original text:”, text) print(“Tokens:”, tokens) print(“Number of tokens:”, len(tokens))

実行結果:

Original text: I love natural language processing! Tokens: [‘i’, ‘love’, ‘natural’, ‘language’, ‘processing’, ‘!’] Number of tokens: 6

「I」が「i」に変換されています。これはuncasedモデルのため、 全て小文字に変換されるからです。

ステップ2: トークンをIDに変換

# ======================================== # ステップ2: トークン → ID変換 # ======================================== # convert_tokens_to_ids(): トークンを語彙内のIDに変換 # なぜ使う: モデルは数値しか処理できないため token_ids = tokenizer.convert_tokens_to_ids(tokens) print(“Tokens:”, tokens) print(“Token IDs:”, token_ids) # IDとトークンの対応を確認 print(“\n— Token → ID の対応 —“) for token, tid in zip(tokens, token_ids): print(f” ‘{token}’ → ID {tid}”)

実行結果:

Tokens: [‘i’, ‘love’, ‘natural’, ‘language’, ‘processing’, ‘!’] Token IDs: [1045, 2293, 3019, 2653, 6364, 999] — Token → ID の対応 — ‘i’ → ID 1045 ‘love’ → ID 2293 ‘natural’ → ID 3019 ‘language’ → ID 2653 ‘processing’ → ID 6364 ‘!’ → ID 999

ステップ3: 特殊トークンを追加

# ======================================== # ステップ3: 特殊トークンを含めたエンコード # ======================================== # encode(): トークン化 + ID変換 + 特殊トークン追加 を一度に実行 # なぜ使う: [CLS]と[SEP]を自動で追加してくれる encoded = tokenizer.encode(text) print(“Encoded (with special tokens):”, encoded) # IDをトークンに戻して確認 # convert_ids_to_tokens(): IDをトークンに逆変換 decoded_tokens = tokenizer.convert_ids_to_tokens(encoded) print(“Decoded tokens:”, decoded_tokens) # 特殊トークンの説明 print(“\n— 特殊トークンの意味 —“) print(” [CLS] (ID=101): 文の先頭を示す。分類タスクで使用”) print(” [SEP] (ID=102): 文の区切りを示す。文の終わりに追加”)

実行結果:

Encoded (with special tokens): [101, 1045, 2293, 3019, 2653, 6364, 999, 102] Decoded tokens: [‘[CLS]’, ‘i’, ‘love’, ‘natural’, ‘language’, ‘processing’, ‘!’, ‘[SEP]’] — 特殊トークンの意味 — [CLS] (ID=101): 文の先頭を示す。分類タスクで使用 [SEP] (ID=102): 文の区切りを示す。文の終わりに追加

2-4. 実際の使い方(推奨方法)

実際には、tokenizer()を直接呼び出すのが最も便利です。 パディングや切り詰めも自動で行えます。

# ======================================== # 推奨される使い方: tokenizer()を直接呼び出す # ======================================== # tokenizer()を直接呼び出す(最も便利な方法) inputs = tokenizer( text, # 入力テキスト padding=’max_length’, # パディング: max_lengthまで0で埋める truncation=True, # 切り詰め: max_lengthを超えたらカット max_length=20, # 最大長: 20トークン(例として短めに設定) return_tensors=’pt’ # 戻り値の形式: PyTorchテンソル # ‘pt’=PyTorch, ‘tf’=TensorFlow, ‘np’=NumPy ) # 返り値の確認 print(“Output keys:”, inputs.keys()) print() # input_idsの確認 print(“— input_ids —“) print(f”Shape: {inputs[‘input_ids’].shape}”) # (バッチサイズ, 系列長) print(f”Values: {inputs[‘input_ids’]}”) print() # attention_maskの確認 print(“— attention_mask —“) print(f”Shape: {inputs[‘attention_mask’].shape}”) print(f”Values: {inputs[‘attention_mask’]}”)

実行結果:

Output keys: dict_keys([‘input_ids’, ‘token_type_ids’, ‘attention_mask’]) — input_ids — Shape: torch.Size([1, 20]) Values: tensor([[ 101, 1045, 2293, 3019, 2653, 6364, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]) — attention_mask — Shape: torch.Size([1, 20]) Values: tensor([[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
💡 出力の意味を理解する
  • input_ids: トークンのID列。末尾の0はパディング [PAD]
  • attention_mask: 1=有効なトークン、0=パディング(Attentionで無視される)
  • token_type_ids: 文A/文Bの区別。単一文では全て0
  • Shape [1, 20]: 1文、20トークン(バッチサイズ1、系列長20)

2-5. モデルの読み込み

次に、分類タスク用のBERTモデルを読み込みます。

# ======================================== # モデルの読み込み # ======================================== # BertForSequenceClassificationクラスをインポート # BertForSequenceClassification: 文分類タスク用のBERT # 通常のBERTに分類用の全結合層が追加されている from transformers import BertForSequenceClassification # モデルを読み込み # from_pretrained: 事前学習済みの重みをダウンロード # num_labels=2: 2クラス分類(Positive/Negative) # 3クラスなら num_labels=3 model = BertForSequenceClassification.from_pretrained( model_name, num_labels=2 ) # モデルをGPUに転送 # cuda: NVIDIA GPU # cpu: CPU(GPUがない場合) device = torch.device(‘cuda’ if torch.cuda.is_available() else ‘cpu’) model = model.to(device) # モデルの情報を表示 print(f”Model: {model_name}”) print(f”Device: {device}”) # パラメータ数を計算 # p.numel(): 各パラメータの要素数 # sum(): 全パラメータの要素数を合計 total_params = sum(p.numel() for p in model.parameters()) print(f”Total parameters: {total_params:,}”)

実行結果:

Model: bert-base-uncased Device: cuda Total parameters: 109,483,778

約1.1億パラメータのモデルが読み込まれました。

💡 利用可能なBERTモデル一覧
  • 英語: bert-base-uncased(110M)、bert-large-uncased(340M)
  • 日本語: cl-tohoku/bert-base-japanese-whole-word-masking(110M)
  • 多言語: bert-base-multilingual-cased(110M、104言語対応)

BaseとLargeの違い: Largeは精度が高いが、3倍のメモリが必要

📊 3. データセットの準備

今回はIMDBデータセットを使って感情分析を行います。 IMDBは映画レビューのデータセットで、PositiveとNegativeの2クラス分類です。

3-1. IMDBデータセットとは

【IMDBデータセットの概要】 ■ 内容 ・映画レビューのテキスト(英語) ・感情ラベル – 0 = Negative(否定的) – 1 = Positive(肯定的) ■ データ数 ・訓練データ: 25,000件 ・テストデータ: 25,000件 ・合計: 50,000件 ■ データの特徴 ・レビューは比較的長い(平均200語程度) ・Positive/Negative各12,500件(バランス良好) ・実際の映画レビューなので自然な文章 ■ 例 Positiveの例: “This movie is absolutely fantastic! The acting was superb and the storyline kept me engaged throughout.” ラベル: 1 Negativeの例: “Terrible waste of time. The plot made no sense and the acting was wooden. Do not watch this film.” ラベル: 0

3-2. データセットの読み込み

# ======================================== # データセットの読み込み # ======================================== # load_datasetをインポート # load_dataset: Hugging Faceのデータセットを簡単に読み込む関数 from datasets import load_dataset # IMDBデータセットを読み込み # 初回実行時: 自動でダウンロード(約80MB) # 2回目以降: キャッシュから読み込み dataset = load_dataset(‘imdb’) # データセットの構造を確認 print(“Dataset structure:”) print(dataset) print() # 訓練データとテストデータの件数 print(f”Train examples: {len(dataset[‘train’])}”) print(f”Test examples: {len(dataset[‘test’])}”)

実行結果:

Dataset structure: DatasetDict({ train: Dataset({ features: [‘text’, ‘label’], num_rows: 25000 }) test: Dataset({ features: [‘text’, ‘label’], num_rows: 25000 }) }) Train examples: 25000 Test examples: 25000

3-3. データの中身を確認

# ======================================== # データの中身を確認 # ======================================== # 最初のサンプルを表示 print(“— First example —“) print(f”Label: {dataset[‘train’][0][‘label’]}”) print(f”Text (first 200 chars):”) print(f”{dataset[‘train’][0][‘text’][:200]}…”) print() # ラベルの分布を確認 # なぜ確認: データが偏っていないかチェックするため import collections label_counts = collections.Counter(dataset[‘train’][‘label’]) print(“— Label distribution —“) print(f” 0 (Negative): {label_counts[0]} examples”) print(f” 1 (Positive): {label_counts[1]} examples”) print(f” Balance: {‘Good’ if abs(label_counts[0] – label_counts[1]) < 1000 else 'Imbalanced'}")

実行結果:

— First example — Label: 1 Text (first 200 chars): I rented I AM CURIOUS-YELLOW from my video store because of all the controversy that surrounded it when it was first released in 1967. I also heard that at first it was seized by U.S. customs if it… — Label distribution — 0 (Negative): 12500 examples 1 (Positive): 12500 examples Balance: Good

3-4. データのトークン化

データセット全体をトークン化します。 map関数を使うとバッチ処理で高速に処理できます。

# ======================================== # データのトークン化 # ======================================== # トークン化関数を定義 def tokenize_function(examples): “”” データセットをトークン化する関数 Args: examples: データセットのバッチ(辞書形式) examples[‘text’] = テキストのリスト Returns: トークン化された結果(input_ids, attention_mask) “”” return tokenizer( examples[‘text’], # テキストのリスト padding=’max_length’, # 最大長までパディング truncation=True, # 最大長で切り詰め max_length=256 # IMDBは長いので256に設定 # 短すぎると情報が失われる ) # データセット全体をトークン化 # map(): 各サンプルに関数を適用 # batched=True: バッチ単位で処理(高速) # remove_columns=[‘text’]: 元のテキストは削除(メモリ節約) print(“Tokenizing dataset…”) tokenized_datasets = dataset.map( tokenize_function, batched=True, remove_columns=[‘text’] ) print(“Tokenized dataset:”) print(tokenized_datasets)

実行結果:

Tokenizing dataset… Tokenized dataset: DatasetDict({ train: Dataset({ features: [‘label’, ‘input_ids’, ‘token_type_ids’, ‘attention_mask’], num_rows: 25000 }) test: Dataset({ features: [‘label’, ‘input_ids’, ‘token_type_ids’, ‘attention_mask’], num_rows: 25000 }) })

text列がinput_idsattention_maskに変換されました。

3-5. 訓練/検証データの分割

# ======================================== # 訓練/検証データの分割 # ======================================== # 訓練データを訓練/検証に分割 # train_test_split: データを分割するメソッド # test_size=0.1: 10%を検証データに # seed=42: 乱数シードを固定(再現性のため) split_dataset = tokenized_datasets[‘train’].train_test_split( test_size=0.1, seed=42 ) # 分割結果を変数に代入 train_dataset = split_dataset[‘train’] # 訓練データ(90%) valid_dataset = split_dataset[‘test’] # 検証データ(10%) # ※splitの’test’が検証データ test_dataset = tokenized_datasets[‘test’] # 元のテストデータ print(“Dataset splits:”) print(f” Train: {len(train_dataset)} examples”) print(f” Valid: {len(valid_dataset)} examples”) print(f” Test: {len(test_dataset)} examples”)

実行結果:

Dataset splits: Train: 22500 examples Valid: 2500 examples Test: 25000 examples

3-6. 開発用の小規模データ作成

開発・デバッグ時は小規模データで実験すると効率的です。 コードが正しく動くことを確認してからフルデータで訓練しましょう。

# ======================================== # 開発用の小規模データ作成 # ======================================== # shuffle: データをランダムに並べ替え # select(range(N)): 最初のN件を選択 small_train = train_dataset.shuffle(seed=42).select(range(1000)) small_valid = valid_dataset.shuffle(seed=42).select(range(200)) print(“Small dataset for development:”) print(f” Train: {len(small_train)} examples”) print(f” Valid: {len(small_valid)} examples”) # 本番訓練では元のデータセットを使用 # train_dataset = train_dataset # 22,500件 # valid_dataset = valid_dataset # 2,500件

実行結果:

Small dataset for development: Train: 1000 examples Valid: 200 examples
✅ 開発時のコツ

最初は小規模データ(1,000件程度)で実験し、 コードが正しく動くことを確認してからフルデータで訓練すると効率的です。 これにより、エラーの早期発見と時間の節約ができます。

🎯 4. ファインチューニングの実装

Hugging FaceのTrainerクラスを使うと、 複雑な訓練ループを書かずにファインチューニングができます。

4-1. 評価指標の定義

訓練中に精度やF1スコアを計算する関数を定義します。

# ======================================== # 評価指標の定義 # ======================================== # scikit-learnから評価指標をインポート from sklearn.metrics import accuracy_score, precision_recall_fscore_support def compute_metrics(eval_pred): “”” 評価指標を計算する関数 Trainerが評価時に自動的に呼び出す Args: eval_pred: (predictions, labels)のタプル predictions: モデルの出力(logits、形状は[N, 2]) labels: 正解ラベル(形状は[N]) Returns: 評価指標の辞書 “”” # predictionsとlabelsを取り出す predictions, labels = eval_pred # logitsから予測ラベルを取得 # argmax(axis=-1): 各サンプルで最大値のインデックスを取得 # 例: [[0.2, 0.8], [0.9, 0.1]] → [1, 0] # 0.2 < 0.8 なのでインデックス1、0.9 > 0.1 なのでインデックス0 preds = predictions.argmax(axis=-1) # 精度(Accuracy)を計算 # 正解した数 / 全体の数 accuracy = accuracy_score(labels, preds) # Precision、Recall、F1を計算 # precision: 予測がPositiveの中で、実際にPositiveだった割合 # recall: 実際にPositiveの中で、Positiveと予測できた割合 # f1: PrecisionとRecallの調和平均 # average=’binary’: 2クラス分類用 precision, recall, f1, _ = precision_recall_fscore_support( labels, preds, average=’binary’ ) return { ‘accuracy’: accuracy, ‘precision’: precision, ‘recall’: recall, ‘f1’: f1 }

4-2. 訓練設定(TrainingArguments)

訓練の詳細設定を作成します。各パラメータの意味を説明します。

# ======================================== # 訓練設定の作成 # ======================================== # TrainerとTrainingArgumentsをインポート from transformers import Trainer, TrainingArguments # 訓練設定を作成 training_args = TrainingArguments( # === 出力設定 === output_dir=’./results’, # チェックポイントの保存先 # === 訓練設定 === num_train_epochs=3, # エポック数 # BERTは3-4エポックが一般的 per_device_train_batch_size=16, # 訓練バッチサイズ # GPUメモリに応じて調整(8, 16, 32) per_device_eval_batch_size=32, # 評価バッチサイズ # 訓練より大きくてOK(勾配計算なし) # === 最適化設定 === learning_rate=2e-5, # 学習率(★重要!) # BERTは2e-5〜5e-5が推奨 # 大きすぎると事前学習の知識が失われる weight_decay=0.01, # 重み減衰(L2正則化) # 過学習を防ぐ warmup_steps=500, # ウォームアップステップ数 # 最初は学習率を徐々に上げる # === 評価・保存設定 === evaluation_strategy=’steps’, # 評価タイミング: ステップごと eval_steps=500, # 500ステップごとに評価 save_strategy=’steps’, # 保存タイミング: ステップごと save_steps=500, # 500ステップごとに保存 save_total_limit=2, # 保存する最大チェックポイント数 load_best_model_at_end=True, # 訓練終了時に最良モデルをロード metric_for_best_model=’f1′, # 最良の基準: F1スコア # === ログ設定 === logging_dir=’./logs’, # ログの保存先 logging_steps=100, # 100ステップごとにログ出力 # === 高速化設定 === fp16=True, # 混合精度訓練 # メモリ使用量が半減、速度が2倍 # === その他 === report_to=’none’ # 外部ツールへの報告を無効化 )
💡 重要なパラメータの説明
  • learning_rate=2e-5: BERTは事前学習済みなので小さい値。大きすぎると事前学習の知識が失われる
  • num_train_epochs=3: BERTは少ないエポックで十分。多すぎると過学習
  • warmup_steps=500: 最初は学習率を徐々に上げて訓練を安定させる
  • fp16=True: 16ビット精度で計算。メモリ使用量が半減、速度が約2倍

4-3. Trainerの初期化と訓練実行

# ======================================== # Trainerの初期化 # ======================================== # Trainerを作成 trainer = Trainer( model=model, # 訓練するモデル args=training_args, # 訓練設定 train_dataset=small_train, # 訓練データ(開発用は小規模) eval_dataset=small_valid, # 検証データ compute_metrics=compute_metrics # 評価関数 ) # 訓練設定を確認 print(“Training configuration:”) print(f” Epochs: {training_args.num_train_epochs}”) print(f” Batch size: {training_args.per_device_train_batch_size}”) print(f” Learning rate: {training_args.learning_rate}”) print(f” Train samples: {len(small_train)}”) # ステップ数を計算 steps_per_epoch = len(small_train) // training_args.per_device_train_batch_size total_steps = steps_per_epoch * int(training_args.num_train_epochs) print(f” Steps per epoch: {steps_per_epoch}”) print(f” Total steps: {total_steps}”)

実行結果:

Training configuration: Epochs: 3 Batch size: 16 Learning rate: 2e-05 Train samples: 1000 Steps per epoch: 62 Total steps: 186

4-4. 訓練の実行

# ======================================== # 訓練の実行 # ======================================== print(“Starting training…”) print(“=” * 50) # train(): 訓練を実行 # 進捗バーが表示される train_result = trainer.train() print(“=” * 50) print(“Training completed!”) # 訓練結果を表示 print(f”Training loss: {train_result.training_loss:.4f}”) print(f”Training time: {train_result.metrics[‘train_runtime’]:.1f} seconds”)

実行結果(例):

Starting training… ================================================== [186/186 02:30, Epoch 3/3] ================================================== Training completed! Training loss: 0.2834 Training time: 150.3 seconds

4-5. 検証データでの評価

# ======================================== # 検証データでの評価 # ======================================== # evaluate(): 検証データで評価 eval_result = trainer.evaluate() print(“Validation Results:”) print(f” Loss: {eval_result[‘eval_loss’]:.4f}”) print(f” Accuracy: {eval_result[‘eval_accuracy’]:.4f}”) print(f” Precision: {eval_result[‘eval_precision’]:.4f}”) print(f” Recall: {eval_result[‘eval_recall’]:.4f}”) print(f” F1 Score: {eval_result[‘eval_f1’]:.4f}”)

実行結果(例):

Validation Results: Loss: 0.2856 Accuracy: 0.8850 Precision: 0.8912 Recall: 0.8789 F1 Score: 0.8850

💾 5. モデルの保存と推論

5-1. モデルの保存

# ======================================== # モデルの保存 # ======================================== # 保存先ディレクトリ save_directory = ‘./my_bert_model’ # モデルを保存 # save_pretrained: モデルの重みと設定を保存 model.save_pretrained(save_directory) # トークナイザーも保存(推論時に必要) tokenizer.save_pretrained(save_directory) print(f”Model saved to: {save_directory}”) # 保存されたファイルを確認 import os print(“\nSaved files:”) for f in os.listdir(save_directory): file_path = os.path.join(save_directory, f) size_mb = os.path.getsize(file_path) / (1024**2) print(f” {f}: {size_mb:.2f} MB”)

実行結果:

Model saved to: ./my_bert_model Saved files: config.json: 0.00 MB model.safetensors: 417.65 MB tokenizer_config.json: 0.00 MB vocab.txt: 0.21 MB special_tokens_map.json: 0.00 MB

5-2. 保存したモデルの読み込み

# ======================================== # 保存したモデルの読み込み # ======================================== # 保存したモデルを読み込み loaded_model = BertForSequenceClassification.from_pretrained(save_directory) loaded_tokenizer = BertTokenizer.from_pretrained(save_directory) # GPUへ転送 loaded_model = loaded_model.to(device) # 評価モードに設定 # eval(): Dropoutやバッチ正規化を無効化 # 推論時は必ずeval()を呼ぶ loaded_model.eval() print(“Model loaded successfully!”)

5-3. 推論関数の作成

# ======================================== # 推論関数の作成 # ======================================== def predict_sentiment(text, model, tokenizer, device): “”” テキストの感情を予測する関数 Args: text: 予測したいテキスト model: 学習済みモデル tokenizer: トークナイザー device: デバイス(cuda/cpu) Returns: label: 予測ラベル(’Positive’ or ‘Negative’) confidence: 信頼度(0〜1) “”” # テキストをトークン化 inputs = tokenizer( text, padding=True, truncation=True, max_length=256, return_tensors=’pt’ ) # GPUへ転送 # 辞書の各値をGPUに転送 inputs = {k: v.to(device) for k, v in inputs.items()} # 予測 model.eval() # 評価モード with torch.no_grad(): # 勾配計算を無効化(メモリ節約・高速化) # モデルに入力 outputs = model(**inputs) # logitsを確率に変換 # softmax: 出力を0〜1の確率に変換 probs = torch.softmax(outputs.logits, dim=-1) # 最も確率が高いクラスを取得 pred_class = torch.argmax(probs, dim=-1).item() confidence = probs[0, pred_class].item() # ラベルに変換 label = ‘Positive’ if pred_class == 1 else ‘Negative’ return label, confidence

5-4. 予測のテスト

# ======================================== # 予測のテスト # ======================================== # テスト用テキスト test_texts = [ “This movie is absolutely fantastic! I loved every minute of it.”, “Terrible film. Complete waste of time and money.”, “It was okay. Nothing special but not bad either.”, “Best movie I’ve seen this year! Highly recommended!”, “Boring and predictable. Very disappointed.” ] # 各テキストを予測 print(“Sentiment Predictions:”) print(“=” * 60) for text in test_texts: label, confidence = predict_sentiment( text, loaded_model, loaded_tokenizer, device ) # テキストが長い場合は省略 display_text = text[:50] + “…” if len(text) > 50 else text print(f”\nText: {display_text}”) print(f”Prediction: {label} (confidence: {confidence:.2%})”)

実行結果(例):

Sentiment Predictions: ============================================================ Text: This movie is absolutely fantastic! I loved every… Prediction: Positive (confidence: 99.87%) Text: Terrible film. Complete waste of time and money…. Prediction: Negative (confidence: 99.82%) Text: It was okay. Nothing special but not bad either…. Prediction: Positive (confidence: 62.34%) Text: Best movie I’ve seen this year! Highly recommende… Prediction: Positive (confidence: 99.94%) Text: Boring and predictable. Very disappointed…. Prediction: Negative (confidence: 99.71%)

「It was okay」のような中立的なテキストは信頼度が低くなっています。 これは正しい挙動で、モデルが自信を持てない場合に低い信頼度を出力しています。

🇯🇵 6. 日本語BERTの使い方

日本語のテキスト分類には東北大学BERTが広く使われています。 基本的な使い方は英語版と同じですが、トークナイザーが異なります。

6-1. 日本語BERTの読み込み

# ======================================== # 日本語BERTの読み込み # ======================================== # 日本語BERT用のトークナイザーをインポート from transformers import BertJapaneseTokenizer, BertForSequenceClassification # 東北大学の日本語BERT # whole-word-masking: 形態素単位でマスクする版(性能が良い) model_name_jp = ‘cl-tohoku/bert-base-japanese-whole-word-masking’ # トークナイザーを読み込み # 日本語BERTはMeCabベースの形態素解析を使用 tokenizer_jp = BertJapaneseTokenizer.from_pretrained(model_name_jp) # モデルを読み込み model_jp = BertForSequenceClassification.from_pretrained( model_name_jp, num_labels=2 ) print(f”Japanese BERT loaded: {model_name_jp}”) print(f”Vocabulary size: {tokenizer_jp.vocab_size}”)

実行結果:

Japanese BERT loaded: cl-tohoku/bert-base-japanese-whole-word-masking Vocabulary size: 32000

6-2. 日本語のトークン化

# ======================================== # 日本語のトークン化 # ======================================== # 日本語テキストのトークン化テスト text_jp = “この映画は本当に素晴らしかった!感動しました。” # トークン化 tokens_jp = tokenizer_jp.tokenize(text_jp) print(f”Text: {text_jp}”) print(f”Tokens: {tokens_jp}”) print(f”Number of tokens: {len(tokens_jp)}”)

実行結果:

Text: この映画は本当に素晴らしかった!感動しました。 Tokens: [‘この’, ‘映画’, ‘は’, ‘本当に’, ‘素晴らしかっ’, ‘た’, ‘!’, ‘感動’, ‘し’, ‘まし’, ‘た’, ‘。’] Number of tokens: 12

日本語BERTはMeCabによる形態素解析を使ってトークン化します。 日本語は単語の区切りがないため、形態素解析で単語を分割しています。

💡 日本語BERTの特徴
  • トークナイザー: MeCabによる形態素解析ベース
  • 語彙サイズ: 32,000(英語は30,522)
  • 訓練データ: Wikipedia日本語版
  • Whole Word Masking: 形態素単位でマスク(推奨)
⚠️ 日本語BERTの注意点

日本語BERTを実際に使うには、日本語のデータセットでファインチューニングが必要です。 上記のコードではファインチューニングしていないため、予測は正確ではありません。 実際に使う場合は、Livedoorニュースコーパスなどの日本語データセットで訓練してください。

📝 練習問題

問題1:モデルの読み込み

事前学習済みBERTモデルを読み込むメソッドは?

  1. load_model()
  2. from_pretrained()
  3. get_model()
  4. download_model()
正解:b

from_pretrained()メソッドで事前学習済みモデルを読み込みます。

使用例:

  • model = BertForSequenceClassification.from_pretrained(‘bert-base-uncased’)
  • tokenizer = BertTokenizer.from_pretrained(‘bert-base-uncased’)

このメソッドは初回実行時に自動でモデルをダウンロードし、 2回目以降はキャッシュから読み込みます。

問題2:学習率の設定

BERTのファインチューニングで推奨される学習率は?

  1. 1e-3〜1e-2(大きめ)
  2. 2e-5〜5e-5(小さめ)
  3. 1e-1(非常に大きい)
  4. 1e-7(非常に小さい)
正解:b

BERTは既に事前学習済みなので、 小さい学習率(2e-5〜5e-5)でファインチューニングします。

理由:

  • 大きすぎる学習率 → 事前学習の知識が失われる(壊れる)
  • 小さすぎる学習率 → 収束が遅い(学習に時間がかかる)

BERT論文でも2e-5、3e-5、5e-5が推奨されています。

問題3:トークナイザーのパラメータ

tokenizer(text, padding=True, truncation=True)の役割は?

  1. paddingのみ実行
  2. truncationのみ実行
  3. 系列長を揃えて、長すぎる場合は切り詰める
  4. 特殊トークンを追加する
正解:c

paddingtruncationを組み合わせることで、 系列長を揃えて長すぎる場合は切り詰めます。

  • padding=True: 短い系列をmax_lengthまでパディング(0で埋める)
  • truncation=True: max_lengthを超えた系列を切り詰め

両方を使うことで、バッチ処理に必要な「同じ長さの系列」が作れます。

問題4:過学習の対策

BERTのファインチューニングで過学習を防ぐ方法として適切でないものは?

  1. 学習率を下げる(2e-5)
  2. エポック数を減らす(3-4)
  3. 訓練データを減らす
  4. Weight Decayを使う
正解:c

訓練データを減らすのは過学習対策として適切ではありません。 むしろ過学習しやすくなります。

正しい過学習対策:

  • ✅ 小さい学習率(2e-5程度)
  • ✅ 少ないエポック数(3-4エポック)
  • ✅ Weight Decay(L2正則化)
  • ✅ Early Stopping
  • ✅ Dropout(BERTに組み込み済み)

問題5:日本語BERTの特徴

日本語BERTのトークナイザーの特徴として正しいものは?

  1. 英語と同じWordPieceを使用
  2. MeCabによる形態素解析ベース
  3. 文字単位でトークン化
  4. スペースで単語を区切る
正解:b

日本語BERTはMeCabによる形態素解析をベースにしています。

理由:

  • 日本語には単語の区切り(スペース)がない
  • 形態素解析で単語を分割する必要がある
  • 分割後、WordPieceで細分化する場合もある

例: “この映画は素晴らしい” → [“この”, “映画”, “は”, “素晴らしい”]

📝 STEP 17 のまとめ

✅ このステップで学んだこと
  • Hugging Face Transformers: 事前学習モデルを簡単に使えるライブラリ
  • from_pretrained(): 3行でモデルを読み込める
  • トークナイザー: テキスト → input_ids + attention_mask に変換
  • データ準備: datasets.map()でバッチトークン化
  • Trainer API: 訓練ループを書かずにファインチューニング
  • ハイパーパラメータ: 学習率2e-5、3-4エポックが基本
  • 保存と推論: save_pretrained()で保存、独自関数で推論
  • 日本語BERT: MeCabベースの形態素解析トークナイザー
🎯 次のステップの準備

STEP 18: GPTシリーズの理解では、 生成タスクに強いGPTモデルを学びます!

  • GPT、GPT-2、GPT-3の進化
  • Decoder-onlyアーキテクチャ
  • ChatGPTの仕組み(RLHF)
  • テキスト生成の実装

BERTは理解が得意、GPTは生成が得意!

📝

学習メモ

自然言語処理(NLP) - Step 17

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE