ふくふくHukuhuku Inc.
EP.08Anomaly 9分公開: 2026-05-10

異常検知の評価指標:ラベルなしのケースも含めて

ラベルが揃った教師あり評価(Precision/Recall/F1)と、ラベルがない教師なし評価(クラスタの分離度・Silhouette)。両方の使い分け。

#評価指標#Precision#Recall
CO📔 Google Colab で開く(上から順にセルを実行)
シェア

異常検知の評価は他の タスクと違い、異常率が極めて低い(1% 以下)ため、Accuracy では計れません。

Imbalanced data の罠

「Accuracy 99%!」のウソ

異常率 1% のデータで「全部正常」と予測しても Accuracy 99%Precision・Recall・F1で見ること、特に PR-AUC が異常検知の標準。

教師あり評価

指標意味重視するケース
Precision異常と判定した中で本当に異常な割合False positive が高コスト
Recall本当の異常のうち検出できた割合見逃しが致命的(医療等)
F1Precision と Recall の調和平均バランス型
PR-AUCPR 曲線の下面積異常検知の標準指標
ROC-AUCROC 曲線の下面積imbalanced で過大評価ぎみ

ラベルなし評価

  • Silhouette score:クラスタの分離度
  • Calinski-Harabasz index:クラスタの分散比
  • Davies-Bouldin index:クラスタ内/間距離比
  • A/B 運用:本番でチャンピオン vs チャレンジャー比較

Python 実装:教師あり評価指標

Precision / Recall / F1 / PR-AUC の計算
Python
import numpy as npfrom sklearn.metrics import (    precision_score, recall_score, f1_score,    average_precision_score, roc_auc_score,    precision_recall_curve, classification_report,)
# y_true: 真のラベル(0=正常、1=異常)# y_pred: 予測ラベル# scores: 異常度スコア(連続値)
# 1. Confusion Matrix ベースの指標print(f"Precision: {precision_score(y_true, y_pred):.3f}")print(f"Recall:    {recall_score(y_true, y_pred):.3f}")print(f"F1:        {f1_score(y_true, y_pred):.3f}")print(classification_report(y_true, y_pred, target_names=["正常", "異常"]))
# 2. AUC 系(連続スコアから)print(f"PR-AUC:   {average_precision_score(y_true, scores):.3f}")  # 異常検知の標準print(f"ROC-AUC:  {roc_auc_score(y_true, scores):.3f}")              # imbalanced で過大評価ぎみ
PR 曲線の可視化
Python
import matplotlib.pyplot as pltimport japanize_matplotlib  # noqa: F401
precision, recall, thresholds = precision_recall_curve(y_true, scores)ap = average_precision_score(y_true, scores)
fig, ax = plt.subplots(figsize=(8, 6))ax.plot(recall, precision, label=f"PR-AUC = {ap:.3f}")ax.set_xlabel("Recall")ax.set_ylabel("Precision")ax.set_title("Precision-Recall 曲線")ax.legend(); ax.grid(alpha=0.3)plt.tight_layout(); plt.show()
Imbalanced data の罠を体感
Python
import numpy as npfrom sklearn.metrics import accuracy_score
# 異常率 1% のデータを作るnp.random.seed(42)y_true = np.zeros(10000, dtype=int)y_true[:100] = 1  # 100/10000 = 1%
# 「全部正常」と予測y_pred_dummy = np.zeros(10000, dtype=int)
print(f"Accuracy: {accuracy_score(y_true, y_pred_dummy):.3f}")  # 0.990 → 一見高得点print(f"Recall:   {recall_score(y_true, y_pred_dummy):.3f}")     # 0.000 → 異常を全く検出してないprint(f"F1:       {f1_score(y_true, y_pred_dummy, zero_division=0):.3f}")  # 0.000
ラベルなし評価(Silhouette score)
Python
from sklearn.metrics import silhouette_scorefrom sklearn.cluster import KMeans
# 2 クラスタに分けて分離度を測るkmeans = KMeans(n_clusters=2, random_state=42)labels = kmeans.fit_predict(X)score = silhouette_score(X, labels)print(f"Silhouette score: {score:.3f}")# > 0.5: クラスタが明確に分離(=異常検知うまく機能)# 0.0〜0.5: 一部重なり# < 0: 異常クラスタリング失敗

ふくふくの進め方

異常検知モデルの評価設計、ベンチマークデータ整備からご支援します。

次回予告

EP.09 はビジネス活用:不正検知・故障予測・

シェア

この記事の感想を教えてください

あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。

シリーズの外も探す:

まずは、現状を聞かせてください。

要件が固まっていなくて大丈夫です。現状診断と方針提案までを無料でお手伝いします。

無料相談フォームへ hello [at] hukuhuku [dot] co [dot] jp