検知の適合率・再現率・所要時間
正解を付けた5事例を集計し、混同行列、適合率、再現率を求めます。検知時間は発生から取込み、取込みから通知に分け、見逃しを0秒として平均しない方法を学びます。
説明
検知の評価では、正しく検知した件数だけでなく、誤検知と見逃しを含む分母が必要です。適合率は通知のうち正しかった割合、再現率は対象事例のうち検知できた割合を表します。
正解を付けた5事例を集計し、混同行列、適合率、再現率を求めます。検知時間は発生から取込み、取込みから通知に分け、見逃しを0秒として平均しない方法を学びます。
1評価対象と判定を読む
positiveは正解の対象事例、detectedはルールが検知した事例です。1が該当を表します。
from oplab import *
db=connect(14)
show(db, 'SELECT評価対象と判定を読む * FROM labels ORDER BY case_id')対象2件のうち1件を検知し、正常3件のうち1件を誤検知しています。未通知のalert_tはNULLです。
2四つの判定を集計する
正解と検知の2軸を組み合わせます。
show(db, 'SELECT四つの判定を集計する positive,detected,COUNT(*) AS cases FROM labels GROUP BY positive,detected ORDER BY positive DESC,detected DESC')真陽性1、偽陰性1、偽陽性1、真陰性2です。件数の定義をそろえたうえで割合を計算します。
3適合率と再現率を求める
実数の除算とNULLIFを使い、分母0ではNULLにします。
show(db, 'SELECT適合率と再現率を求める 1.0*SUM(positive*detected)/NULLIF(SUM(detected),0) AS precision,1.0*SUM(positive*detected)/NULLIF(SUM(positive),0) AS recall FROM labels')どちらも0.5です。値が同じなのはこの試料の件数によるもので、二つの指標が同じ意味ということではありません。
4正しく検知した事例の遅延を分解する
実際の検知対象に対する通知時間を確認します。
show(db, 'SELECT正しく検知した事例の遅延を分解する case_id,ingested_t-event_t AS ingest_delay,alert_t-ingested_t AS search_delay,alert_t-event_t AS total_delay FROM labels WHERE positive=1 AND detected=1')c1は取込み5秒、検索・通知3秒、合計8秒です。取込み遅延を含む値と含まない値を同じ指標として比較しません。
5見逃しを別に数える
通知されていない対象は遅延0秒ではなく、検知できなかった事例です。
show(db, 'SELECT見逃しを別に数える case_id,event_t,alert_t FROM labels WHERE positive=1 AND detected=0')c2の通知時刻はNULLです。見逃しを平均から外した場合は、その件数を併記しないと性能をよく見せてしまいます。
6分母を添えて評価値を残す
検知した対象数と、対象全体の数を同時に表示します。
show(db, 'SELECT分母を添えて評価値を残す SUM(positive) AS positives,SUM(positive*detected) AS detected_positives,AVG(CASE WHEN positive=1 AND detected=1 THEN alert_t-event_t END) AS mean_detected_delay FROM labels')対象2件中1件の検知で、検知済み事例の平均は8秒です。評価結果は、この5事例を対象とした値です。
まとめ
対象2件中1件の検知で、検知済み事例の平均は8秒です。評価結果は、この5事例を対象とした値です。
表示された証拠IDと検索条件を残すと、調査結果を元の記録までたどれます。
操作履歴と解説を振り返れます。
Python 3.15 / SQLite・固定の調査記録
from oplab import *
db=connect(14)
show(db, 'SELECT * FROM labels ORDER BY case_id')case_id | positive | detected | event_t | ingested_t | alert_t c1 | 1 | 1 | 20 | 25 | 28 c2 | 1 | 0 | 110 | 115 | NULL c3 | 0 | 1 | 20 | 22 | 24 c4 | 0 | 0 | 30 | 32 | NULL c5 | 0 | 0 | 40 | 41 | NULL
show(db, 'SELECT positive,detected,COUNT(*) AS cases FROM labels GROUP BY positive,detected ORDER BY positive DESC,detected DESC')positive | detected | cases 1 | 1 | 1 1 | 0 | 1 0 | 1 | 1 0 | 0 | 2
show(db, 'SELECT 1.0*SUM(positive*detected)/NULLIF(SUM(detected),0) AS precision,1.0*SUM(positive*detected)/NULLIF(SUM(positive),0) AS recall FROM labels')precision | recall 0.5 | 0.5
show(db, 'SELECT case_id,ingested_t-event_t AS ingest_delay,alert_t-ingested_t AS search_delay,alert_t-event_t AS total_delay FROM labels WHERE positive=1 AND detected=1')case_id | ingest_delay | search_delay | total_delay c1 | 5 | 3 | 8
show(db, 'SELECT case_id,event_t,alert_t FROM labels WHERE positive=1 AND detected=0')case_id | event_t | alert_t c2 | 110 | NULL
show(db, 'SELECT SUM(positive) AS positives,SUM(positive*detected) AS detected_positives,AVG(CASE WHEN positive=1 AND detected=1 THEN alert_t-event_t END) AS mean_detected_delay FROM labels')positives | detected_positives | mean_detected_delay 2 | 1 | 8.0
演習完了