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

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

  • 테스트 세트에 있는 별도의 샘플처럼 새로운 데이터의 스케일을 조정하고 압축하기 위해 훈련 세트에서 학습한 파라미터를 재사용해야 한다.
  • 이 장에서는 이를 위해 아주 유용하게 사용할 수 있는 사이킷런의 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)

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

  • 위스콘신 유방암 데이터셋을 선형 분류기에 주입하기 전에 특성을 표준화 해야 한다.
    • 여기서는 우선 주성분 분석(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)
  • 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개의 폴드로 나누어지고 열 번의 반복 동안 아홉 개의 폴드는 훈련에, 한 개의 폴드는 모델 평가를 위해 사용된다.
    • 각 폴드의 추정 성능 E_{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)
  • 먼저 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)
  • 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()

  • 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()

  • 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)
  • 위 코드는 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)
  • 반면 평균 교차 검증 점수는 모델의 하이퍼파라미터를 튜닝했을 때 처음 본 데이터에서 기대할 수 있는 추정값이 된다.
    • 예컨대 중첩 교차 검증을 사용하여 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()

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

  • 예측 오차(ERR)와 정확도(AcC) 모두 얼마나 많은 샘플을 잘못 분류했는지 일반적인 정보를 알려준다.
    • 오차는 잘못된 예측의 합을 전체 예측 샘플 개수로 나눈 것이다.
    • 정확도는 옳은 예측의 합을 전체 예측 샘플 개수로 나누어 계산한다.

ERR = {FP + FN \over FP + FN + TP + TN}

  • 예측 정확도는 오차에서 바로 계산할 수 있다.

ACC = {TP + TN \over FP + FN + TP + TN} = 1 - ERR

  • 진짜 양성 비율 (True Positive Rate, TPR)과 거짓 양성 비율 (False Positive Rate, FPR)은 클래스 비율이 다른 경우 유용한 성능 지표이다.

FPR = {FP \over N} = {FP \over FP + TN}

TPR = {TP \over P} = {TP \over FN + TP}

  • 정확도(PRE)와 재현율(REC) 성능 지표는 진짜 양성과 진짜 음성 샘플의 비율과 관련이 있다. 사실 재현율은 TPR의 다른 이름이다.

PRE = {TP \over TP + FP}

REC = TPR = {TP \over P} = {TP \over FN + TP}

  • 실전에서는 PRE와 REC를 조합한 F1-Score를 자주 사용한다.

F1 = 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))
  • 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()

  • 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개의 클래스가 있는 경우 정밀도의 마이크로 평균은 다음과 같이 계산한다.

PRE_{micro} = {TP_{1} + TP_{2} + ... + TP_{k} \over TP_{1} + TP_{2} + ... + TP_{k} + FP_{1} + FP_{2} + ... + FP_{k}}

  • 매크로 평균은 단순하게 클래스별 정밀도의 평균이다.

PRE_{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
  • 이런 데이터셋에 분류 모델을 훈련할 때 모델을 비교하기 위해 정확도를 사용하는 것보다 다른 지표를 사용하는 것이 낫다.
    • 애플리케이션에서 주요 관심 대상이 무엇인지에 따라 정밀도, 재현율, 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)
  • 샘플을 추출한 후 클래스 0인 원본 샘플과 업샘플링된 클래스 1을 연결하여 균형 잡힌 데이터셋을 얻을 수 있다.
  • 비슷하게 데이터셋에서 다수 클래스의 훈련 샘플을 삭제하여 다운샘플링(downsampling)을 할 수 있다. resample 함수를 사용하여 다운샘플링을 수행하려면 클래스 레이블 1과 0을 서로 바꾸면 된다.
[ssba]

The author

지성을 추구하는 사람/ suyeongpark@abyne.com

댓글 남기기

이 사이트는 스팸을 줄이는 아키스밋을 사용합니다. 댓글이 어떻게 처리되는지 알아보십시오.