본문 바로가기

Euron/정리

[Week1] 03. 평가

머신러닝은 데이터 가공/변환, 모델 학습/예측, 그리고 평가(Evaluation)의 프로세스로 구성된다.

회귀의 성능 평가 지표는 주로 실제값과 예측값의 오차 평균값에 기반한다.

 

분류의 성능 평가 지표

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

분류는 2개의 결괏값만을 가지는 이진 분류와 여러 결정 클래스 값을 가지는 멀티 분류로 나뉜다.(위는 전부 가능)


01. 정확도(Accuracy)

실제 데이터에서 예측 데이터가 얼마나 같은지를 판단하는 지표

불균형한 레이블 값 분포에서 ML 모델의 성능을 판단할 경우 적합하지 않다.

 


02. 오차 행렬

 

이진 분류의 예측 오류가 얼마인지와 어떠한 유형의 예측 오류가 발생하고 있는지를 함께 나타내는 지표

예측 클래스와 실제 클래스의 Positive 결정 값과 Negative 결정 값의 결합에 따라 TN, FP, FN, TP가 결정된다.

T/F 는 예측값과 실제값이 '같은가/다른가'

N/P 는 예측 결과 값이 부정/긍정을 의미한다.

사이킷런은 오파 행렬을 구하기 위해 confusion_matrix() api를 제공한다.

 

정확도 = (TN + TP)/(TN + FP + FN + TP)

 


03. 정밀도와 재현율

 

  • 정밀도
    • TP/(FP + TP)
    • 예측을 Positive로 한 대상 중에 실제 값과 일치한 데이터의 비율
    • 실제 Negative 음성인 데이터 예측을 Positive 양성으로 잘못 판단하게 되면 업무상 큰 영향이 발생하는 경우
    • FP를 낮추는 데 초점을 맞춘다.
    • precison_score() 사용
  • 재현율
    • TP/(FN + TP)
    • 실제 값이 Positive한 대상 중에 예측값과 실제 값이 Positive로 일치한 데이터의 비율
    • 민감도(Sensitivity) 혹은 TPR(True,Positive Rate)이라고도 불린다.
    • 실제 Positive 양성 데이터를 Negative로 잘못 판단하게 되면 업무상 큰 영향이 발생하는 경우 
    • FN을 낮추는 데 초점을 둔다.
    • recall_score() 사용
  • confusion_matrix, precision_score, 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))

# output(중간 생략)

오차 행렬
[[108  10]
 [ 14  47]]
정확도: 0.8659, 정밀도: 0.8246, 재현율: 0.7705

 

정밀도/재현율 트레이드오프

 

정밀도 혹은 재현율이 특별히 강조되어야 하는 경우 분류의 결정 임곗값(Threshold)를 조정해 수치를 높일 수 있다.

분류 알고리즘은 예측 데이터가 특정 레이블에 속하는지를 계산하기 위해 먼저 개별 레이블별로 결정 확률을 구한다.

  • predict_proba()
    • 개별 데이터별로 예측 확률을 반환하는 메서드
    • Classifier 객체에서 호출 가능, 테스트 피처 데이터 세트를 파라미터로 입력해주면 개별 클래스의 예측 확률을 반환한다.
    • 이진 분류에서 predict_proba()를 수행해 반환되는 ndarray는 첫 번째 칼럼이 클래스 값 0에 대한 예측 확률, 두 번째 칼럼이 클래스 값 1에 대한 예측 확률이다.
    • predict() 메서드는 predict_proba() 결과에서 두 칼럼 중 더 큰 확률 값으로 최종 예측한다.

predict()는 predict_proba() 메서드가 반환하는 확률 값을 가진 ndarray에서 정해진 임곗값(default=0.5)을 만족하는 ndarray의 칼럼 위치를 최종 예측 클래스로 정한다.

  • Binarizer 클래스
    • Binarizer 객체의 fit_transform() 메서드를 이용해 ndarray를 입력하면 입력된 ndarray의 값을 지정된 threshold(임곗값)보다 같거나 작으면 0값으로, 크면 1값으로 변환해 반환한다.
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))

# output

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

분류 결정 임곗값은 Positive 예측값을 결정하는 확률의 기준이 된다.(0.4인 경우 60%를 positive로 예측)

Positive 예측값이 많아지면 상대적으로 재현율 값이 높아진다. 즉, FN이 줄어든다.

  • precision_recall_curve() 
    • 사이킷런에서 제공하는 임곗값 변화에 따른 평가 지표 반환 api
    • 입력 파라미터 : y_true(실제 클래스 값 배열), probas_pred( Positive 칼럼의 예측 확률 배열)
    • 반환 값 : 정밀도, 재현율(배열로 반환)
    • 일반적으로 0.11~0.95 정도의 임곗값을 담은 ndarray와 정밀도 및 재현율 값을 담은 ndarray를 반환한다.
    • arange를 이용하여 임곗값 계산을 조절할 수 있다.
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))

# output

반환된 분류 결정 임곗값 배열의 Shape: (165,)
반환된 precisions 배열의 Shape: (166,)
반환된 recalls 배열의 Shape: (166,)
thresholds 5 sample: [0.01974987 0.06956413 0.08402808 0.08474207 0.0892016 ]
precisions 5 sample: [0.34078212 0.34269663 0.34463277 0.34659091 0.34857143]
recalls 5 sample: [1. 1. 1. 1. 1.]
샘플 추출을 위한 임계값 배열의 index 10개: [  0  15  30  45  60  75  90 105 120 135 150]
샘플용 10개의 임곗값:  [0.02 0.11 0.13 0.14 0.16 0.24 0.32 0.45 0.62 0.73 0.87]
샘플 임계값별 정밀도:  [0.341 0.372 0.401 0.44  0.505 0.598 0.688 0.774 0.915 0.968 0.938]
샘플 임계값별 재현율:  [1.    1.    0.967 0.902 0.902 0.902 0.869 0.787 0.705 0.492 0.246]

임계값이 낮을수록 많은 수의 양성예측으로 인해 재현율 값이 극도록 높아지고 정밀도 값이 극도록 낮아진다.

 

 

정밀도와 재현율의 맹점

 

정밀도 100% : 확실한 기분이 되는 경우만 Positive로 예측하고 나머지는 모두 Negative로 예측한다.

재현율 100% : 모든 환자를 Positive로 예측한다.

 


04. F1 스코어

 

정밀도와 재현율을 결합한 지표.

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

f1_score() api 사용

 


05. ROC 곡선과 AUC

 

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

  • ROC 곡선
    • FPR이 변할 때 TPR이 어떻게 변하는지를 나타내는 곡선
    • FPR = FP/(FP + TN) = 1 - TNR = 1 - 특이성
    • ROC 곡선이 가운데 직선에 가까울수록 성능이 떨어진다.
  • roc_curve()
    • ROC 곡선을 구하기 위한 api
    • 입력 파라미터 : y_true(실제 클래스 값 array), y_score(predict_proba의 반환 값 array에서 positive 예측 확률)
    • 반환 값 : FPR, TPR, thresholds(array 형태)
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))

# output

샘플 추출을 위한 임곗값 배열의 index: [ 1  6 11 16 21 26 31 36 41 46]
샘플 index로 추출한 임곗값:  [0.94 0.73 0.62 0.52 0.44 0.28 0.15 0.14 0.13 0.12]
샘플 임곗값별 FPR:  [0.    0.008 0.025 0.076 0.127 0.254 0.576 0.61  0.746 0.847]
샘플 임곗값별 TPR:  [0.016 0.492 0.705 0.738 0.803 0.885 0.902 0.951 0.967 1.   ]

임계값이 1에 가까운 값에서 점점 작아지며 FPR이 점점 커진다.

FPR이 커질때 TPR은 가파르게 커진다.

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

# output(시각화)

ROC 곡선과 ALU

AUC 값은 ROX 곡선 밑의 면적을 구한 것으로서 일반적으로 1에 가까울수록 좋은 수치이다.

AUC 수치가 커지려면 FPR이 작은 상태에서 얼마나 큰 TPR을 얻을 수 있냐가 중요하다.

보통의 분류는 0.5 이상의 AUC 값을 가진다.

roc_auc_score()를 이용해 ROC AUC 값을 측정한다.

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

06. 피마 인디언 당뇨병 예측

 

과정

  • 데이터 세트를 다운 받고 결정값과 데이터를 개략적으로 확인한다.
    • outcome이라는 클래스 결정 값이 있다.(0 or 1)
  • feature의 타입과 Null 개수를 확인한다.
    • Null값은 없으며, 별도의 피처 인코딩은 불필요하다.
  • 로지스틱 회귀를 이용해 예측을 수행하거 예측 및 평가를 수행한다.
    • 전체 데이터의 65%가 Negative이므로 정확도 보다는 재현율 성능에 초점을 맞춘다.
# 피처 데이터 세트 X, 레이블 데이터 세트 y를 추출. 
# 맨 끝이 Outcome 컬럼으로 레이블 값임. 컬럼 위치 -1을 이용해 추출 
X = diabetes_data.iloc[:, :-1]
y = diabetes_data.iloc[:, -1]

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 156, stratify=y)

# 로지스틱 회귀로 학습,예측 및 평가 수행. 
lr_clf = LogisticRegression(solver='liblinear')
lr_clf.fit(X_train , y_train)
pred = lr_clf.predict(X_test)
pred_proba = lr_clf.predict_proba(X_test)[:, 1]

get_clf_eval(y_test , pred, pred_proba)

pred_proba_c1 = lr_clf.predict_proba(X_test)[:, 1]
precision_recall_curve_plot(y_test, pred_proba_c1)

# outcome

오차 행렬
[[87 13]
 [22 32]]
정확도: 0.7727, 정밀도: 0.7111, 재현율: 0.5926,    F1: 0.6465, AUC:0.8083

결과

  • 임곗값 0.42에서 정밀도와 재현율이 균형을 맞춘다.
    • 그러나 두 지표 모두 값이 낮으므로 임곗값을 조절한다.
    • 조작을 위해 원본 데이터 DataFrame의 describe() 메서드를 호출해 피처 값의 분포도를 살펴본다.
    • 전체 데이터 중 SkinThickness와 Insulin의 0값이 많으므로 replace()를 이용해 0값을 평균값으로 대체한다.
  • 다시 임곗값을 0.3에서 0.5로 0.03씩 변화시키면서 재현율과 다른 평가 지표 값 변화를 출력한다.
    • 임곗값 0.48이 전체적인 성능에서 좋은 것을 알 수 있다.
  • 로지스틱 회귀모델을 이용해 임곗값을 0.48로 낮춘 상태에서 다시 예측을 한다.
    • Binarizer 클래스를 이용해 predict_proda()로 추출한 예측 결과 확률 값을 변환해 변경된 임곗값에 따른 예측 클래스 값을 구한다.
# 임곗값를 0.48로 설정한 Binarizer 생성
binarizer = Binarizer(threshold=0.48)

# 위에서 구한 lr_clf의 predict_proba() 예측 확률 array에서 1에 해당하는 컬럼값을 Binarizer변환. 
pred_th_048 = binarizer.fit_transform(pred_proba[:, 1].reshape(-1,1)) 

get_clf_eval(y_test , pred_th_048, pred_proba[:, 1])

# output

오차 행렬
[[88 12]
 [19 35]]
정확도: 0.7987, 정밀도: 0.7447, 재현율: 0.6481,    F1: 0.6931, AUC:0.8433

Week1 예습과제 끝!

내용이 많이 어렵지는 않았다. 수학적 지식이 좀 빠져있기도 하고...하지만 클래스나 메소드 외울게 아직 많다!

그리고 처음이라 정리하는데도 꽤 오랜시간이 걸렸다. 다음에는 더 빠르게...