[Hands-on-ml-2] 모델 평가
핸즈온머신러닝 3장에서 MNIST 숫자 데이터를 분류하는 모델을 만들었습니다.
간편하게 알아보기 위해서 숫자가 5인지 아닌지를 분류하는 분류기를 SGDClassifier로 만들었습니다.
이제 다양한 모델 평가 지표를 활용하여 모델의 성능이 어떤지 평가할 것입니다.
각각의 모델 평가 지표에 대해 알아보겠습니다.
1. 교차 검증을 사용한 정확도 측정
k-fold 교차 검증
먼저, 모든 샘플을 k개의 크기가 같은 하위 샘플로 나눕니다.
이 k개의 샘플 세트는 돌아가면서 검정 세트가 됩니다. 이때 나머지 샘플들은 모델 훈련에 사용됩니다.
최종적으로 k번의 평가 지표의 평균값을 최종 평가 지표로 사용합니다.
사이킷런의 cross_val_cross() 함수로 폴드가 3개인 k-fold 교차 검증을 사용해 SGDClassifier 모델을 평가할 것입니다.
from sklearn.model_selection import cross_val_score
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring='accuracy')
≫ array([0.95035, 0.96035, 0.9604 ])
모든 교차 검증 폴드에 대해 정확도가 95% 이상입니다.
그런데 여기서 정확도의 한계성을 확인할 수 있습니다.
모든 이미지를 '5가 아니다'라고 분류하는 분류기를 만들어봅시다.
from sklearn.base import BaseEstimator
class Never5Classifier(BaseEstimator):
def fit(self, X, y=None):
return self
def predict(self,X):
return np.zeros((len(X),1), dtype=bool)
이 분류기의 정확도를 측정해보겠습니다.
never_5_clf=Never5Classifier()
cross_val_score(never_5_clf, X_train, y_train_5, cv=3, scoring='accuracy')
≫ array([0.91125, 0.90855, 0.90915])
위와 같이 정확도가 90% 이상으로 나왔습니다. 이미지의 10% 정도만 숫자 5이기 때문에 무조건 '5가 아니다'라고 예측하면 정확히 맞출 확률이 90%입니다. 즉, 위에서 SGDClassifier로 예측한 것이 그렇게 좋은 결과는 아니라는 것입니다. 모두 5가 아니라고 해도 정확도는 90%이기 때문이죠!
정확도(accuracy)의 정의를 살펴보면 정확하게 분류된 샘플 개수를 총 샘플 개수로 나눈 것입니다.
$$Accuracy = {n_{correct} \over n_{total}}$$
정확도는 분류 문제에서 가장 간단하고 직관적인 평가 지표지만 명백한 단점이 있습니다.
정확도는 불균형 데이터의 영향을 많이 받게 됩니다.
2. 오차 행렬
기본적인 아이디어는 클래스 A의 샘플이 클래스 B로 분류된 횟수를 세는 것입니다.
오차 행렬을 만들려면 실제 타깃과 비교할 수 있도록 먼저 예측값을 만들어야 합니다.
cross_val_predict() 함수 사용합니다.
# 평가 점수를 반환하지 않고 각 테스트 폴드에서 얻은 예측을 반환한다.
from sklearn.model_selection import cross_val_predict
from sklearn.metrics import confusion_matrix
y_train_pred=cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)
confusion_matrix(y_train_5, y_train_pred)
≫ array([[53892, 687],
[1891, 3530]])
오차 행렬의 행은 실제 클래스를 나타내고 열은 예측한 클래스를 나타냅니다.
3. 정밀도와 재현율
정밀도(precision)
분류기가 양상 샘플이라고 분류한 것 중에 실제 양성 샘플인 것의 비율
정밀도 $= {TP \over {TP+FP}}$
재현율(recall)
실제 양성 샘플인 것 중에서 분류기가 정확히 분류해 낸 양성 샘플의 비율
민감도, 진짜 양성 비율(TPR)
재현율 $= {TP \over {TP+FN}}$
파이썬에서 sklearn을 이용하여 정밀도와 재현율을 구할 수 있습니다.
from sklearn.metrics import precision_score, recall_score
precision_score(y_train_5, y_train_pred) # 3530 / (687 + 3530)
≫ 0.8370879772350012
recall_score(y_train_5, y_train_pred) # 3530 / (1891 +3530)
≫ 0.6511713705958311
F1 score
정밀도와 재현율의 조화평균입니다.
$F1 = {2 \over {{1 \over precision}+{1 \over recall}}} = {2*{precision*recall} \over {precision+recall}} = {TP \over {TP + {{FN+FP} \over 2}}}$
정밀도와 재현율이 비슷한 분류기에서는 F1 score가 높습니다.
하지만 이게 항상 바람직한 것은 아닙니다.
상황에 따라 정밀도가 중요할 수도 있고 재현율이 중요할 수도 있습니다.
파이썬에서 sklearn을 이용하여 F1 score를 구하는 방법은 다음과 같습니다.
from sklearn.metrics import f1_score
f1_score(y_train_5, y_train_pred)
≫ 0.7325171197343846
4. 정밀도/재현율 트레이드오프
SGDClassifier가 분류를 어떻게 결정하는지 살펴보며 이 트레이드오프를 이해해볼 것입니다.
이 분류기는 결정함수(decision function)을 사용하여 각 샘플의 점수를 계산합니다.
이 점수가 임계값보다 크면 샘플은 양성 클래스에 할당하고 그렇지 않으면 음성클래스에 할당합니다.
임계값이 높을수록 재현율은 낮아지고 반대로 (보통) 정밀도는 높아집니다. (진짜 양성이 가짜 양성이 될 수 있으므로)
반대로 임계값을 내리면 재현율이 높아지고 정밀도가 줄어듭니다.
P-R 곡선
x축은 재현율, y축은 정밀도입니다.
def plot_precision_vs_recall(precisions, recalls):
plt.plot(recalls, precisions, "b-", linewidth=2)
plt.xlabel("Recall", fontsize=16)
plt.ylabel("Precision", fontsize=16)
plt.axis([0, 1, 0, 1])
plt.grid(True)
plt.figure(figsize=(8, 6))
plot_precision_vs_recall(precisions, recalls)
plt.plot([0.4368, 0.4368], [0., 0.9], "r:")
plt.plot([0.0, 0.4368], [0.9, 0.9], "r:")
plt.plot([0.4368], [0.9], "ro")
plt.show()
P-R곡선은 임계값을 높은 곳에서 낮은 곳으로 이동시키며 만들어집니다.
하강점 직전을 정밀도/재현율 트레이드오프로 선택하는 것이 좋습니다. 물론 이런 선택은 프로젝트에 따라 달라집니다.
5. ROC 곡선
ROC 곡선은 Receiver Operating Characteristic Curve의 약자입니다.
ROC 곡선은 원래 군사 영역에서 유래된 개념으로 나중에는 의학 영역에서 발전하였습니다.
ROC 곡선의 가로축은 거짓 양성 비율(FPR)을 나타내고
세로축은 실제 양성비율(TPR)을 나타냅니다.
$FPR={FP \over N}$
$TPR={TP \over P}$
P는 실제 양성 샘플 수
N은 실제 음성 샘플 수
TP는 P개의 양성 샘플 중에서 분류기가 양성 샘플로 예측한 샘플의 개수
FP는 N개의 음성 샘플 중에서 분류기가 양성 샘플로 예측한 샘플의 개수
예를들어 10명의 암 의심 환자가 있는데 여기서 3명이 실제 암에 걸렸다고 가정합시다(P=3).
그 외 7명은 암에 걸리지 않았습니다(N=7).
병원에서 10명을 진단해서 3명의 암 환자가 있다고 결론을 내렸습니다. 하지만 여기서 실제 암환자는 2명뿐입니다.(TP=2).
이런 상황이라면 다음 표에서도 볼 수 있듯이 실제 양성 비율 TPR=TP/P=2/3입니다.
거짓양성비율 FPR=FP/N=1/7입니다.
해당 병원의 진단 자체를 하나의 분류기로 생각한다면, 이 분류기의 분류 결과는 ROC 곡선상의 점(1/7.2/3)이 됩니다.
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--') # dashed diagonal
plt.axis([0, 1, 0, 1])
plt.xlabel('False Positive Rate (Fall-Out)', fontsize=16)
plt.ylabel('True Positive Rate (Recall)', fontsize=16)
plt.grid(True)
plt.figure(figsize=(8, 6))
plot_roc_curve(fpr, tpr)
plt.plot([4.837e-3, 4.837e-3], [0., 0.4368], "r:")
plt.plot([0.0, 4.837e-3], [0.4368, 0.4368], "r:")
plt.plot([4.837e-3], [0.4368], "ro")
plt.show()
여기서도 트레이드오프가 있습니다.
재현율(TPR)이 높을수록 분류기가 만드는 거짓 양성(FPR)이 늘어납니다.
점선은 완전한 랜덤 분류기의 ROC 곡선을 뜻합니다.
좋은 분류기는 점선에서 최대한 멀리 떨어져 있어야 합니다.
곡선 아래의 면적(AUC, Area Under Curve)을 측정하면 분류기들을 비교할 수 있습니다.
여기서 AUC값은 ROC 곡선의 x축을 따라 적분을 해주면 됩니다.
AUC기 0.5이면 랜덤에 가까운 성능, 1이면 최고의 성능임을 나타냅니다.
※ ROC 곡선과 P-R 곡선 비교
양성, 음성 샘플의 분포에 변화가 생겼을 때 ROC 곡선의 형태는 기본적으로 변하지 않고 유지되지만, P-R 곡선의 형태는 일반적으로 급격한 변화를 보입니다.
이러한 특징은 ROC 곡선이 다양한 테스트 세트를 만날 때마다 견고한 결과를 보여줄 수 있게 만들어 더 객관적으로 모델 자체의 성능을 평가할 수 있게 해줍니다.
출처
[1] 핸즈온머신러닝2
'Deep Learning > 딥러닝 기초' 카테고리의 다른 글
[패캠] Autoencoders (0) | 2021.12.14 |
---|---|
[패캠] Dimension Reduction (1) | 2021.11.03 |
[hands-on-ml-2] 범주형 데이터 다루기 (0) | 2020.07.25 |
[모두를 위한 머신러닝/딥러닝] ML (0) | 2020.07.09 |
댓글