EP.01 で 3σ rule の限界を扱いましたが、本記事ではその改良版である Z-score を深掘りします。MAD(Median Absolute Deviation)ベースの計算と、業務文脈での閾値設計が中心。
Robust Z-score の式
Modified Z-score = 0.6745 × (x - median) / MAD。0.6745 は MAD を標準偏差に変換する定数(正規分布における関係係数)。これにより、外れ値の影響を受けにくい標準化が可能。
業界別の閾値ベンチマーク
| 業界 | 推奨閾値 | 理由 |
|---|---|---|
| 金融取引 | Modified Z > 3.5 | False positive を抑える |
| 医療 | Modified Z > 2.5 | 見逃しを最小化(高 Recall) |
| 製造業 | Modified Z > 4.0 | アラート疲れを防ぐ |
| / IT 監視 | Modified Z > 3.0 | 標準的 |
閾値の自動チューニング
- パーセンタイルベース:「上位 0.1% を異常」と決めれば閾値は自動
- ROC 曲線:ラベルがあれば Precision/Recall のバランスで決定
- 業務インパクト:False positive のコスト × 件数 = アラート疲れコスト
Python 実装:Robust Z-score
MAD ベースの Modified Z-score
Python
import numpy as np
def modified_zscore(data: np.ndarray) -> np.ndarray: """MAD ベースの Modified Z-score を返す。 0.6745 は正規分布における MAD と σ の関係係数。""" median = np.median(data) mad = np.median(np.abs(data - median)) if mad == 0: return np.zeros(len(data)) return 0.6745 * (data - median) / mad
# 使用例np.random.seed(42)data = np.concatenate([ np.random.normal(50, 10, 1000), # 正常 [200, -100, 250] # 外れ値])
z = modified_zscore(data)anomalies = data[np.abs(z) > 3.5]print(f"検出件数: {len(anomalies)}, 値: {anomalies}")ROC 曲線で最適閾値を見つける
Python
import numpy as npimport matplotlib.pyplot as pltfrom sklearn.metrics import roc_curve, precision_recall_curve
# y_true: 0 / 1 のラベル、scores: 異常度スコア(連続値)fpr, tpr, thresholds = roc_curve(y_true, scores)
# Youden's J 統計量で最適閾値j_scores = tpr - fproptimal_idx = np.argmax(j_scores)optimal_threshold = thresholds[optimal_idx]print(f"最適閾値: {optimal_threshold:.2f}")print(f" TPR: {tpr[optimal_idx]:.2%}, FPR: {fpr[optimal_idx]:.2%}")
# Precision/Recall 重視ならprecision, recall, pr_thresholds = precision_recall_curve(y_true, scores)f1 = 2 * precision * recall / (precision + recall + 1e-9)best_pr_idx = np.argmax(f1)print(f"F1 最大化閾値: {pr_thresholds[best_pr_idx]:.2f} (F1={f1[best_pr_idx]:.3f})")業務文脈での閾値設計(コスト最小化)
Python
def find_business_threshold(scores, y_true, fp_cost=1.0, fn_cost=10.0): """偽陽性コスト × 件数 + 偽陰性コスト × 件数 を最小化する閾値""" thresholds = np.percentile(scores, np.arange(50, 100, 0.5)) best_threshold = None best_cost = float("inf") for t in thresholds: pred = (scores > t).astype(int) fp = ((pred == 1) & (y_true == 0)).sum() fn = ((pred == 0) & (y_true == 1)).sum() total_cost = fp * fp_cost + fn * fn_cost if total_cost < best_cost: best_cost = total_cost best_threshold = t return best_threshold, best_cost
# 例:医療(見逃しコスト > 偽陽性コスト)threshold, cost = find_business_threshold(scores, y_true, fp_cost=1, fn_cost=20)print(f"医療向け閾値: {threshold:.2f} (コスト: {cost})")ふくふくの進め方
「Z-score の閾値が業務に合わない」というご相談には、過去データのバックテスト → 閾値最適化 → 月次自動チューニングを 1〜2 週間で。
次回予告
EP.03 は Isolation Forest と LOF。教師なし機械学習の二大手法。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。