一覧OP-14-2 · 検知設計・テスト・評価

検知の適合率・再現率・所要時間

正解を付けた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・固定の調査記録

1
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
2
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
3
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
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')
case_id | ingest_delay | search_delay | total_delay
c1 | 5 | 3 | 8
5
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
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')
positives | detected_positives | mean_detected_delay
2 | 1 | 8.0

演習完了