📋 このステップで学ぶこと
テキスト分類タスクの種類(二値分類、多クラス分類)
感情分析(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:テキスト分類のタイプ
「メールをスパムか正常かに分類する」のは何分類?
Binary Classification(二値分類)
Multi-class Classification(多クラス分類)
Multi-label Classification(多ラベル分類)
Regression(回帰)
解答を見る
正解:a(Binary Classification)
スパム検出はBinary Classification(二値分類) です。
理由:
2つのクラス: Spam / Not Spam(Ham)
排他的(どちらか1つだけ)
参考:
Multi-class : ニュースカテゴリ(政治/経済/スポーツ/…)
Multi-label : 映画ジャンル(複数タグ可能)
問題2:評価指標の選択
スパム検出で最も重視すべき指標は?
Accuracy(正解率)
Precision(適合率)
Recall(再現率)
全て同じく重要
解答を見る
正解:b(Precision)
スパム検出ではPrecision(適合率) が最も重要です。
理由:
誤検知(False Positive)を避けたい
正常なメールをスパムと判定すると、重要なメールを見逃す
Precision = TP / (TP + FP) で誤検知率を測定
一方、病気診断ではRecall(見逃しを減らす)が重要です。
問題3:クラス不均衡
正常95%、異常5%のデータでの対策として不適切 なものは?
クラス重み付けを使用
オーバーサンプリング(少数派を増やす)
Accuracyを主要評価指標にする
F1スコアを評価指標にする
解答を見る
正解:c(Accuracyを主要評価指標にする)
不均衡データでAccuracyを主要指標にするのは不適切 です。
理由:
全て「正常」と予測してもAccuracy=95%になる
しかし異常を1件も検出できていない
実用的でない
適切な対策:
クラス重み付け
オーバーサンプリング / SMOTE
F1スコア、Precision、Recallで評価
問題4:マルチクラス分類
5段階評価(1-5星)の予測で、モデルの出力層は?
1ユニット(回帰として扱う)
2ユニット(Binary)
5ユニット(各クラス)
10ユニット(ペア比較)
解答を見る
正解:c(5ユニット)
5クラス分類では5ユニットの出力層 が必要です。
理由:
各クラス(1-5星)に1つのユニット
Softmaxで確率分布に変換
最も確率の高いクラスを予測
出力例: [0.05, 0.10, 0.15, 0.30, 0.40] → 5星と予測
問題5:本番デプロイ
本番環境で推論速度を上げる方法として効果が低い ものは?
DistilBERTなど軽量モデルを使用
バッチ処理で複数テキストを同時処理
学習率を上げる
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