お気持ち練習帳

気持ちの整理や数学等の書きたいことを書きます

ROC曲線のAUCが良いけどPR曲線のAUCが悪い例

はじめに

2値分類のモデルを評価する際に、ROC曲線がよく用いられます。 特に、その曲線下の面積(Area under the curve, AUC)が指標として使われます。 その他にも、混同行列から算出されるPrecisionとRecallからなるPR曲線があります。

ネガティブケースにデータが偏っている場合は、ROC曲線よりPR曲線が適していると言われてます。 じゃあ、偏ってるときはPR曲線使えばいいかと思いましたが、そもそもROC曲線でAUCを比較することとPR曲線でAUCを比較することは等価なのかが気になりました。 結果は、等価ではありませんでした。 以下では、そのことを例を使って見ていきます。

問題設定

陽性、陰性の数がそれぞれ1つ以上のデータを用意する。 そのデータに対して、陽性を当てる確率を A, B 2パターン付与する。 このとき、以下は成立するか?

 
\begin{aligned}
ROC(A) \gt ROC(B) \Leftrightarrow PR(A) \gt PR(B)
\end{aligned}

ここで、 ROC(A), PR(A) AからなるROC曲線、PR曲線のAUCとする。

反例

陽性2つ、陰性8つのデータを使用します。 確率の方は、numpy.random.rand()でseedを振って算出します。 実際は、適切な例を見つけるためにseedを振って探索してますが、コードは割愛します。

import random

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn.metrics import (average_precision_score, precision_recall_curve,
                             roc_auc_score, roc_curve)

# 10点のデータと2種類の確率の振り方A,Bを準備
np.random.seed(98)
probA = np.random.rand(10)
np.random.seed(63)
probB = np.random.rand(10)
df = pd.DataFrame({
    'class': [1]*2 + [0]*8,
    'probA': probA,
    'probB': probB
})

# A,Bに対して、ROC曲線、PR曲線とそのAUCを算出
rocA = roc_curve(df['class'], df['probA'])
rocB = roc_curve(df['class'], df['probB'])
prA = precision_recall_curve(df['class'], df['probA'])
prB = precision_recall_curve(df['class'], df['probB'])

roc_aucA = roc_auc_score(df['class'], df['probA'])
roc_aucB = roc_auc_score(df['class'], df['probB'])
pr_aucA = average_precision_score(df['class'], df['probA'])
pr_aucB = average_precision_score(df['class'], df['probB'])

# ROC曲線、PR曲線を可視化
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(10, 4))

ax1.plot(rocA[0], rocA[1], label='ROC A (AUC = %.2f)' % roc_aucA)
ax1.plot(rocB[0], rocB[1], label='ROC B (AUC = %.2f)' % roc_aucB)
ax1.legend()
ax1.set_title('ROC curve')
ax1.set_xlabel('FPR')
ax1.set_ylabel('TPR')
ax1.grid()

ax2.plot(prA[1], prA[0], label='PR A (AUC = %.2f)' % pr_aucA)
ax2.plot(prB[1], prB[0], label='PR B (AUC = %.2f)' % pr_aucB)
ax2.legend()
ax2.set_title('PR curve')
ax2.set_xlabel('Recall')
ax2.set_ylabel('Precision')
ax2.grid()

f:id:nareO7:20190901224003p:plain

結果は、 ROC(A) \gt ROC(B)かつPR(A) \lt PR(B)となっています。 つまり、ROC曲線のAUCが高くても、PR曲線のAUCが低い場合があります。

考察

 A Bの確率を表示させます。

display(df[['class', 'probA']].sort_values(
    'probA', ascending=False).reset_index(drop=True))
display(df[['class', 'probB']].sort_values(
    'probB', ascending=False).reset_index(drop=True))

f:id:nareO7:20190901225558p:plain

  • PR曲線について

 Aの場合、一番大きい確率ではずしているので、PR曲線が (0,0)まで移動します。 この段階で、 AのAUCは最大でも、 0.5と低い値です。 また、一番大きい確率で当てている Bは、 (0.5, 1)に移動します。 そのため、 BのAUCは最小でも 0.75と大きい値になります。

  • ROC曲線について

 Aの場合、最初こそ外してますが、 3番目までですべての陽性が出尽くしたので、AUCが大きい値になります。 一方、 Bは最初当ててますが、次の陽性は 6番目と遅く、 TPR=1となるのが遅れてAUCが少し低めに出ています。

おわりに

最初は、今回の問題が正しいと思って証明を考えてましたが、よくわからなかったのでコード書いたら、すんなりと反例が出てきました。 こうやって、調べたいことをぱっとコード書いて正しそうか確かめられるようになりたいなと思いました。

今回は、反例を構成しましたが、ある条件をつければ上記の問題が正しくなることが知られています。 暇があるときに、その証明をブログで紹介しようかなと思います。