본문 바로가기
ML&DATA/핸즈온 머신러닝

3 - 분류기의 성능 측정

by sun__ 2020. 8. 25.

 

분류기(classifier)의 성능을 측정하기 위해 정확도측정, 오차행렬조사, 정밀도와 재현율측정, ROC 곡선 조사 등을 할 수 있다.

 


https://velog.io/@skyepodium/K-Fold-%EA%B5%90%EC%B0%A8%EA%B2%80%EC%A6%9D

(참고 블로그)

 

 

<mnist 데이터셋>

mnist 손글씨 데이터셋에 대해 SGD분류기(확률적 경사 하강법 분류기)의 성능을 측정해보자.

 

 

데이터 가져오기

from sklearn.datasets import fetch_openml

mnist = fetch_openml('mnist_784',version=1)
mnist.keys()
# dict_keys(['data', 'target', 'feature_names', 'DESCR', 'details', 'categories', 'url'])

X, y =mnist['data'], mnist['target']
X.shape # (70000, 784)
y.shape # (70000, )

 

데이터 예시. 28px*28px의 정보가 크기가 784인 1차원 벡터에 저장돼 있다. 

 

mnist 데이터셋은 인덱스 60000을 기준으로 train set과 test set을 나눌 수 있도록 잘 섞여있다.

SGDClassifier로 어떤 글자가 5인지 5가 아닌지를 분류해내는 이진 분류기를 만든다.

위 손글씨가 5라는 것을 잘 분류한다.

from sklearn.linear_model import SGDClassifier

sgd_clf = SGDClassifier(random_state=42)
sgd_clf.fit(X_train, y_train_5) #train set으로 학습시킴

sgd_clf.predict([X[0]]) #array([ True])

 

 

1. 교차 검증으로 정확도 측정

더보기

일반적인 의미의 교차 검증은 '하나의 주장을 서로 다른 시각,자료를 토대로 검증(정확성을 높이는)하는 검사 방법'이다.

머신러닝에서의 교차검증은 test set에 과대적합되는 것을 피하기 위해 train set에서 일부를 validation set으로 떼어서 성능을 평가하는 방법들을 총칭하는 것으로 이해했다.

 

가장 단순한 형태의 교차검증은 hold out 검증이다. validation set에 과대적합될 위험이 있다. 잘 안쓰는것 같다.

 

가장 일반적인 교차검증은 k-fold 교차검증이다. 이때 정확도는 각 경우마다의 정확도의 평균으로 계산한다.

정확도는 단순히 맞춘예측/전체크기 로 계산한다.

 

기본적으로 sklearn이 제공하는 cross_val_score()함수로 교차 검증하여 정확도를 간단히 구할 수 있다.

모델, 훈련 데이터, 라벨, 폴드 수, scoring=정확도 를 파라미터로 넘겨준다.

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 ])

 

가끔 sklearn이 제공하는 기능보다 교차 검증 과정을 더 많이 제어해야 할 필요가 있다. 이 때는 교차 검증 기능을 직접 구현하면 된다. 아래는 각 폴드마다 5인 것과 5가 아닌 것의 비율을 모비율과 맞춰준 예시이다.

from sklearn.model_selection import StratifiedKFold
from sklearn.base import clone

#3개의 fold로 데이터를 나눌 수 있는 객체
#각 폴드마다 5인 것과 5가 아닌 것의 비율이 모비율과 같도록 분리한다.
skfolds = StratifiedKFold(n_splits=3, random_state=42, shuffle=True)

for train_index, test_index in skfolds.split(X_train, y_train_5):
	#sgd_clf의 데이터를 제외한 모델만 복사하여 clone_clf 만든다.
	clone_clf = clone(sgd_clf)
    
    #나눠진 fold를 받아준다.
	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))

#out: 0.9559, 0.9559, 0.96565

????clone은 왜 사용하는 걸까????

 

 

정확도가 95퍼센트가 나와서 좋은 성능이라고 착각할 수 있다. 하지만 모든 경우 5가 아니라고 판단하는 분류기의 정확도가 약 90퍼센트이다. 

->어떤 클래스가 다른 클래스보다 월등히 많은 경우(불균형한 데이터 셋) 정확도를 분류기의 성능 측정 지표로 삼는 것은 바람직하지 않다.

 

항상 5가 아니라고 판단하는 분류기의 예

from sklearn.base import BaseEstimator

#BaseEstimator를 상속받아야 함.
#fit, predict 정의(덕타이핑)
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])

 

 

 

2. 오차행렬

기본적인 아이디어는 클래스 A의 샘플이 클래스 B로 분류된 횟수를 세는 것. 

이진 분류기이기 때문에 오차행렬을 만든다면 2x2 행렬이 나온다.

 

cross_val_predict로 3겹 교차 검증을 수행할 때 만든 예측 벡터를 만든다.

from sklearn.model_selection import cross_val_predict

y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)
#array([ True, False, False, ...,  True, False, False])

 

실제 라벨과 위 예측 벡터를 confusion_matrix로 넘겨서 오차행렬을 만들 수 있다.

from sklearn.metrics import confusion_matrix
confusion_matrix(y_train_5, y_train_pred)
#array([[53892,   687],
#     [ 1891,  3530]], dtype=int64)

 

오차행렬 각 셀의 의미는 다음과 같다. 예측이 맞은 경우 T, 틀린 경우 F가 앞에 붙는다.

 

정밀도 precision, 재현율(민감도, 진짜양성비율true positive rate) recall은 다음과 같이 구한다.

$$precision = \frac{TP}{TP+FP}$$

$$recall = \frac{TP}{TP+FN}$$

 

 

3. 정밀도와 재현율

사이킷런은 정밀도와 재현율을 포함해서 분류기의 성능을 나타내는 지표를 계산하는 여러 함수를 제공한다.

precision_score, recall_score로 정밀도와 재현율을 알 수 있다.

 

 

5로 판멸된 이미지 중 83퍼센트 맞췄고, 전체 '5' 중에서 65퍼센트만 제대로 감지했다.

 

정밀도와 재현율을 $F_1$ 점수라고 하는 하나의 숫자로 만들면 편할 때가 있다. 이 점수는 정밀도와 재현율의 조화평균이다. 이따금씩 상수를 추가해서 정밀도 또는 재현율에 가중치를 더해주기도 한다.

$$F_1 = \frac{2}{\frac{1}{precision}+\frac{1}{recall}}$$

 

f1_score()함수로 f1점수를 계산할 수 있다.

 

4. 정밀도/재현율 트레이드오프

정밀도: 분류기가 true라고 판단한 것들 중 실제로 true인 것의 비율

재현율: 실제로 true인 것들 중 분류기가 true라고 판단한 것의 비율

 

ex1. 어린이들에게 안전한 동영상을 분류하는 모델은 정밀도가 더 중요할 것. 

(안전하다고 판단한 동영상이 사실 불건전하다면 문제가 되지만,

실제로 안전한 동영상을 불건전하다고 판단하는 것은 큰 문제가 되지 않을 것.)

 

ex2. 행인들 중 도둑을 분류하는 감시카메라의 모델은 재현율이 더 중요할 것.

(도둑이라고 판단한 행인이 사실 일반인이라면 문제가 되지 않지만,

실제 도둑을 일반인이라고 판단하는 것은 문제가 될 것.)

 

정밀도와 재현율 간엔 tradeoff 관계가 있어서 모두 얻을 수는 없다.

(결정함수에서 결정 임계값보다 높은 값이 나오면 true라고 판단하는데, 이 임계값이 높아질수록 precision이 좋아지고 낮아질수록 recall이 좋아진다.)

 

 

예제에서 정밀도 90퍼센트를 달성하기 위한 임계값을 설정해보자

cross_val_predict의 method옵션을 'decision_function'으로 넘기면 결정함수 값들의 배열을 반환한다.

y_scores = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3,
                             method = 'decision_function')
#array([  1200.93051237, -26883.79202424, -33072.03475406, ...,
#        13272.12718981,  -7258.47203373, -16877.50840447])

 

precision_recall_curve()함수를 사용해서 모든 임계값(y_scores의 범위)에 대해 정밀도와 재현율, 그때 임계값을 알 수 있다.

from sklearn.metrics import precision_recall_curve

precisions, recalls, thresholds = precision_recall_curve(y_train_5, y_scores)

 

위 정밀도,재현율을 임계값에 따라 맷플롯립으로 그려준다. 정밀도의 형태가 부드러운 곡선이 아닌 이유는 임계값을 한단계 올려서 '5'를 '5'가 아니라고 판단한 경우 정밀도가 오히려 떨어질 수도 있기 때문이다.

 

precision과 recall간의 관계를 보기위해 그래프를 그려서 90퍼센트 정밀도를 달성할 때 재현율일 어느정도인지 확인.

재현율이 0.0x인 곳에서도 정밀도가 90퍼센트인 것에 주의해야 한다. 프로젝트의 목표가 90퍼센트의 정밀도라면 재현율 얼마에서?라는 질문이 나와야 한다.

 

이제 정밀도가 90퍼센트인 곳의 역치값을 찾아보자. np.argmax를 활용하면 된다.(최대값의 인덱스 반환)

precision은 threshold의 값이 최소일때 ~ 최대일때 순서대로 배열에 담겨있으므로 다음처럼 하면 된다.

threshold_90_precision = thresholds[np.argmax(precisions >= 0.9)]

 

분류기의 predict()메서드는 임계값 0을 기준으로 한다. 이를 호출하는 대신 다음 코드를 실행하면 된다.

 

5. ROC 곡선

$$precision = \frac{TP}{TP+FP}$$

$$recall = \frac{TP}{TP+FN}$$

 

재현율을 진짜 양성 비율(TPR, true positive rate, 민감도)라고도 한다.

정밀도와 재현율 외에 유용한 지표가 몇가지 더 있다.

거짓 양성 비율(FPR, false positive rate, fall-out)과 진짜 음성 비율(TNR, true negative rate, specificity, 특이도)이다.

 

거짓 양성 비율(fpr)은 실제로 false인 것들 중 예측이 positive인 비율이고, 

진짜 음성 비율(tnr)은 실제로 false인 것들 중 예측이 negative인 비율이다. 

(**r은 분자에 **가있는 형태다)

$$fpr = \frac{FP}{TN+FP}$$

$$tnr = \frac{TN}{TN+FP}$$

$$fpr = 1-tnr$$

 

 

 

ROC 곡선은 recall에 대한 (1-specitify)의 그래프이다.

 

roc_curve() 함수로 fpr, tpr, 임계값을 받아서 그림을 그려줄 수 있다.

 

from sklearn.metrics import roc_curve

fpr, tpr, thresholds = roc_curve(y_train_5, y_scores)

 

recall이 높을수록 fall-out이 커진다.

분류기가 true를 positive라고 판단하는것이 많아질수록, false를 positive라고 판단하는 것이 많아진다는 의미다.

 

좋은 분류기는 곡선 아래의 면적(area under the curve, AUC)가 1에 가까워야 하고 y=x선에서 최대한 멀리 떨어져 있어야 한다.

완벽한 분류기는 ROC의 AUC가 1이고, 완전한 랜덤 분류기(ROC곡선이 y=x형태)는 0.5이다.

 

현재 SGDClassifier의 ROC의 AUC는 다음과 같이 0.96정도로 1에 가까워서 나쁘지 않아보인다.

from sklearn.metrics import roc_auc_score
roc_auc_score(y_train_5, y_scores)
#0.9604938554008616

 

RandomForesetClassifier를 훈련시켜서 SGDClassifier과 비교해보자. 

from sklearn.ensemble import RandomForestClassifier

forest_clf = RandomForestClassifier(random_state=42)
#랜덤포레스트분류기는 그 특성때문에 decision_function이 없고 확률예측 행렬을 반환한다.
y_probas_forest = cross_val_predict(forest_clf, X_train, y_train_5, cv=3,
                                   method = 'predict_proba')

#1차원 배열로 만들어준다.
y_scores_forest = y_probas_forest[:,1]
fpr_forest, tpr_forest, thresholds_forest = roc_curve(y_train_5, y_scores_forest)

기존 figure에 새로운 plot을 얹은것

랜덤포레스트분류기가 더 좋아보인다.  ROC의 AUC도 높다.

roc_auc_score(y_train_5, y_scores_forest)
#0.9920527492698306

'ML&DATA > 핸즈온 머신러닝' 카테고리의 다른 글

4 - 선형 회귀 (정규방정식)  (0) 2020.09.04
3 - 다중 레이블 분류, 다중 출력 분류  (2) 2020.08.27
3 - 에러분석  (0) 2020.08.27
3 - 다중분류  (0) 2020.08.27
개요  (2) 2020.07.16