●이진 분류: 클래스 변수의 상태 공간이 크기가 2인 분류
●다중 분류:클래스 변수의 상태 공간이 크기가 3이상인 분류
각 클래스를 긍정으로 간주하여 평가 지표를 계산한 뒤, 이들의 산술 평균(macro average), 가중 평균(micro average, weighted average)으로 평가한다
1. 정확도
실제 데이터에서 예측 데이터가 얼마나 같은지를 판단하는 지표
●정확도는 직관적으로 모델 예측 성능을 나타내는 평가 지표이다. 하지만 이진 분류의 경우 데이터의 구성에 따라 ML 모델의 성능을 왜곡할 수 있기 때문에 정확도 수치 하나만 가지고 성능을 평가하지 않는다.
●특히 정확도는 불균형한 레이블 값 분포에서 ML 모델의 성능을 판단할 경우, 적합한 평가 지표가 아니다.
●정확도 = (TN+TP) / (TN+FP+FN+TP)
유명한 MNIST 데이터 세트를 변환해 불균형한 데이터 세트로 만든 뒤에 정확도 지표 적용 시 어떤 문제가 발생할 수 있는지 살펴보자.
MNIST 데이터 세트는 0부터 9까지의 숫자 이미지의 픽셀 정보를 가지고 있으며, 이를 기반으로 숫자 Digit를 예측하는데 사용된다. 원래 MNIST 데이터 세트는 레이블 값이 0부터 9까지 있는 다중 분류를 위한 것이다. 이것을 레이블 값이 7인 것만 True, 나머지 값은 모두 False로 변환해 이진 분류 문제로 바꿔 전체 데이터의 10%만 True, 나머지 90%는 False인 불균형한 데이터 세트로 변형한다.
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.base import BaseEstimator # 사이킷런은 BaseEstimator를 상속받으면 맞춤형 형태의 Estimator를 개발자가 생성할 수 있음
from sklearn.metrics import accuracy_score
import numpy as np
import pandas as pd
class MyFakeClassifier(BaseEstimator):
def fit(self, X, y):
pass
# 입력값으로 들어오는 X 데이터 세트의 크기만큼 모두 0값으로 만들어서 반환
def predict(self, X):
return np.zeros((len(X), 1), dtype=bool)
# 사이킷런의 내장 데이터 세트인 load_digits()를 이용해 MNIST 데이터 로딩
digits = load_digits()
# digits 번호가 7번이면 True이고 이를 astype(int)로 1로 변환, 7번이 아니면 False이고 0으로 변환
y = (digits.target == 7).astype(int)
X_train, X_test, y_train, y_test = train_test_split(digits.data, y, random_state=11)
# 불균형한 레이블 데이터 분포도 확인
print('레이블 테스트 세트 크기:', y_test.shape)
print('테스트 세트 레이블 0과 1의 분포도')
print(pd.Series(y_test).value_counts())
# MyFakeClassifier로 학습/예측/정확도 평가
fakeclf = MyFakeClassifier()
fakeclf.fit(X_train, y_train)
fakepred = fakeclf.predict(X_test) # 위 코드를 보면 반환값으로 다 0으로 반환→모든 예측을 0으로 하도록 반환
# 실제값과 모든 예측을 0으로 반환한 예측값의 정확도
print('모든 예측을 0으로 하여도 정확도는:{0:.3f}'.format(accuracy_score(y_test, fakepred)))
2. 오차행렬(혼동행렬)
일반적으로 불균형한 레이블을 가지는 이진 분류 모델에서는 많은 데이터 중에서 중점적으로 찾아야 하는 매우 적은 수의 결과값에 Postive를 설정해 1값을 부여하고, 그렇지 않은 경우는 Negative로 0값을 부여하는 경우가 많다.
예를 들어 사기 행위 예측 모델에서는 사기 행위가 Positive 양성으로 1, 정상 행위가 Negative 음성으로 0 값이 결정 값으로 할당되거나 암 검진 예측 모델에서는 암이 양성일 경우 Positive 양성으로 1, 암이 음성일 경우 Negative 음성으로 0값이 할당되는 경우가 일반적이다.
불균형한 이진 분류 데이터 세트에서는 Positive 데이터 건수가 매우 작기 때문에 데이터에 기반한 ML 알고리즘은 Positive보다는 Negative로 예측 정확도가 높아지는 경향이 발생한다(학습 목적식은 정확도를 최대화한다). 10,000건의 데이터 세트에서 9,900건이 Negative이고 100건이 Positive라면 Negative로 예측하는 경향이 더 강해져서 TN은 매우 커지고 TP는 매우 작아진다. 또한 Negative로 예측할 때 정확도가 높기 때문에 FN이 매우 작고, Positive로 예측하는 경우가 작기 때문에 FP 역시 매우 작아진다.
결과적으로 정확도 지표는 불균형한 레이블을 가지는 데이터 세트에서 Positive에 대한 예측 정확도를 판단하지 못한 채 Negative에 대한 예측 정확도만으로도 분류의 정확도가 매우 높게 나타나는 수치적인 판단 오류를 일으키게 된다.
정확도는 분류 모델의 성능을 측정할 수 있는 하나의 요소일 뿐이다. 앞에서도 말했듯 불균형한 데이터 세트에서 정확도만으로는 모델 신뢰도가 떨어질 수 있는 사례가 많기 때문에 불균형한 데이터 세트에서 정확도보다 더 선호되는 평가 지표인 정밀도(Precision)와 재현율(Recall)을 더 선호하는 측면이 있다.
3. 정밀도와 재현율
정밀도와 재현율은 Positive 데이터 세트의 예측 성능에 좀 더 초점을 맞춘 평가 지표이다.
●정밀도는 예측을 Positive로 한 대상 중에 예측과 실제 값이 Positive로 일치한 데이터의 비율
정밀도 = TP / (FP+TP)
●재현율은 실제 값이 Positive인 대상 중에 예측과 실제 값이 Positive로 일치한 데이터의 비율
재현율 = TP / (FN+TP)
정밀도와 재현율 지표의 중요성은 비즈니스의 특성에 따라 달라질 수 있다.
1) 재현율이 상대적으로 더 중요한 지표인 경우는 실제 Positive 양성인 데이터 예측을 Negative로 잘못 판단하게 되면 업무상 큰 영향이 발생하는 경우: 암 진단, 금융사기 판별
2) 정밀도가 상대적으로 더 중요한 지표인 경우는 실제 Negative 음성인 데이터 예측을 Positive 양성으로 잘못 판단하게 되면 업무상 큰 영향이 발생하는 경우: 스팸 메일 판별
→일반적으로 재현율이 정밀도보다 상대적으로 중요한 업무가 많다. 즉, 보통은 위음성비용(FN)이 위양성비용(FP)보다 훨씬 크다.(예시- 암 진단: 위양성 비용(정상인→암환자) vs 위음성 비용(암환자→정상인))
재현율과 정밀도의 공식을 살펴보면 재현율과 정밀도 모두 TP를 높이는 데 동일하게 초점을 맞추지만, 재현율은 FN(위음성비용)을 낮추는데, 정밀도는 FP(위양성비용)를 낮추는데 초점을 맞춤
from sklearn.metrics import *
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 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.2, random_state=11)
lr_clf = LogisticRegression(solver='liblinear')
lr_clf.fit(X_train, y_train)
pred = lr_clf.predict(X_test)
get_clf_eval(y_test, pred)
<정밀도/재현율 트레이드오프>
분류하려는 업무의 특성상 정밀도 또는 재현율이 특별히 강조돼야 할 경우 분류의 결정 임계값(Threshold)을 조정해 정밀도 또는 재현율의 수치를 높일 수 있다. 하지만 정밀도와 재현율은 상호 보완적인 평가 지표이기 때문에 어느 한 쪽을 강제로 높이면 다른 하나의 수치는 떨어지기 쉽다. 이를 정밀도/재현율의 트레이드오프(Trade-off)라고 부른다.
사이킷런의 분류 알고리즘은 예측 데이터가 특정 레이블에 속하는지를 계산하기 위해 먼저 개별 레이블별로 결정 확률을 구한다. 그리고 예측 확률이 큰 레이블값으로 예측하게 된다. 예를 들어 특정 데이터가 0이 될 확률이 10%, 1이 될 확률이 90%로 예측됐다면 최종 예측은 1로 예측한다. 일반적으로 분류에서는 분류 결정 임계값을 0.5, 즉 50%로 정하고 이 기준값보다 확률이 크면 Positive, 작으면 Negative로 결정한다.
개별 레이블별로 결정 확률을 반환하는 predict_proba() 메서드를 이용한다.
predict() 메서드와 유사하지만 단지 반환 결과가 예측 결과 클래스값이 아닌 예측 확률 결과이다.
이진 분류에서 predict_proba()를 수행해 반환되는 ndarray는 첫 번째 컬럼이 클래스 값 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를 병합해 예측 확률과 예측 결과값을 한눈에 확인
pred_proba_result = np.concatenate([pred_proba, pred.reshape(-1,1)], axis=1)
print('예측 확률이 큰 레이블값으로 예측\n', pred_proba_result[:3])
지금부터 분류 결정 임계값을 조절해 정밀도와 재현율의 성능 수치를 상호 보완적으로 조정해보자!
사이킷런의 Binarizer 클래스에서 threshold를 조절해 진행함. threshold보다 같거나 작으면 0값으로, 크면 1값으로 반환
● threshold=0.5
# 분류 결정 임계값을 0.5로 설정했을 때
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)
일반적으로 분류에서는 분류 경정 임계값을 0.5, 즉 50%로 정하고 이 기준값보다 확률이 크면 Positive, 작으면 Negative로 결정하기 때문에 threshold를 0.5로 설정했으므로 앞에서 타이타닉 데이터로 학습된 로지스틱 회귀 Classifier 객체에서 호출된 predict()로 계산된 지표 값과 정확히 같다.
● threshold=0.4
# 분류 결정 임계값을 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)
임계값을 낮추니 재현율 값이 올라가고 정밀도가 떨어졌다. 왜냐하면 분류 결정 임계값은 Postive 예측값을 결정하는 확률의 기준으로 확률이 0.5가 아닌 0.4부터 Positive로 예측을 더 너그럽게 하기 때문에 임계값을 낮출수록 True인 1 값이 많아지기 때문이다.
Positive 예측값이 많아지면, 양성 예측을 많이 하다 보니 실제 양성을 음성으로 예측하는 횟수인 FN이 상대적으로 줄어들기 때문에 재현율 값이 높아지고, 양성 예측을 많이 하다 보니 FP가 커지기 때문에 정밀도 값이 낮아짐(공식 생각)
임계값이 낮아지면서 재현율이 0.770에서 0.820으로 좋아졌고, 정밀도는 0.825에서 0.704로 나빠졌다. 그리고 정확도도 0.866에서 0.821로 나빠졌다.
● threshold=0.4 ~ 0.6 (in steps of 0.05)
# 분류 결정 임계값을 0.4 ~ 0.6(in stpes of 0.05)로 설정했을 때
# 테스트를 수행할 모든 임계값을 리스트 객체로 저장
thresholds = [0.4, 0.45, 0.5, 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)
print('-'*60)
get_clf_eval(y_test, custom_predict)
print('='*60)
get_eval_by_threshold(y_test, pred_proba[:,1].reshape(-1,1), thresholds)
임계값이 0.45일 경우에 디폴트 0.5인 경우와 비교해서 정확도가 약간 감소했고 정밀도도 약간 감소했으나 재현율이 올랐다. 만약 재현율을 향상시키면서 다른 수치를 어느 정도 감소시켜야 하는 경우라면 임계값은 0.45가 가장 적당할 것이다.
★precision_reacall_curve()★
사이킷런은 threshold 변화에 따른 정밀도와 재현율을 구할 수 있게 해주는 precision_recall_curve() API를 제공한다.
- 입력 파라미터
- y_true : 실제 클래스값 배열(배열크기 = [데이터 건수])
- probas_pred : Positive 컬럼의 예측 확률 배열(배열크기 = [데이터 건수])
- 반환값
- 정밀도 : 임계값별 정밀도 값을 배열로 반환
- 재현율 : 임계값별 재현율 값을 배열로 반환
- thresholds : 임계값(일반적으로 0.11 ~ 0.95 정도의 임계값)
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])
print('-'*60)
# 반환된 임계값 배열 크기가 165건이므로 샘플로 11건만 추출하되, 임계값을 16 step으로 추출
thr_index = np.arange(0, thresholds.shape[0], 16)
print('샘플 추출을 위한 임계값 배열의 index 11개:', thr_index)
print('샘플용 11개의 임계값:', np.round(thresholds[thr_index], 2))
# 16 step 단위로 추출된 임계값에 따른 정밀도와 재현율 값
print('샘플 임계값별 정밀도:', np.round(precisions[thr_index], 2))
print('샘플 임계값별 재현율:', np.round(recalls[thr_index], 2))
결과를 보면 threshold가 증가할수록 정밀도는 높아지나 재현율은 낮아지는 것을 알 수 있다.
precision_recall_curve() API는 threshold에 따른 정밀도와 재현율의 변화를 시각화하는데 이용할 수 있다.
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])
그래프에서 보다시피 정밀도와 재현율에 trade-off가 존재하며 로지스틱 회귀 기반의 타이타닉 생존자 예측 모델의 경우 약 0.45 정도의 threshold에서 정밀도와 재현율이 비슷해지는 것을 볼 수 있다.
→threshold에 따른 정밀도와 재현율은 업무 환경에 맞게 상호 보완할 수 있는 수준에서 적용돼야 한다.
이처럼 정밀도와 재현율 성능 수치도 어느 한 쪽만 참조하면 극단적인 수치 조작이 가능하다. 따라서 정밀도와 재현율 중 하나만 스코어가 좋고 다른 하나는 스코어가 나쁜 분류는 성능이 좋지 않은 분류로 간주할 수 있다.
⇛그래서 정밀도와 재현율의 수치가 적절하게 조합돼 분류의 종합적인 성능 평가에 사용될 수 있는 평가 지표인
'F1 score'가 있다!!
'파이썬 머신러닝 완벽가이드 > [3장] 분류의 성능 평가 지표' 카테고리의 다른 글
피마 인디언 당뇨병 예측 실습 (0) | 2023.02.04 |
---|---|
f1 score, roc auc (0) | 2023.01.31 |