1. Confusion Matrix (오차 행렬)

 

오차행렬은 이진 분류의 예측 오류가 얼마인지, 어떤 유형의 예측 오류가 발생하고 있는지를 나타내는 지표이다.

 

예측결과, 예측

True Positive : 참으로 예측했는데, 실제로도 참일 때 = 맞음

False Positive : 참으로 예측했는데, 실제로는 거짓일 때 = 틀림

False Negative : 거짓으로 예측했는데, 실제로는 참일 때 = 틀림

True Negative : 거짓으로 예측했는데, 실제로도 거짓일 때 = 맞음 

 


오차행렬로 예측 결과를 살펴보자. 

from sklearn.metrics import confusion_matrix

# 앞절의 예측 결과인 fakepred와 실제 결과인 y_test의 Confusion Matrix출력
confusion_matrix(y_test , fakepred)

[[TN, FN]

  [FP, TP]]

 

실제 값과 앞에서 예측한 값을 넣으면 위와 같이 나온다.

Negative로 예측했기 때문에 정확도가 90%가 나오는 것이다.

 

1~10 중에서 7이 아닐것이다 라고 예측했는데 대부분 7이므로 정확도가 높게 나오는 것이다.

그렇기 때문에 정확도를 불균일한 데이터셋을 이진분류할 때 쓰면 안된다.


2. 정밀도(Precision)와 재현율(Recall)

2.1. 정밀도 (Precision)

  • TP / (FP + TP)
  • 참으로 예측한 것 중에 예측한 것이 맞았을 비율
  • FP가 낮아져야 한다.
  • "참이라고 예측한 것이 틀리면 안된다" 가 정밀도를 높이는 주된 목표

2.2. 재현율 (Recall)

  • TP / (FN + TP)
  • 실제 값이 참인 것 중에서 참으로 예측한 것의 비율
  • FN이 낮아져야 한다.
  • "거짓이라고 예측한 것이 틀리면 안된다" 가 재현율을 높이는 주된 목표

 

정밀도는 precision_score()

재현율은 recall_score()

 

from sklearn.metrics import accuracy_score, precision_score , recall_score

print("정밀도:", precision_score(y_test, fakepred))
print("재현율:", recall_score(y_test, fakepred))

둘다 0으로 나온다.

참으로 예측한게 맞은게 하나도 없기 때문이다.

한마디로 모델이 쓰레기라는 것이다.

 

 

오차행렬, 정밀도, 재현율 다 구하는 함수 만들기

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)


2.3. 업무에 따른 재현율과 정밀도의 상대적 중요도

재현율(Recall)이 중요한 경우

  1. 실제로 참인 것을 거짓으로 예측하면 큰일 나는 경우
  2. 거짓인 것을 중점적으로 찾을 때
  3. 거짓으로 예측한게 틀리면 안된다.
  4. ex) 암진단

정밀도(Precision)가 중요한 경우

  1. 실제로 거짓인 것을 참으로 예측하면 큰일 나는 경우
  2. 참인 것을 중점적으로 찾을 때
  3. 참이라고 예측한게 틀리면 안된다.
  4. ex) 스팸 메일

 

Trade-off

정밀도나 재현율 중 둘중 하나가 좋아야 될 경우, Theshold 를 조정함으로써 성능 수치를 높일 수 있다.

그러나 서로 상호 보완적이기 때문에 한쪽을 의도적으로 높이면 반대쪽은 낮아지게 된다. 이런 것을 Trade-off 라고 부른다.

재현율 측면에서 보았을 때, (FN)

  1. Threshold를 낮춘다는 것은 거짓으로 예측할 확률을 낮추는 것이다.
  2. 즉, 암이 아니라고 판단할 확률을 낮춘다는 것이다.
  3. Threshold가 낮아질수록 재현율이 증가한다.
  4. FN값(거짓으로 예측한 것 중에 틀린 것)이 작아진다.

반면에 정밀도 측면에서 보았을 때, (FP)

  1. Threshold를 낮춘다는 것은 참으로 예측할 확률을 높이는 것이다.
  2. 즉, 암이 맞다라고 판단할 확률을 높인다는 것이다.
  3. Threshold가 낮아졌기 때문에 틀릴 확률도 높아진다.
  4. FP(참이라고 예측한 것 중에서 틀린 것)값은 높아진다.

그래서 Threshold를 낮추면 재현율은 증가한다.

 

사이킷런 Estimator 객체의 predict_proba() 는 분류 결정 예측 확률을 반환한다.

사이킷런의 precision_recall_curve() 는 임계값에 따른 정밀도, 재현율의 변화값을 제공한다.


3. predict_proba()

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

[0이될 확률, 1이될 확률] 순으로 나온다.

 

3.1. Binarizer 활용

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

1.1 보다 작은 것은 다 0을 반환, 큰 것은 다 1을 반환하는 것을 볼 수 있다.

 

3.2. 분류 Threshold 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)

 

일반적으로 predict()가 0이냐 1이냐를 판단하는 디폴트 threshold 값은 0.5이다.

pred_proba의 두번째 컬럼인 positive만 추출해서 2차원으로 만든다. Binarizer에 들어가는 값은 2차원이 되어야 하기 때문.

 

위에서 오차행렬 구했을 때의 결과와 똑같은 것을 확인할 수 있다.

 

이제 threshold 를 0.4로 해보자.

# Binarizer의 threshold 설정값을 0.4로 설정. 즉 분류 결정 임곗값을 0.5에서 0.4로 낮춤  
custom_threshold = 0.4
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)

재현율은 높아지고, 반대로 정밀도는 낮아진 것을 확인할 수 있다.

 

 

이제 여러개의 Threshold를 변경하면서 예측값을 봐보자.

# 테스트를 수행할 모든 임곗값을 리스트 객체로 저장. 
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 )

임계값이 점점 올라가면 정밀도도 올라가고, 재현율을 낮아진다.

따라서 뭐가 중요한지는 threshold를 변경해가면서 선택하면 된다.

 


4. precision_recall_curve()

precision_recall_curve()를 이용하여 threshold에 따른 정밀도-재현율을 봐보자.

 

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()
print("thresholds 5 sample:", thresholds[:5])
print("precisions 5 sample:", precisions[:5])
print("recalls 5 sample:", recalls[:5])
print()
#반환된 임계값 배열 로우가 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))
print()
# 15 step 단위로 추출된 임계값에 따른 정밀도와 재현율 값 
print('샘플 임계값별 정밀도: ', np.round(precisions[thr_index], 3))
print('샘플 임계값별 재현율: ', np.round(recalls[thr_index], 3))

 

임계값 마다의 정밀도와 재현율을 확인할 수 있다.

그러면 곡선 그림을 그릴 수 있다.

 

인자는 실제 test값, 예측 확률 값이 들어간다.

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

 

threshold 에 따른 정밀도와 재현율의 변화를 볼 수 있다.

'💡 AI > ML' 카테고리의 다른 글

ML - Decision Tree  (0) 2021.11.21
ML - F1 score, ROC-AUC  (0) 2021.11.19
ML - 평가(evaluation)  (0) 2021.11.12
ML - fit(), transform() 과 fit_transform()의 차이  (0) 2021.09.15
ML - Estimator의 fit()과 비지도 학습의 fit()의 차이  (0) 2021.09.15
복사했습니다!