不均衡データの理解
「いよいよデータに触れるフェーズだ。だが、このデータには大きな罠がある。」
田中VPoEがKaggleのデータセットページを開く。
「28万件の取引のうち不正はたった492件。0.17%だ。普通にモデルを作ったら全く使い物にならない。まずはこの不均衡と正面から向き合おう。」
Credit Card Fraud Detectionデータセット
Kaggle の Credit Card Fraud Detection データセットは、欧州のカード保有者の2013年9月の取引データである。
データの概要
import pandas as pd
# データの読み込み
df = pd.read_csv('creditcard.csv')
print(f"データ形状: {df.shape}")
print(f"\nカラム数: {df.shape[1]}")
print(f" - V1〜V28: PCA変換済み特徴量")
print(f" - Time: 最初の取引からの経過秒数")
print(f" - Amount: 取引金額")
print(f" - Class: 0=正常, 1=不正")
# データ形状: (284807, 31)
クラス分布の確認
class_counts = df['Class'].value_counts()
class_ratio = df['Class'].value_counts(normalize=True)
print("クラス分布:")
print(f" 正常 (0): {class_counts[0]:,}件 ({class_ratio[0]:.4%})")
print(f" 不正 (1): {class_counts[1]:,}件 ({class_ratio[1]:.4%})")
print(f"\n不均衡比率: 1:{class_counts[0] // class_counts[1]}")
# クラス分布:
# 正常 (0): 284,315件 (99.8274%)
# 不正 (1): 492件 ( 0.1726%)
# 不均衡比率: 1:578
可視化による分布確認
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
# クラス分布
axes[0].bar(['正常', '不正'], class_counts.values, color=['steelblue', 'crimson'])
axes[0].set_title('クラス分布')
axes[0].set_ylabel('件数')
# 取引金額の分布
df[df['Class'] == 0]['Amount'].hist(bins=50, ax=axes[1], alpha=0.7, label='正常')
df[df['Class'] == 1]['Amount'].hist(bins=50, ax=axes[1], alpha=0.7, label='不正')
axes[1].set_title('取引金額の分布')
axes[1].legend()
# 時系列分布
df[df['Class'] == 0]['Time'].hist(bins=48, ax=axes[2], alpha=0.7, label='正常')
df[df['Class'] == 1]['Time'].hist(bins=48, ax=axes[2], alpha=0.7, label='不正')
axes[2].set_title('時間帯分布')
axes[2].legend()
plt.tight_layout()
plt.show()
評価指標の罠
正解率(Accuracy)が使えない理由
from sklearn.metrics import accuracy_score, classification_report
import numpy as np
y_true = df['Class'].values
# 戦略1: 全て正常と予測
y_pred_all_normal = np.zeros_like(y_true)
print(f"全て正常と予測した場合:")
print(f" Accuracy: {accuracy_score(y_true, y_pred_all_normal):.4f}")
print(f" 不正の検知数: 0件")
# 全て正常と予測した場合:
# Accuracy: 0.9983
# 不正の検知数: 0件
99.83%の正解率でも不正検知としては0点。正解率は不均衡データでは全く使えない指標である。
不均衡データで使うべき指標
| 指標 | 計算式 | 意味 | 重視すべき場面 |
|---|---|---|---|
| 再現率(Recall) | TP / (TP+FN) | 不正のうちどれだけ検知できたか | 見逃しを減らしたい |
| 適合率(Precision) | TP / (TP+FP) | 不正と判定したうち本当に不正な割合 | 誤検知を減らしたい |
| F1-Score | 2 * P * R / (P+R) | 適合率と再現率の調和平均 | バランス重視 |
| PR-AUC | PR曲線の面積 | 全閾値での性能 | 総合評価 |
| ROC-AUC | ROC曲線の面積 | 識別能力の総合指標 | 参考値として |
なぜPR-AUCがROC-AUCより重要か
from sklearn.metrics import roc_auc_score, average_precision_score
# 極端な不均衡下では、ROC-AUCは楽観的になりやすい
# 理由: TNが圧倒的に多いため、FPRが低く出る
# 例: 10件の不正中8件検知、10,000件の正常中100件を誤検知
tp, fn, fp, tn = 8, 2, 100, 9900
recall = tp / (tp + fn) # 0.80
precision = tp / (tp + fp) # 0.074
fpr = fp / (fp + tn) # 0.01
print(f"Recall: {recall:.2f}")
print(f"Precision: {precision:.3f}")
print(f"FPR: {fpr:.3f}")
print(f"→ ROCでは良く見えるが、Precisionは7.4%と低い")
print(f"→ PR-AUCの方が実態を反映する")
不均衡データへの対処方針
大きく3つのアプローチがある。
不均衡データへの対処:
├── 1. データレベル(サンプリング)
│ ├── オーバーサンプリング: 少数クラスを増やす
│ ├── アンダーサンプリング: 多数クラスを減らす
│ └── ハイブリッド: 両方を組み合わせる
│
├── 2. アルゴリズムレベル
│ ├── コスト敏感学習: クラス重みの調整
│ ├── アンサンブル: BalancedRandomForest等
│ └── 異常検知: 不正を「異常」として検知
│
└── 3. 評価レベル
├── 適切な指標の選択: PR-AUC, F1
├── 閾値の最適化: コストベースで最適閾値を決定
└── 層化分割: 学習/検証データの分割でクラス比率を維持
まとめ
| 項目 | ポイント |
|---|---|
| データセット | 284,807件中、不正はわずか492件(0.17%) |
| 正解率の罠 | 全て正常と予測しても99.83%、だが不正検知は0% |
| 推奨指標 | PR-AUC、F1-Score、Recall、Precision |
| 対処方針 | データレベル、アルゴリズムレベル、評価レベルの3層 |
チェックリスト
- Credit Card Fraud Detectionデータセットの概要を説明できる
- 不均衡比率(1)の意味を理解した
- 正解率が不均衡データで使えない理由を説明できる
- PR-AUCがROC-AUCより重要な理由を説明できる
次のステップへ
不均衡データの問題を理解したところで、次はサンプリング手法による対処法を学ぼう。
推定読了時間: 30分