LESSON

不均衡データの理解

「いよいよデータに触れるフェーズだ。だが、このデータには大きな罠がある。」

田中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-Score2 * P * R / (P+R)適合率と再現率の調和平均バランス重視
PR-AUCPR曲線の面積全閾値での性能総合評価
ROC-AUCROC曲線の面積識別能力の総合指標参考値として

なぜ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分