Search
Duplicate

머신 러닝 교과서/ 모델 평가와 하이퍼파라미터 튜닝의 모범 사례

파이프라인을 사용한 효율적인 워크플로

테스트 세트에 있는 별도의 샘플처럼 새로운 데이터의 스케일을 조정하고 압축하기 위해 훈련 세트에서 학습한 파라미터를 재사용해야 한다.
이 장에서는 이를 위해 아주 유용하게 사용할 수 있는 사이킷런의 Pipeline 클래스를 배우겠다.

위스콘신 유방암 데이터셋

이 장에서는 위스콘신 유방암 데이터셋을 사용하겠다. 여기에는 악성과 양성인 종양 세포 샘플 569개가 포함되어 있다.
이 절에서는 3단계로 나누어 데이터셋을 읽고 훈련 세트와 테스트 세트로 분할 하겠다.
1.
pandas를 사용하여 UCI 서버에서 데이터셋을 Load
2.
30개의 특성을 넘파이 배열 X에 할당.
LabelEncoder 객체를 사용하여 클래스 레이블을 원본 문자열 표현에서 정수로 변환한다.
3.
첫 번째 모델 파이프라인을 구성하기 전에 데이터셋을 훈련 세트(80%)와 별도의 테스트 세트(20%)로 나눈다.
import pandas as pd from sklearn.preprocessing import LabelEncoder from sklearn.model_selection import train_test_split df = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data', header=None) X = df.loc[:, 2:].values y = df.loc[:, 1].values le = LabelEncoder() y = le.fit_transform(y) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, stratify=y, random_state=1)
Python

파이프라인으로 변환기와 추정기 연결

위스콘신 유방암 데이터셋을 선형 분류기에 주입하기 전에 특성을 표준화 해야 한다.
여기서는 우선 주성분 분석(PCA)를 통해 초기 30차원에서 좀 더 낮은 2차원 부분 공간으로 데이터를 압축한다.
from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA from sklearn.linear_model import LogisticRegression from sklearn.pipeline import make_pipeline pipe_lr = make_pipeline(StandardScaler(), PCA(n_components=2), LogisticRegression(solver='liblinear', random_state=1)) pipe_lr.fit(X_train, y_train) y_pred = pipe_lr.predict(X_test)
Python
make_pipeline 함수는 여러 개의 사이킷런 변환기(입력에 대해 fit 메서드와 transform 메서드를 지원하는 객체)와 그 뒤에 fit 메서드와 predict 메서드를 구현한 사이킷런 추정기를 연결할 수 있다.
위 코드에서는 StandardScaler와 PCA 두 개의 변환기와 LogisticRegression 추정기를 make_pipeline 의 입력으로 넣었고, 이 함수는 이 객체들을 사용하여 사이킷런의 Pipeline 클래스 객체를 생성하여 반환한다.
사이킷런의 Pipeline 클래스를 메타 추정기(meta-estimator)나 개별 변환기와 추정기를 감싼 래퍼(wrapper)로 생각할 수 있다.
Pipeline 객체의 fit 메서드를 호출하면 데이터가 중간 단계에 있는 모든 변환기의 fit 메서드와 transform 메서드를 차례로 거쳐 추정기 객체에 도달한다.
추정기는 변환된 훈련 세트를 사용하여 학습한다.
위 예제에서 pipe_lr 파이프라인의 fit 메서드를 호출할 때 먼저 훈련 세트에 StandardScaler의 fit 메서드와 transform 메서드가 호출된다.
변환된 훈련 데이터는 파이프라인의 다음 요소인 PCA 객체로 전달된다. 이전 단계와 비슷하게 스케일 조정된 입력 데이터에 PCA의 fit 메서드와 transform 메서드가 호출된다.
그 다음 파이프라인의 최종 요소인 추정기에 훈련 데이터가 전달된다.
마침내 LogisticRegression 추정기가 StandardScaler와 PCA로 변환된 훈련 데이터로 학습한다.
파이프라인의 중간 단계 횟수에는 제한이 없지만, 파이프라인의 마지막 요소는 추정기가 되어야 한다.
파이프라인에서 fit 메서드를 호출하는 것과 비슷하게 predict 메서드도 제공한다.
Pipeline 인스턴스의 predict 메서드를 호출할 때 주입된 데이터는 중간 단계의 transform 메서드를 통과한다.
마지막 단계에서 추정기 객체가 변환된 데이터에 대한 예측을 반환한다.
사이킷런의 파이프라인은 매우 유용한 래퍼 도구이기 때문이 책의 나머지 부분에서 자주 사용할 것이다.
객체가 동작하는 아래 그림을 참고하라.

k-겹 교차 검증을 사용한 모델 성능 평가

모델이 너무 간단하면 under-fitting 이 발생하고 너무 복잡하면 훈련 데이터에 over-fitting이 될 수 있다.
적절한 편향-분산 트레이드오프를 찾으려면 모델을 주의 깊게 평가해야 한다. 이 절에서는 보편적인 교차 검증 기법인 홀드아웃 교차 검증(holdout cross-validation)과 k-겹 교차 검증(k-fold corss-validation)을 다루겠다.

홀드아웃 방법

홀드아웃 방법은 데이터셋을 훈련 세트와 테스트 세트로 나눈 후, 전자는 모델 훈련에 사용하고 후자는 일반화 성능을 추정하는데 사용한다.
일반적인 머신 러닝 애플리케이션에서는 처음 본 데이터에서 예측 성능을 높이기 위해 하이퍼파라미터를 튜닝하고 비교해야 하는데, 이 과정을 모델 선택이라고 한다.
모델 선택이라는 용어는 주어진 분류 문제에서 튜닝할 파라미터(또는 하이퍼 파라미터)의 최적 값을 선택해야 하는 것을 의미한다.
모델 선택에 같은 테스트 세트를 반복해서 재사용하면 훈련 세트의 일부가 되는 셈이고 결국 모델은 over-fitting이 될 것이다.
아직도 많은 사람이 모델 선택을 위해 테스트 세트를 사용하는데 이는 좋은 머신 러닝 작업 방식이 아니다.
모델 선택에 홀드아웃 방법을 사용하는 가장 좋은 방법은 데이터를 훈련 세트, 검증 세트, 테스트 세트 3개의 부분으로 나누는 것이다.
훈련 세트는 여러 모델을 훈련하는데 사용한다.
검증 세트에 대한 성능은 모델 선택에 사용한다.
테스트 세트를 분리했기 때문에 새로운 데이터에 대한 일반화 능력을 덜 편향되게 추정할 수 있는 장점이 있다.
아래 그림은 홀드아웃 교차 검증의 개념을 보여준다. 검증 세트를 사용하여 반복적으로 다른 파라미터 값에서 모델을 훈련한 후 성능을 평가한다.
만족할 만한 하이퍼 파라미터 값을 얻었다면 테스트 세트에서 모델의 일반화 성능을 추정한다.
홀드아웃 방법은 훈련 데이터를 훈련 세트와 검증 세트로 나누는 방법에 따라 성능 추정이 민감할 수 있다는 것이 단점이다.
검증 세트의 성능 추정이 어떤 샘플을 사용하느냐에 따라 달라질 것이다.

k-겹 교차 검증

k-겹 교차 검증에서는 중복을 허락하지 않고 훈련 데이터셋을 k개의 폴드로 랜덤하게 나눈다.
k-1 개의 폴드로 모델을 훈련하고 나머지 하나의 폴드로 성능을 평가한다.
이 과정을 k번 반복하여 k개의 모델과 성능 추정을 얻는다.
그 다음 서로 독립적인 폴드에서 얻은 성능 추정을 기반으로 모델의 평균 성능을 계산한다.
홀드아웃 방법에 비해 훈련 세트의 분할에 덜 민감한 성능 추정을 얻을 수 있다.
일반적으로 모델 튜닝에 k-겹 교차 검증을 사용한다. 즉, 만족할 만한 일반화 성능을 내는 최적의 하이퍼파라미터 값을 찾기 위해 사용한다.
만족스러운 하이퍼파라미터 값을 찾은 후에는 전체 훈련 세트를 사용하여 모델을 다시 훈련한다.
그 다음 독립적인 테스트 세트를 사용하여 최종 성능 추정을 한다.
k-겹 교차 검증 후에 전체 훈련 세트로 모델을 학습하는 이유는 훈련 샘플이 많을수록 학습 알고리즘이 더 정확하고 안정적인 모델을 만들기 때문이다.
k-겹 교차 검증이 중복을 허락하지 않는 리샘플링 기법이기 때문에 모든 샘플 포인트가 훈련하는 동안 (테스트 폴드로) 검증에 딱 한번 사용되는 장점이 있다.
이로 인해 홀드아웃 방법보다 모델 성능의 추정에 분산이 낮다.
아래 그림은 k=10일 때 k-겹 교차 검증의 개념을 요약한 것이다.
훈련 데이터는 10개의 폴드로 나누어지고 열 번의 반복 동안 아홉 개의 폴드는 훈련에, 한 개의 폴드는 모델 평가를 위해 사용된다.
각 폴드의 추정 성능 EiE_{i} 를 사용하여 모델의 평균 성능 E를 계산한다.
경험적으로 보았을 때 k-겹 교차 검증에서 좋은 기본값은 k=10이다.
예컨대 론 코하비(Ron Kohavi)는 여러 종류의 실제 데이터셋에서 수행한 실험을 통해 10-겹 교차 검증이 가장 뛰어난 편향-분산 트레이드오프를 가진다고 제안했다
비교적 작은 훈련 세트로 작업한다면 폴드 개수를 늘리는 것이 좋다.
k값이 증가하면 더 많은 훈련 데이터가 각 반복에 사용되고 모델 성능을 평균하여 일반화 성능을 추정할 때 더 낮은 편향을 만든다.
k 값이 아주 크면 교차 검증 알고리즘의 실행 시간이 늘어나고 분산이 높은 추정을 만든다. 이는 훈련 폴드가 서로 많이 비슷해지기 때문이다.
다른 말로 하면 대규모 데이터셋으로 작업할 때는 k=5와 같은 작은 k 값을 선택해도 모델의 평균 성능을 정확하게 추정할 수 있다.
또 폴드마다 모델을 학습하고 평가하는 계산 비용을 줄일 수 있다.
기본 k-겹 교차 검증 방법보다 좀 더 향상된 방법은 계층적 k-겹 교차 검증(stratified k-fold cross-validation)이다.
이는 좀 더 나은 편향과 분산 추정을 만든다. 특히 론 코하비가 보인 것처럼 클래스 비율이 동등하지 않을 때도 그렇다.
계층적 교차 검증은 각 폴드에서 클래스 비율이 전체 훈련 세트에 있는 클래스 비율을 대표하도록 유지한다.
사이킷런의 StratifiedKFold 반복자를 사용하여 구현할 수 있다.
import numpy as np from sklearn.model_selection import StratifiedKFold kfold = StratifiedKFold(n_splits=10, random_state=1).split(X_train, y_train) scores = [] for k, (train, test) in enumerate(kfold): pipe_lr.fit(X_train[train], y_train[train]) score = pipe_lr.score(X_train[test], y_train[test]) scores.append(score)
Python
먼저 sklearn.model_selection 모듈에 있는 StratifiedKFold 클래스를 훈련 세트의 y_train 클래스 레이블에 전달하여 초기화한다. 그 후 n_splits 매개변수로 폴드 개수를 지정한다.
kfold 반복자를 사용하여 k개의 폴드를 반복하여 얻은 train의 인덱스를 로지스틱 회귀 파이프라인을 훈련하는데 사용할 수 있다.
pipe_lr 파이프라인을 사용하므로 각 반복에서 샘플의 스케일이 적절하게 조정된다 (예컨대 표준화를 통해)
그 다음 테스트 인덱스를 사용하여 모델의 정확도 점수를 계산한다.
이 점수를 리스트에 모아서 추정한 정확도의 평균과 표준편차를 계산한다.
사이킷런은 k-겹 교차 검증 함수를 제공하기 때문에 좀 더 간단하게 계층별 k-겹 교차 검증을 사용하여 모델을 평가할 수 있다.
from sklearn.model_selection import cross_val_score scores = cross_val_score(estimator=pipe_lr, X=X_train, y=y_train, cv=10, n_jobs=1)
Python
cross_val_score 함수의 아주 유용한 기능은 각 폴드의 평가를 컴퓨터에 있는 복수 개의 CPU 코어에 분산할 수 있다는 점이다.
n_jobs 매개변수를 1로 설정하면 하나의 CPU 코어만 성능 평가에 사용되며, 2로 설정하면 2개의 CPU 코어에 교차 검증을 10회씩 분산할 수 있다. -1을 설정하면 컴퓨터에 설치된 모든 CPU 코어를 사용하여 병렬처리를 한다.

학습 곡선과 검증 곡선을 사용한 알고리즘 디버깅

학습 곡선으로 편향과 분산 문제 분석

훈련 데이터셋에 비해 모델이 너무 복잡하면, 즉 모델의 자유도나 모델 파라미터가 너무 많으면 모델이 훈련 데이터에 과대적합되고 처음 본 데이터에 일반화 되지 못하는 경향이 있다.
보통 훈련 샘플을 더 모으면 과대적합을 줄이는데 도움이 되지만, 실전에서는 데이터를 더 모으는 것이 매우 비싸거나 불가능한 경우가 많다.
모델의 훈련 정확도와 검증 정확도를 훈련 세트의 크기 함수로 그래프를 그려보면 모델에 높은 분산의 문제가 있는지 높은 편향의 문제가 있느지 쉽게 감지할 수 있다.
왼쪽 위 그래프는 편향이 높은 모델로서 훈련 정확도와 교차 검증 정확도가 모두 낮다. 이는 훈련 데이터에 과소적합되었다는 것을 나타낸다.
이 문제를 해결하는 일반적인 방법은 모델의 파라미터 개수를 늘리는 것이다.
오른쪽 위 그래프는 분산이 높은 모델을 보여준다. 훈련 정확도와 교차 검증 정확도 사이에 큰 차이가 있다는 것을 나타낸다.
과대적합 문제를 해결하려면 더 많은 훈련 데이터를 모으거나 모델 복잡도를 낮추거나 규제를 증가시킬 수 있다.
사이킷런의 학습 곡선 함수를 사용하면 모델을 평가할 수 있다.
from sklearn.model_selection import learning_curve import numpy as np import matplotlib.pyplot as plt pipe_lr = make_pipeline(StandardScaler(), LogisticRegression(solver='liblinear', penalty='l2', random_state=1)) train_sizes, train_scores, test_scores = learning_curve(estimator=pipe_lr, X=X_train, y=y_train, train_sizes=np.linspace(0.1, 1.0, 10), cv=10, n_jobs=1) train_mean = np.mean(train_scores, axis=1) train_std = np.std(train_scores, axis=1) test_mean = np.mean(test_scores, axis=1) test_std = np.std(test_scores, axis=1) plt.plot(train_sizes, train_mean, color='blue', marker='o', markersize=5, label='training accuracy') plt.fill_between(train_sizes, train_mean + train_std, train_mean - train_std, alpha=0.15, color='blue') plt.plot(train_sizes, test_mean, color='green', linestyle='--', marker='s', markersize=5, label='validation accuracy') plt.fill_between(train_sizes, test_mean + test_std, test_mean - test_std, alpha=0.15, color='green') plt.grid() plt.xlabel('Number of training samples') plt.ylabel('Accuracy') plt.legend(loc='lower right') plt.ylim([0.8, 1.03]) plt.tight_layout() plt.show()
Python
learning_curve 함수의 train_sizes 매개변수를 통해 학습 곡선을 생성하는데 사용할 훈련 샘플의 개수나 비율을 지정할 수 있다.
기본적으로 learning_curve 함수는 계층별 k-겹 교차 검증을 사용하여 분류기의 교차 검증 정확도를 계산한다.
cv 매개변수를 통해 k 값을 10으로 지정했기 때문에 계층별 10-겹 교차 검증을 사용한다.

검증 곡선으로 과대적합과 과소적합 조사

검증 곡선은 과대적합과 과소적합 문제를 해결하여 모델 성능을 높일 수 있는 유용한 도구이다.
사이킷런의 validation_curve를 이용해서 검증곡선을 만들 수 있다.
from sklearn.model_selection import validation_curve import numpy as np import matplotlib.pyplot as plt param_range = [0.001, 0.01, 0.1, 1.0, 10.0, 100.0] train_scores, test_scores = validation_curve(estimator=pipe_lr, X=X_train, y=y_train, param_name='logisticregression__C', param_range=param_range, cv=10) train_mean = np.mean(train_scores, axis=1) train_std = np.std(train_scores, axis=1) test_mean = np.mean(test_scores, axis=1) test_std = np.std(test_scores, axis=1) plt.plot(param_range, train_mean, color='blue', marker='o', markersize=5, label='training accuracy') plt.fill_between(param_range, train_mean + train_std, train_mean - train_std, alpha=0.15, color='blue') plt.plot(param_range, test_mean, color='green', linestyle='--', marker='s', markersize=5, label='validation accuracy') plt.fill_between(param_range, test_mean + test_std, test_mean - test_std, alpha=0.15, color='green') plt.grid() plt.xscale('log') plt.legend(loc='lower right') plt.xlabel('Parameter C') plt.ylabel('Accuracy') plt.ylim([0.8, 1.0]) plt.tight_layout() plt.show()
Python
learning_curve와 유사하게 validation_curve도 기본적으로 계층별 k-겹 교차 검증을 사용하여 모델의 성능을 추정한다.
validation_curve 함수 안에서 평가하기 원하는 매개변수를 지정한다. 위 경우에는 LogisticRegression 분류기의 규제 매개변수인 C이다.
param_range 매개변수에는 값 범위를 지정한다.
C 값이 바뀜에 따라 정확도 차이가 미묘하지만 규제 강도를 높이면 (C값을 줄이면) 모델이 데이터에 조금 과소적합 되는 것을 볼 수 있다. 규제 강도가 낮아지는 큰 C 값에서는 모델이 데이터에 조금 과대적합 되는 경향을 보인다.

그리드 서치를 사용한 머신 로닝 모델 세부 튜닝

머신 러닝에는 두 종류의 파라미터가 있는데, 하나는 훈련 데이터에 학습되는 파라미터로 로지스틱 회귀의 가중치가 그 예이다.
다른 하나는 별도로 최적화 되는 학습 알고리즘의 파라미터로 튜닝 파라미터고, 하이퍼파라미터라고도 부른다. 예컨대 로지스틱 회귀의 규제 매개변수나 결정 트리의 깊이 매개변수이다.
이전 절에서는 검증 곡선을 사용하여 하이퍼파라미터를 튜닝하여 모델 성능을 향상시켰다면 이번 절에서는 그리드 서치라는 인기 있는 하이퍼파라미터 최적화 기법을 살펴보겠다.

그리드 서치를 사용한 하이퍼파라미터 튜닝

그리드 서치가 사용하는 방법은 아주 간단한데, 리스트로 지정된 여러 가지 하이퍼파라미터 값 전체를 모두 조사한다.
이 리스트에 있는 값의 모든 조합에 대해 모델 성능을 평가하여 최적의 조합을 찾는다.
from sklearn.model_selection import GridSearchCV from sklearn.svm import SVC pipe_svc = make_pipeline(StandardScaler(), SVC(random_state=1)) param_range = [0.0001, 0.001, 0.01, 0.1, 1.0, 10.0, 100.0, 1000.0] param_grid = [{'svc__C': param_range, 'svc__kernel':['linear']}, {'svc__C': param_range, 'svc__gamma':param_range, 'svc__kernel':['rbf']}] gs = GridSearchCV(estimator=pipe_svc, param_grid=param_grid, scoring='accuracy', cv=10, n_jobs=1) gs = gs.fit(X_train, y_train) clf = gs.best_estimator_ clf.fit(X_train, y_train)
Python
위 코드는 sklearn.model_selection 모듈에 있는 GridSearchCV 클래스의 객체를 만들고 SVM을 위한 파이프라인을 훈련하고 튜닝한다.
훈련 세트를 사용하여 그리드 서치를 수행한 후 최상의 모델 점수는 best_score_ 속성에서, 이 모델의 매개변수는 betst_params_ 속성에서 확인할 수 있다.
독립적인 테스트 세트를 사용하여 최고 모델의 성능을 추정할 수 있는데 이 모델은 GridSearchCV 객체의 best_estimator_ 속성에서 얻을 수 있다.

중첩 교차 검증을 사용한 알고리즘 선택

그리드 서치와 k-겹 교차 검증을 함께 사용하면 머신 러닝 모델의 성능을 세부 튜닝하기 좋다.
여러 종류의 머신 러닝 알고리즘을 비교하려면 중첩 교차 검증(nested cross-validation) 방법이 권장된다.
오차 예측에 대한 편향을 연구하는 중에 바르마(Varma)와 사이몬(Simon)은 중첩된 교차 검증을 사용했을 때 테스트 세트에 대한 추정 오차는 거의 편향되지 않는다는 결론을 얻었다.
중첩 교차 검증은 바깥쪽 k-겹 교차 검증 루프가 데이터를 훈련 폴드와 테스트 폴드로 나누고 안쪽 루프가 훈련 폴드에서 k-겹 교차 검증을 수행하여 모델을 선택한다.
모델이 선택되면 테스트 폴드를 사용하여 모델 성능을 평가한다.
아래 그림은 바깥 루프에 다섯 개의 폴드를 사용하고 안쪽 루프에 두 개의 폴드를 사용하는 중첩 교차 검증의 개념을 보여준다.
이런 방식은 계산 성능이 중요한 대용량 데이터셋에서 유용하다.
중첩 교차 검증의 폴드 개수를 고려하여 5 x 2 교차 검증이라고도 한다.
사이킷런에서는 다음과 같이 중첩 교차 검증을 수행할 수 있다.
gs = GridSearchCV(estimator=pipe_svc, param_grid=param_grid, scoring='accuracy', cv=2) scores = cross_val_score(gs, X_train, y_train, scoring='accuracy', cv=5)
Python
반면 평균 교차 검증 점수는 모델의 하이퍼파라미터를 튜닝했을 때 처음 본 데이터에서 기대할 수 있는 추정값이 된다.
예컨대 중첩 교차 검증을 사용하여 SVM 모델과 단일 결정 트리 분류기를 비교할 수 있다.

여러 가지 성능 평가 지표

이전 절에서는 정확도를 사용하여 모델을 평가했다. 이 지표는 일반적으로 분류 모델의 성능을 정량화 하는데 유용하다.
주어진 문제에 모델이 적합한지 측정할 수 있는 다른 성능 지표도 여럿 있는데, 정밀도(precision), 재현율(recall), F1-Score 이다.

오차 행렬

학습 알고리즘의 성능을 행렬로 펼쳐 놓은 오차 행렬(confusion matrix)를 을 살펴보자.
오차 행렬은 아래 그림과 같이 진짜 양성(True Positive, TP), 진짜 음성(True Negative, TN), 거짓 양성(False Positive, FP), 거짓 음성(False Negative, FN)의 개수를 적은 정방 행렬이다.
이 행렬은 타깃 클래스와 예측 클래스의 레이블을 직접 세어 계산할 수 있지만 사이킷런에서 제공하는 편리한 confusion_matrix 함수를 사용할 수 있다.
from sklearn.metrics import confusion_matrix pipe_svc.fit(X_train, y_train) y_pred = pipe_svc.predict(X_test) confmat = confusion_matrix(y_true=y_test, y_pred=y_pred) fig, ax = plt.subplots(figsize=(2.5,2.5)) ax.matshow(confmat, cmap=plt.cm.Blues, alpha=0.3) for i in range(confmat.shape[0]): for j in range(confmat.shape[1]): ax.text(x=j, y=i, s=confmat[i, j], va='center', ha='center') plt.xlabel('predicted label') plt.ylabel('true label') plt.tight_layout() plt.show()
Python

분류 모델의 정밀도와 재현율 최적화

예측 오차(ERR)와 정확도(AcC) 모두 얼마나 많은 샘플을 잘못 분류했는지 일반적인 정보를 알려준다.
오차는 잘못된 예측의 합을 전체 예측 샘플 개수로 나눈 것이다.
정확도는 옳은 예측의 합을 전체 예측 샘플 개수로 나누어 계산한다.
ERR=FP+FNFP+FN+TP+TNERR = {FP + FN \over FP + FN + TP + TN}
예측 정확도는 오차에서 바로 계산할 수 있다.
ACC=TP+TNFP+FN+TP+TN=1ERRACC = {TP + TN \over FP + FN + TP + TN} = 1 - ERR
진짜 양성 비율 (True Positive Rate, TPR)과 거짓 양성 비율 (False Positive Rate, FPR)은 클래스 비율이 다른 경우 유용한 성능 지표이다.
FPR=FPN=FPFP+TNFPR = {FP \over N} = {FP \over FP + TN}
TPR=TPP=TPFN+TPTPR = {TP \over P} = {TP \over FN + TP}
정확도(PRE)와 재현율(REC) 성능 지표는 진짜 양성과 진짜 음성 샘플의 비율과 관련이 있다. 사실 재현율은 TPR의 다른 이름이다.
PRE=TPTP+FPPRE = {TP \over TP + FP}
REC=TPR=TPP=TPFN+TPREC = TPR = {TP \over P} = {TP \over FN + TP}
실전에서는 PRE와 REC를 조합한 F1-Score를 자주 사용한다.
F1=2×PRE×RECPRE+RECF1 = 2 \times {PRE \times REC \over PRE + REC}
(위 식에서 2배를 해주는 까닭은 2배를 안 하면 점수가 0-0.5 사이의 값이 되기 때문. 보기 좋게 0-1의 값을 만들어주기 위해 2배를 하는 것이다)
이런 성능 지표들은 모두 사이킷런에 구현되어 있다.
from sklearn.metrics import precision_score, recall_score, f1_score print ('정밀도: %.3f' % precision_score(y_true=y_test, y_pred=y_pred)) print ('재현율: %.3f' % recall_score(y_true=y_test, y_pred=y_pred)) print ('F1: %.3f' % f1_score(y_true=y_test, y_pred=y_pred))
Python
GridSearchCV의 scoring 매개변수를 사용하여 정확도 대신 다른 성능 지표를 사용할 수 도 있다.
사이킷런에서 양성 클래스는 레이블이 1인 클래스이다.
양성 레이블을 바꾸고 싶다면 make_scorer 함수를 사용하여 자신만의 함수를 만들 수 있다.
(예시 생략)

ROC 곡선 그리기

ROC(Receiver Operating Characteristic) 그래프는 분류기의 임계 값을 바꾸어 가며 계산된 FPR과 TPR 점수를 기반으로 분류 모델을 선택하는 유용한 도구이다.
ROC 그래프의 대각선은 랜덤 추측으로 해석할 수 있고 대각선 아래에 위치한 분류 모델은 랜덤 추측 보다 나쁜 셈이다.
완벽한 분류기의 그래프는 TPR이 1이고 FPR이 0인 왼쪽 위 구석에 위치한다.
ROC 곡선의 아래 면적인 ROC AUC(ROC Area Under the Curve)를 계산하여 분류 모델의 성능을 종합할 수 있다.
ROC 곡선과 비슷하게 분류 모델의 확률 임계값을 바꾸어가며 정밀도-재현율 곡선을 그릴 수 있다.
정밀도-재현율 곡선을 그리는 함수도 사이킷런에 구현되어 있다.
from sklearn.metrics import roc_curve, auc from scipy import interp pipe_lr = make_pipeline(StandardScaler(), PCA(n_components=2), LogisticRegression(solver='liblinear', penalty='l2', random_state=1, C=100.0)) X_train2 = X_train[:, [4, 14]] cv = list(StratifiedKFold(n_splits=3, random_state=1).split(X_train, y_train)) fig = plt.figure(figsize=(7,5)) mean_tpr = 0.0 mean_fpr = np.linspace(0, 1, 100) all_tpr = [] for i, (train, test) in enumerate(cv): probas = pipe_lr.fit(X_train2[train], y_train[train]).predict_proba(X_train2[test]) fpr, tpr, thresholds = roc_curve(y_train[test], probas[:, 1], pos_label=1) mean_tpr += interp(mean_fpr, fpr, tpr) mean_tpr[0] = 0.0 roc_auc = auc(fpr, tpr) plt.plot(fpr, tpr, label='ROC fold %d (area=%0.2f)' % (i+1, roc_auc)) plt.plot([0,1], [0,1], linestyle='--', color=(0.6,0.6,0.6), label='random guessing') mean_tpr /= len(cv) mean_tpr[-1] = 1.0 mean_auc = auc(mean_fpr, mean_tpr) plt.plot(mean_fpr, mean_tpr, 'k--', label='mean ROC (area = %0.2f)' % mean_auc, lw=2) plt.plot([0, 0, 1], [0, 1, 1], linestyle=':', color='black', label='perfect performance') plt.xlim([-0.05, 1.05]) plt.ylim([-0.05, 1.05]) plt.legend(loc='lower right') plt.xlabel('false positive rate') plt.ylabel('true positive rate') plt.tight_layout() plt.show()
Python
ROC AUC 점쉥만 관심 있다면 sklearn.metrics 모듈의 roc_auc_score 함수를 사용할 수도 있다.
ROC AUC로 분류 모델의 성능을 조사하면 불균형한 데이터셋에서 분류기의 성능에 대해 더 많은 통찰을 얻을 수 있다.
정확도를 ROC 곡선 하나의 구분점으로 해석할 수 있지만 브래들리(A. P. Bradley)는 ROC AUC와 정확도가 대부분 서로 비례한다는 것을 보였다.

다중 분류의 성능 지표

이 절에서 언급한 성능 지표는 이진 분류에 대한 것이다. 사이킷런은 이런 평균 지표에 매크로(Macro)와 마이크로(Micro) 평균 방식을 구현하여 OvA(One-Versus-All) 방식을 사용하는 다중 분류로 확장한다.
마이크로 평균은 클래스별로 TP, TN, FP, FN을 계산한다.
예컨대 k개의 클래스가 있는 경우 정밀도의 마이크로 평균은 다음과 같이 계산한다.
PREmicro=TP1+TP2+...+TPkTP1+TP2+...+TPk+FP1+FP2+...+FPkPRE_{micro} = {TP_{1} + TP_{2} + ... + TP_{k} \over TP_{1} + TP_{2} + ... + TP_{k} + FP_{1} + FP_{2} + ... + FP_{k}}
매크로 평균은 단순하게 클래스별 정밀도의 평균이다.
PREmacro=PRE1+PRE2+...+PREkkPRE_{macro} = {PRE_{1} + PRE_{2} + ... + PRE_{k} \over k}
마이크로 평균은 각 샘플이나 예측에 동일한 가중치를 부여하고자 할 때 사용하고, 매크로 평균은 모든 클래스에 동일한 가중치를 부여하여 분류기의 전반적인 성능을 평가한다. 이 방식에서는 가장 빈도 높은 클래스 레이블의 성능이 중요하다.
사이킷런에서 이진 성능 지표로 다중 분류 모델을 평가하면 정규화 또는 가중치가 적용된 매크로 평균이 기본으로 적용된다.
가중치가 적용된 매크로 평균은 평균을 계산할 때 각 클래스 레이블의 샘플 개수를 가중하여 계산한다.
가중치 적용된 매크로 평균은 레이블마다 샘플 개수가 다른 불균형한 클래스를 다룰 때 유용하다.
사이킷런에서 가중치가 적용된 매크로 평균이 다중 분류 문제에서 기본값이지만 sklearn.metrics 모듈 아래에 있는 측정함수들은 average 매개변수로 평균 계산 방식을 지정할 수 있다.
예컨대 precision_score나 make_scorer 함수이다.

불균형한 클래스 다루기

클래스 불균형은 실전 데이터를 다룰 때 자주 나타나는 문제로 한개 또는 여러 개의 클래스 샘플이 데이터셋에 너무 많을 때 등장한다.
이 장에서 사용한 유방암 데이터셋이 90%는 건강한 환자라고 가정할 때, 지도 학습 알고리즘을 사용하지 않고 모든 샘플에 대해 다수의 클래스(양성 종양)를 예측하기만 해도 테스트 세트에서 90% 정확도를 달성할 수 있다.
불균형한 데이터셋을 다룰 때 도움이 되는 몇 가지 기법을 알아보겠다.
우선 불균형한 데이터셋을 만들어 보자.
X_imb = np.vstack((X[y == 0], X[y == 1][:40])) y_imb = np.hstack((y[y == 0], y[y == 1][:40])) y_pred = np.zeros(y_imb.shape[0]) np.mean(y_pred == y_imb) * 100
Python
이런 데이터셋에 분류 모델을 훈련할 때 모델을 비교하기 위해 정확도를 사용하는 것보다 다른 지표를 사용하는 것이 낫다.
애플리케이션에서 주요 관심 대상이 무엇인지에 따라 정밀도, 재현율, ROC 곡선 등을 사용할 수 있다.
머신 로닝 모델을 평가하는 것과 별개로 클래스 불균형은 모델이 훈련되는 동안 학습 알고리즘 자체에 영향을 미친다.
머신 러닝 알고리즘이 일반적으로 훈련하는 동안 처리한 샘플에서 계산한 보상 또는 비용 함수의 합을 최적화 한다.
결정 규칙은 다수 클래스 쪽으로 편향되기 쉽다.
다른 말로 하면 알고리즘이 훈련 과정에서 비용을 최소화하거나 보상을 최대화하기 위해 데이터셋에서 가장 빈도가 높은 클래스의 예측을 최적화하는 모델을 학습한다.
모델을 훈련하는 동안 불균형한 클래스를 다루는 한 가지 방법은 소수 클래스에서 발생한 예측 오류에 큰 벌칙을 부여하는 것이다.
사이킷런에서 대부분의 분류기에 구현된 class_weight 매개변수를 class_weight=’balanced’로 설정해서 이런 벌칙을 편리하게 조정할 수 있다.
불균형한 클래스를 다루는데 널리 사용되는 다른 전략은 소수 클래스의 샘플을 늘리거나 다수 클래스의 샘플을 줄이거나 인공적으로 훈련 샘플을 생성하는 것이다.
아쉽지만 여러 도메인에 걸쳐 가장 잘 작동하는 보편적인 솔루션이나 기법은 없기 때문에 실전에서는 주어진 문제에 여러 전략을 시도해서 결과를 평가하고 가장 적절한 기법을 선택하는 것이 좋다.
사이킷런 라이브러리에서는 데이터셋에서 중복을 허용한 샘플 추출 방식으로 소수 클래스의 샘플을 늘리는데 사용할 수 있는 resample 함수를 제공한다.
다음 코드는 불균형한 유방암 데이터셋에서 소수 클래스를 선택하여 클래스 레이블이 0인 샘플 개수와 동일할 때까지 새로운 샘플을 반복적으로 추출한다.
from sklearn.utils import resample X_upsampled, y_upsampled = resample(X_imb[y_imb==1], y_imb[y_imb==1], replace=True, n_samples=X_imb[y_imb==0].shape[0], random_state=123)
Python
샘플을 추출한 후 클래스 0인 원본 샘플과 업샘플링된 클래스 1을 연결하여 균형 잡힌 데이터셋을 얻을 수 있다.
비슷하게 데이터셋에서 다수 클래스의 훈련 샘플을 삭제하여 다운샘플링(downsampling)을 할 수 있다. resample 함수를 사용하여 다운샘플링을 수행하려면 클래스 레이블 1과 0을 서로 바꾸면 된다.