96パーセントなのに、役に立たない
迷惑メールのフィルターを、1000通で試します。960通で当たったので、96パーセントです。チームは喜びます。
ところが、このフィルターは、どのメールにも「迷惑メールではない」と答えていました。1000通のうち40通は迷惑メールでした。1つの点数が、誤りを丸ごと隠していたのです。
このレッスンのゴール
メモ
正解率だけでなく、混同行列、適合率、再現率で、分類器を評価できる。
キーワード
| ことば | いみ |
|---|---|
| 正解率 | すべてのメールのうち、モデルが正しく分けた割合 |
| 偏った表 | 片方のラベルが、もう片方よりずっと多い表 |
| 混同行列 | ラベルごとの正しい答えと誤った答えを数えた表 |
| 適合率 | 迷惑メールと判定したもののうち、本当にそうだった割合 |
| 再現率 | 本物の迷惑メールのうち、見つけた割合 |
| しきい値 | この点数以上を迷惑メールと判定する、境目の点数 |
学生が入力するセルは、このプレビューでは空白です。
正解率は、2種類の間違いを1つにする
正解率は、正しい答えをすべて同じに数えます。ラベルが同じくらいの数なら、それで十分です。
表が偏っていると、通用しません。1000通のうち40通が迷惑メールなら、いつも「迷惑メールではない」と答えるモデルは960回当たります。96パーセント当たり、迷惑メールは1通も見つけません。
いつも「ちがう」と答えるモデルを採点する
docker exec jupyter python3 -c "t=[1]*40+[0]*960; g=[0]*1000; print(sum(a==b for a,b in zip(t,g))/1000)"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
答えを4種類に数える
探しているものは迷惑メールなので、それを「陽性」と呼びます。どのメールも、4つの箱のどれかに入ります。
見つけた: 迷惑メールで、判定した。誤検知: 迷惑メールではないのに、判定した。見逃し: 迷惑メールなのに、判定しなかった。正しく通した: 迷惑メールではなく、判定しなかった。4つの数をまとめたものが、混同行列です。
例: 1000通でのフィルター
| 迷惑メールと判定 | 判定しなかった | |
|---|---|---|
| 本当に迷惑メール (40) | 見つけた: 30 | 見逃し: 10 |
| 迷惑メールではない (960) | 誤検知: 20 | 正しく通した: 940 |
学生が入力するセルは、このプレビューでは空白です。
行列、適合率、再現率を表示する
docker exec jupyter python3 -c "from sklearn.metrics import confusion_matrix, precision_score, recall_score; t=[1]*40+[0]*960; g=[1]*30+[0]*10+[1]*20+[0]*940; print(confusion_matrix(t,g)); print(round(precision_score(t,g),3), round(recall_score(t,g),3), (30+940)/1000)"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
適合率と再現率は、別の問い
適合率は問います。フィルターが迷惑メールと言ったとき、どれくらい合っているか。ここでは、判定した50通のうち30通で、0.6です。
再現率は問います。本物の迷惑メールの、どれだけを見つけたか。ここでは40通のうち30通で、0.75です。正解率は 0.97 でした。いつも「ちがう」と答えるものよりよいのですが、正解率では、その差がほとんど見えません。
しきい値を動かすと、一方が増える
多くのモデルは点数を出し、しきい値がそれをラベルに変えます。しきい値を下げると、判定されるメールが増えます。迷惑メールは多く見つかりますが、普通のメールも多く判定されます。
つまり適合率と再現率は引き合い、人が選ばなければなりません。コードを Run して、数が動くのを見ましょう。
10通で、4つのしきい値
# 10通のメール: モデルが付けた点数 (0 から 1) と、本当に迷惑メールだったか。
messages = [
(0.95, 1), (0.9, 1), (0.8, 0), (0.7, 1), (0.6, 0),
(0.5, 1), (0.4, 0), (0.3, 0), (0.2, 0), (0.1, 0),
]
for limit in [0.9, 0.7, 0.5, 0.3]:
caught = 0
false_alarms = 0
missed = 0
for pair in messages:
score = pair[0]
spam = pair[1]
flagged = score >= limit
if flagged and spam == 1:
caught = caught + 1
if flagged and spam == 0:
false_alarms = false_alarms + 1
if not flagged and spam == 1:
missed = missed + 1
print("しきい値", limit, "見つけた", caught, "誤検知", false_alarms, "見逃し", missed)
# 受信箱なら、どのしきい値にしますか。銀行なら、どうでしょう。
例: しきい値ごとの結果
| しきい値 | 見つけた | 誤検知 | 見逃し | 適合率 | 再現率 |
|---|---|---|---|---|---|
| 0.9 | 2 | 0 | 2 | 1.0 | 0.5 |
| 0.7 | 3 | 1 | 1 | 0.75 | 0.75 |
| 0.5 | 4 | 2 | 0 | 0.667 | 1.0 |
| 0.3 | 4 | 4 | 0 | 0.5 | 1.0 |
学生が入力するセルは、このプレビューでは空白です。
よくある間違い
注意
1つの数がいちばんよいしきい値が、最良だと思う人が多いです。費用を知らなければ、最良のしきい値はありません。受信箱は、少しの迷惑メールとは暮らせます。不正検知は、1件も通せません。
さらに: 間違いの費用
見逃しは読む手間が1分、誤検知は失くしたメールを探す手間が5分かかるとします。4つのしきい値の表を使います。
しきい値ごとに、失う分数を計算します。いちばん安いのはどれですか。誤検知が1分だけなら、変わりますか。