이번 포스팅에서는 분류 문제 성능을 평가하는 대표적인 metric으로서 Precision과 Recall에 대해 알아보고 이 둘 간의 관계, 그리고 ROC Curve와 이를 Score로 환산한 AUC에 대해서 알아보려고 한다. 그리고 Scikit-learn을 통해서 구현하는 방법에 대해서도 소개한다.
목차는 다음과 같다.
1. Precision 과 Recall
2. Precision과 Recall의 Trade-off
3. F1 - Score
4. ROC Curve와 AUC
본격적인 내용에 들어가기 앞서 Cofusion Matrix에 대한 사전적인 지식이 필요하다. 이에 대한 개념을 모른다면 다음의 포스팅 내용들 중 Confusion Matrix에 대한 내용을 숙지하자.
#https://techblog-history-younghunjo1.tistory.com/60
1. Precision 과 Recall
보통 분류 결과 성능을 평가할 때 Accuracy(정확도)를 지표로 많이 쓰기도 한다. 하지만 이진분류(Binary classification)에서는 정확도를 지표로 잘 사용하지 않고 정밀도와 재현율을 가지고 평가를 한다. Precision은 우리말로 '정밀도', Recall은 '재현율' 이라고도 부른다. 재현율은 '민감도', '특이도' 라는 용어로도 불린다. 정밀도와 재현율에 대한 개념을 간단하게만 짚고 넘어가자.
- Precision(정밀도) : 모델이 True라고 예측했을 때, 실제값이 True인 비율
- Recall(재현율) : 실제값이 True일 때, 그 값들을 모델이 True라고 예측한 비율
위 Matrix를 기반으로 해서 정밀도에 대한 공식은 다음과 같다.
다음은 재현율에 대한 공식이다.
2. Precision 과 Recall의 Trade-off
정밀도와 재현율은 왜 개별적으로 측정하는 것일까? 이는 정의되는 문제의 종류에 따라 다르다. 재현율은 보통 실제값이 Positive일 때 예측한 값이 Positive일 경우가 매우 중요한 상황일 때 사용된다. 단적인 예로, 암 판단 분류 예측을 하는 문제에 있어서 실제로 암이 걸렸는데 예측을 암이 걸리지 않았다고 판단하게 되면 수반되는 위험이 매우 커지게 된다.
반대로 정밀도는 모델이 Positive라고 예측했을 때 실제값이 Negative일 때 위험이 수반되는 경우에 사용된다. 예시로는 스팸메일 분류를 들 수 있다. 만약 분류 모델이 중요한 업무내용을 담고 있는 메일(Negative)을 스팸메일(Positive)이라고 분류하게 된다면 중요한 업무내용이 전달되지 못하는 위험상황이 발생한다.
그런데 이 재현율과 정밀도 사이는 Trade-off라는 관계가 존재한다. Trade-off란, 쉽게 말해서 "두 마리 토끼를 다 잡을 순 없다" 라는 의미이다. 즉, 정밀도와 재현율을 둘 다 모두 최대값으로 할 수가 없다는 것이다. 정밀도가 극도로 높게 된다면 재현율은 낮아질 수 밖에 없고 반대로 재현율이 극도로 높게 된다면 정밀도값은 낮아질 수 밖에 없다. 마치 머신러닝에서 Bias와 Variance를 둘 다 동시에 높일 수 없는 것처럼 말이다.
이진분류인 상황에서 Positive(1) label로 분류하는 확률값의 디폴트값은 0.5이다. 이 디폴트값을 낮추고 높여줌으로써 정밀도와 재현율을 조절할 수가 있다. 이 디폴트값을 '분류 결정 임계값(threshold)' 라고 부른다. 만약 이 임계값을 0.5에서 0.4로 낮추어 준다면 정밀도와 재현율에는 어떤 변화가 발생할까?
임계값을 낮추게 된다는 것은 그만큼 모델이 Positive라고 예측하는 횟수가 많아진다는 것과 동일한 의미이다. 재현율의 의미는 실제값이 Positive인데 모델이 Positive로 예측한 비율이다. 따라서 분모의 FN(실제값이 Positive인데 모델이 Negative라고 예측한 횟수) 값이 낮아질 수 밖에 없다. 따라서 임계값을 낮추게 되면 재현율은 커진다.
반대로 정밀도는 모델이 Positive 라고 예측했을 때 실제값이 Positive인 비율이다. 그래서 분모의 FP(모델이 Positive라고 예측했을 때 실제값이 Negative인 횟수) 값이 커지게 된다. 따라서 임계값을 낮추게 되면 정밀도는 작아지게 된다.
이러한 임계치 변경을 Scikit-learn을 통해서 구현할 수 있다. 코드 예시는 Titanic 데이터를 사용했으며 Scikit-learn의 pred_prob() 함수와 Binarizer 라이브러리를 이용해 구현할 수 있다.
import numpy as np
import pandas as pd
import warnings
warnings.filterwarnings(action='ignore')
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
# 원본 데이터를 재로딩, 데이터 가공, 학습데이터/테스트 데이터 분할.
titanic_df = pd.read_csv('./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함수는 Confusion Matrix, Accuracy, Precision, Recall을 한 번에 출력하기 위해 정의한 함수.
get_clf_eval(y_test , pred)
위 코드는 기본적으로 설정된 임계치가 0.5인 값인 상태로 분류 성능을 평가했다. 평가결과는 다음과 같다.
다음은 임계치를 0.4로 설정한 후 분류 성능을 평가했다.
# 임계값을 0.4로 설정했을 때
from sklearn.preprocessing import Binarizer
custom_threshold = 0.4
pred_proba_1 = pred_proba[:,1].reshape(-1,1)
custom_predict = Binarizer(threshold=custom_threshold).fit_transform(pred_proba_1)
# 결과값이 달라짐
get_clf_eval(y_test, custom_predict)
결과값은 다음과 같으며 정밀도와 재현율의 값이 바뀐 것에 주목해보자.
또한 Scikit-learn에서 제공하는 precision_recall_curve() 함수를 사용해 각 임곗값에 따른 정밀도-재현율을 출력해볼 수 있다.
# precision_recall_curve를 이용해 임곗값에 따른 정밀도-재현율 값 추출
from sklearn.metrics import precision_recall_curve
# label이 Positive(1)일 때의 예측확률을 추출
pred_proba_class1 = lr_clf.predict_proba(X_test)[:, 1]
# precision_recall_curve는 실제값과 Positive예측확률을 인자로 넣고
# 정밀도, 재현율, 임곗값 3가지를 반환받음
precision, recalls, thresholds = precision_recall_curve(y_test,
pred_proba_class1)
print("임곗값 shape", thresholds.shape)
print("정밀도 shape", precision.shape)
print("재현율 shape", recalls.shape)
#반환된 임곗값 배열 row가 143건이므로 샘플로 10개 추출하되, 14번의 step으로 추출
thr_idx = np.arange(0, thresholds.shape[0], 14)
print("추출한 임곗값 인덱스", thr_idx)
print("샘플링한 10개 임곗값", np.round(thresholds[thr_idx], 2))
#위에 설정한 임곗값 인덱스에 매핑되는 정밀도와 재현율 출력
print("정밀도:", np.round(precision[thr_idx], 3))
print("재현율:", np.round(recalls[thr_idx], 3))
결과값들은 다음과 같다.
3. F1 - score
F1-score는 정밀도와 재현율이 어느 한쪽으로 치우치지 않는 수치를 나타낼 때 상대적으로 높은 값을 갖는다. 만약 A라는 모델의 정밀도가 0.9, 재현율이 0.1 이고 B라는 모델의 정밀도가 0.5, 재현율이 0.5라고 했을 때, A와 B모델의 F1-score는 각각 0.18, 0.5라는 값을 갖게 된다. 결국 B라는 모델이 더 높은 F1-score를 가지며 결국 더욱 더 객관적인 분류 성능을 갖고 있음을 알 수 있다.
F1-score도 Scikit-learn에서 f1_score() 라는 함수로 편리하게 제공해주고 있다.
from sklearn.metrics import f1_score
f1 = f1_score(y_test, pred)
print(f" F1-score : {f1:.2f}")
4. ROC Curve 와 AUC
ROC Curve와 AUC는 서로 동일하다고 볼 수 있다. 하지만 표현하는 방식에 차이가 있을 뿐이다. ROC Curve는 X축을 FPR(Fall-out)지표, Y축을 TPR(Recall)지표로 하는 그래프로 나타내는 방식이다. AUC는 이 ROC Curve를 점수로 환산한 방식이다. 값이 1에 가까울 수록 좋은 값이다.
#FPR지표란, 실제 데이터가 Negative인데 모델이 Positive라고 예측한 비율이다.
ROC Curve와 AUC도 Scikit-learn 라이브러리에서 편리하게 제공하고 있다.
from sklearn.metrics import roc_curve
# label이 Positive(1)일때의 예측확률을 추출
pred_proba_class1 = lr_clf.predict_proba(X_test)[:,1]
# roc_curve함수는 실제값, 예측확률값(1일때)를 인자로 주고 3개의 값들을 반환받는다.
# fpr : Fall-out
# tpr : Recall
fprs, tprs, thresholds = roc_curve(y_test, pred_proba_class1)
thr_idx = np.arange(0, thresholds.shape[0], 5)
print("임곗값 인덱스", thr_idx)
print("샘플용 10개 임곗값", np.round(thresholds[thr_idx], 2))
print("임곗값에 따른 FPR", np.round(fprs[thr_idx], 3))
print("임곗값에 따른 TPR", np.round(tprs[thr_idx], 3))
결과값은 다음과 같다. 이러한 결과값을 가지고 밑의 코드를 실행해 ROC Curve Plot을 그릴 수 있다.
# ROC curve 그래프 그리기
def roc_curve_plot(y_test, pred_proba_c1):
fprs, tprs, thresholds = roc_curve(y_test, pred_proba_c1)
# ROC Curve를 plot으로 그림
plt.plot(fprs, tprs, label='ROC')
# Random한 가운데선(0.5)을 직선으로 그림
plt.plot([0, 1], [0, 1], 'k--', label='Random')
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 score 출력
# roc_auc_score함수 사용
from sklearn.metrics import roc_auc_score
# roc_auc_score(y_test, predict_proba가 반환한 객체)를 인자로 한다.
pred_proba = lr_clf.predict_proba(X_test)[:,1]
roc_score = roc_auc_score(y_test, pred_proba)
print(f"ROC score : {roc_score:.3f}")
'Data Science > Machine Learning 구현' 카테고리의 다른 글
[ML] Regression metric과 Polynominal Regression 구현하기 (2) | 2020.08.05 |
---|---|
[ML] Scikit-learn을 이용한 Feature engineering 구현하기 (0) | 2020.08.03 |
[ML] Scikit-learn을 이용한 Stacking 구현하기 (4) | 2020.08.03 |
[ML] Scikit-learn을 이용해 Ensemble 모델들 구현하기 (0) | 2020.07.30 |
[ML] Cross validation과 GridSearch하는 방법 (0) | 2020.07.25 |