ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [평가] 정확도/정밀도/재현율/F1 스코어/ROC AUC
    머신러닝 & 딥러닝 2021. 10. 5. 17:06

    분류 (Classification) 성능 평가 지표

    • 정확도 Accuracy
    • 오차 행렬 Confusion Matrix
    • 정밀도 Precision
    • 재현율 Recall
    • F1 스코어
    • ROC AUC

     

     

    정확도

    • 정확도 = 예측 결과가 동일한 데이터 건수 / 전체 예측 데이터 건수
    • 직관적으로 모델 예측 성능을 나타내는 평가 지표
    • 이진 분류의 경우 정확도 하나만 갖고 성능을 평가하지 않는다
    • 불균형한 레이블 값 분포에서 ML 모델의 성능을 판단할 경우, 적합한 평가 지표가 아니다.

     

     

    성능 평가의 잘못된 예

    • 이진 분류의 경우 데이터의 구성에 따라 ML모델의 성능을 왜곡할 수 있기 때문에 정확도 수치 하나만으로 성능 평가를 하지 않는다.
    • 남자인 경우 사망, 여자인 경우 생존으로 예측해도 알고리즘이 높은 정확도를 나타내는 상황 발생. (즉 잘못된 예시)

     

     

    오차 행렬 (Confusion Matrix)

    • 이진 분류의 예측 오류가 얼마인지와 어떠한 유형의 예측 오류가 발생하는지 나타내는 지표.
    • 정확도 = 예측 결과와 실제 값이 동일한 건수 / 전체 데이터 수 = (TP+TN) / (TN+TP+FN+FP)
    from sklearn.metrics import confusion_matrix
    
    # 앞절의 예측 결과인 fakepred와 실제 결과인 y_test의 Confusion Matrix출력
    confusion_matrix(y_test , fakepred)

    array([[405, 0], [ 45, 0]])

    위 예시와 같이 데이터가 불균형한 셋은 쓰면 안된다.

     

     

    정밀도(Precision) 과 재현율(Recall)

    • 정밀도 : 예측을 Positive로 한 대상 중에서 예측과 실제값이 positive로 일치한 데이터의 비율.
      • = TP / (FP+TP)
      • precision_score()
    • 재현율 : 실제값이 positive인 대상 중에서 예측과 실제 값이 positive로 일치한 데이터의 비율.
      • = TP / (FN+TP)
      • recall_score()

     

    오차행렬, 정확도, 정밀도, 재현율을 한꺼번에 계산하는 함수 생성

    from sklearn.metrics import accuracy_score, precision_score , recall_score , confusion_matrix
    
    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)
        print('오차 행렬')
        print(confusion)
        print('정확도: {0:.4f}, 정밀도: {1:.4f}, 재현율: {2:.4f}'.format(accuracy , precision ,recall))
    import numpy as np
    import pandas as pd
    
    from sklearn.model_selection import train_test_split 
    from sklearn.linear_model import LogisticRegression
    
    # 원본 데이터를 재로딩, 데이터 가공, 학습데이터/테스트 데이터 분할. 
    titanic_df = pd.read_csv('./titanic_train.csv')
    y_titanic_df = titanic_df['Survived']
    X_titanic_df= titanic_df.drop('Survived', axis=1)
    X_titanic_df = transform_features(X_titanic_df)
    
    X_train, X_test, y_train, y_test = train_test_split(X_titanic_df, y_titanic_df, \
                                                        test_size=0.20, random_state=11)
    
    lr_clf = LogisticRegression()
    
    lr_clf.fit(X_train , y_train)
    pred = lr_clf.predict(X_test)
    get_clf_eval(y_test , pred)

    오차 행렬 [[104 14] [ 13 48]]

    정확도: 0.8492, 정밀도: 0.7742, 재현율: 0.7869

     

     

    Precision/Recall Trade-off

    • 재현율이 상대적으로 더 중요한 경우: 실제 positive 양성인 데이터 예측을 negative로 잘못 판단하게 되면 큰 영향이 발생하는 경우.
      • 예: 암 진단, 금융 사기 판별
    • 정밀도가 상대적으로 더 중요한 경우: 실제 negative 음성인 데이터 예측을 positive 양성으로 잘못 판단하게 되면 큰 영향이 발생.
      • 예: 스팸 메일 (스팸 메일이 아닌 메일을 스팸함에 분류하는 경우)
    • 경정 임계값을 조정해서 정밀도 또는 재현율을 높일 수 있다. (정밀도가 높아지면 재현율은 낮아짐, 서로 반대)
    • 분류 결정 임계값이 낮아질 수록 positive로 예측할 확률이 높아진다 -> 재현율 증가
    • precision_recall_curve()를 통해 임계값에 따른 정밀도, 재현율의 변화값을 제공

     

    predict_proba() 메소드

    • 이진 분류일 때, 0이 될 확률, 1이 될 확률이 얼마인지 반환
    pred_proba = lr_clf.predict_proba(X_test)
    pred  = lr_clf.predict(X_test)
    print('pred_proba()결과 Shape : {0}'.format(pred_proba.shape))
    print('pred_proba array에서 앞 3개만 샘플로 추출 \n:', pred_proba[:3])
    
    # 예측 확률 array 와 예측 결과값 array 를 concatenate 하여 예측 확률과 결과값을 한눈에 확인
    pred_proba_result = np.concatenate([pred_proba , pred.reshape(-1,1)],axis=1)
    print('두개의 class 중에서 더 큰 확률을 클래스 값으로 예측 \n',pred_proba_result[:3])

    pred_proba()결과 Shape : (179, 2)
    pred_proba array에서 앞 3개만 샘플로 추출
    : [[0.46196457 0.53803543]
    [0.87861802 0.12138198]
    [0.8771453 0.1228547 ]]
    두개의 class 중에서 더 큰 확률을 클래스 값으로 예측
    [[0.46196457 0.53803543 1. ]
    [0.87861802 0.12138198 0. ]
    [0.8771453 0.1228547 0. ]]

     

    Binarize 활용

    from sklearn.preprocessing import Binarizer
    
    X = [[ 1, -1,  2],
         [ 2,  0,  0],
         [ 0,  1.1, 1.2]]
    
    # threshold 기준값보다 같거나 작으면 0을, 크면 1을 반환
    binarizer = Binarizer(threshold=1.1)                     
    print(binarizer.fit_transform(X))

    [[0. 0. 1.]
    [1. 0. 0.]
    [0. 0. 1.]]

     

     

    분류 결정 임계값 0.5 기반에서 Binarizer를 이용하여 예측값 변환

    from sklearn.preprocessing import Binarizer
    
    #Binarizer의 threshold 설정값. 분류 결정 임곗값임.  
    custom_threshold = 0.5
    
    # predict_proba( ) 반환값의 두번째 컬럼 , 즉 Positive 클래스 컬럼 하나만 추출하여 Binarizer를 적용
    pred_proba_1 = pred_proba[:,1].reshape(-1,1)
    
    binarizer = Binarizer(threshold=custom_threshold).fit(pred_proba_1) 
    custom_predict = binarizer.transform(pred_proba_1)
    
    get_clf_eval(y_test, custom_predict)

    오차 행렬 [[104 14] [ 13 48]]
    정확도: 0.8492, 정밀도: 0.7742, 재현율: 0.7869

     

     

    여러개의 분류 결정 임곗값을 변경하면서 Binarizer를 이용하여 예측값 변환

    # 테스트를 수행할 모든 임곗값을 리스트 객체로 저장. 
    thresholds = [0.4, 0.45, 0.50, 0.55, 0.60]
    
    def get_eval_by_threshold(y_test , pred_proba_c1, thresholds):
        # thresholds list객체내의 값을 차례로 iteration하면서 Evaluation 수행.
        for custom_threshold in thresholds:
            binarizer = Binarizer(threshold=custom_threshold).fit(pred_proba_c1) 
            custom_predict = binarizer.transform(pred_proba_c1)
            print('임곗값:',custom_threshold)
            get_clf_eval(y_test , custom_predict)
    
    get_eval_by_threshold(y_test ,pred_proba[:,1].reshape(-1,1), thresholds )

    임곗값: 0.4 오차 행렬 [[98 20] [10 51]] 정확도: 0.8324, 정밀도: 0.7183, 재현율: 0.8361

    임곗값: 0.45 오차 행렬 [[103 15] [ 12 49]] 정확도: 0.8492, 정밀도: 0.7656, 재현율: 0.8033

    임곗값: 0.5 오차 행렬 [[104 14] [ 13 48]] 정확도: 0.8492, 정밀도: 0.7742, 재현율: 0.7869

    임곗값: 0.55 오차 행렬 [[109 9] [ 15 46]] 정확도: 0.8659, 정밀도: 0.8364, 재현율: 0.7541

    임곗값: 0.6 오차 행렬 [[112 6] [ 16 45]] 정확도: 0.8771, 정밀도: 0.8824, 재현율: 0.7377

     

     

    precision_recall_curve( ) 를 이용하여 임곗값에 따른 정밀도-재현율 값 추출

    from sklearn.metrics import precision_recall_curve
    
    # 레이블 값이 1일때의 예측 확률을 추출 
    pred_proba_class1 = lr_clf.predict_proba(X_test)[:, 1] 
    
    # 실제값 데이터 셋과 레이블 값이 1일 때의 예측 확률을 precision_recall_curve 인자로 입력 
    precisions, recalls, thresholds = precision_recall_curve(y_test, pred_proba_class1 )
    print('반환된 분류 결정 임곗값 배열의 Shape:', thresholds.shape)
    print('반환된 precisions 배열의 Shape:', precisions.shape)
    print('반환된 recalls 배열의 Shape:', recalls.shape)
    
    print("thresholds 5 sample:", thresholds[:5])
    print("precisions 5 sample:", precisions[:5])
    print("recalls 5 sample:", recalls[:5])
    
    #반환된 임계값 배열 로우가 147건이므로 샘플로 10건만 추출하되, 임곗값을 15 Step으로 추출. 
    thr_index = np.arange(0, thresholds.shape[0], 15)
    print('샘플 추출을 위한 임계값 배열의 index 10개:', thr_index)
    print('샘플용 10개의 임곗값: ', np.round(thresholds[thr_index], 2))
    
    # 15 step 단위로 추출된 임계값에 따른 정밀도와 재현율 값 
    print('샘플 임계값별 정밀도: ', np.round(precisions[thr_index], 3))
    print('샘플 임계값별 재현율: ', np.round(recalls[thr_index], 3))

    반환된 분류 결정 임곗값 배열의 Shape: (143,)
    반환된 precisions 배열의 Shape: (144,)
    반환된 recalls 배열의 Shape: (144,)
    thresholds 5 sample: [0.10396968 0.10397196 0.10399758 0.10776186 0.10894507]
    precisions 5 sample: [0.38853503 0.38461538 0.38709677 0.38961039 0.38562092]
    recalls 5 sample: [1. 0.98360656 0.98360656 0.98360656 0.96721311]
    샘플 추출을 위한 임계값 배열의 index 10개: [ 0 15 30 45 60 75 90 105 120 135]
    샘플용 10개의 임곗값: [0.1 0.12 0.14 0.19 0.28 0.4 0.56 0.67 0.82 0.95]
    샘플 임계값별 정밀도: [0.389 0.44 0.466 0.539 0.647 0.729 0.836 0.949 0.958 1. ]
    샘플 임계값별 재현율: [1. 0.967 0.902 0.902 0.902 0.836 0.754 0.607 0.377 0.148]

     

     

    임곗값의 변경에 따른 정밀도-재현율 변화 곡선을 그림

    import matplotlib.pyplot as plt
    import matplotlib.ticker as ticker
    %matplotlib inline
    
    def precision_recall_curve_plot(y_test , pred_proba_c1):
        # threshold ndarray와 이 threshold에 따른 정밀도, 재현율 ndarray 추출. 
        precisions, recalls, thresholds = precision_recall_curve( y_test, pred_proba_c1)
        
        # X축을 threshold값으로, Y축은 정밀도, 재현율 값으로 각각 Plot 수행. 정밀도는 점선으로 표시
        plt.figure(figsize=(8,6))
        threshold_boundary = thresholds.shape[0]
        plt.plot(thresholds, precisions[0:threshold_boundary], linestyle='--', label='precision')
        plt.plot(thresholds, recalls[0:threshold_boundary],label='recall')
        
        # threshold 값 X 축의 Scale을 0.1 단위로 변경
        start, end = plt.xlim()
        plt.xticks(np.round(np.arange(start, end, 0.1),2))
        
        # x축, y축 label과 legend, 그리고 grid 설정
        plt.xlabel('Threshold value'); plt.ylabel('Precision and Recall value')
        plt.legend(); plt.grid()
        plt.show()
        
    precision_recall_curve_plot( y_test, lr_clf.predict_proba(X_test)[:, 1] )

     

     

    F1 Score

    • 정밀도와 재현율을 결합한 지표이다.
    • F1 스코어는 정밀도와 재현율이 어느 한쪽으로 치우치지 않는 수치를 나타낼 때 상대적으로 높은 값을 갖는다.

     

    ROC 곡선과 AUC

    • 이진 분류의 예측 성능 측정에서 중요하게 사용되는 지표이다.
    • ROC 곡선은 FPR(False Positive Rate)이 변할 때 TPR(True Positive Rate)이 어떻게 변하는지를 나타내는 곡선이다.
    • AUC 값은 ROC곡선 밑의 면적을 구한 것으로, 일반적으로 1에 가까울수록 좋은 수치이다.
    • TPR = 재현율 = TP / (FN+TP)
    • FPR = FP / (FP+TN)
    from sklearn.metrics import f1_score 
    f1 = f1_score(y_test , pred)
    print('F1 스코어: {0:.4f}'.format(f1))
    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)

     

     

    ROC Curve와 AUC 예시

    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)
    # 반환된 임곗값 배열 로우가 47건이므로 샘플로 10건만 추출하되, 임곗값을 5 Step으로 추출. 
    thr_index = np.arange(0, thresholds.shape[0], 5)
    print('샘플 추출을 위한 임곗값 배열의 index 10개:', thr_index)
    print('샘플용 10개의 임곗값: ', 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))

    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] )

    from sklearn.metrics import roc_auc_score
    
    ### 아래는 roc_auc_score()의 인자를 잘못 입력한 것으로, 책에서 수정이 필요한 부분입니다. 
    ### 책에서는 roc_auc_score(y_test, pred)로 예측 타겟값을 입력하였으나 
    ### roc_auc_score(y_test, y_score)로 y_score는 predict_proba()로 호출된 예측 확률 ndarray중 Positive 열에 해당하는 ndarray입니다. 
    
    #pred = lr_clf.predict(X_test)
    #roc_score = roc_auc_score(y_test, pred)
    
    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

     

    Summary

    - 이진 분류에서 정밀도, 재현율, F1 스코어, AUC 스코어가 주로 성능 평가 지표로 활용된다.

    - 오차 행렬은 실제/예측 클래스 값에 따라 TN, TP, FN, FP로 나뉘는 행렬을 제공한다.

    - 정밀도와 재현율은 positive 데이터셋의 예측 성능에 초점을 맞춘 지표이다. 분류 결정 이계값에 따라 정밀도와 재현율을 조절할 수 있다.

    - F1 스코어는 정밀도와 재현율이 어느 한쪽으로 치우치지 않을 때 좋은 값을 갖는다.

    - AUC 스코어는 ROC 곡선 밑의 면적을 구한 것으로 1에 가까울수록 좋은 수치이다.

    댓글