[ML] 평가 - F1 Score와 ROC-AUC

강주형·2022년 7월 6일
0
post-custom-banner

F1 Score와 재현율/정밀도 관계

F1 Score는 재현율과 정밀도에 밀접한 관련이 있음
관련 내용을 먼저 살펴보자


정밀도와 재현율의 맹점

Precision=TPFP+TPPrecision = \frac{TP}{FP+TP}
Recall=TPFN+TPRecall = \frac{TP}{FN+TP}

정밀도를 100%로 만드는 법
확실한 기준이 되는 경우만 Positive로 예측하고 나머지는 모두 Negative로 예측하기
1000명 중 확실한 Positive 징후만 가진 환자는 단 1명이라고 하면
이 한 명만 Positive로 예측하고, 나머지는 모두 Negative로 예측하더라도
FP = 0, TP = 1 되므로, 정밀도는 1 / (1+0) 으로 100%가 됨

재현율을 100%로 만드는 법
모든 환자를 Positive로 예측하면 됨
전체 환자 1000명을 다 Positive로 예측했을 때 이 중 실제 양성인 사람이 30명이라도
TN이 수치에 포함되지 않고 FN은 아예 0이므로 30/(30+0)으로 100%가 됨

이렇게 정밀도와 재현율 역시 완벽한 평가지표가 아님!


F1 Score

어느 한쪽으로 치우치지 않고, 정밀도와 재현율을 결합한 지표

정밀도와 재현율의 조화평균

F1Score=21Recall+1Precision=2PrecisionRecallPrecision+RecallF1\,\,Score = \frac{2}{\frac{1}{Recall} + \frac{1}{Precision} } = \frac{2 *Precision * Recall}{Precision + Recall}

앞에 식은 복잡하니까 뒤에꺼만 잘 알아두자 2PR / (P+R)

scikit-learn에서 F1 Score를 구해보자
코드는 앞에 하던 거에서 이어서 한다.
pred가 타이타닉 데이터 LogisitcRegression으로 fit() 후 predict() 한 것임

from sklearn.metrics import f1_score 
f1 = f1_score(y_test , pred)
print('F1 스코어: {0:.4f}'.format(f1))
F1 스코어: 0.7805

이렇게 간단하게 F1 Score를 구해줄 수 있음

앞에서 만들었던 평가지표들을 출력하는 get_clf_eval() 에 F1 Score를 추가해서 돌려보자
get_eval_by_threshold() 함수는 임곗값에 따라 반복해서 평가지표를 업데이트하는 함수로, 이전 글에 코드 있음

def get_clf_eval(y_test , pred):
    confusion = confusion_matrix( y_test, pred)
    accuracy = accuracy_score(y_test , pred)
    precision = precision_score(y_test , pred)
    recall = recall_score(y_test , pred)
    # F1 스코어 추가
    f1 = f1_score(y_test,pred)
    print('오차 행렬')
    print(confusion)
    # f1 score print 추가
    print('정확도: {0:.4f}, 정밀도: {1:.4f}, 재현율: {2:.4f}, F1:{3:.4f}'.format(accuracy, precision, recall, f1))

thresholds = [0.4 , 0.45 , 0.50 , 0.55 , 0.60]
pred_proba = lr_clf.predict_proba(X_test)
get_eval_by_threshold(y_test, pred_proba[:,1].reshape(-1,1), thresholds)
임곗값: 0.4
오차 행렬
[[99 19]
 [10 51]]
정확도: 0.8380, 정밀도: 0.7286, 재현율: 0.8361, F1:0.7786
임곗값: 0.45
오차 행렬
[[103  15]
 [ 12  49]]
정확도: 0.8492, 정밀도: 0.7656, 재현율: 0.8033, F1:0.7840
임곗값: 0.5
오차 행렬
[[104  14]
 [ 13  48]]
정확도: 0.8492, 정밀도: 0.7742, 재현율: 0.7869, F1:0.7805
임곗값: 0.55
오차 행렬
[[109   9]
 [ 15  46]]
정확도: 0.8659, 정밀도: 0.8364, 재현율: 0.7541, F1:0.7931
임곗값: 0.6
오차 행렬
[[112   6]
 [ 16  45]]
정확도: 0.8771, 정밀도: 0.8824, 재현율: 0.7377, F1:0.8036

임곗값이 0.6일 때 F1 Score가 가장 큰 것을 확인하자


ROC Curve와 AUC

ROC 곡선과 이에 기반한 AUC 스코어는 이진 분류의 예측 성능 측정에서 중요하게 사용되는 지표임


ROC Curve

ROC Curve는 Receiver Operating Characteristic Curve로 "수신자 판단 곡선" 이라는 뜻임
2차 대전 때 통신 장비 성능 평가를 위해 고안된 수치라 이렇게 이상한 이름이라고 함
알 필요 없음

일반적으로 의학 분야에서 많이 사용되지만,
머신러닝 이진 분류 모델의 예측 성능을 판단하는 중요한 평가지표이다.

ROC Curve
FPR(False Postive Rate) 변할 때 TPR (True Positive Rate) 어떻게 변하는지 나타내는 곡선
FPR은 1 - TNR (True Negative Rate, Specificity, 특이도)이기도 함
여기서 TPR이 우리가 아는 재현율 (Recall) (= 민감도 (Sensitivity))
식으로 정리해보자

TNR=예측:Negative,실제:Negative실제:Negative=TNFP+TNTNR = \frac{예측: Negative,\, 실제: Negative}{실제: Negative}=\frac{TN}{FP+TN}

FPR=예측:Positive,실제:Negative실제:Negative=FPFP+TN=1Specificity(TNR)FPR = \frac{예측: Positive,\, 실제: Negative}{실제: Negative}=\frac{FP}{FP+TN} = 1 - Specificity\,(TNR)

TPR=예측:Positive,실제:Positive실제:Positive=TPFN+TP=Recall(=Sensitivity)TPR = \frac{예측: Positive,\, 실제: Positive}{실제: Positive}=\frac{TP}{FN+TP} = Recall \,\,(= Sensitivity)

어휴 드럽게 복잡하다.
아래 그래프 보면서 다시 이해해보자

앞에서 임곗값에 대해서 알아봤음. 그 임곗값의 변화에 따라서 FPR이 변화할 것이다.
ROC Curve는 그 FPR의 변화에 따른 TPR의 변화를 그래프로 나타낸 것이다.
아래 예시를 보자

scikit-learn에서는 roc_curve() 함수로 Roc Curve 값을 구함
아래 예시를 천천히 살펴보자

  • predict_proba()[:,1]로 1 (Positive)로 예측할 확률을 뽑음
  • roc_curve()로 FPR, TPR, 그에 해당하는 임곗값(Threshold)를 뽑음 (ndarray)
  • 임곗값을 5 Step으로 추출해서 thr_index에 저장
from sklearn.metrics import roc_curve

# 레이블 값이 1일때의 예측 확률을 추출 
pred_proba_class1 = lr_clf.predict_proba(X_test)[:, 1] 

fprs , tprs , thresholds = roc_curve(y_test, pred_proba_class1)
# 반환된 임곗값 배열에서 샘플로 데이터를 추출하되, 임곗값을 5 Step으로 추출. 
# thresholds[0]은 max(예측확률)+1로 임의 설정됨. 이를 제외하기 위해 np.arange는 1부터 시작
thr_index = np.arange(1, thresholds.shape[0], 5)
print('샘플 추출을 위한 임곗값 배열의 index:', thr_index)
print('샘플 index로 추출한 임곗값: ', np.round(thresholds[thr_index], 2))

# 5 step 단위로 추출된 임계값에 따른 FPR, TPR 값
print('샘플 임곗값별 FPR: ', np.round(fprs[thr_index], 3))
print('샘플 임곗값별 TPR: ', np.round(tprs[thr_index], 3))
샘플 추출을 위한 임곗값 배열의 index: [ 1  6 11 16 21 26 31 36 41 46 51]
샘플 index로 추출한 임곗값:  [0.97 0.65 0.63 0.57 0.45 0.38 0.31 0.13 0.12 0.11 0.1 ]
샘플 임곗값별 FPR:  [0.    0.017 0.034 0.076 0.127 0.186 0.237 0.576 0.619 0.754 0.814]
샘플 임곗값별 TPR:  [0.033 0.639 0.705 0.754 0.803 0.852 0.902 0.902 0.951 0.967 1.   ]

숫자로만 보면 경향성을 보기가 힘드니까 시각화해보자

def roc_curve_plot(y_test , pred_proba_c1):
    # 임곗값에 따른 FPR, TPR 값을 반환 받음. 
    fprs , tprs , thresholds = roc_curve(y_test ,pred_proba_c1)

    # ROC Curve를 plot 곡선으로 그림. 
    plt.plot(fprs , tprs, label='ROC')
    # 가운데 대각선 직선을 그림. 
    plt.plot([0, 1], [0, 1], 'k--', label='Random')
    
    # FPR X 축의 Scale을 0.1 단위로 변경, X,Y 축명 설정등   
    start, end = plt.xlim()
    plt.xticks(np.round(np.arange(start, end, 0.1),2))
    plt.xlim(0,1); plt.ylim(0,1)
    plt.xlabel('FPR( 1 - Sensitivity )'); plt.ylabel('TPR( Recall )')
    plt.legend()
    plt.show()
    
roc_curve_plot(y_test, lr_clf.predict_proba(X_test)[:, 1] )


AUC

그럼 AUC는 뭘까?

AUC: Area Under Curve로 ROC 곡선 아래의 면적을 말함

  • 일반적으로 1에 가까울수록 좋은 수치
  • AUC 수치가 커지려면 FPR이 작은 상태에서 얼마나 큰 TPR을 얻을 수 있느냐가 관건!
  • 위에 시각화 그래프를 보면 가운데에 선 그려져 있는데 그 아래 면적이 0.5
  • 이렇게 동전 던지기 같은 랜덤 수준의 이진 분류의 AUC 값이 0.5
  • 그니까 보통의 분류는 0.5 이상의 AUC 값이 나오겠지? Random보단 높아야 의미가 있으니까!

scikit-learn에서는 roc_auc_score() 함수로 ROC AUC 스코어를 구할 수 있음
ROC AUC Score를 구하려면 predict_proba() (0 또는 1을 예측할 확률) 꼭 필요함!
아래 코드를 확인하자

from sklearn.metrics import roc_auc_score

pred_proba = lr_clf.predict_proba(X_test)[:, 1]
roc_score = roc_auc_score(y_test, pred_proba)
print('ROC AUC 값: {0:.4f}'.format(roc_score))
ROC AUC 값: 0.9024

마지막으로 성능지표를 구하는 get_clf_eval() 함수에 ROC AUC Score까지 추가해서 출력해보자

def get_clf_eval(y_test, pred=None, pred_proba=None):
    confusion = confusion_matrix( y_test, pred)
    accuracy = accuracy_score(y_test , pred)
    precision = precision_score(y_test , pred)
    recall = recall_score(y_test , pred)
    f1 = f1_score(y_test,pred)
    # ROC-AUC 추가 
    roc_auc = roc_auc_score(y_test, pred_proba)
    print('오차 행렬')
    print(confusion)
    # ROC-AUC print 추가
    print('정확도: {0:.4f}, 정밀도: {1:.4f}, 재현율: {2:.4f}, F1: {3:.4f}, AUC:{4:.4f}'.format(accuracy, precision, recall, f1, roc_auc))
    
get_clf_eval(y_test, pred, pred_proba)
오차 행렬
[[104  14]
 [ 13  48]]
정확도: 0.8492, 정밀도: 0.7742, 재현율: 0.7869, F1: 0.7805, AUC:0.9024
profile
Statistics & Data Science
post-custom-banner

0개의 댓글