🌴

【評価指標】ROCとAUCを理解する

2024/04/09に公開

1. ROC/AUCとは

・ROC曲線(Receiver Operating Characteristic curve)
2クラス分類モデルの性能を評価するために使用される曲線です。

AUC (Area Under the Curve)
評価指標そのものです。ROC曲線の右下の面積を表します。
大きい方が性能が高く、最大値が1で最小値が0.5です。

・例

2. 計算方法

真陽性率(TPR)と偽陽性率(FPR)を、それぞれグラフの縦軸と横軸としてプロットします。

  • 真陽性率(TPR)
    TPR = \dfrac{TP}{TP + FN}
    正と予測した中で、実際に正であった割合を表します。

TPR要約: 答えが正の問題の正答率。高い方が良い

  • 偽陽性率(FPR)
    FPR = \dfrac{FP}{FP + TN}
    負と予測した中で、実際には正であった割合を表します。

FPR要約: 答えが負の問題の誤答率。低い方が良い

3. ROC曲線

ROC曲線の描き方を説明します。
少し複雑ですが大切な部分です。

初めに、2値分類にはしきい値が存在します。
例えば しきい値=0.5 の場合、予測値で0.5以上のものが1、0.5より小さいものが0に分類されます。
このしきい値を1~0で変更し、それに対するTPRを縦軸に、FPRを横軸にをプロットしたものがROC曲線です。
※しきい値は、曲線が右に向かうにつれて減少していきます。

要約: ROCは、分類判断のしきい値を変えたときのTPRとFPRをプロットしたもの

イメージ: 正ラベル問題の予測が正確なら曲線は上に移動し、負ラベル問題の予測が正確なら曲線は左に移動します。

4. 例

ROC曲線の例を示します。
TPRとFPRを計算する必要があるため、ある程度の予測数が必要です。

4.1 ランダム予測のROC

コード
import matplotlib.pyplot as plt
from sklearn.metrics import roc_curve, auc
import numpy as np

# 例として、ランダムな予測スコアと実際のラベルを生成
np.random.seed(42)
y_true = np.random.randint(0, 2, size=100)  # 実際のラベル: 0 or 1 
y_preds = np.random.rand(100)               # 予測スコア: 0~1の正規分布に従う乱数 

# ROC曲線の計算
fpr, tpr, thresholds = roc_curve(y_true, y_preds)
roc_auc = auc(fpr, tpr)

# ROC曲線のプロット
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (area = {roc_auc:.2f})')
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend(loc="lower right")
plt.savefig('roc_example.png')
plt.show()
正解ラベルと予測値
# 正解ラベル
y_true:
[0 1 0 0 0 1 0 0 0 1 0 0 0 0 1 0 1 1 1 0 1 0 1 1 1 1 1 1 1 1 0 0 1 1 1 0 1
 0 0 0 0 0 1 1 1 1 1 0 1 1 0 1 0 1 0 1 1 0 0 0 0 0 0 0 0 1 1 0 1 1 1 1 0 1
 0 1 1 1 0 1 0 1 0 1 0 0 1 0 1 1 1 1 1 1 1 1 1 1 1 0]
# 予測値
y_preds:
[0.96958463 0.77513282 0.93949894 0.89482735 0.59789998 0.92187424
 0.0884925  0.19598286 0.04522729 0.32533033 0.38867729 0.27134903
 0.82873751 0.35675333 0.28093451 0.54269608 0.14092422 0.80219698
 0.07455064 0.98688694 0.77224477 0.19871568 0.00552212 0.81546143
 0.70685734 0.72900717 0.77127035 0.07404465 0.35846573 0.11586906
 0.86310343 0.62329813 0.33089802 0.06355835 0.31098232 0.32518332
 0.72960618 0.63755747 0.88721274 0.47221493 0.11959425 0.71324479
 0.76078505 0.5612772  0.77096718 0.4937956  0.52273283 0.42754102
 0.02541913 0.10789143 0.03142919 0.63641041 0.31435598 0.50857069
 0.90756647 0.24929223 0.41038292 0.75555114 0.22879817 0.07697991
 0.28975145 0.16122129 0.92969765 0.80812038 0.63340376 0.87146059
 0.80367208 0.18657006 0.892559   0.53934224 0.80744016 0.8960913
 0.31800347 0.11005192 0.22793516 0.42710779 0.81801477 0.86073058
 0.00695213 0.5107473  0.417411   0.22210781 0.11986537 0.33761517
 0.9429097  0.32320293 0.51879062 0.70301896 0.3636296  0.97178208
 0.96244729 0.2517823  0.49724851 0.30087831 0.28484049 0.03688695
 0.60956433 0.50267902 0.05147875 0.27864646]

上図は、ランダムな予測におけるROC曲線です。
「正ラベル問題の正答率」と「負ラベル問題の誤答率」が、しきい値の低下に伴って同じ割合で向上しています。
これはつまり、正ラベルの問題も負ラベルの問題も、半分程間違えていることを意味します。

このように予測がランダムの場合、AUCは0.5程度になります。

4.2 理想のROC

コード
import matplotlib.pyplot as plt
from sklearn.metrics import roc_curve, auc
import numpy as np

# 例として、ランダムな予測スコアと実際のラベルを生成
np.random.seed(42)
y_true = np.random.randint(0, 2, size=100)  # 実際のラベル: 0 or 1 
y_preds = y_true                            # 予測スコア: ラベルと同値

# ROC曲線の計算
fpr, tpr, thresholds = roc_curve(y_true, y_preds)
roc_auc = auc(fpr, tpr)

# ROC曲線のプロット
plt.figure(figsize=(8, 6))
plt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (area = {roc_auc:.2f})')
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve')
plt.legend(loc="lower right")
plt.savefig('roc_ideal.png')
plt.show()
正解ラベルと予測値
# 正解ラベル
[0 1 0 0 0 1 0 0 0 1 0 0 0 0 1 0 1 1 1 0 1 0 1 1 1 1 1 1 1 1 0 0 1 1 1 0 1
 0 0 0 0 0 1 1 1 1 1 0 1 1 0 1 0 1 0 1 1 0 0 0 0 0 0 0 0 1 1 0 1 1 1 1 0 1
 0 1 1 1 0 1 0 1 0 1 0 0 1 0 1 1 1 1 1 1 1 1 1 1 1 0]
# 予測値
[0 1 0 0 0 1 0 0 0 1 0 0 0 0 1 0 1 1 1 0 1 0 1 1 1 1 1 1 1 1 0 0 1 1 1 0 1
 0 0 0 0 0 1 1 1 1 1 0 1 1 0 1 0 1 0 1 1 0 0 0 0 0 0 0 0 1 1 0 1 1 1 1 0 1
 0 1 1 1 0 1 0 1 0 1 0 0 1 0 1 1 1 1 1 1 1 1 1 1 1 0]

上図は、予測値が全てラベルと等しい時のROC曲線です。
「正ラベル問題の正答率」が100%、
「負ラベル問題の誤答率」が0%
なので、グラフはに完全に振り切っています。

この時AUCは1となります。

5. 多クラス分類の評価

最初にROC-AUCは2クラス分類用の指標であると説明しました。
ここではROC-AUCを多クラス分類に適用する、マクロ平均ROC-AUCを説明します。

・マクロ平均ROC-AUC
それぞれのクラスにおける予測確率に対してROC-AUCを計算し、その平均値を指標とする

手法は非常にシンプルで、2値分類のROC-AUCを各クラスに対して行なった結果を平均するだけです。

6. まとめ

ROC曲線は、正ラベル問題負ラベル問題両方の精度を評価する指標です。
そのため予測の精度として包括的に使用でき、視覚的にも分かりやすいため多くの場面で使用されています。

今回は以上です。


付録: しきい値の生成方法

sklearn.metricsによるROCのしきい値は、自動的に生成されます。

・しきい値の自動生成プロセス

  1. 予測値(y_preds)をsortし、一意の値を取得します。これがしきい値の候補になります。
  2. しきい値の候補に、y_predsの最大値よりも大きい値(例えば、最大値+1)を追加します。これは、全てのサンプルが負と予測される場合に対応するためです。
    ※ y_predの最小値よりも小さい値は追加されません。これは、しきい値と予測値が同じ場合は正に分類されるため、全てのサンプルが正と予測される場合は包括できているためです。

上記ステップでしきい値が設定されます。この手法ではy_predの一意の値のみがしきい値の候補となるため、場合によっては明示的にしきい値を設定する必要があります。

しきい値確認用コード
import numpy as np
from sklearn.metrics import roc_curve, auc
import matplotlib.pyplot as plt

num_elements = 10  # 各クラスに対する要素数
pred_values = np.linspace(0.7, 0.8, num_elements)  # 0.7から0.8まで等間隔の値


# 各行の予測確率の合計が1になるように、残りの要素に値を割り振る
# 予測確率の値が0.7から0.8の間で、残りの要素には(1 - 予測確率) / 2 を割り振る
y_score = np.array([[(x if i == j else (1 - x) / 2) for j in range(3)]
                             for i in range(3) for x in pred_values])

print(y_score)

# 正解ラベル(猫が正解で、最後にゴリラを正解とするケースを1つ加える)
# 正しい正解ラベルの生成
y_true = np.vstack([np.array([1, 0, 0] * (num_elements - 2)).reshape(-1, 3),
                    np.array([0, 1, 0] * (num_elements + 3)).reshape(-1, 3),
                    np.array([0, 0, 1] * (num_elements - 1)).reshape(-1, 3)])


# クラス名
classes = ['Dog', 'Cat', 'Gorilla']

# 各クラスに対するROC-AUCを計算し、マクロ平均を求める
roc_aucs = []
for i in range(len(classes)):
    fpr, tpr, threshold = roc_curve(y_true[:, i], y_score[:, i])
    print(threshold)
    roc_auc = auc(fpr, tpr)
    roc_aucs.append(roc_auc)
    print(f'ROC-AUC for {classes[i]}: {roc_auc:.2f}')

    # ROC曲線をプロット
    plt.plot(fpr, tpr, label=f'{classes[i]} (AUC = {roc_auc:.2f})')

# マクロ平均ROC-AUC
macro_auc = np.mean(roc_aucs)
print(f'Macro Average ROC-AUC: {macro_auc:.2f}')

plt.plot([0, 1], [0, 1], 'k--')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve for each class')
plt.legend()
plt.show()

・出力

[[0.7        0.15       0.15      ]
 [0.71111111 0.14444444 0.14444444]
 [0.72222222 0.13888889 0.13888889]
 [0.73333333 0.13333333 0.13333333]
 [0.74444444 0.12777778 0.12777778]
 [0.75555556 0.12222222 0.12222222]
 [0.76666667 0.11666667 0.11666667]
 [0.77777778 0.11111111 0.11111111]
 [0.78888889 0.10555556 0.10555556]
 [0.8        0.1        0.1       ]
 [0.15       0.7        0.15      ]
 [0.14444444 0.71111111 0.14444444]
 [0.13888889 0.72222222 0.13888889]
 [0.13333333 0.73333333 0.13333333]
 [0.12777778 0.74444444 0.12777778]
 [0.12222222 0.75555556 0.12222222]
 [0.11666667 0.76666667 0.11666667]
 [0.11111111 0.77777778 0.11111111]
 [0.10555556 0.78888889 0.10555556]
 [0.1        0.8        0.1       ]
 [0.15       0.15       0.7       ]
 [0.14444444 0.14444444 0.71111111]
 [0.13888889 0.13888889 0.72222222]
 [0.13333333 0.13333333 0.73333333]
 [0.12777778 0.12777778 0.74444444]
 [0.12222222 0.12222222 0.75555556]
 [0.11666667 0.11666667 0.76666667]
 [0.11111111 0.11111111 0.77777778]
 [0.10555556 0.10555556 0.78888889]
 [0.1        0.1        0.8       ]]
[1.8        0.8        0.78888889 0.7        0.1       ]
ROC-AUC for Dog: 0.91
[1.8        0.8        0.7        0.15       0.11111111 0.1       ]
ROC-AUC for Cat: 0.85
[1.8        0.8        0.71111111 0.7        0.1       ]
ROC-AUC for Gorilla: 1.00
Macro Average ROC-AUC: 0.92

Discussion