Machine Learning

ROC Curve

devson 2024. 5. 23. 23:38

ROC Curve

ROC(Receiver Operating Characteristic) Curvethreshold에 대한 이진 분류기 모델의 성능을 표현한 것이다.

ROC Curve 그래프를 보면 이는 threshold에 따른 false positive ratetrue positive rate에 대한 그래프이다.

  • threshold: 이진 분류에 대한 임계값으로 이 값이 커질 수록 true라고 판단하는 비율이 줄어든다.
    • e.g.) threshold가 0.3이면 true일 예측 확률이 0.3 이상이면 true라고 판단한다. (일반적으로는 0.5)
  • false positive rate: $\frac{\text{FP}}{\text{FP} + \text{TN}}$
    • 실제 negative 중에서 positive로 잘못 판단한 비율
  • true positive rate: $\frac{\text{TP}}{\text{TP} + \text{FN}}$
    • 실제 positive 중에서 positive로 옳게 판단한 비율
예측  \ 실제 True False
True  TP, True Positive FP, False Posotive
False FN, False Negative TN, True Negative

 

 

ROC Curve 그래프를 이진 분류기 모델의 각 클래스에 대한 분류 확률 밀도 그래프와 함께 보면 아래와 같다.

이때 좌측의 그래프에 있는 세로 점선은 threshold로,

threshold가 좌우로 움직이는 것은 우측 ROC Curve 그래프에서의 위치가 반대로 움직이는 것과 같다.

 

예를 들어 threshold가 낮을 수록 쉽게 positive라고 판단을 하기 때문에

  • 실제 negative 중에서 positive로 잘못 판단할 확률(false positive rate)이 높아지고
  • 실제 positive 중에서 positive로 옳게 판단할 확률(true positive rate)도 높아진다.

여기서 false positive rate와 true positive rate는 threshold에 따른 일종의 비례 관계를 갖는 것을 확인할 수 있다.

(false positive rate와 true positive rate가 threshold와 비례라는 얘기는 아니다)

 

분류기 모델 성능에 따른 ROC Curve 변화

모델 성능이 좋아졌을 때

클래스를 더 정확하게 분류할 수 있게 되니 true positive rate가 높아지게 되고 반대로 false posotive rate는 낮아지게 될 것이다.

그렇기 때문에 ROC Curve 그래프는 아래와 같이 좌상단에 더 가깝게 휘어진다.

 

모델 성능이 안좋아졌을 때

클래스를 잘못 분류하는 경우가 많아져 true positive rate가 낮아지게 되고 반대로 false posotive rate는 높아지게 될 것이다.

그렇기 때문에 ROC Curve 그래프는 아래와 같이 우하단에 더 가깝게 휘어진다.

 

이렇듯 ROC Curve 그래프를 보고 성능을 직관적으로 확인할 수 있게되며, 그래프가 좌상단에 가깝게 휘어 있으면 성능이 좋다는 것을 의미한다.

 

또한 성능이 좋아질 수록 ROC Curve 하단의 면적이 넓어지게 되는데, 이 면적은 이진 분류기 모델의 성능을 측정하는 또 하나의 지표가 된다.

이 면적을 AUC(Area Under the Curve)라고 한다.

(Area Under the Curve는 범용적으로 쓰이는 용어로, 꼭 ROC Curve에 대해서만 사용하는 용어가 아니다)

 

(관련된 데모 예제는 이 블로그 글의 상단 그래프를 참고하길 바란다 - 손으로 움직이면서 이해하는 것을 추천한다)

 

코드 예제

간단한 이진 분류 예제를 통해 scikit-learn으로 ROC Curve와 AUC를 구하는 방법에 대해 알아보도록 하겠다.

 

  • 이진 분류 데이터셋으로 scikit-learn에서 제공하는 유방암 종양 이진 분류 데이터셋을 사용한다.
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

RANDOM_SEED = 42

breast_cancer_data = load_breast_cancer()
X = breast_cancer_data.data
y = breast_cancer_data.target

X_train, X_test, y_train, y_test = train_test_split(X, y, 
                                                    test_size=0.3, stratify=y,
                                                    random_state=RANDOM_SEED)

X_train.shape, X_test.shape, y_train.shape, y_test.shape

# ((398, 30), (171, 30), (398,), (171,))

 

  • 이진 분류기 모델로 LogisticRegression을 사용하여 모델을 훈련시킨다.
    • 이 모델의 test accuracy는 약 0.9415가 나왔다.
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# train
model = LogisticRegression(random_state=RANDOM_SEED)
model.fit(X_train, y_train)

# evaluate
y_pred = model.predict(X_test)
accuracy_score(y_test, y_pred)

# 0.9415204678362573

 

  • sklearn.metrics.roc_curve(y_true, y_pred)를 사용하여 ROC Curve 데이터를 가져온다.
    • 리턴되는 값은 threshold에 대한 false positive rate, true positive rate이다.
from sklearn.metrics import roc_curve

positive_prediction_probabilities = model.predict_proba(X_test)[:, 1]
false_positive_rates, true_positive_rates, thresholds = \
    roc_curve(y_test, positive_prediction_probabilities)

for fpr, tpr, th in zip(false_positive_rates, true_positive_rates, thresholds):
    print(f"Threshold: {th} - FPR: {fpr}, TPR: {tpr}")
    
# Threshold: inf - FPR: 0.0, TPR: 0.0
# Threshold: 0.9999550596085504 - FPR: 0.0, TPR: 0.009345794392523364
# Threshold: 0.9848986625939533 - FPR: 0.0, TPR: 0.6261682242990654
# Threshold: 0.9834229169982125 - FPR: 0.015625, TPR: 0.6261682242990654
# Threshold: 0.9391698962484204 - FPR: 0.015625, TPR: 0.8130841121495327
# Threshold: 0.9347940251848812 - FPR: 0.03125, TPR: 0.8130841121495327
# Threshold: 0.8625769438111659 - FPR: 0.03125, TPR: 0.9065420560747663
# Threshold: 0.8475391852064701 - FPR: 0.0625, TPR: 0.9065420560747663
# Threshold: 0.7023202565248234 - FPR: 0.0625, TPR: 0.9719626168224299
# Threshold: 0.6904250200409706 - FPR: 0.078125, TPR: 0.9719626168224299
# Threshold: 0.6791986522466532 - FPR: 0.078125, TPR: 0.9813084112149533
# Threshold: 0.5887847716439181 - FPR: 0.125, TPR: 0.9813084112149533
# Threshold: 0.35883415654168577 - FPR: 0.125, TPR: 1.0
# Threshold: 1.4799762153953177e-45 - FPR: 1.0, TPR: 1.0

 

  • ROC curve plot
import matplotlib.pyplot as plt

plt.plot(false_positive_rates, true_positive_rates)
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")

 

 

  • AUC 구하기
from sklearn.metrics import roc_auc_score

roc_auc = roc_auc_score(y_test, positive_prediction_probabilities)
roc_auc

 

 

  • 혹은 ROC Curve plot 시 AUC를 같이 plot 할 수도 있다.
from sklearn.metrics import roc_auc_score
import matplotlib.pyplot as plt

roc_auc = roc_auc_score(y_test, positive_prediction_probabilities)

plt.plot(false_positive_rates, true_positive_rates)
plt.title(f"AUC: {roc_auc:.4f}")
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")

 

최적의 threshold (optimal cut-off point) 찾기

이진 분류기 모델의 분류 임계점인 thresold는 기본적으로 0.5로 사용되는데,
ROC Curve를 통해서 최적의 분류 성능을 갖는 threshold(optimal cut-off point)를 구할 수 있다.

 

그 방법으로는 여러가지가 있는데, (참고)
그 중 자주 사용되는 방법인 Youden's Index를 통해 최적의 threshold를 찾는 방법을 알아보겠다.

 

Youden's Index(J)는 아래 그래프에서의 J 이고, true positive rate false positive rate의 차를 통해 구할 수 있다.
각 threshold 별 J 를 구한 뒤, 가장 큰 J 를 갖는 threshold가 최적의 threshold로 볼 수 있다.

코드 예제

  • threshold 별 Youden's Index 하기
for fpr, tpr, th in zip(false_positive_rates, true_positive_rates, thresholds):
    j = tpr - fpr
    print(f"Threshold: {th} - youden index {j}")
    
# Threshold: inf - youden's index 0.0
# Threshold: 0.9999550596085504 - youden's index 0.009345794392523364
# Threshold: 0.9848986625939533 - youden's index 0.6261682242990654
# Threshold: 0.9834229169982125 - youden's index 0.6105432242990654
# Threshold: 0.9391698962484204 - youden's index 0.7974591121495327
# Threshold: 0.9347940251848812 - youden's index 0.7818341121495327
# Threshold: 0.8625769438111659 - youden's index 0.8752920560747663
# Threshold: 0.8475391852064701 - youden's index 0.8440420560747663
# Threshold: 0.7023202565248234 - youden's index 0.9094626168224299
# Threshold: 0.6904250200409706 - youden's index 0.8938376168224299
# Threshold: 0.6791986522466532 - youden's index 0.9031834112149533
# Threshold: 0.5887847716439181 - youden's index 0.8563084112149533
# Threshold: 0.35883415654168577 - youden's index 0.875
# Threshold: 1.4799762153953177e-45 - youden's index 0.0

 

  • Youden's Index를 통해 최적의 threshold를 구하기
import numpy as np

optimal_threshold_index = np.argmax(true_positive_rates - false_positive_rates)
fpr, tpr, optimal_threshold = (
	false_positive_rates[optimal_threshold_index], 
    true_positive_rates[optimal_threshold_index], 
    thresholds[optimal_threshold_index])
j = tpr - fpr
print(f"{optimal_threshold=}, {j=}, {fpr=}, {tpr=}")

# optimal_threshold=0.7023202565248234, j=0.9094626168224299, fpr=0.0625, tpr=0.9719626168224299

 

  • optimal threshold plot
from sklearn.metrics import roc_auc_score
import matplotlib.pyplot as plt

roc_auc = roc_auc_score(y_test, positive_prediction_probabilities)

plt.plot(false_positive_rates, true_positive_rates)
# text 추가
plt.plot(fpr, tpr, "r*")
plt.text(fpr+0.02, tpr-0.07, f"Optimal Threshold: {optimal_threshold:.4f}", fontsize=12, color="red")

plt.title(f"AUC: {roc_auc:.4f}")
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")

 


참고