注意
本記事は自分の知識が浅いうちに書いた記事なので、ツッコミどころが多い。 本記事を読むにあたっては、以下のことに注目して読んでいただきたい。
- ROC曲線も悪くはない。むしろ自分はこちらばかり使っている。
- PR曲線との振る舞いの違いを確かめた記事として読んでほしい。
- 少数クラスに注目する上ではPR曲線のほうが数値としての変動が大きい。それがわかりやすいという文脈でPR曲線のほうが"適している"と本文で書きがちであるが、面積最大化という意味ではROC曲線と等価(?ほぼ等価なはず)である。
PR曲線について
PR曲線とは
ROC曲線の親戚です。PrecisionとRecallを使って書かれます。(これについては後述で詳しく説明します。)
どんなときにPR曲線を使うのか
インバランスデータ(著しくPositiveに対してNegativeが多いデータ)に使われます。異常検知問題のデータセットはほとんどがこういう場合です。異常がPositive、正常がNegativeになります。
前提知識
ROC曲線
このブログにたどり着いている方はROC曲線の概要は知っていると思いますので割愛します。step by stepで書き方を言える自信がないかたは、過去のブログ記事を見てください。
Confusion Matrix
TP, FN, FP, TNの意味がわかれば充分だと思います。わからない方はこちらへ
PrecisionとRecall
Wikipediaの図がわかりやすいです。 https://en.wikipedia.org/wiki/Precision_and_recall
先程のConfusion Matrixと関連します。Precisionは式で書くと で、「Positiveと選んだものの内、本当にPositiveだったのはどれぐらいか」を意味します。
Recallは式で書くと で、「本当はPositiveだったものの内、Positiveと選べたのはどれぐらいか」を意味します。 ROC曲線のTPRと同じ物を示してます。言い方が違うだけです。
インバランスデータではこの2つの指標が大事です。というのはPositiveがNegativeに比べて著しく少ないという仮定があるからです。例えばposが1個、negが99個のテストデータがあったときを想定してみましょう。そして判別器は100個すべてnegだと判別したと仮定しましょう。このとき正答率(Accuracy)を求めると、 $$Accuracy = \frac{99}{100} = 99\ [\%]$$ となります。しかし、異常検知等で知りたいposを当てることができていません。一方RecallやPrecisionならばposを当てた数によって変動するので、よりインバランスデータの評価に向いていることがわかります。またこれを使って描いているPR曲線もなんとなくインバランスデータの評価に適しているんじゃないかと推測できます。
PR曲線の書き方
基本的にはROC曲線の書き方と同じです。これでピンと来ない方は過去のブログ記事を見てください。
では実際に描いてみようと思います。
prob | actual_label | |
---|---|---|
0 | 0.9900 | 1 |
1 | 0.9405 | 1 |
2 | 0.8910 | 1 |
3 | 0.8415 | 0 |
4 | 0.7920 | 0 |
5 | 0.7425 | 0 |
6 | 0.6930 | 0 |
7 | 0.6435 | 0 |
8 | 0.5940 | 0 |
9 | 0.5445 | 0 |
10 | 0.4950 | 0 |
11 | 0.4455 | 0 |
12 | 0.3960 | 0 |
13 | 0.3465 | 0 |
14 | 0.2970 | 0 |
15 | 0.2475 | 0 |
16 | 0.1980 | 0 |
17 | 0.1485 | 0 |
18 | 0.0990 | 0 |
19 | 0.0495 | 0 |
こんなデータを用意してみました、probが1(Positive)である確率を示しています。判別器にデータを突っ込んだ結果とでも思ってください。actual_labelが実際にPositiveかどうか示しています。1が少ないのはインバランスデータを意識しています。判別の閾値(Threshold)を0.85にでもおけばきれいに1と0を判別できます。
ここからPR曲線を書くために、閾値を動かしたときのRecallとPrecisionを求めます。
for i in [0.01*x for x in range(95, -1 , -5)]: df['pred_label'] = (df.prob > i).astype(int) tn, fp, fn, tp = confusion_matrix(df.actual_label, df.pred_label, ).ravel() print('Threshold=',f"{i:.2f}",'\tRecall=',f"{tp/(tp+fn):.2f}",'\tPrecision=',f"{tp/(tp+fp):.2f}")
Threshold= 0.95 Recall= 0.33 Precision= 1.00
Threshold= 0.90 Recall= 0.67 Precision= 1.00
Threshold= 0.85 Recall= 1.00 Precision= 1.00
Threshold= 0.80 Recall= 1.00 Precision= 0.75
Threshold= 0.75 Recall= 1.00 Precision= 0.60
Threshold= 0.70 Recall= 1.00 Precision= 0.50
Threshold= 0.65 Recall= 1.00 Precision= 0.43
Threshold= 0.60 Recall= 1.00 Precision= 0.38
Threshold= 0.55 Recall= 1.00 Precision= 0.33
Threshold= 0.50 Recall= 1.00 Precision= 0.30
Threshold= 0.45 Recall= 1.00 Precision= 0.27
Threshold= 0.40 Recall= 1.00 Precision= 0.25
Threshold= 0.35 Recall= 1.00 Precision= 0.23
Threshold= 0.30 Recall= 1.00 Precision= 0.21
Threshold= 0.25 Recall= 1.00 Precision= 0.20
Threshold= 0.20 Recall= 1.00 Precision= 0.19
Threshold= 0.15 Recall= 1.00 Precision= 0.18
Threshold= 0.10 Recall= 1.00 Precision= 0.17
Threshold= 0.05 Recall= 1.00 Precision= 0.16
Threshold= 0.00 Recall= 1.00 Precision= 0.15
横軸にRecallを取り、縦軸にPrecisionを取って、軌跡(トラジェクトリ)を書いて完成です。すでにお気づきかもしれませんが、正方形となります。ちょっと描いてみます。
AUCスコア: 1.0
ちゃんと正方形になりました。PR曲線のAUCスコアも計算すると1になりました。モデルがうまく機能してきちんとPositiveとNegativeを分離できる場合には、PR曲線はROC曲線と同じ性質を示すようです。
計算しませんがROC曲線のAUCも当然1となります。
インバランスデータにおけるPR曲線とROC曲線の違い
ここではPR曲線がなぜインバランスデータの評価に適しているのか示します。理論はともかく、図的に視覚的に示していきます。
面積の比較
ちょっと悪い場合
先程のインバランスデータにおいて、2番目のactual_labelと3番目のを入れ替えました(0番目と1番目はそのまま)。
df = pd.DataFrame({ 'prob': [0.99*0.05*x for x in range(20,0,-1)], 'actual_label': [1]*2 + [0] + [1] + [0]*16, }) df = df[['prob', 'actual_label']] df
prob | actual_label | |
---|---|---|
0 | 0.9900 | 1 |
1 | 0.9405 | 1 |
2 | 0.8910 | 0 |
3 | 0.8415 | 1 |
4 | 0.7920 | 0 |
5 | 0.7425 | 0 |
6 | 0.6930 | 0 |
7 | 0.6435 | 0 |
8 | 0.5940 | 0 |
9 | 0.5445 | 0 |
10 | 0.4950 | 0 |
11 | 0.4455 | 0 |
12 | 0.3960 | 0 |
13 | 0.3465 | 0 |
14 | 0.2970 | 0 |
15 | 0.2475 | 0 |
16 | 0.1980 | 0 |
17 | 0.1485 | 0 |
18 | 0.0990 | 0 |
19 | 0.0495 | 0 |
これを使って、PR曲線とROC曲線を描いて比較します。またAUCスコアの比較もしていきます。
PR曲線のAUCスコア: 0.916666666667 ROC曲線のAUCスコア: 0.980392156863
見ての通り、PR曲線はROC曲線と違い右上が欠けました。重要なのはAUCスコアの方です。PR曲線のAUCは完璧なモデルと比べて0.085ほど減少しているのに対し、ROC曲線のAUCは0.02しか減少してません。インバランスデータにおいてPR曲線のAUCのほうが過敏なのです。
もっと悪い場合
上位から[1,0]を四回繰り返し、以下のようなデータを作りました。(あとで気づきましたがここでうっかりpositiveが一個増えてました。正確な比較にはなりませんがあくまで傾向をつかむということで…)
df = pd.DataFrame({ 'prob': [0.99*0.05*x for x in range(20,0,-1)], 'actual_label': [1, 0]*4 + [0]*12, }) df = df[['prob', 'actual_label']] df
prob | actual_label | |
---|---|---|
0 | 0.9900 | 1 |
1 | 0.9405 | 0 |
2 | 0.8910 | 1 |
3 | 0.8415 | 0 |
4 | 0.7920 | 1 |
5 | 0.7425 | 0 |
6 | 0.6930 | 1 |
7 | 0.6435 | 0 |
8 | 0.5940 | 0 |
9 | 0.5445 | 0 |
10 | 0.4950 | 0 |
11 | 0.4455 | 0 |
12 | 0.3960 | 0 |
13 | 0.3465 | 0 |
14 | 0.2970 | 0 |
15 | 0.2475 | 0 |
16 | 0.1980 | 0 |
17 | 0.1485 | 0 |
18 | 0.0990 | 0 |
19 | 0.0495 | 0 |
この面積も同様に見ていきます。
PR曲線のAUCスコア: 0.709523809524 ROC曲線のAUCスコア: 0.90625
PR曲線のAUCは目に見えて減少が激しいです。どうしてこのように面積に大きな違いが出るのか次で見ていきます。
軌跡の比較(アニメーション)
なぜこのように面積に違いが出るのかトラジェトリを一画一画追っていくことで可視化しようと思います。データはちょっと悪い場合を使います。
prob | actual_label | |
---|---|---|
0 | 0.9900 | 1 |
1 | 0.9405 | 1 |
2 | 0.8910 | 0 |
3 | 0.8415 | 1 |
4 | 0.7920 | 0 |
5 | 0.7425 | 0 |
6 | 0.6930 | 0 |
7 | 0.6435 | 0 |
8 | 0.5940 | 0 |
9 | 0.5445 | 0 |
10 | 0.4950 | 0 |
11 | 0.4455 | 0 |
12 | 0.3960 | 0 |
13 | 0.3465 | 0 |
14 | 0.2970 | 0 |
15 | 0.2475 | 0 |
16 | 0.1980 | 0 |
17 | 0.1485 | 0 |
18 | 0.0990 | 0 |
19 | 0.0495 | 0 |
(環境によってgifがループしないようです。解決策を探してます。対処法として右クリック→新しいタブで画像を開く としてくれれば動いているのが見られると思います。)
iはThresholdに対応していて、$$Threshold=1-(i-1)\times0.05$$になっています。またiはprobが大きいデータから何行目までを1と判定するかの指標になっています。(そうなるようにdfを設定した。)
注目すべきところとそのときのThresholdによる判別結果(pred_label)を並べてアニメーションにしました。オレンジ色に塗られている行は1と判別された行です。
最初は面積はありません。点しかないのであたりまえですね。iを2に動かしてみると(1行目だけ1と判別)、PR曲線では早速面積が生じました。
iを4まで動かしてはじめてROC曲線に面積が生じました。一方PR曲線の方はすでに面積が0.66程度生じました。これではPR曲線のほうが大きな面積になりそうな気がしますが、PR曲線にはすでに損失が発生しています。PR曲線においてプロットしたマーカーを見ると、大きく下がっています。これは0を外して1と予測してしまったために生じた損失だと理解してください。これが次の点と繋がったときに右上の面積が大きく欠けることになります。
iが5のとき(probの上位4行を1と判別したとき)、PR曲線の面積はすでに完成しました。これで面積は確定です。PR曲線はPositiveをすべて当て終わった時点で完成します。一方ROC曲線はまだまだ完成には程遠いです。
iが21まで行ってROC曲線はやっと完成しました。ここからもROC曲線は0の当たっている状況を面積に反映していることがわかります。
ここから以下のことがわかります。
まとめ
- ROC曲線はデータ全体に対してThresholdを変えて評価している。(従ってインバランスデータだと多いNegativeを当てるだけで面積が増えてしまう。)
- PR曲線はPositiveをすべて当て終わるThresholdまでのデータに対して、そこだけを拡大して評価している。(だからきちんとPositiveを当てないと面積が増えない。Negativeの多いさにも引きづられにくい)
PR曲線はオレンジ色の部分範囲をThresholdが変動したとき、それだけで面積が決定してしまう。よってPR曲線のほうが少数のPositiveに注目して評価する曲線だと言えるんですね。
ただ基本的な性質は同じです。
- 良いモデルならAUCが1に近づく。
- 悪いモデルなら面積が減る。
- PR-AUCが悪ければROC-AUCも悪い。逆も然り。
参考
実はここらへんに書いてあることを動かせるようにプログラミングし、自分なりに整理しただけです。