STEP 20:感情分析・テキスト分類

😊 STEP 20: 感情分析・テキスト分類

BERTを使った実践的なテキスト分類タスクの完全実装

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

  • テキスト分類タスクの種類(二値分類、多クラス分類)
  • 感情分析(Sentiment Analysis)の仕組みと実装
  • IMDBデータセットを使った感情分析
  • 評価指標(Accuracy、Precision、Recall、F1)の理解
  • クラス不均衡データへの対処法
  • 推論パイプラインの構築

練習問題: 5問

⚠️ 実行環境について

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

📊 1. テキスト分類とは

テキスト分類とは、テキストを予め定義されたカテゴリに分類するタスクです。 NLPの中で最も基本的かつ実用的なタスクの一つです。

1-1. テキスト分類の種類

【テキスト分類の3つのタイプ】 ■ 二値分類(Binary Classification) 2つのクラスに分類 例: ・感情分析: Positive / Negative ・スパム検出: Spam / Not Spam ・フェイク検出: Real / Fake 出力: 2クラスの確率 [0.1, 0.9] → Positive ■ 多クラス分類(Multi-class Classification) 3つ以上のクラスに分類(排他的: 1つだけ選択) 例: ・ニュース分類: 政治 / 経済 / スポーツ / 芸能 / … ・星評価予測: 1 / 2 / 3 / 4 / 5 ・意図認識: 質問 / 依頼 / 雑談 / … 出力: 各クラスの確率 [0.1, 0.2, 0.6, 0.1] → 3番目のクラス ■ 多ラベル分類(Multi-label Classification) 複数のラベルを同時に付与可能 例: ・映画ジャンル: アクション + コメディ + ロマンス ・ニュースタグ: 政治 + 経済 ・商品カテゴリ: 家電 + ガジェット + プレゼント 出力: 各ラベルの有無 [1, 0, 1, 1, 0] → 1, 3, 4番目のラベル

1-2. 感情分析(Sentiment Analysis)とは

【感情分析の概要】 定義: テキストに含まれる感情や意見を分析するタスク 【主なタイプ】 1. 極性分類(最も一般的) Positive(肯定的)/ Negative(否定的)/ Neutral(中立) 例: “This movie is amazing!” → Positive “Terrible product, never buy.” → Negative “It’s okay, nothing special.” → Neutral 2. 感情分類 喜び / 悲しみ / 怒り / 恐怖 / 驚き / 嫌悪 例: “I’m so happy today!” → 喜び “I can’t believe they did that!” → 怒り 3. 評価予測 1-5段階の星評価を予測 例: “Perfect! Exactly what I needed!” → ★★★★★ “Good but could be better.” → ★★★★☆ 【実用例】 ・レビュー分析(商品、映画、レストラン) ・SNSモニタリング(ブランドイメージ分析) ・カスタマーサポート(不満の早期検出) ・市場調査(消費者の声の分析)
タスク クラス数 出力
感情分析 2(Pos/Neg) レビュー分析 Positive: 0.95
スパム検出 2(Spam/Ham) メールフィルタ Spam: 0.87
ニュース分類 多(5-10) 記事カテゴリ スポーツ: 0.82
星評価予測 5(1-5星) 商品レビュー 4星: 0.73

🔧 2. 環境構築

まず、必要なライブラリをインストールし、GPUが使えることを確認します。

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

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

# ======================================== # 必要なライブラリのインストール # ======================================== # transformers: BERTなどの事前学習モデルを使うためのライブラリ # datasets: Hugging Faceのデータセットを読み込むライブラリ # scikit-learn: 評価指標の計算に使用 # accelerate: 訓練の高速化 !pip install transformers==4.35.0 datasets==2.14.0 scikit-learn accelerate -q

2-2. ライブラリのインポートとGPU確認

# ======================================== # ライブラリのインポート # ======================================== import torch from transformers import BertTokenizer, BertForSequenceClassification from transformers import Trainer, TrainingArguments from datasets import load_dataset from sklearn.metrics import accuracy_score, precision_recall_fscore_support import numpy as np # GPU確認 # cuda: NVIDIA GPU、cpu: CPU device = torch.device(‘cuda’ if torch.cuda.is_available() else ‘cpu’) print(f”Using device: {device}”) # GPU名を表示(GPUが使える場合) if torch.cuda.is_available(): print(f”GPU: {torch.cuda.get_device_name(0)}”) print(f”Memory: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB”)

実行結果:

Using device: cuda GPU: Tesla T4 Memory: 15.1 GB
⚠️ GPUが表示されない場合

「ランタイム」→「ランタイムのタイプを変更」→「GPU」を選択してください。 GPUがないと訓練に非常に時間がかかります。

📁 3. IMDBデータセットの準備

IMDBは映画レビューの感情分析データセットです。 25,000件の訓練データと25,000件のテストデータで構成されています。

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

# ======================================== # IMDBデータセットの読み込み # ======================================== # load_dataset: Hugging Faceからデータセットをダウンロード # ‘imdb’: 映画レビューの感情分析データセット dataset = load_dataset(‘imdb’) # データセットの構造を確認 print(“=== データセットの構造 ===”) print(dataset)

実行結果:

=== データセットの構造 === DatasetDict({ train: Dataset({ features: [‘text’, ‘label’], num_rows: 25000 }) test: Dataset({ features: [‘text’, ‘label’], num_rows: 25000 }) })

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

# ======================================== # データの中身を確認 # ======================================== print(“=== サンプルレビュー ===”) for i in range(3): # i番目のサンプルを取得 sample = dataset[‘train’][i] # ラベルを人間が読める形式に変換 # 0: Negative(否定的)、1: Positive(肯定的) label = “Positive” if sample[‘label’] == 1 else “Negative” # テキストの最初の200文字を表示 text_preview = sample[‘text’][:200] print(f”\n【{label}】”) print(f”{text_preview}…”) print(“-” * 50)

実行結果:

=== サンプルレビュー === 【Negative】 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… ————————————————– 【Positive】 “I Am Curious: Yellow” is a risque film that, although it contains lots of nudity, was nonetheless a massive art-house success. The film is actually two films: the first was released in… ————————————————– 【Negative】 If only to avoid making this type of film in the future. This film is interesting as an experiment but fails to be anything much beyond that. It is a film that tries to be meaningful… ————————————————–

3-3. ラベル分布の確認

# ======================================== # ラベル分布の確認 # ======================================== # 訓練データのラベルを取得 train_labels = [sample[‘label’] for sample in dataset[‘train’]] # ラベルの数をカウント negative_count = train_labels.count(0) positive_count = train_labels.count(1) print(“=== ラベル分布(訓練データ)===”) print(f”Negative (0): {negative_count} 件”) print(f”Positive (1): {positive_count} 件”) print(f”合計: {len(train_labels)} 件”) print(f”\n比率: Negative {negative_count/len(train_labels)*100:.1f}% / Positive {positive_count/len(train_labels)*100:.1f}%”)

実行結果:

=== ラベル分布(訓練データ)=== Negative (0): 12500 件 Positive (1): 12500 件 合計: 25000 件 比率: Negative 50.0% / Positive 50.0%
✅ IMDBデータセットの特徴
  • サイズ: 訓練25,000件、テスト25,000件
  • バランス: Positive/Negativeが50%ずつ(均衡データ)
  • 内容: 映画レビューの全文(長い文章が多い)
  • 言語: 英語

🔤 4. トークン化

BERTモデルを使うには、テキストをトークン(数値ID)に変換する必要があります。

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

# ======================================== # トークナイザーの読み込み # ======================================== # 使用するモデル名 # bert-base-uncased: 小文字化された英語BERT(110Mパラメータ) model_name = ‘bert-base-uncased’ # トークナイザーを読み込み # from_pretrained: 事前学習済みのトークナイザーをダウンロード tokenizer = BertTokenizer.from_pretrained(model_name) print(f”トークナイザー: {model_name}”) print(f”語彙サイズ: {tokenizer.vocab_size}”)

実行結果:

トークナイザー: bert-base-uncased 語彙サイズ: 30522

4-2. トークン化の仕組み

# ======================================== # トークン化の仕組みを確認 # ======================================== # サンプルテキスト sample_text = “This movie is absolutely fantastic!” # トークン化の各ステップを確認 # ステップ1: テキストをトークンに分割 tokens = tokenizer.tokenize(sample_text) print(“1. トークン化:”) print(f” {tokens}”) # ステップ2: トークンをIDに変換 token_ids = tokenizer.convert_tokens_to_ids(tokens) print(“\n2. トークンID:”) print(f” {token_ids}”) # ステップ3: 特殊トークンを追加([CLS]と[SEP]) # encode: 特殊トークンを自動で追加 encoded = tokenizer.encode(sample_text) print(“\n3. 特殊トークン追加後:”) print(f” {encoded}”) print(f” [CLS]=101, [SEP]=102″)

実行結果:

1. トークン化: [‘this’, ‘movie’, ‘is’, ‘absolutely’, ‘fantastic’, ‘!’] 2. トークンID: [2023, 3185, 2003, 7078, 10392, 999] 3. 特殊トークン追加後: [101, 2023, 3185, 2003, 7078, 10392, 999, 102] [CLS]=101, [SEP]=102

4-3. データセット全体のトークン化

# ======================================== # データセット全体のトークン化 # ======================================== def tokenize_function(examples): “”” テキストをトークン化する関数 Args: examples: データセットのバッチ(複数のサンプル) Returns: トークン化されたデータ “”” return tokenizer( examples[‘text’], # トークン化するテキスト padding=’max_length’, # 最大長までパディング truncation=True, # 最大長を超えたらカット max_length=256 # 最大256トークン ) # データセット全体をトークン化 # map: 各サンプルに関数を適用 # batched=True: バッチ処理で高速化 # remove_columns=[‘text’]: 元のテキストは不要なので削除 print(“トークン化を開始…”) tokenized_datasets = dataset.map( tokenize_function, batched=True, remove_columns=[‘text’] ) print(“トークン化完了!”) # トークン化後の構造を確認 print(“\n=== トークン化後のデータ構造 ===”) print(tokenized_datasets)

実行結果:

トークン化を開始… トークン化完了! === トークン化後のデータ構造 === 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 }) })
【トークン化後のデータの意味】 input_ids: トークンIDのリスト [101, 2023, 3185, 2003, …, 102, 0, 0, 0] ↑ ↑ ↑ [CLS] [SEP] パディング attention_mask: 実際のトークン(1)とパディング(0)を区別 [1, 1, 1, 1, …, 1, 0, 0, 0] token_type_ids: 文の区別(単一文なら全て0) [0, 0, 0, 0, …, 0, 0, 0, 0] label: 正解ラベル 0(Negative)または 1(Positive)

📊 5. データの分割

訓練データから一部を検証データとして分割します。 検証データは訓練中のモデル性能をモニタリングするために使います。

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

# ======================================== # 訓練/検証データの分割 # ======================================== # 訓練データを90%訓練、10%検証に分割 # train_test_split: データを分割するメソッド # test_size=0.1: 10%を検証データに # seed=42: 再現性のための乱数シード train_testvalid = tokenized_datasets[‘train’].train_test_split( test_size=0.1, seed=42 ) # 分割結果を変数に格納 train_dataset = train_testvalid[‘train’] # 訓練データ(90%) valid_dataset = train_testvalid[‘test’] # 検証データ(10%) test_dataset = tokenized_datasets[‘test’] # テストデータ(元のまま) print(“=== データ分割結果 ===”) print(f”訓練データ: {len(train_dataset):,} 件”) print(f”検証データ: {len(valid_dataset):,} 件”) print(f”テストデータ: {len(test_dataset):,} 件”)

実行結果:

=== データ分割結果 === 訓練データ: 22,500 件 検証データ: 2,500 件 テストデータ: 25,000 件

5-2. 小さいデータセットの作成(開発用)

訓練時間を短縮するため、最初は小さいデータセットで試すことをお勧めします。

# ======================================== # 小さいデータセットの作成(開発用) # ======================================== # 開発用に小さいデータセットを作成 # select: 指定したインデックスのサンプルを選択 # range(1000): 最初の1000件を選択 small_train = train_dataset.select(range(1000)) small_valid = valid_dataset.select(range(200)) print(“=== 開発用データセット ===”) print(f”小訓練データ: {len(small_train):,} 件”) print(f”小検証データ: {len(small_valid):,} 件”) print(“\n※ 最初はこのサイズで動作確認し、”) print(” 問題なければフルデータで訓練します。”)

実行結果:

=== 開発用データセット === 小訓練データ: 1,000 件 小検証データ: 200 件 ※ 最初はこのサイズで動作確認し、 問題なければフルデータで訓練します。
💡 データ分割の考え方
  • 訓練データ: モデルの学習に使用(パラメータ更新)
  • 検証データ: 訓練中のモニタリング(過学習検出)
  • テストデータ: 最終評価(訓練には使わない)

典型的な分割比率: 訓練80-90% / 検証10-20% / テスト別途用意

🎯 6. モデルの訓練

BERTモデルを感情分析タスク用にファインチューニングします。

6-1. モデルの読み込み

# ======================================== # モデルの読み込み # ======================================== # BertForSequenceClassification: 文分類用のBERT # num_labels=2: 2クラス分類(Positive/Negative) model = BertForSequenceClassification.from_pretrained( model_name, num_labels=2 ) # モデル情報を表示 total_params = sum(p.numel() for p in model.parameters()) print(f”モデル: {model_name}”) print(f”パラメータ数: {total_params:,}”)

実行結果:

モデル: bert-base-uncased パラメータ数: 109,483,778

6-2. 評価指標の定義

# ======================================== # 評価指標の定義 # ======================================== def compute_metrics(eval_pred): “”” 評価指標を計算する関数 Args: eval_pred: (予測結果, 正解ラベル)のタプル Returns: 各評価指標の辞書 “”” # 予測結果と正解ラベルを取得 predictions, labels = eval_pred # logitsから予測クラスを取得 # argmax: 最も値が大きいインデックス(=予測クラス) predictions = predictions.argmax(axis=-1) # Accuracy(正解率)を計算 accuracy = accuracy_score(labels, predictions) # Precision, Recall, F1を計算 # average=’binary’: 二値分類用 precision, recall, f1, _ = precision_recall_fscore_support( labels, predictions, average=’binary’ ) return { ‘accuracy’: accuracy, ‘precision’: precision, ‘recall’: recall, ‘f1’: f1 } print(“評価指標関数を定義しました”)

実行結果:

評価指標関数を定義しました

6-3. 訓練設定

# ======================================== # 訓練設定 # ======================================== training_args = TrainingArguments( # 出力ディレクトリ(モデル保存先) output_dir=’./results’, # エポック数(データセット全体を何回学習するか) # BERTは2-4エポックで十分 num_train_epochs=3, # バッチサイズ(一度に処理するサンプル数) # GPUメモリに応じて調整(T4なら16が目安) per_device_train_batch_size=16, per_device_eval_batch_size=32, # 学習率(BERTは2e-5〜5e-5が推奨) learning_rate=2e-5, # 重み減衰(L2正則化、過学習防止) weight_decay=0.01, # ウォームアップステップ(学習率を徐々に上げる) warmup_steps=500, # ログ出力間隔 logging_steps=100, # 評価タイミング(各エポック終了時) evaluation_strategy=’epoch’, # モデル保存タイミング(各エポック終了時) save_strategy=’epoch’, # 最良モデルを保持 load_best_model_at_end=True, metric_for_best_model=’f1′, # 混合精度訓練(メモリ節約&高速化) fp16=True ) print(“訓練設定を定義しました”)

実行結果:

訓練設定を定義しました
【主要パラメータの意味】 num_train_epochs=3 → データセット全体を3回学習 → BERTは少ないエポックで収束する learning_rate=2e-5 → 0.00002(非常に小さい) → 事前学習済みモデルは小さい学習率が必要 → 大きいと事前学習の知識が壊れる warmup_steps=500 → 最初の500ステップは学習率を徐々に上げる → 急激な更新を避けて安定させる fp16=True → 16ビット浮動小数点で計算 → メモリ半減、速度2倍向上

6-4. 訓練の実行

# ======================================== # 訓練の実行 # ======================================== # Trainerの初期化 trainer = Trainer( model=model, # 訓練するモデル args=training_args, # 訓練設定 train_dataset=small_train, # 訓練データ(開発用に小さいデータ) eval_dataset=small_valid, # 検証データ compute_metrics=compute_metrics # 評価指標 ) # 訓練開始 print(“=” * 50) print(“訓練を開始します…”) print(“=” * 50) trainer.train() print(“\n訓練完了!”)

実行結果(例):

================================================== 訓練を開始します… ================================================== Epoch 1/3: 100%|██████████| 63/63 [00:45<00:00] Evaluation: {‘accuracy’: 0.865, ‘precision’: 0.872, ‘recall’: 0.858, ‘f1’: 0.865} Epoch 2/3: 100%|██████████| 63/63 [00:44<00:00] Evaluation: {‘accuracy’: 0.895, ‘precision’: 0.901, ‘recall’: 0.889, ‘f1’: 0.895} Epoch 3/3: 100%|██████████| 63/63 [00:44<00:00] Evaluation: {‘accuracy’: 0.910, ‘precision’: 0.912, ‘recall’: 0.908, ‘f1’: 0.910} 訓練完了!
✅ 訓練のポイント
  • 各エポックで検証データの精度が上がっているか確認
  • 精度が上がらなくなったら早期終了(Early Stopping)
  • 過学習の兆候(訓練は上がるが検証は下がる)に注意

📊 7. モデルの評価

訓練したモデルをテストデータで評価します。

7-1. テストデータでの評価

# ======================================== # テストデータでの評価 # ======================================== # テストデータ(小さいサブセット)で評価 small_test = test_dataset.select(range(500)) # evaluate: テストデータで評価 test_results = trainer.evaluate(small_test) print(“=== テスト結果 ===”) for key, value in test_results.items(): if ‘eval_’ in key: metric_name = key.replace(‘eval_’, ”) print(f”{metric_name}: {value:.4f}”)

実行結果(例):

=== テスト結果 === loss: 0.2456 accuracy: 0.9020 precision: 0.9087 recall: 0.8956 f1: 0.9021 runtime: 2.34 samples_per_second: 213.68

7-2. 評価指標の詳細

【評価指標の意味】 ■ 混同行列(Confusion Matrix) 予測 Pos Neg 実際 Pos TP FN Neg FP TN TP (True Positive): Positive を正しく Positive と予測 TN (True Negative): Negative を正しく Negative と予測 FP (False Positive): Negative を誤って Positive と予測 FN (False Negative): Positive を誤って Negative と予測 ■ Accuracy(正解率) = (TP + TN) / (TP + TN + FP + FN) 意味: 全体のうち正解した割合 注意: クラス不均衡では不適切 ■ Precision(適合率) = TP / (TP + FP) 意味: Positiveと予測したうち、本当にPositiveの割合 重視: 誤検知を減らしたい場合(スパム検出など) ■ Recall(再現率) = TP / (TP + FN) 意味: 実際のPositiveのうち、正しく予測した割合 重視: 見逃しを減らしたい場合(病気診断など) ■ F1 Score = 2 × (Precision × Recall) / (Precision + Recall) 意味: PrecisionとRecallの調和平均 重視: バランスを取りたい場合(一般的な分類)
指標 計算式 重視するケース
Accuracy (TP+TN)/(全体) 均衡データの全体精度
Precision TP/(TP+FP) 誤検知を減らしたい(スパム)
Recall TP/(TP+FN) 見逃しを減らしたい(病気)
F1 2×P×R/(P+R) バランス重視(一般的)

🔮 8. 推論(予測)

訓練したモデルを使って、新しいテキストの感情を予測します。

8-1. 推論関数の作成

# ======================================== # 推論関数の作成 # ======================================== def predict_sentiment(text, model, tokenizer, device): “”” テキストの感情を予測する関数 Args: text: 予測したいテキスト model: 訓練済みモデル tokenizer: トークナイザー device: デバイス(cuda/cpu) Returns: label: 予測ラベル(Positive/Negative) confidence: 確信度(0-1) “”” # モデルを評価モードに設定 # eval(): Dropoutなどを無効化 model.eval() # テキストをトークン化 inputs = tokenizer( text, return_tensors=’pt’, # PyTorchテンソルで返す padding=True, truncation=True, max_length=256 ) # GPUに転送 inputs = {k: v.to(device) for k, v in inputs.items()} # 予測を実行 # torch.no_grad(): 勾配計算を無効化(推論時は不要) with torch.no_grad(): outputs = model(**inputs) # logitsを確率に変換 probs = torch.softmax(outputs.logits, dim=-1) # 最も確率の高いクラスを取得 predicted_class = torch.argmax(probs, dim=-1).item() # 確信度を取得 confidence = probs[0, predicted_class].item() # クラスIDをラベルに変換 label = ‘Positive’ if predicted_class == 1 else ‘Negative’ return label, confidence print(“推論関数を定義しました”)

実行結果:

推論関数を定義しました

8-2. 予測の実行

# ======================================== # 予測の実行 # ======================================== # モデルをGPUに転送 model = model.to(device) # テスト用のレビュー test_reviews = [ “This movie is absolutely fantastic! Best film I’ve seen this year.”, “Terrible movie. Complete waste of time and money.”, “It was okay. Nothing special but not bad either.”, “Brilliant performances and captivating story!”, “Boring and predictable. Very disappointed.” ] print(“=== 感情分析の結果 ===\n”) for review in test_reviews: label, confidence = predict_sentiment(review, model, tokenizer, device) # 確信度に応じた絵文字 if confidence > 0.9: emoji = “😄” if label == “Positive” else “😢” else: emoji = “🙂” if label == “Positive” else “😐” print(f”{emoji} {label} ({confidence:.1%})”) print(f” \”{review[:50]}…\””) print()

実行結果(例):

=== 感情分析の結果 === 😄 Positive (97.2%) “This movie is absolutely fantastic! Best film I’ve…” 😢 Negative (98.5%) “Terrible movie. Complete waste of time and money….” 🙂 Positive (62.3%) “It was okay. Nothing special but not bad either….” 😄 Positive (99.1%) “Brilliant performances and captivating story!…” 😢 Negative (96.8%) “Boring and predictable. Very disappointed….”
💡 予測結果の解釈
  • 高確信度(90%以上): モデルが確信を持って予測
  • 中確信度(70-90%): やや自信あり
  • 低確信度(50-70%): 曖昧な判断、中立的な文章の可能性

「It was okay」のような中立的な文章は確信度が低くなります。 これは正しい挙動です。

⚖️ 9. クラス不均衡への対処

実際のデータでは、クラス間のサンプル数が大きく異なることがあります。 これをクラス不均衡といい、適切な対処が必要です。

9-1. クラス不均衡の問題

【クラス不均衡の例】 シナリオ: 異常検出 ・正常: 9,500件(95%) ・異常: 500件(5%) 問題: モデルが全て「正常」と予測しても、Accuracy = 95%になる! しかし、異常を1件も検出できていない。 実例: ・スパム検出: スパムは全体の5%程度 ・不正検出: 不正取引は0.1%未満 ・病気診断: 病気の人は少数 【なぜ問題か】 モデルは「多数派を予測すればほぼ正解」と学習してしまう → 少数派クラスを無視する → 実用的でない 【対処法】 1. クラス重み付け(Class Weighting) 2. オーバーサンプリング(少数派を増やす) 3. アンダーサンプリング(多数派を減らす) 4. SMOTE(合成データ生成) 5. 適切な評価指標(F1, Precision, Recall)

9-2. クラス重み付けの実装

# ======================================== # クラス重み付けの実装 # ======================================== from sklearn.utils.class_weight import compute_class_weight def compute_class_weights(labels): “”” クラスの重みを計算 少数派クラスに大きな重みを付ける Args: labels: 正解ラベルのリスト Returns: クラスごとの重み辞書 “”” # ユニークなクラスを取得 classes = np.unique(labels) # 重みを計算 # class_weight=’balanced’: クラス比率の逆数で重み付け weights = compute_class_weight( class_weight=’balanced’, classes=classes, y=labels ) return dict(zip(classes, weights)) # 例: 不均衡データ imbalanced_labels = [0]*950 + [1]*50 # 95%:5%の不均衡 class_weights = compute_class_weights(imbalanced_labels) print(“=== クラス重み ===”) print(f”Class 0 (多数派): {class_weights[0]:.4f}”) print(f”Class 1 (少数派): {class_weights[1]:.4f}”) print(f”\n少数派クラスの重みが約{class_weights[1]/class_weights[0]:.0f}倍になっています”)

実行結果:

=== クラス重み === Class 0 (多数派): 0.5263 Class 1 (少数派): 10.0000 少数派クラスの重みが約19倍になっています
【重みの意味】 Class 0の重み: 0.5263(低い) Class 1の重み: 10.0000(高い) → Class 1を間違えた場合、損失が10倍になる → モデルはClass 1を正しく予測することを重視する 【適用方法】 損失関数に重みを適用: loss = CrossEntropyLoss(weight=[0.5263, 10.0]) これにより: ・多数派を間違えても損失は小さい ・少数派を間違えると損失が大きい → 少数派クラスの学習が促進される
✅ 不均衡対策の選び方
  • 軽度(60:40程度): クラス重み付けのみで十分
  • 中程度(80:20程度): クラス重み + F1評価
  • 重度(95:5以上): オーバーサンプリング or SMOTE + F1評価

💾 10. モデルの保存と読み込み

訓練したモデルを保存し、後で再利用できるようにします。

10-1. モデルの保存

# ======================================== # モデルの保存 # ======================================== # 保存先ディレクトリ save_directory = ‘./sentiment_model’ # モデルを保存 # save_pretrained: モデルの設定とパラメータを保存 model.save_pretrained(save_directory) # トークナイザーを保存 tokenizer.save_pretrained(save_directory) print(f”モデルを {save_directory} に保存しました”) # 保存されたファイルを確認 import os print(“\n=== 保存されたファイル ===”) for file in os.listdir(save_directory): file_path = os.path.join(save_directory, file) size = os.path.getsize(file_path) / (1024**2) # MB print(f” {file}: {size:.1f} MB”)

実行結果:

モデルを ./sentiment_model に保存しました === 保存されたファイル === config.json: 0.0 MB model.safetensors: 417.7 MB special_tokens_map.json: 0.0 MB tokenizer_config.json: 0.0 MB vocab.txt: 0.2 MB

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

# ======================================== # モデルの読み込み # ======================================== from transformers import AutoTokenizer, AutoModelForSequenceClassification # 保存したモデルを読み込み # from_pretrained: 保存されたモデルを読み込み loaded_model = AutoModelForSequenceClassification.from_pretrained(save_directory) loaded_tokenizer = AutoTokenizer.from_pretrained(save_directory) # GPUに転送 loaded_model = loaded_model.to(device) print(“モデルを読み込みました!”) # テスト test_text = “This is a great product!” label, confidence = predict_sentiment(test_text, loaded_model, loaded_tokenizer, device) print(f”\nテスト: \”{test_text}\””) print(f”結果: {label} ({confidence:.1%})”)

実行結果:

モデルを読み込みました! テスト: “This is a great product!” 結果: Positive (96.8%)

📝 練習問題

問題1:テキスト分類のタイプ

「メールをスパムか正常かに分類する」のは何分類?

  1. Binary Classification(二値分類)
  2. Multi-class Classification(多クラス分類)
  3. Multi-label Classification(多ラベル分類)
  4. Regression(回帰)
正解:a(Binary Classification)

スパム検出はBinary Classification(二値分類)です。

理由:

  • 2つのクラス: Spam / Not Spam(Ham)
  • 排他的(どちらか1つだけ)

参考:

  • Multi-class: ニュースカテゴリ(政治/経済/スポーツ/…)
  • Multi-label: 映画ジャンル(複数タグ可能)

問題2:評価指標の選択

スパム検出で最も重視すべき指標は?

  1. Accuracy(正解率)
  2. Precision(適合率)
  3. Recall(再現率)
  4. 全て同じく重要
正解:b(Precision)

スパム検出ではPrecision(適合率)が最も重要です。

理由:

  • 誤検知(False Positive)を避けたい
  • 正常なメールをスパムと判定すると、重要なメールを見逃す
  • Precision = TP / (TP + FP) で誤検知率を測定

一方、病気診断ではRecall(見逃しを減らす)が重要です。

問題3:クラス不均衡

正常95%、異常5%のデータでの対策として不適切なものは?

  1. クラス重み付けを使用
  2. オーバーサンプリング(少数派を増やす)
  3. Accuracyを主要評価指標にする
  4. F1スコアを評価指標にする
正解:c(Accuracyを主要評価指標にする)

不均衡データでAccuracyを主要指標にするのは不適切です。

理由:

  • 全て「正常」と予測してもAccuracy=95%になる
  • しかし異常を1件も検出できていない
  • 実用的でない

適切な対策:

  • クラス重み付け
  • オーバーサンプリング / SMOTE
  • F1スコア、Precision、Recallで評価

問題4:マルチクラス分類

5段階評価(1-5星)の予測で、モデルの出力層は?

  1. 1ユニット(回帰として扱う)
  2. 2ユニット(Binary)
  3. 5ユニット(各クラス)
  4. 10ユニット(ペア比較)
正解:c(5ユニット)

5クラス分類では5ユニットの出力層が必要です。

理由:

  • 各クラス(1-5星)に1つのユニット
  • Softmaxで確率分布に変換
  • 最も確率の高いクラスを予測

出力例: [0.05, 0.10, 0.15, 0.30, 0.40] → 5星と予測

問題5:本番デプロイ

本番環境で推論速度を上げる方法として効果が低いものは?

  1. DistilBERTなど軽量モデルを使用
  2. バッチ処理で複数テキストを同時処理
  3. 学習率を上げる
  4. FP16(混合精度)を使用
正解:c(学習率を上げる)

学習率は訓練時のパラメータであり、推論速度には影響しません。

効果的な高速化手法:

  • 軽量モデル: DistilBERT(1.6倍高速)
  • バッチ処理: GPU並列処理を活用
  • FP16: 16ビット計算でメモリ削減&高速化
  • ONNX変換: 最適化された推論エンジン

📝 STEP 20 のまとめ

✅ このステップで学んだこと
  • テキスト分類: 二値、多クラス、多ラベルの違い
  • 感情分析: IMDBデータでのPositive/Negative分類
  • データ準備: トークン化、データ分割
  • モデル訓練: Trainerを使ったファインチューニング
  • 評価指標: Accuracy、Precision、Recall、F1の使い分け
  • クラス不均衡: 重み付け、サンプリングでの対処
  • 推論: 訓練済みモデルでの予測
🎯 次のステップの準備

STEP 21: 固有表現認識(NER)では、 テキストから人名・地名・組織名などを抽出します!

  • 固有表現認識(NER)とは
  • BIO/IOBタグ付けスキーマ
  • BERTでのNER実装
  • 日本語NER
📝

学習メモ

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

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