2-3. Model Selection 모듈 소개

model_selection :

학습 데이터와 테스트 데이터 세트를 분리하거나 교차 검증 분할 및 평가, 그리고 Estimator의 하이퍼파라미터 튜닝을 위한 다양한 함수와 클래스를 제공

1. 학습/테스트 데이터 세트 분리 : train_test_split()

  • 테스트 데이터 세트를 이용하지 않고 학습 데이터 세트로만 학습하고 예측할 때의 문제
In [1]:
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

iris = load_iris()
dt_clf = DecisionTreeClassifier()
train_data = iris.data
train_label = iris.target
dt_clf.fit(train_data, train_label)

# 학습된 데이터로 예측 수행
pred = dt_clf.predict(train_data)
print('예측 정확도: ', accuracy_score(train_label, pred))
예측 정확도:  1.0

위의 예측결과가 100% 정확한 이유는 이미 학습한 데이터 세트 기반으로 예측했기 때문

실제 예측에서는 학습하지 않은 데이터를 기반으로 예측을 해야함

때문에 학습데이터 자체를 학습데이터와 테스트 데이터로 분할하여 모델이 얼마나 잘 예측하는지 평가해봄

train_test_split() 의 파라미터

  • test_size : 전체 데이터 세트에서 테스트 데이터 세트 크기를 얼마로 샘플링할 것인지 결정, 기본값 : 0.25
  • train_size : 전체 데이터 세트에서 학습용 데이터 세트 크기를 얼마로 샘플링 할 것인지 결정, test_size를 주로 사용해서 잘 쓰는 파라미터는 아님
  • shuffle : 데이터를 분리하기 전에 데이터를 미리 섞을지 결정, 기본값은 True, 데이터를 분산시켜 보다 효율적인 학습/테스트 데이터 세트를 만드는데 사용
  • random_state : 난수 값을 지정하면 여러번 다시 수행해도 동일한 결과가 나오게 해줌

반환 값은 튜플 형태: (1) 학습용 데이터 피처 데이터 셋 (2) 테스트용 데이터 피처 데이터셋, (3) 학습용 데이터 레이블 데이터 셋, (4) 훈련용 데이터 레이블 데이터셋

In [7]:
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

dt_clf = DecisionTreeClassifier()
iris_data = load_iris()

X_train, X_test, y_train, y_test = train_test_split(iris_data.data, iris_data.target, test_size = 0.3, random_state=121)
In [8]:
# 학습용 데이터를 기반으로 DecisionTreeClassifier를 학습하고 모델을 이용해 예측 정확도를 측정
dt_clf.fit(X_train, y_train)
pred = dt_clf.predict(X_test)
print('예측 정확도: {0:.4f}'.format(accuracy_score(y_test, pred)))
예측 정확도: 0.9556

2. 교차 검증(Cross Validation)

위의 방법으로 고정된 학습용 데이터와 테스트 데이터를 나누어 모델을 평가할 때는 과적합(Overfitting)에 취약함

교차 검증 : 결국 모의고사를 많이 보는 것 --> 데이터 편중을 막기 위해 별도의 여러 세트로 구성된 학습 데이터 셋과 검증 데이터 셋에서 학습과 평가를 수행

이 결과를 바탕으로 하이퍼 파라미터 튜닝 등의 모델 최적화를 더욱 손쉽게 할 수 있음

(1) K-Fold 교차검증(K-Fold Cross Validation)

K개의 데이터 폴드 세트를 만들어서 K번만큼 각 폴드 세트에 학습과 검증평가를 반복적으로 수행하는 방법

사이킷런에서는 KFoldStratifiedKFold를 제공함

In [16]:
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import KFold
import numpy as np

iris = load_iris()
features = iris.data
label = iris.target
dt_clf = DecisionTreeClassifier()

# 5개의 폴드세트를 분리하는 kFold 객체와 폴드 세트별 정확도를 담을 리스트 객체 생성
kfold = KFold(n_splits=5)
cv_accuracy = []
print('붓꽃 데이터세트 크기: ', features.shape[0])
붓꽃 데이터세트 크기:  150

KFold(n_splits= 5)로 객체를 생성했기 때문에 150개의 데이터세트에서 120개의 학습용 데이터, 30개의 테스트 데이터 세트를 5번 만든다

  • KFold 객체는 split() 을 호출하면 학습용/검증용 데이터로 분할할 수 있는 인덱스를 반환하므로 실제 분할된 데이터 추출은 코드로 직접 수행해야함
In [21]:
n_iter = 0 

# KFold 객체의 split() 을 호출하면 폴드 별 학습용, 검증용 테스트의 로우 인덱스를 array로 반환
for train_index, test_index in kfold.split(features):
    # kfold.split() 으로 반환된 인덱스를 이용해 학습용, 검증용 테스트 데이터 세트 추출
    X_train, X_test = features[train_index], features[test_index]
    y_train, y_test = label[train_index], label[test_index]
    # 학습 및 예측
    dt_clf.fit(X_train, y_train)
    pred = dt_clf.predict(X_test)
    n_iter += 1
    # 반복 시 마다 정확도 측정
    accuracy = np.round(accuracy_score(y_test, pred), 4)
    train_size = X_train.shape[0]
    test_size = X_test.shape[0]
    print('\n{0} 교차검증 정확도 : {1}, 학습 데이터 크기 : {2}, 검증 데이터 크기 : {3}'.format(n_iter, accuracy, train_size, test_size))
    print('#{0} 검증 세트 인덱스: {1}'.format(n_iter, test_index))
    cv_accuracy.append(accuracy)
    
# 개별 iteration별 정확도를 합하여 평균 정확도 계산
print('\n## 평균검증 정확도: ', np.mean(cv_accuracy))
1 교차검증 정확도 : 1.0, 학습 데이터 크기 : 120, 검증 데이터 크기 : 30
#1 검증 세트 인덱스: [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 24 25 26 27 28 29]

2 교차검증 정확도 : 1.0, 학습 데이터 크기 : 120, 검증 데이터 크기 : 30
#2 검증 세트 인덱스: [30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
 54 55 56 57 58 59]

3 교차검증 정확도 : 0.8333, 학습 데이터 크기 : 120, 검증 데이터 크기 : 30
#3 검증 세트 인덱스: [60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
 84 85 86 87 88 89]

4 교차검증 정확도 : 0.9333, 학습 데이터 크기 : 120, 검증 데이터 크기 : 30
#4 검증 세트 인덱스: [ 90  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 106 107
 108 109 110 111 112 113 114 115 116 117 118 119]

5 교차검증 정확도 : 0.7333, 학습 데이터 크기 : 120, 검증 데이터 크기 : 30
#5 검증 세트 인덱스: [120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
 138 139 140 141 142 143 144 145 146 147 148 149]

## 평균검증 정확도:  0.9044266666666668

(2) Stratified KFold

불균형한(imbalanced) 분포도를 가진 레이블 데이터 집합을 위한 KFold 방식

--> 레이블 분포를 먼저 고려한 뒤 이 분포와 동일하게 학습과 검증 데이터 세트를 분배함

  • 기존 KFold의 문제점 확인
In [22]:
import pandas as pd

iris = load_iris()
iris_df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
iris_df['label']  = iris.target
iris_df['label'].value_counts()
Out[22]:
2    50
1    50
0    50
Name: label, dtype: int64

레이블 값은 모두 50개로 동일

In [26]:
kfold = KFold(n_splits=3)
n_iter = 0

for train_index, test_index in kfold.split(iris_df):
    n_iter+=1
    label_train = iris_df['label'].iloc[train_index]
    label_test = iris_df['label'].iloc[test_index]
    print('\n##교차 검증: {0}'.format(n_iter))
    print('학습 레이블 데이터 분포: \n', label_train.value_counts())
    print('검증 레이블 데이터 분포: \n', label_test.value_counts())
##교차 검증: 1
학습 레이블 데이터 분포: 
 2    50
1    50
Name: label, dtype: int64
검증 레이블 데이터 분포: 
 0    50
Name: label, dtype: int64

##교차 검증: 2
학습 레이블 데이터 분포: 
 2    50
0    50
Name: label, dtype: int64
검증 레이블 데이터 분포: 
 1    50
Name: label, dtype: int64

##교차 검증: 3
학습 레이블 데이터 분포: 
 1    50
0    50
Name: label, dtype: int64
검증 레이블 데이터 분포: 
 2    50
Name: label, dtype: int64

위 결과를 보면, 3개의 폴드 세트로 생성된 학습 레이블과 검증 레이블이 완전히 다른 값으로 추출됨

이런 상태에서 학습모델로 검증데이터를 예측하면 정확도가 0이 나오게 됨

StratifiedKFold 는 분할된 데이터 세트가 전체 레이블 값의 분포를 반영하지 못하는 문제를 해결 해줌

실행방법은 거의 비슷하지만 split 메서드에 피처 데이터 세트뿐만 아니라 레이블 데이터 세트도 반드시 필요함

In [27]:
from sklearn.model_selection import StratifiedKFold

skf = StratifiedKFold(n_splits=3)
n_iter = 0

for train_index, test_index in skf.split(iris_df, iris_df['label']):
    n_iter += 1
    label_train = iris_df['label'].iloc[train_index]
    label_test = iris_df['label'].iloc[test_index]
    print('\n## 교차검증: {0}'.format(n_iter))
    print('학습 레이블 데이터 분포: \n', label_train.value_counts())
    print('검증 레이블 데이터 분포: \n', label_test.value_counts())
## 교차검증: 1
학습 레이블 데이터 분포: 
 2    33
1    33
0    33
Name: label, dtype: int64
검증 레이블 데이터 분포: 
 2    17
1    17
0    17
Name: label, dtype: int64

## 교차검증: 2
학습 레이블 데이터 분포: 
 2    33
1    33
0    33
Name: label, dtype: int64
검증 레이블 데이터 분포: 
 2    17
1    17
0    17
Name: label, dtype: int64

## 교차검증: 3
학습 레이블 데이터 분포: 
 2    34
1    34
0    34
Name: label, dtype: int64
검증 레이블 데이터 분포: 
 2    16
1    16
0    16
Name: label, dtype: int64

출력 결과를 보면 학습 레이블과 검증 레이블의 데이터 값 분포가 동일하게 할당됐음을 알 수 있음

In [32]:
# 붓꽃 데이터 세트에서 Stratified Kfold 를 이용한 검증

dt_clf = DecisionTreeClassifier(random_state=156)

skfold = StratifiedKFold(n_splits=3)
n_iter = 0
cv_accuracy = []

# StratifiedKFold의 split 호출 시 반드시 레이블 데이터 세트도 추가 입력 필요
for train_index, test_index in skfold.split(features, label):
    #split()으로 반환된 인덱스를 이용해 학습용, 검증용 테스트 데이터 추출
    X_train, X_test = features[train_index], features[test_index]
    y_train, y_test = label[train_index], label[test_index]
    # 학습 및 예측
    dt_clf.fit(X_train, y_train)
    pred = dt_clf.predict(X_test)
    # 반복 시마다 정확도 측정
    n_iter += 1
    accuracy = np.round(accuracy_score(y_test, pred),4)
    train_size = X_train.shape[0]
    test_size = X_test.shape[0]
    print('{0} 교차검증 정확도: {1}, 학습데이터 크기: {2}, 검증데이터 크기: {3}'.format(n_iter, accuracy, train_size, test_size))
    cv_accuracy.append(accuracy)
    
# 교차검증별 정확도 및 평균정확도 계산
print('\n 교차검증별 정확도: ', cv_accuracy)
print('## 평균 검증 정확도: ', np.mean(cv_accuracy))
1 교차검증 정확도: 0.9804, 학습데이터 크기: 99, 검증데이터 크기: 51
2 교차검증 정확도: 0.9216, 학습데이터 크기: 99, 검증데이터 크기: 51
3 교차검증 정확도: 0.9792, 학습데이터 크기: 102, 검증데이터 크기: 48

 교차검증별 정확도:  [0.9804, 0.9216, 0.9792]
## 평균 검증 정확도:  0.9604
  • 일반적으로 분류에서의 교차검증은 KFold가 아니라 Stratified Kfold로 분할이 되어야함

  • 반면, 회귀에서는 Stratified Kfold가 지원되지 않음

    • 결정값이 이산형의 레이블이 아니라 연속된 숫자 값이기 때문

(3) cross_val_score() : 교차 검증을 보다 간단히게

  • 앞서 수행한 Kfold로 데이터를 학습하고, 예측하는 코드를 보면 아래와 같은 프로세스로 검증이 진행되었음

    (1) 폴드 세트 설정 (2) for 루프 반복으로 학습 및 테스트 데이터의 인덱스를 추출 (3) 반복적으로 학습과 예측을 수행하고 예측수행을 반환

  • cross_val_score() 는 이 과정을 한꺼번에 수행해주는 API

cross_val_score() API의 선언형태

--> cross_val_score(estimator, X, y=None, scoring=None, cv=None, n_jobs=1, verbose =0, fit_params=None, pre_dispatch = '2*n_jobs')

  • estimator : 예측모델
  • X : 피처 데이터 세트
  • y : 레이블 데이터 세트,
  • scoring : 예측성능평가 지표
  • cv : 교차검증 폴드 수 (estimator로 classifier가 입력되면 Stratified Kfold 방식으로 학습/테스트 데이터 세트 분할)
  • 수행 후 scoring 파라미터 값으로 지정된 성능 지표를 배열 형태로 반환
In [34]:
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score, cross_validate
from sklearn.datasets import load_iris

iris_data = load_iris()
dt_clf = DecisionTreeClassifier(random_state=156)

data = iris_data.data
label = iris_data.target

# 성능지표는 정확도(accuracy), 교차검증 세트는 3개
scores = cross_val_score(dt_clf, data, label, cv =3 , scoring = 'accuracy')
print('교차 검증별 정확도: ', np.round(scores, 4))
print('평균 검증 정확도: ', np.round(np.mean(scores),4))
교차 검증별 정확도:  [0.9804 0.9216 0.9792]
평균 검증 정확도:  0.9604
  • cross_val_score() API는 내부에서 estimator를 학습, 예측, 평가시켜주므로 간단히 교차검증을 수행할 수 있음
  • 이와 유사하게 cross_validate() 은 여러개의 평가지표를 반환하며, 성능 평가지표와 수행시간도 같이 제공

(4) GridSearchCV - 교차검증과 하이퍼파라미터 튜닝을 한번에

GridSearchCV를 이용하여 모델에 사용되는 하이퍼 파라미터를 순차적으로 입력하면서 최적의 파라미터를 도출

In [35]:
grid_parameters = {'max_depth': [1, 2, 3], 
                              'min_samples_split': [2, 3]}

위와 같이 파라미터를 설정했다면 GridSearch에서는 아래와 같이 순차적으로 파라미터 조합을 적용하여 실행

In [37]:
pd.read_csv('Grid.csv', index_col= '순번')
Out[37]:
max_depth min_samples_split
순번
1 1 2
2 1 3
3 2 2
4 2 3
5 3 2
6 3 3

교차검증을 기반으로 실행되기 때문에 수행시간이 오래 걸림 (가령, 위의 예에서 cv=3으로 수행한다면 "cv 3회 * 6개 파라미터조합 = 18회"의 학습/평가가 이루어짐

GridSearch의 주요 파라미터

  • estimator : 적용 알고리즘 모델로 classifier, regressor, pipeline 등이 사용
  • param_grid : ket+리스트 값을 가지는 딕셔너리가 주어짐. estimator의 튜닝을 위해 파라미터명과 사용될 여러 파라미터 값을 지어
  • scoring : 예측 성능을 평가할 평가 방법을 지정, 문자열로 사이킷런의 성능평가 지표를 입력하나 별도의 함수 지정도 가능
  • refit : 기본값은 True이며 True로 생성시 가장 최적의 하이퍼 파라미터를 찾은 뒤 입력된 estimator 객체를 해당 하이퍼 파리미터로 재학습시킴
In [45]:
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV

# 데이터를 로딩하고 학습데이터와 테스트 데이터 분리
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris_data.data, iris_data.target, test_size = 0.2, random_state=121)

dtree = DecisionTreeClassifier()

## 파라미터를 딕셔너리 형태로 설정
parameters = {'max_depth' : [1, 2, 3], 'min_samples_split' : [2, 3]}

학습 데이터 세트를 GridSearchCV 객체의 fit 메서드에 인자로 입력

--> 학습데이터를 cv에 입력된 폴딩 수로 분할

--> param_grid에 기술된 하이퍼파라미터로 순차적으로 변경하며 학습/평가 수행

--> 개별 평가결과 : cvresults 에 기록

--> 최고 성능 파라미터 : bestparams 에 기록

--> 최고 점수 : bestscores 에 기록

In [49]:
# param_grid의  하이퍼 파라미터를 3개의 train, test set fold로 나누어 테스트 수행 설정
## refit=True가 기본 설정으로 True이면 가장 좋은 파라미터 설정으로 재학습 시킴

grid_dtree = GridSearchCV(dtree, param_grid=parameters, cv=3, refit=True)

# 붓꽃 학습 데이터로 param_grid의 하이퍼 파라미터를 순차적으로 학습, 평가
grid_dtree.fit(X_train, y_train)

# GridSearchCV 결과를 추출해 데이터 프레임으로 반환
scores_df = pd.DataFrame(grid_dtree.cv_results_)
scores_df[['params', 'mean_test_score', 'rank_test_score', 'split0_test_score', 'split1_test_score', 'split2_test_score']]
/anaconda3/lib/python3.7/site-packages/sklearn/utils/deprecation.py:125: FutureWarning: You are accessing a training score ('split0_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/anaconda3/lib/python3.7/site-packages/sklearn/utils/deprecation.py:125: FutureWarning: You are accessing a training score ('split1_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/anaconda3/lib/python3.7/site-packages/sklearn/utils/deprecation.py:125: FutureWarning: You are accessing a training score ('split2_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/anaconda3/lib/python3.7/site-packages/sklearn/utils/deprecation.py:125: FutureWarning: You are accessing a training score ('mean_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
/anaconda3/lib/python3.7/site-packages/sklearn/utils/deprecation.py:125: FutureWarning: You are accessing a training score ('std_train_score'), which will not be available by default any more in 0.21. If you need training scores, please set return_train_score=True
  warnings.warn(*warn_args, **warn_kwargs)
Out[49]:
params mean_test_score rank_test_score split0_test_score split1_test_score split2_test_score
0 {'max_depth': 1, 'min_samples_split': 2} 0.700000 5 0.700 0.7 0.70
1 {'max_depth': 1, 'min_samples_split': 3} 0.700000 5 0.700 0.7 0.70
2 {'max_depth': 2, 'min_samples_split': 2} 0.958333 3 0.925 1.0 0.95
3 {'max_depth': 2, 'min_samples_split': 3} 0.958333 3 0.925 1.0 0.95
4 {'max_depth': 3, 'min_samples_split': 2} 0.975000 1 0.975 1.0 0.95
5 {'max_depth': 3, 'min_samples_split': 3} 0.975000 1 0.975 1.0 0.95
  • params : 적용한 개별 하이퍼 파라미터 값
  • rank_test_score : 성능이 좋게 나온 score 순위
  • mean_test_score : 개별 하이퍼파라미터별로 CV 폴딩 테스트의 평가 평균 값
In [53]:
print('GridSearch 최적 파라미터: ', grid_dtree.best_params_)
print('GridSearch 최고 점수: ', grid_dtree.best_score_)
GridSearch 최적 파라미터:  {'max_depth': 3, 'min_samples_split': 2}
GridSearch 최고 점수:  0.975

refit = True 이면, GridSearchCV가 이미 최적 성능을 나타내는 하이퍼파라미터로 Estimator를 학습해 bestestimator 로 저장했음

In [54]:
# GridSearchCV의 refit으로 학습된 estimator 반환
estimator = grid_dtree.best_estimator_

# GridSearchCV의 best_estmator_ 는 이미 최적 학습이 됐으므로 별도 학습이 필요 없음
pred = estimator.predict(X_test)
print('테스트 데이터세트 정확도: {0: .4f}'.format(accuracy_score(y_test, pred)))
테스트 데이터세트 정확도:  0.9667

+ Recent posts