[C03 분류]02 이진분류기 성능측정 방법
train test set 준비
import numpy as np
from sklearn.datasets import fetch_openml
mnist = fetch_openml('mnist_784', version=1, as_frame=False, parser='auto')
X, y = mnist["data"], mnist["target"]
y = y.astype(np.uint8)
X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]
y_train_5 = (y_train == 5) # 5는 True, 다른 숫자는 모두 False
y_test_5 = (y_test == 5)
훈련
from sklearn.linear_model import SGDClassifier
sgd_clf = SGDClassifier(random_state=42)
sgd_clf.fit(X_train, y_train_5)
교차 검증을 통한 정확도 측정
from sklearn.model_selection import cross_val_score
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")
교차 검증 정확도 측정의 문제점
- 확실하게 5인 것 빼고 나머지를 다 5가 아니라고 해도 90% 이상의 정확도를 얻기 매우 쉽다
- 예를 들어 모두 ‘5가 아니다’ 라고 답하는 모델의 정확도도 90%가 나올 것이다.
시사점
- 분류기에서 정확도로 성능을 측정하는 것은 큰 의미가 없는 경우가 많다
- 불균형한(특정 클래스가 다른 것보다 월등히 많은 경우) 데이터셋을 다룰 때 특히 그렇다.
더 나은 방법: Confusion Matrix(오차 행렬)
- 다음 네 가지로 분류하는 것이다
- 뒷글자는 뭐라고 예측했는지 (Positive vs Negative)
- 앞글자는 그 예측이 맞았는지 (True vs False)
- TP: 긍정예측이 사실이었을 경우(사실:긍정) (True Positive)
- FP: 부정예측이 틀렸을 경우(사실:부정) (False Positive)
- TN: 긍정예측이 사실이었을 경우(사실:부정) (True Negative)
- FN: 부정예측이 틀렸을 경우(사실:긍정) (False Negative)
from sklearn.model_selection import cross_val_predict
y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)
from sklearn.metrics import confusion_matrix
confusion_matrix(y_train_5, y_train_pred)
confusion matrix 해석
- 열(가로, column)은 예측값: Negative, Positive
-
행(세로, row)은 실제값: Negative, Positive
- (0,0)은(왼쪽위 ) Negative를 Negative로 예측했으므로 True Negative
- (0,1)은(왼쪽아래) Positive를 Negative로 예측했으므로 False Negative
- (1,0)은(오른위 ) Negative를 Positive로 예측했으므로 False Positive
- (1,1)은(오른아래) Positive를 Positive로 예측했으므로 True Positive
정밀도와 재현율
정밀도(precision)
- 양성 예측의 정확도
- Positive로 예측한 것이 맞았을 확률
- TP / (TP + FP)
재현율(recall)
- 민감도(sensitivity) 또는 진짜 양성 비율(true positive rate)라고도 함
- 실제 Positive인 것 중 얼마나 많이 Positive로 예측했나
- TP / (FN + TP)
정밀도와 재현율을 같이 봐야 하는 이유
- 숫자 5가 2000개, 5가 아닌것이 8000개 있다고 가정
- 확실한 하나만 5라고 예측하고 나머지는 다 5가 아니라고 예측한 경우
- 정밀도는 1/1로 100%가 됨
- 재현율을 보면, 1/2000으로 매우 낮음
- 즉, 확실하지 않아도 과감하게 Positive로 예측하면 정밀도는 낮아지지만 재현율은 높아짐
- 정말 확실한 것만 Positive로 예측하면 정밀도는 높아지지만 재현율은 낮아짐
from sklearn.metrics import precision_score, recall_score
print('precision_score:', precision_score(y_train_5, y_train_pred))
print('recall_score:', recall_score(y_train_5, y_train_pred))
- 5라고 답한 것 중에서 83.7%가 실제 5였다
- 전체 숫자 5 중에서 2/3만 5라고 답했다
(암 진단이었다면)
- 암이라고 판단한 환자 중 83.7%가 실제 암이었다
- 전체 암환자 중 2/3만 암이라고 진단했다 > 1/3 환자는 암이 아니라고 진단해 치료 기회를 놓쳤다!
F1 Score
- 정밀도와 재현율의 조화 평균
- 둘 다 점수가 높아야 높게 나옴
- 하나는 높고 하나는 중간정도라면 점수가 확 떨어짐
F1이 항상 옳은가?
- 예를 들어 어린아이에게 안전한 영상을 걸러낸다면
- 좋은 동영상은 다 잡아내지만 때때로 나쁜 영상이 노출되는 것(높은 재현율, 조금 떨어지는 정밀도)보다
- 좋은 동영상을 더 많이 놓치더라도 나쁜 영상을 확실히 거르는 것(낮은 재현율, 높은 정밀도)가 더 나을 것
- 위는 ‘좋은 동영상’을 Positive로 봤을 경우임
from sklearn.metrics import f1_score
f1_score(y_train_5, y_train_pred)
정밀도 vs 재현율: Threshold(임계값)
- 분류기는 결정 함수(decition function)를 사용하여 각 샘플의 점수를 계산한다
- 예를 들어 5인지 판별하는 분류기는, 이 이미지가 몇 점 정도로 5라고 예측되는지를 계산하는 결정 함수를 돌리는 것이다
- 임계값은 점수가 얼마를 넘어야 Positive로 판별할지를 정해주는 값이다.
- 예를 들어 일반적으로 점수가 100~300인데 임계값이 290이라면 아주아주 확실한 것만 Positive로 판별할 것이다.
- 따라서 임계값이 높아지면 일반적으로 재현율은 낮아지고 정밀도는 높아진다
- 이 트레이드오프를 곡선으로 그려 최적점을 결정(개발자의 선택이다)할 수 있다
임계값을 직접 정하기
- 임계값을 직접 정하는 것은 불가능
- decision_function() 메서드를 호출해 각 샘플의 ‘점수’를 얻을 수 있는데
- 이를 이용해 직접 예측을 만들면 됨
y_some_scores = sgd_clf.decision_function(X[:12])
print('scores:', y_some_scores)
threshold = 3000
y_some_pred = (y_some_scores > threshold)
print('pred:', y_some_pred)
- 원래라먼 첫번째도 양수이므로 True로 예측했어야 했다.
- 임의로 임계값을 3000으로 정했기 때문에 False로 판별했다
- 사실은 True가 맞기 때문에 놓친 것, 즉 재현율이 줄어든 것이다
정밀도와 재현율을 시각화해 적절한 임계값 정하기
- cross_val_predict() 함수를 통해 훈련 세트에 있는 모든 샘플의 점수를 구해야 한다.
- 예측 결과가 아니라 결정 점수를 반환받도록 method=’decision_function’ 파라미터를 지정해야 한다.
- precision_recall_curve() 함수를 사용해 가능한 모든 임곗값에 대해 정밀도와 재현율을 계산 가능하다
- 계산된 결과를 matplotlib을 통해 그린다
# 적절한 임계값을 정하기 위해 훈련 세트에 있는 모든 샘플의 점수를 구하기
y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3, method="decision_function")
# 모든 임계값에 대해 정밀도와 재현율을 계산
from sklearn.metrics import precision_recall_curve
precisions, recalls, thresholds = precision_recall_curve(y_train_5, y_scores)
# 임계값에 대한 정밀도와 재현율 그래프 그리기
import matplotlib.pyplot as plt
plt.rcParams["font.family"] = "AppleGothic"
plt.rcParams['axes.unicode_minus'] = False # 마이너스 기호 깨짐 방지
def plot_precision_recall_vs_threshold(precisions, recalls, thresholds):
plt.plot(thresholds, precisions[:-1], 'b--', label='정밀도')
plt.plot(thresholds, recalls[:-1], 'g-', label='재현율')
plt.legend()
plt.grid()
plt.xlabel('임계값')
plt.show()
plot_precision_recall_vs_threshold(precisions, recalls, thresholds)
임계값이 오를 때 정밀도는 낮아질 수 있다
- 임계값을 올릴 때 재현율은 절대 낮아질 수 없지만 정밀도는 낮아질 수 있음
- 예를 들어 임계값이 높아져 Positive 예측의 전체 갯수 자체가 5개에서 4개로 줄어든다면
- 4/5 (80%)에서 3/4(75%)로 정밀도가 떨어짐
# 재현율에 대한 정밀도 곡선 그리기
def plot_precision_vs_recall(precisions, recalls):
plt.plot(recalls, precisions, 'b-')
plt.xlabel('재현율')
plt.ylabel('정밀도')
plt.grid()
plt.show()
plot_precision_vs_recall(precisions, recalls)
프로젝트 목표에 따른 임계값 설정하기
- 정밀도 90%가 목표라면
- 정밀도 90%를 달성하는 가장 낮은 임계값을 아래와 같이 사용할 수 있음
threshold_90_precision = thresholds[np.argmax(precisions >= 0.90)] # 90% 정밀도를 달성하는 가장 낮은 임계값
y_train_pred_90 = (y_scores >= threshold_90_precision) # 임계값보다 크거나 같은 점수를 갖는 샘플들을 True로 반환
print('정밀도:', precision_score(y_train_5, y_train_pred_90))
print('재현율:', recall_score(y_train_5, y_train_pred_90))
이것이 안전한 영상을 찾아내는 분류기였다면, 안전한 영상의 절반 이상을 포기하는 대신 나쁜 영상 비율을 10% 미만으로 유지하는 것
ROC (Receiver Operating Characteristic) 곡선
- “최대한 좋은 영상을 안 버리고 싶다”(재현율을 높이고 싶다) 가 목표일 때
- 그러면 원래 나쁜 영상(Negative)인데 좋은 영상(Positive)으로 잘못 분류되는 비율이 얼마나 늘어나는지를 보는 곡선
- 거짓 양성 비율(FPR) 대비 진짜 양성 비율(TPR = 재현율)을 보는 곡선
- FPR은 N을 P로 분류한 비율이기 때문에, 1 - TNR(N을 N으로 분류한 비율)과 일치함
- TNR을 특이도(specificity)라고 함
- 즉 ROC 곡선은 민감도(재현율)에 대한 1-특이도 그래프
from sklearn.metrics import roc_curve
fpr, tpr, thresholds = roc_curve(y_train_5, y_scores)
def plot_roc_curve(fpr, tpr, label=None):
plt.plot(fpr, tpr, linewidth=2, label=label)
plt.plot([0,1], [0,1], 'k--') # 대각 점선
plt.xlabel('거짓 양성 비율(FPR)')
plt.ylabel('진짜 양성 비율(TPR, 재현율)')
plt.grid()
plot_roc_curve(fpr, tpr)
대략적인 해석
- 재현율을 0.9 이상 높이기 시작하면 FPR이 가파르게 증가한다
ROC vs 정밀도/재현율 곡선, 어느 것을 보나요?
- 양성(Positive)이 드물거나, 거짓 음성보다 거짓 양성이 중요할 때 PR 곡선을 사용
- 그 반대는 ROC를 사용
- 예를 들어 이 분류기는 ROC 곡선이 아주 훌륭하게 나옴(FPR을 낮게 유지하고도 TPR을 끌어올릴 수 있음)
- 그런데 이것은 Positive(is 5?)가 드물기 때문
- 이런 경우에는 정밀도/재현율 곡선을 확인하는 것이 의사결정에 훨씬 도움이 됨
- 최대한 나쁜 영상을 확실하게 제거하고 싶다 = False Positive를 낮게 컨트롤하고싶다 = 정밀도/재현율 곡선
-
최대한 좋은 영상을 많이 확보하고 싶다 = False Negative를 낮게 컨트롤하고 싶다(=True Positive를 높게 컨트롤하고 싶다) = ROC곡선
- ‘시리야’ 라고 부르는 것에 대답하는 음성 인식기를 만든다
- ‘시리야’ 라고 부르는 것을 Positive로 놓고 이진분류하는 판별기를 만든다
- 시리라고 불렀을때 종종 대답 안하더라도, ‘싫어’ 등 다른 말을 했을때 잘못 반응하는 경우를 줄이고 싶다 = False Positive를 줄이고 싶다(시리야가 아닌데 시리야로 판별하는 경우 줄이기)
-
이럴때는 정밀도/재현율 곡선을 확인하면서 조정
- 종종 다른 말을 했을때 잘못 응답하더라도, 시리야라고 불렀을 때 제깍제깍 대답하게 하고 싶다면 = False Negative를 줄이고 싶다(시리야라고 불렀는데 시리야가 아니라고 판별하는 경우 줄이기)
- 이럴때는 ROC 곡선을 확인하면서 조정
두 개 모델의 ROC곡선 비교하기
RandomForest에서 훈련 세트의 샘플에 대한 점수 얻기
- 작동 방식의 차이 대문에 RandomForest에는 decision_function() 메서드가 없다
- 대신 predict_proba() 메서드가 있다
- 사이킷런 분류기는 일반적으로 이 두 메서드 중 하나 또는 둘 모두를 가지고 있다
- predict_proba()는 샘플이 row, 클래스가 column으로 하여 샘플이 주어진 클래스에 속할 확률을 담은 배열을 반환한다
from sklearn.ensemble import RandomForestClassifier
forest_clf = RandomForestClassifier(random_state=42)
y_probas_forest = cross_val_predict(forest_clf, X_train, y_train_5, cv=3, method='predict_proba') # 예측 확률 반환
print('y_probas_forest:', y_probas_forest[:5])
# 2차원 배열로, 1열은 5가 아닌 것을 의미하는 음성 클래스에 대한 확률, 2열은 양성 클래스에 대한 확률
y_scores_forest = y_probas_forest[:, 1] # 양성 클래스에 대한 확률을 점수로 사용
fpr_forest, tpr_forest, thresholds_forest = roc_curve(y_train_5, y_scores_forest)
plt.plot(fpr, tpr, 'b:', label='SGD')
plot_roc_curve(fpr_forest, tpr_forest, '랜덤 포레스트')
plt.legend(loc='lower right')
결과
- 랜덤포레스트의 ROC곡선이 왼쪽 위 모서리에 훨씬 가깝다 = 거짓 양성 비율을 낮게 유지하면서 재현율을 훨씬 더 높게 올릴 수 있다
Leave a comment