노션 정리 : https://www.notion.so/bmo29/chapter-3-0b3c96ef54fc492aa5bbbff553dc8312
분류기 평가는 회귀 모델보다 훨씬 어렵다. 따라서 사용할 수 있는 성능 지표가 많다.
교차 검증 구현을 사이킷런의 cross_val_score()
을 통해 구현할 수 있다.
for train_index, test_index in skfolds.split(X_train, y_train_5):
clone_clf = clone(sgd_clf)
X_train_folds = X_train[train_index]
y_train_folds = y_train_5[train_index]
X_test_fold = X_train[test_index]
y_test_fold = y_train_5[test_index]
clone_clf.fit(X_train_folds, y_train_folds)
y_pred = clone_clf.predict(X_test_fold)
n_correct = sum(y_pred == y_test_fold)
print(n_correct / len(y_pred))
StratifiedKFold
는 클래스별 비율이 유지되도록 폴드를 만들기 위해 계층적 샘플링을 수행한다.
매 반복에서 분류기 객체를 복제하여 훈련 폴드로 훈련하고(clone_clf.fit(X_train_folds, y_train_folds)
) 테스트 폴드로 예측을 만든다(y_pred = clone_clf.predict(X_test_fold)
). 마지막으로는 올바른 예측의 수를 세어 정확한 예측의 비율을 출력받는다(n_correct = sum(y_pred == y_test_fold)
).
k-겹 교차 검증
은 훈련 세트를 k개의 폴드로 나누고 각 폴드에 대해 예측을 만들고 평가하기 위해 나머지 폴드로 훈련시킨 모델을 사용한다.
from sklearn.model_selection import cross_val_score
cross_val_score(sgd_clf, X_train, y_train_5, cv = 3, scoring = "accuracy")
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")
모델의 정확도
; accuracy 는 다음과 같이 나온다.
숫자 10개 중에 1개가 5이니까 나머지 5가 아닌 집단은 90%가 맞다.
정확도를 분류기의 성능 측정 지표로 선호하지 않는 이유를 보여준다. 특히 불균형한 데이터셋
을 다룰 때 (즉, 어떤 클래스가 다른 것보다 월등히 많은 경우) 특히 그렇다.
분류기의 성능을 평가하는 방법으로는 오차 행렬
; confusion matrix 를 조사하는 것이다. 기본적인 아이디어로 클래스 A의 샘플이 클래스 B로 분류되는 횟수를 세는 것이다. → 정답은 행으로 오답은 열로, (정답 행, 오답 열)로 확인할 수 있다.
오차 행렬을 만드려면 실제 타깃과 비교할 수 있도록 먼저 예측값을 만들어야 한다. 테스트 세트로는 출시 준비를 마치고 나서 프로젝트의 맨 마지막에 사용되어야 하므로 건드리면 안되고 대신 cross_val_predict()
함수를 이용한다.
from sklearn.model_selection import cross_val_predict
y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)
cross_val_score()
함수처럼 cross_val_predict()
함수는 k-겹 교차 검증을 수행하지만 평가 점수를 반환하지 않고 각 테스트 폴드에서 얻은 예측을 반환한다. 즉, 훈련 세트의 모든 샘플에 대해 깨끗한(== 깨끗하다는 말인 즉슨 모델이 훈련하는 동안 한번도 보지 못했던 데이터에 대해 예측했다는 것) 예측을 얻게 된다는 소리다.
confusion_matrix()
를 사용해 오차 행렬을 만들자. 타깃 클래스(y_train_5
)와 예측 클래스(y_train_pred
)를 넣고 호출하면 된다.
오차 행렬의 행은 실제 클래스를 나타내고 열은 예측한 클래스를 나타낸다.
이 행렬의 첫번째 행은 '5 아님'에 대한 이미지 음성 클래스
; negative class 에 대한 것이고 53892개를 '5 아님' 클래스로 정확히 분류한(진짜 음성
; true negative)이고 나머지 687를 '5'라고 잘못 분류한 거짓 분류
; false negative 이다.
두번째 행은 '5' 이미지 양성 클래스
; positive class 에 대한 것으로, 1891개를 '5 아님'으로 잘못 분류한 거짓 음성
; false negative 이고 나머지 3530개를 정확히 '5'로 분류한 진짜 양성
; true positive 이다.
완벽한 분류기라면 진짜 양성과 진짜 음성만을 가지고 있을 것이므로 다음과 같이 오차 행렬의 주대각선만 0이 아닌 값이 된다.
이보다 더 요약된 지표가 필요할 때 양성 예측의 정확도를 보여주는 분류기의 정밀도
; prediction 을 확인해보자.
TP
는 진짜 양성의 수이고 FP
는 거짓 양성의 수다.
정밀도는 재현율
; recall 이라는 지표와 같이 사용한다. 재현율은 분류기가 정확하게 감지한 양성 샘플의 비율로 민감도
; sensitivity 또는 진짜 양성 비율
; true positive rate (TPR) 이라고 한다.
FN
은 거짓 음성의 수이다.
정밀도와 재현율을 점수 를 통해 하나의 숫자로 만들어 편리하게 사용한다. 특히 두 분류기를 비교할 때 그렇다. 점수는 정밀도와 재현율의 조화 평균
; harmonic mean 이다.
score은 다음과 같다.
>>> cm[1, 1] / (cm[1, 1] + (cm[1, 0] + cm[0, 1]) / 2)
다음과 같이 입력하여도 같은 값이 나온다. 이 코드는 위의 맨 오른쪽 공식을 참고한 것이다.
정밀도와 재현율이 비슷한 분류기는 F1 점수가 높다. 하지만 이게 항상 바람직하지는 않다. 상황에 따라 정밀도 또는 재현율이 중요할 수도 있다.
그러나 정밀도를 올리면 재현율이 줄고 그 반대도 마찬가지다. 이를 정밀도/재현율 트레이드오프
라고 한다.
SGDClassifier
가 분류를 어떻게 결정하는지 살펴보자. 이 분류기는 결정 함수
; decision function 을 사용하여 각 샘플의 점수를 계산한다. 이 점수가 임곗값보다 크면 샘플을 양성 클래스에 할당, 낮으면 음성 클래스에 할당한다. 여러 임계값 중 결정 임곗값
; decision threshold 를 설정하여 정밀도와 재현율을 조정한다.
보통 임곗값이 높을수록 재현율(감지)은 낮아지고 반대로 보통 정밀도(정확)는 높아진다.
각 샘플의 점수를 확인할 수 있다.
만약 임곗값을 0으로 만든다면 predict() 메서드는 True 결괏값을 보인다.
임곗값을 높이면 재현율(감지)이 줄어든다는 것을 볼 수 있다.
이미지가 실제로 숫자 5일 때 임곗값이 0 이라면 분류기가 이를 감지했지만, 임곗값을 8000으로 높이면 이를 놓치게 된다.
y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3,
method="decision_function")
적절한 임곗값을 설정하기 위해 cross_val_predict 함수를 이용하여 모든 샘플의 점수를 구한다. 여기서 예측 결과가 아닌 결정 점수를 반환받는다.
from sklearn.metrics import precision_recall_curve
precisions, recalls, thresholds = precision_recall_curve(y_train_5, y_scores)
def plot_precision_recall_vs_threshold(precisions, recalls, thresholds):
plt.plot(thresholds, precisions[:-1], "b--", label="Precision", linewidth=2)
plt.plot(thresholds, recalls[:-1], "g-", label="Recall", linewidth=2)
plot_precision_recall_vs_threshold(precisions, recalls, thresholds)
plt.show()
왜 정밀도 곡선이 재현율 곡선보다 울퉁불퉁할까? 임곗값을 올리더라도 정밀도가 낮아지는 일이 있기 때문. (보통은 높아져야 한다.)
또는 정밀도/재현율 트레이드오프를 선택하기 위해 재현율에 대한 정밀도 곡선을 그리는 것도 방법이다.
재현율 80% 근처에서 정밀도가 급격하게 줄어드는 것을 볼 수 있는데 이 하강점 직전을 정밀도/재현율 트레이드오프로 선택하는 것이 좋다.
정밀도 90%를 달성하는 것을 목표로 하자.
threshold_90_precision = thresholds[np.argmax(precisions >= 0.90)]
다음과 같이 90% 이상의 정밀도를 갖는 임곗값을 찾은 후, 훈련 세트에 대한 예측을 만들기 위해 predict 함수를 호출하는 대신 다음과 같이 부등호(≥)를 이용해 분류한다. 그 후정밀도와 재현율을 확인한 사진이다.
수신기 조작 특성
; receiver operating characteristic (ROC) 곡선은 이진 분류에서 널리 사용하는 도구다. 정밀도/재현율 곡선과 매우 비슷하지만, ROC 곡선은 정밀도에 대한 재현율 곡선이 아니고 거짓 양성 비율
; false positive rate (FPR)에 대한 진짜 양성 비율
; true negative rate (TNR)의 곡선이다.
양성으로 잘못 분류한 음성 샘플의 비율을 FPR이라 하고 이는 1에서 음성으로 정확하게 분류한 음성 샘플의 비율 TNR을 뺀 값이다.
TNR
을 특이도
; specify 이라고 한다. 그러므로 ROC는 민감도(재현율)에 대한 1-특이도
그래프이다.
여러 임계삾에서 TPR과 FPR을 계산할 수 있다.
from sklearn.metrics import roc_curve
fpr, tpr, thresholds = roc_curve(y_train_5, y_scores)
모든 가능한 임곗값에서 진짜 양성 비율(TPR)에 대한 거짓 양성 비율(FPR)을 나타낸 곡선이다.
→ TPR 즉 민감도는 재현율이라고도 부르는데 이는 분류기가 정확 하게 감지한 양성 샘플의 비율을 말한다. 이를 진짜 양성 비율이라고 부른다. 거짓 양성 비율 FPR은 거짓 양성 수(FP)와 진짜 음성 수(TN)를 더해 총 거짓 양성 수로 나눈 값인데 양성 클래스에서 잘못된 예측의 비율이라 보는 것이 쉽다. 모르겠다면 다음을 참고해라 : 혼동행렬_기억법 , 각 용어 정리
재현율(TPR)이 높을 수록 분류기가 만드는 FPR이 늘어난다. 점선은 완전한 랜덤 분류기의 ROC곡선을 의미하는데 좋은 분류기는 최대한 이 점선으로부터 멀리 떨어져 있어야 한다.
곡선 아래 면적
; area under the curve (AUC) 을 통해 분류기들을 비교할 수 있다. 완벽한 분류기는 ROC의 AUC가 1이고 완전한 분류기는 0.5이다.
일반적으로 양성 클래스가 드물거나 거짓 음성보다 거짓 양성이 더 중요하다면 PR 곡선을 사용한다 그렇지 않으면 ROC 곡선을 사용한다.
predict_proba()
메서드는 샘플이 행, 클래스가 열이고 샘플이 주어진 클래스에 속할 확률을 담은 배열을 반환한다.
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")
y_scores_forest= y_probas_forest[:, 1]
fpr_forest, tpr_forest, thresholds_forest = roc_curve(y_train_5, y_scores_forest)
AUC 값이 큰 것이 좋다. 따라서 이 그래프 기반으로는 Random Forest가 더 좋다는 것을 알 수 있다.