📚 背景知識(読んでから問題へ)
🎯
Day 055 — Day 054 で学んだ F1 スコアは「閾値 0.5 固定」の評価でした。ROC 曲線はすべての閾値での性能を一枚のグラフに可視化します。AUC はモデルの本質的な識別能力を表します。
2つの指標:感度(TPR)と 1-特異度(FPR)
| 指標 | 別名 | 計算式 | 身近な例え |
|---|---|---|---|
| 感度(Sensitivity) | TPR / Recall | TP / (TP + FN) |
川の魚のうち何割を釣ったか(見逃しゼロを目指す) |
| 特異度(Specificity) | TNR | TN / (TN + FP) |
石・ゴミを何割「魚じゃない」と正しく判定できたか |
| 1 - 特異度 | FPR(偽陽性率) | FP / (FP + TN) |
石・ゴミを誤って「魚」と判定した割合(小さいほど良い) |
AUC の品質目安
| AUC 値 | 品質 | Kaggleでの目安 |
|---|---|---|
| 1.00 | 完璧(過学習疑い) | リークを疑う |
| 0.90〜1.00 | 非常に優秀 | 上位入賞圏 |
| 0.80〜0.90 | 良好 | 銅メダル圏 |
| 0.70〜0.80 | 許容範囲 | ベースライン以上 |
| 0.50〜0.70 | 弱い | 特徴量改善が必要 |
| 0.50 | ランダムと同じ | モデルに意味なし |
| < 0.50 | ランダムより悪い | ラベル反転を確認 |
📈 ROC 曲線のイメージ
縦軸: TPR(感度)/ 横軸: FPR(1-特異度)/ 閾値を 1.0 → 0.0 と動かしながらプロット
📊 AUC 品質ゲージ
AUC 値とモデル品質の対応
💡
AUC の確率的解釈:ランダムに陽性サンプルと陰性サンプルを1つずつ選んだとき、「陽性サンプルのモデルスコアが高い確率」= AUC。AUC=0.87 なら 87% の確率で陽性を正しく上位ランクに置く。
🗂️ データスキーマ(make_classification)
| パラメータ | 値 | 説明 |
|---|---|---|
n_samples | 1000 | サンプル数 |
n_features | 20 | 特徴量数(全体) |
n_informative | 10 | 予測に有効な特徴量数 |
n_redundant | 5 | 有効特徴量の線形結合(ノイズ) |
weights | [0.8, 0.2] | クラス0: 80% / クラス1: 20%(不均衡) |
random_state | 42 | 再現性のための乱数シード |
クラス分布(不均衡データのイメージ)
📝 問題
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import (
roc_curve, roc_auc_score,
confusion_matrix, classification_report
)
問1 — ROC 曲線の概念理解(空欄を埋めよ)
- ROC 曲線の横軸は
___(FPR = 1 - 特異度)である - ROC 曲線の縦軸は
___(TPR = 感度 = Recall)である - AUC が
___のとき、モデルの性能はランダム予測と同じである - AUC が高いほど、モデルは不均衡データに対して
___(強い/弱い) - 閾値を下げると TPR は
___(上がる/下がる)
問2 — データ生成とモデル学習
X, y = make_classification(
n_samples=1000,
n_features=20,
n_informative=10,
n_redundant=5,
weights=[0.8, 0.2], # 不均衡データ
random_state=42
)
train_test_split(80:20,stratify=y,random_state=42)で分割せよStandardScaler+LogisticRegression(random_state=42)の Pipeline を作成・学習せよy_predとy_prob(陽性クラスの確率:predict_probaの第2列)を取得せよ
問3 — ROC 曲線と AUC の計算
roc_curve(y_test, y_prob)でfpr, tpr, thresholdsを取得せよroc_auc_score(y_test, y_prob)で AUC を計算せよ- AUC が何を意味するかを自分の言葉で説明せよ
問4 — 最適閾値の探索
ROC 曲線上で「左上隅に最も近い点」が最適閾値の候補です。
- 各閾値での距離
distance = sqrt(fpr² + (1-tpr)²)を計算せよ - 距離が最小の閾値(
optimal_threshold)を見つけよ - その閾値での TPR と FPR を確認せよ
問5 — デフォルト閾値 vs 最適閾値の比較
- デフォルト閾値 0.5 での Precision・Recall・F1(macro)を計算せよ
- 最適閾値での Precision・Recall・F1(macro)を計算せよ
- 不均衡データに対してどちらが優れているか?理由も述べよ
🔍 ヒント(段階的開示)
ヒント1 — 方向性
- 問1: ROC曲線の軸は「本物の陽性検出率」vs「偽の陽性検出率」
- 問3:
predict_proba(X_test)[:, 1]で陽性クラスの確率を取得 - 問4:
np.argmin(distances)で最小距離のインデックスを取得 - 問5:
(y_prob >= optimal_threshold).astype(int)で最適閾値でのラベルを取得
ヒント2 — アプローチ
# ROC曲線の取得
fpr, tpr, thresholds = roc_curve(y_test, y_prob)
auc = roc_auc_score(y_test, y_prob)
# AUCの意味: ランダムに陽性1件・陰性1件を選んだとき、
# 陽性のスコアが高い確率
# 最適閾値の探索
distances = np.sqrt(fpr**2 + (1 - tpr)**2)
opt_idx = np.argmin(distances)
optimal_threshold = thresholds[opt_idx]
ヒント3 — コード骨格(ほぼ答え)
# 最適閾値でのラベル生成
y_pred_opt = (y_prob >= optimal_threshold).astype(int)
# 比較
from sklearn.metrics import precision_score, recall_score, f1_score
for name, y_p in [("閾値0.5", y_pred), ("最適閾値", y_pred_opt)]:
print(f"\n--- {name} ---")
print(f"Precision (macro): {precision_score(y_test, y_p, average='macro', zero_division=0):.4f}")
print(f"Recall (macro): {recall_score(y_test, y_p, average='macro', zero_division=0):.4f}")
print(f"F1 (macro): {f1_score(y_test, y_p, average='macro', zero_division=0):.4f}")
✅ 模範解答
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import (
roc_curve, roc_auc_score,
precision_score, recall_score, f1_score,
confusion_matrix, classification_report
)
# ── 問1: 概念理解 ──
print("=== 問1 ===")
print("1. 横軸: FPR(False Positive Rate = 1 - 特異度)")
print("2. 縦軸: TPR(True Positive Rate = 感度 = Recall)")
print("3. AUC = 0.5 のとき、ランダム予測と同じ")
print("4. AUC が高いほど不均衡データに強い")
print("5. 閾値を下げると TPR は上がる(見逃しが減る)")
# ── 問2: データ生成とモデル学習 ──
X, y = make_classification(
n_samples=1000, n_features=20,
n_informative=10, n_redundant=5,
weights=[0.8, 0.2], random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
pipeline = Pipeline([
("scaler", StandardScaler()),
("model", LogisticRegression(random_state=42, max_iter=1000))
])
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
y_prob = pipeline.predict_proba(X_test)[:, 1]
print(f"\n[問2] クラス分布: y=0: {(y_test==0).sum()}件, y=1: {(y_test==1).sum()}件")
# ── 問3: ROC曲線とAUC ──
fpr, tpr, thresholds = roc_curve(y_test, y_prob)
auc = roc_auc_score(y_test, y_prob)
print(f"\n[問3] AUC = {auc:.4f}")
print("AUCの意味: ランダムに陽性サンプルと陰性サンプルを1つずつ選んだとき、")
print(" 陽性サンプルのスコアが高い確率。")
# ── 問4: 最適閾値の探索 ──
distances = np.sqrt(fpr**2 + (1 - tpr)**2)
opt_idx = np.argmin(distances)
optimal_threshold = thresholds[opt_idx]
opt_tpr = tpr[opt_idx]
opt_fpr = fpr[opt_idx]
print(f"\n[問4] 最適閾値: {optimal_threshold:.4f}")
print(f" TPR(感度) = {opt_tpr:.4f}")
print(f" FPR(1-特異度) = {opt_fpr:.4f}")
# ── 問5: 閾値比較 ──
y_pred_opt = (y_prob >= optimal_threshold).astype(int)
print("\n[問5] 閾値比較:")
for name, y_p in [("閾値0.5(デフォルト)", y_pred), ("最適閾値", y_pred_opt)]:
p = precision_score(y_test, y_p, average="macro", zero_division=0)
r = recall_score(y_test, y_p, average="macro", zero_division=0)
f = f1_score(y_test, y_p, average="macro", zero_division=0)
print(f"\n--- {name} ---")
print(f" Precision (macro): {p:.4f}")
print(f" Recall (macro): {r:.4f}")
print(f" F1 (macro): {f:.4f}")
# 結論: 最適閾値は少数クラス(陽性)の Recall を改善し、
# 不均衡データでは Macro F1 が向上する。
# AUC を評価指標とするコンペでは閾値は不要(確率を直接評価)。
⚖️ 閾値と TPR・FPR のトレードオフ
閾値を下げる(例: 0.5 → 0.3)
✅ 陽性判定が増える
✅ TPR(感度)が上がる — 見逃しが減る
❌ FPR も上がる — 誤検知が増える
使いどころ: 癌検診(見逃しのコストが高い)
閾値を上げる(例: 0.5 → 0.7)
✅ FPR が下がる — 誤検知が減る
✅ Precision が上がる — 予測が確実
❌ TPR(感度)が下がる — 見逃しが増える
使いどころ: スパムフィルター(誤検知のコストが高い)
最適閾値の探索方法: 左上隅 (FPR=0, TPR=1) に最も近い点
🪜 Step-by-Step 解説
1 predict_proba で確率を取得する
# predict_proba は2列の行列を返す
# 列0: クラス0(陰性)の確率
# 列1: クラス1(陽性)の確率
y_prob_matrix = pipeline.predict_proba(X_test)
print(y_prob_matrix.shape) # (200, 2)
print(y_prob_matrix[:3])
# 例: [[0.92, 0.08], [0.15, 0.85], ...]
y_prob = y_prob_matrix[:, 1] # 陽性の確率だけ取り出す
2 roc_curve の戻り値を理解する
fpr, tpr, thresholds = roc_curve(y_test, y_prob)
# fpr: 各閾値での FPR(False Positive Rate)の配列
# tpr: 各閾値での TPR(True Positive Rate)の配列
# thresholds: 閾値の配列(降順)
print(f"点の数: {len(fpr)}") # 閾値の数 + 1
print(f"閾値の範囲: {thresholds.max():.3f} 〜 {thresholds.min():.3f}")
3 AUC の計算方法(2種類)
from sklearn.metrics import roc_auc_score, auc
# 方法1: roc_auc_score(推奨・シンプル)
auc1 = roc_auc_score(y_test, y_prob)
# 方法2: roc_curve + auc(同じ結果)
fpr, tpr, _ = roc_curve(y_test, y_prob)
auc2 = auc(fpr, tpr)
print(f"AUC (方法1): {auc1:.4f}")
print(f"AUC (方法2): {auc2:.4f}") # 同じ値
4 最適閾値の探索(Youden's Index との比較)
# 方法1: 左上隅からのユークリッド距離を最小化
distances = np.sqrt(fpr**2 + (1 - tpr)**2)
opt_idx_eu = np.argmin(distances)
# 方法2: Youden's Index を最大化(TPR - FPR)
youden_idx = tpr - fpr
opt_idx_yj = np.argmax(youden_idx)
print(f"最適閾値(距離法): {thresholds[opt_idx_eu]:.4f}")
print(f"最適閾値(Youden): {thresholds[opt_idx_yj]:.4f}")
# → どちらも似た結果になることが多い
5 CV で AUC を最大化する
from sklearn.model_selection import cross_val_score, StratifiedKFold
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
auc_scores = cross_val_score(
pipeline, X, y,
cv=cv,
scoring="roc_auc" # ← AUC で評価
)
print(f"CV AUC: {auc_scores.mean():.4f} ± {auc_scores.std():.4f}")
🔢 数学・統計の補足(文系向け)
TPR と FPR はなぜトレードオフになるのか?
病院での癌検診の例:
閾値を低くする(「少し怪しければ全員陽性と判定」)
→ 本物の患者をほぼ全員発見できる(TPR ↑)
→ でも健康な人も「陽性」と判定してしまう(FPR ↑)
閾値を高くする(「確実に癌の人だけ陽性と判定」)
→ 健康な人は「陰性」と正しく判定できる(FPR ↓)
→ でも軽症の患者を見逃してしまう(TPR ↓)
→ 本物の患者をほぼ全員発見できる(TPR ↑)
→ でも健康な人も「陽性」と判定してしまう(FPR ↑)
閾値を高くする(「確実に癌の人だけ陽性と判定」)
→ 健康な人は「陰性」と正しく判定できる(FPR ↓)
→ でも軽症の患者を見逃してしまう(TPR ↓)
ROC vs Accuracy の違い
Accuracy(問題あり)
不均衡データ (9:1) で
「全部陰性と予測」しただけで
Accuracy = 90%
でも陽性を1件も検出できていない
「全部陰性と予測」しただけで
Accuracy = 90%
でも陽性を1件も検出できていない
AUC(ロバスト)
「全部陰性と予測」するモデルは
確率が全員同じ → 順序に意味なし
AUC = 0.5(ランダムと同じ)
不均衡データの問題が見える
確率が全員同じ → 順序に意味なし
AUC = 0.5(ランダムと同じ)
不均衡データの問題が見える
🏆 Kaggleでの実践的な使い方
| コンペタイプ | 評価指標 | 閾値の必要性 |
|---|---|---|
| 医療(癌診断) | AUC-ROC | 提出は確率でOK(閾値不要) |
| 不正検知(金融) | AUC-ROC / Macro F1 | F1提出時は最適閾値を使う |
| 信用スコアリング | AUC-ROC | 確率を直接提出 |
| 多クラス分類 | Macro F1 / Log Loss | F1は閾値調整、Log Lossは確率 |
| バランスデータ | Accuracy / Macro F1 | 0.5でも良いことが多い |
# Kaggle提出の典型パターン
# パターン1: AUCが評価指標 → 確率をそのまま提出
submission = pd.DataFrame({
"id": test_ids,
"target": y_prob # 確率(0〜1)をそのまま
})
# パターン2: Macro F1 が評価指標 → CV で最適閾値を探索
from sklearn.metrics import f1_score
thrs = np.arange(0.1, 0.9, 0.01)
best_thr = max(thrs, key=lambda t: f1_score(
y_val, (y_prob_val >= t).astype(int),
average="macro", zero_division=0
))
print(f"最適閾値 (CV): {best_thr:.2f}")
# パターン3: class_weight="balanced" で不均衡対応
from sklearn.linear_model import LogisticRegression
model = LogisticRegression(class_weight="balanced", max_iter=1000)
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
| AUC が高ければ閾値 0.5 でいいと思う | AUC と閾値の関係を知らない | AUC は確率の順序だけを評価。Precision/Recall は閾値に依存 |
predict_proba の第1列が陽性確率と思う |
インデックスを間違える | [:, 1] が陽性(クラス1)の確率。[:, 0] は陰性 |
| AUC < 0.5 のモデルを捨てる | 悪いモデルと思う | ラベルを反転すれば AUC > 0.5 になる(ラベル設定ミスの可能性) |
| 不均衡データに Accuracy を使う | デフォルトで使ってしまう | AUC-ROC の方が不均衡に対してロバスト |
| ROC 曲線と PR 曲線を混同する | 両方「曲線」と呼ぶ | PR曲線(Precision-Recall)は極端な不均衡(1:1000など)でより有用 |
🚀 次のステップ
- 発展: PR 曲線(Precision-Recall Curve)と AP スコア — 極端な不均衡データでの評価
- 次回予告 (Day 056): Confusion Matrix の可視化と詳細分析(heatmap・FP/FNのビジネス解釈)
📋 自己評価(解いた後に記入)
理解度:
自分の回答:
気づき・メモ: