Day to_day

[모델 평가] 정밀도-재현율 곡선과 f1 score, ROC, AUC 이해하기 본문

Machine Learning/머신러닝 기초

[모델 평가] 정밀도-재현율 곡선과 f1 score, ROC, AUC 이해하기

m_inglet 2023. 2. 13. 23:17
728x90
반응형

❗본 포스팅은 권철민 선생님의 '파이썬 머신러닝 완벽가이드' 강의와 '파이썬 라이브러리를 활용한 머신러닝' 서적을 기반으로 개인적인 정리 목적 하에 재구성하여 작성된 글입니다. 

 

포스팅 개요

이전 포스팅에서 분류 모델의 성능 평가 지표로 정확도, 정밀도, 재현율에 대해서 포스팅했었다.

하지만 정밀도와 재현율만 가지고 모델을 완벽히 평가할 순 없다. 정밀도와 재현율의 맹점에 대해서 알아보고, 정밀도와 재현율을 결합한 지표인 f1 score과 ROC 곡선, AUC에 대해서 포스팅하려 한다.

 

 

정밀도와 재현율의 맹점

정밀도와 재현율은 서로 상충하기 때문에 하나의 지표만 최적화시키면 다음과 같은 문제가 발생할 수 있다.

 

정밀도만을 100%로 최적화하는 법

정말 정확한 한 건의 문제만 예측한다. 그러면 정확하게 하나의 문제를 맞혀서 정밀도가 100%가 된다. 과녁에 정확히 하나의 화살이 가운데를 맞춘 것이라고 생각하면 된다.

 

재현율만을 100%로 최적화하는 법

모든 환자를 Positive로 예측한다. 그러면 과녁이 다른 한 곳을 모두가 가리킨다고 생각하면 된다. 그러면 재현율은 100%가 된다.

좋은 모델을 만들기 위해서는 각각의 지표를 적당히 최적화시키면서 조화를 찾는 게 중요한데 이 정밀도와 재현율의 조화 평균을 구한 것이 f1 score이다.

 

 

F1 score

F1 score는 정밀도와 재현율을 결합한 지표로 정밀도와 재현율의 조화 평균이다. 

식은 아래와 같다.

F1 score는 정밀도와 재현율이 어느 한쪽으로 치우치지 않는 수치를 나타낼 때 상대적으로 높은 값을 가지게 된다.

예를 들어,

A 모델 : 정밀도 0.9, 재현율 0.1 F1 score 0.18

B 모델 : 정밀도 0.5, 재현율 0.5 F1 score 0.5

F1 score를 사용해 평가를 하면 어떤 모델이 좋은지 직관적으로 판단이 가능하다.

 

 

불확실성 추정

분류기는 예측의 확신을 가늠하기 위해서 decision_function이나 predict_proba 메서드를 사용한다.

쉽게 말해서 양성일 확률과 음성일 확률을 각각 나타내는 것인데 아래와 같이 첫 번째 열은 0이 될 확률, 두 번째 열은 1이 될 확률을 나타낸다.

이진 탐색에서 decision_function은 0을, predict_proba는 0.5를 임계값으로 사용하며 특정 평가 지표(재현율, 정밀도)가 더 중요할 때 임계값을 조절하거나, 데이터가 심하게 불균형할 때 결정 함수의 임계값을 바꾸면 더 나은 결과를 얻을 수 있다. 

 

이때 그냥 임계값을 두지 말고, 확률 자체로 비교하면 되지 않나 생각하겠지만 모든 모델이 쓸모 있는 불확실성을 제공하는 것은 아니기 때문에 (DecisionTree에선 최대 깊이에서는 잘못된 것이라도 항상 100%를 확신한다) 임계값이 기준이 되어 분류한다.

 

 

정밀도 - 재현율 곡선

정밀도와 재현율을 x, y축에 두어 임계값에 따라 해당 모델의 재현율과 정밀도가 어떻게 변하는지를 그래프로 표현한 것이다.

일반적으로 곡선이 오른쪽 위로 갈수록 더 좋은 분류기라고 하며, 왼쪽에서 오른쪽으로 갈수록 임계값이 점점 올라간다. 

임계값이 커지면서 곡선은 정밀도가 높아지는 쪽으로 이동하지만 재현율은 낮아진다. 이때 정밀도가 높아져도 재현율이 높게 유지될수록 더 좋은 모델이라 한다.

추가로 더 살펴보기

1. F1 score는 정밀도-재현율 곡선의 한 지점인 '기본 임계값'에 대한 점수일 뿐이라서 전체 성능은 비교할 수 있지만 임계값의 조절에 따른 세세한 변화는 놓칠 수 있다.

2. 평균 정밀도(Average Precision) : 정밀도 - 재현율 곡선의 아랫부분 면적을 계산한 것으로 전체 곡선에 담긴 정보를 요약한 것이다.

 

 

[실습 코드]

PrecisionRecallDisplay 함수를 사용해 정밀도-재현율 곡선을 쉽게 그릴 수 있는 실습 코드이다.

from sklearn.metrics import PrecisionRecallDisplay
import matplotlib.pyplot as plt
from sklearn.metrics import average_precision_score

# SVC precision recall thresholds 
precision, recall, thresholds = precision_recall_curve(y_test, svc.decision_function(X_test))

# RandomForest precision recall thresholds
precision_rf, recall_rf, thresholds_rf = precision_recall_curve(y_test, rf.predict_proba(X_test)[:, 1])

# 정밀도-재현율 곡선의 아래부분 면적 : 평균 정밀도
ap_rf = average_precision_score(y_test, rf.predict_proba(X_test)[:,1])
ap_svc = average_precision_score(y_test, svc.decision_function(X_test))

# x, y축에 각각 precision, recall 매개변수에 재현율과 정밀도 전달하기
fig, ax = plt.subplots()

# precision과 recall을 반대로 넣은 이유는 x축을 precision y축은 recall로 두기 위해서
disp = PrecisionRecallDisplay(precision=recall, recall=precision, average_precision=ap_svc, estimator_name='SVC')
disp.plot(ax=ax)
disp = PrecisionRecallDisplay(precision=recall_rf, recall=precision_rf, average_precision=ap_rf, estimator_name='RandomForest')

disp.plot(ax=ax)
ax.set(xlabel="정밀도", ylabel="재현율")
plt.show()

 

[출력 결과]

 

 

ROC curve 

ROC 곡선은 여러 임계값에서 분류기의 특성을 분석하는 데 널리 사용하는 도구이다.

 

본격적으로 알아보기 앞서 하나 짚고 넘어가야 할 용어가 있다.

TPR (True Positive Rate) :

: 재현율 (Recall)을 나타냄. 따라서 TPR은 TP / (FN + TP)이다. 즉 전체 양성 샘플 중에서 양성을 잘 맞춘 비율이다.

 

FPR (False Positive Rate)

: 실제 음성(Negative)을 잘못 예측한 비율. FPR = FP / (FP + TN)이다. 즉 전체 음성 샘플 중에서 거짓 양성으로 잘못 분류한 비율이다.

 

 

ROC 커브는 왼쪽 위쪽으로 갈수록 더 좋은 모델을 의미한다. 거짓 양성 비율(FPR)이 낮게 유지되면서 진짜 양성 비율(TPR)이 높은 분류기가 좋다는 것이다. TPR과 FPR이 같은 비율로 떨어지는 경우 랜덤으로 예측한 것과 같은 모델이라는 것이다. (망한 모델이라는 거)

 

 

[실습 코드]

precision_recall_curve를 이용해서 모델에 따른 정밀도, 재현율, 임계값을 반환 (모델 1 : SVC, 모델 2: RandomForest)

이때 precision_recall_curve에 요소로 decision_function 또는 (RandomForest 경우에) predict_proba를 이용해서 불확실성을 반환하여 넣어준다.

from sklearn.datasets import make_blobs
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import precision_recall_curve
import warnings 
warnings.filterwarnings('ignore') # warning 메세지 제거

# 임의의 데이터 생성
X, y = make_blobs(n_samples=(4000, 500), cluster_std=[7.0, 2], random_state=22)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

# SVC
svc = SVC(gamma=.05).fit(X_train, y_train)
precision, recall, thresholds = precision_recall_curve(y_test, svc.decision_function(X_test))

# RandomForestClassifier
rf = RandomForestClassifier(n_estimators=100, random_state=0, max_features=2)
rf.fit(X_train, y_train)
# RandomForest에 decision_function없음
precision_rf, recall_rf, thresholds_rf = precision_recall_curve(y_test, rf.predict_proba(X_test)[:,1])

 

[실습 코드 2] 

roc_curve를 이용해서 FPR, TPR, 임계값을 반환하여 matplotlib로 ROC 곡선 그리기

임계값이 0인 지점을 원으로 표시

from sklearn.metrics import roc_curve
import matplotlib.pyplot as plt
import numpy as np
fpr, tpr, threshold = roc_curve(y_test, svc.decision_function(X_test))

plt.plot(fpr, tpr, label="ROC 곡선")
plt.xlabel("FPR")
plt.ylabel("TPR(재현율)")

# 0 근처의 임계값을 찾음
close_zero = np.argmin(np.abs(threshold))
plt.plot(fpr[close_zero], tpr[close_zero], 'o', markersize=10, label="임계값 0", fillstyle='none', c='k', mew=2)
plt.legend(loc=4)
plt.show()

 

[출력 결과]

 

[결론]

결과를 보면 임계값이 0인 부분에서 FPR을 더 높이면 재현율이 더욱 올라갈 수 있다. 결국 왼쪽 위에 가장 가까운 지점이 기본 임계값으로 찾는 것보다 더 좋은 포인트가 된다는 것을 알 수 있다.

 

 

AUC (Area Under the Curve)

정밀도-재현율 곡선을 하나로 요약하기 위해 정밀도-재현율 곡선의 아래 면적인 평균 정밀도를 사용했듯이 AUC는 ROC 곡선의 아래 면적을 구해서 성능을 요약한다.

AUC는 불균형한 데이터셋에서는 정확도보다 훨씬 좋은 지표이며 AUC가 1에 가까울수록 좋은 모델이라고 할 수 있다.

아까 FPR과 TPR이 같은 비율로 감소하면 랜덤으로 분류한 것과 다름없다고 했는데 그 경우엔 AUC값이 0.5가 나오며 분류 능력이 전혀 없다고 판단할 수 있다.

만약 AUC가 0이라고 하면 그 경우는 전혀 반대로 예측하고 있는 것이다. 

 

[실습 코드]

from sklearn.datasets import load_digits

digits = load_digits()
y = (digits.target == 9)

X_train, X_test, y_train, y_test = train_test_split(digits.data, y, random_state=0)

plt.figure()

for gamma in [1, 0.1, 0.01]:
    svc = SVC(gamma=gamma).fit(X_train, y_train)
    accuracy = svc.score(X_test, y_test)
    auc = roc_auc_score(y_test, svc.decision_function(X_test))
    fpr, tpr, _ = roc_curve(y_test, svc.decision_function(X_test))
    print("gamma : {:.2f} 정확도 : {:.2f} AUC : {:.2f}".format(gamma, accuracy, auc))
    plt.plot(fpr, tpr, label="gamma={:.2f}".format(gamma))

plt.xlabel("FPR")
plt.ylabel("TPR")
plt.xlim(-0.01, 1)
plt.ylim(0, 1.02)
plt.legend(loc="best")
plt.show()

 

[출력 결과]

gamma : 1.00 정확도 : 0.90 AUC : 0.50
gamma : 0.10 정확도 : 0.90 AUC : 0.96
gamma : 0.01 정확도 : 0.90 AUC : 1.00

 

728x90
반응형
Comments