본문 바로가기
머신러닝

머신러닝 - ROC Curve에 대한 확장 : 임계값

by Tiabet 2023. 12. 13.

캐글에서 진행하는 LLM 관련 대회 중, 아래 대회에 참가하려고 마음먹었다.

 

https://www.kaggle.com/competitions/llm-detect-ai-generated-text/overview

 

LLM - Detect AI Generated Text | Kaggle

 

www.kaggle.com

 

그래서 평가 지표를 살펴보던 중, 특이하게 AUC를 평가 지표로 삼고 있는 것을 보았다. 나는 자연스럽게 그러면 test data에 대해 사람이 만들었는지, 기계가 만들었는지를 0과 1로 분류하면 되겠구나, 싶었는데 알고 보니 test data에 들어있는 텍스트 데이터가 사람이 썼을 확률을 구해서 제출하는 것이었다.

 

하지만 내 개념으론 이해가 가지 않았다. 내가 알기로 0 또는 1로 분류되어야 AUC를 구할 수 있기 때문이었다.

https://tiabet0929.tistory.com/41

 

분류 평가 지표 정리 - F1 score, ROC Curve 등

예전에 분류 모델의 평가 지표를 살펴보면서 Precision, Recall, Accuracy 등에 대해 정리했었다. 간단히 짚고 넘어가자면, Precision : Positive로 예측한 것 중 실제 Positive의 비율 (예측이 얼마나 정밀한지

tiabet0929.tistory.com

 

이 글을 쓰면서 ROC Curve와 AUC에 대해 정리하면서 Precision과 Recall의 증가와 감소에 대해 다루었으면서도 "어떻게" Precision과 Recall의 값이 변하는지를 깊게 생각하지 않고 넘어갔기 때문이다.

 

그러다가 "임계값"이라는 개념이 ROC Curve와 AUC에 꼭 필요한 개념이라는 것을 뒤늦게 배웠다.

 

한 분류 문제에 대해 어떤 모델을 돌려서 결과를 긍정/부정으로 산출할 때, 모델은 그 전에 긍정일 확률과 부정일 확률을 계산하게 된다. 부정일 확률을 0.4, 긍정일 확률을 0.6이라 하면 모델은 '긍정'이라고 결과를 분류한다. 이 결과들이 모여서 Confusion Matrix를 산출하고, Precision, Recall, Accuraacy 등의 평가 지표들을 계산할 수 있는 것이다.

 

하지만 이렇게 되면 Precision, Recall 의 값들은 고정된 값이기 때문에 변하지도 않고, 단순히 이것만으로 ROC Curve를 어떻게 그리는지를 생각하지 못한 것이다.

 

이 ROC Curve를 그리려면 긍정과 부정을 구분하는 임계값을 변화시켜줘야 한다.

 

위에서 부정일 확률이 0.4, 긍정일 확률이 0.6으로 두었을 때 자연스레 긍정이라고 분류하는 것은, 모델도 그렇고 우리도 그렇고 0.5를 기준으로 긍정일 확률이 크면 긍정으로 분류하는 것이 일반적이며 훨씬 자연스럽기 때문이다. 여기서 0.5가 일반적인 임계값이다. 이 임계값을 조절하면, Trade-off 관계에 있는 Precision과 Recall 값을 포함해 ROC Curve의 X축에 들어가는 1 - 특이도에서 특이도도 변화하게 될 것이다. 임계값을 0.7로 상향해버리면 긍정일 확률이 0.6이어도 부정으로 분류되는 것이다. 

 

그렇다면 임계값이 0일 때와 1일 때를 생각해봐야 한다. 임계값이 0이라면 모든 값을 다 긍정으로 분류한다. 그러면 애초에 부정으로 분류하는 리뷰가 없어져버리기 때문에, FPR = FP / (FP + TN) 에서 TN이 0이 되어버리고, FPR = 1 이 된다. TPR 또한 TP / (FN + TP) 에서 FN이 없어져버려서 1이 된다. 즉, ROC Curve의 오른쪽 상단 꼭짓점이 되는 것이다. 반면 임계값이 1이라면 TPR과 FPR이 모두 0이 되기 때문에 ROC Curve의 왼쪽 하단 꼭짓점이 완성된다. 이렇게 임계값을 1에서 0까지 변화시켜보면, 임계값마다 FPR과 TPR이 생성되고, 그에 따라 ROC Curve가 완성되는 것이다!

 

그림이 없이 말로만 설명해서 읽는 사람들이 힘들 수도 있어 코드로 간략한 설명을 대체하도록 하겠다.

from sklearn.metrics import roc_curve, roc_auc_score
import matplotlib.pyplot as plt

# Calculate ROC curve
fpr, tpr, thresholds = roc_curve(y_true, y_pred)

# Calculate ROC-AUC score
roc_auc = roc_auc_score(y_true, y_pred)

# Plot the ROC curve
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.show()

 

이건 이전 ROC Curve를 다루는 포스팅에서 사용했던 예시이다.

그 때 그래프가 직선으로 나온 걸 보고 뭔가 이상하다, 싶었는데 역시 문제가 있었다. 내가 roc_curve 에다가 probability가 아닌 값이 0과 1인 예측값 그 자체를 넣었기 때문에, 임계값들을 변환시킬 수가 없던 것이다. 실제로 fpr, tpr, thresholds의 값을 확인해보니 값이 2개밖에 들어있지 않았고, 그러다보니 직선 하나만 그려졌던 것이었다.

 

# Calculate predicted probabilities for each model
y_pred_proba_lgbm = lgb_model_tuned.predict_proba(X_test)[:, 1]
y_pred_proba_xgb = xgb_model_tuned.predict_proba(X_test)[:, 1]
y_pred_proba_cat = cat_model_tuned.predict_proba(X_test)[:, 1]

# Calculate false positive rate, true positive rate, and threshold values for ROC curve
fpr_lgbm, tpr_lgbm, thresholds_lgbm = roc_curve(y_test, y_pred_proba_lgbm)
fpr_xgb, tpr_xgb, thresholds_xgb = roc_curve(y_test, y_pred_proba_xgb)
fpr_cat, tpr_cat, thresholds_cat = roc_curve(y_test, y_pred_proba_cat)

# Calculate AUC scores for each model
auc_lgbm = roc_auc_score(y_test, y_pred_proba_lgbm)
auc_xgb = roc_auc_score(y_test, y_pred_proba_xgb)
auc_cat = roc_auc_score(y_test, y_pred_proba_cat)

# Plot ROC curves
plt.figure(figsize=(8, 6))
plt.plot(fpr_lgbm, tpr_lgbm, label='LGBM (AUC = {:.2f})'.format(auc_lgbm))
plt.plot(fpr_xgb, tpr_xgb, label='XGB (AUC = {:.2f})'.format(auc_xgb))
plt.plot(fpr_cat, tpr_cat, label='CatBoost (AUC = {:.2f})'.format(auc_cat))
plt.plot([0, 1], [0, 1], 'k--')  # Diagonal line
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic (ROC) Curve')
plt.legend(loc='lower right')
plt.show()

반면 예전에 진행했던 프로젝트에서 lgbm, xgb, catboost의 성능을 비교할 때 작성했던 코드다. roc_curve에 predict_proba의 결과 중 긍정으로 예측할 확률을 집어넣어서 임계값들이 원활하게 형성되게 만들어준 모습이다. ChatGPT로만 코드를 짜다 보니 이런 디테일을 놓치고 그래프가 그려지는 것만 보고 넘어갔던 착오이다. 

 

아무튼 이렇게 모르는 내용 하나를 채울 수 있었다. 예전에 수업시간에 배우긴 했을 텐데 어느 샌가 까먹고 그냥저냥 살고 있었다는 것에 자책감이 들었다.