Model Selection Module

CharliePark·2020년 9월 16일
0

TIL

목록 보기
38/67

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

 

학습 / 테스트 데이터 세트 분리 - train_test_split()

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

output

예측 정확도: 1.0

이미 학습한 학습 데이터 세트를 기반으로 예측했기 때문에 정확도가 100%가 나온다.

 

 

train_test_split() 을 이용해 학습 및 테스트 데이터 세트를 분리해야 한다.

train_test_split() 는 첫 번째 파라미터로 피처 데이터 세트, 두 번째 파라미터로 레이블 데이터 세트를 입력받는다. 그리고 선택적으로 다음 파라미터를 입력받는다

 

  • test_size : 전체 데이터에서 테스트 데이터 세트 크기를 얼마로 샘플링할 것인가를 결정한다. 디폴트는 0.25, 즉 25%
  • train_size : 전체 데이터에서 학습용 데이터 세트 크기를 얼마로 샘플랑할 것인가를 결정한다. test_size 파라미터를 통상적으로 사용하기 때문에 train_size 는 잘 사용되지 않는다.
  • shuffle : 데이터를 분리하기 전에 데이터를 미리 섞을지를 결정한다. 디폴트는 True이다. 데이터를 분산시켜서 좀 더 효율적인 학습 및 데이터 세트를 만드는 데 사용된다.
  • random state : random state 는 호출할 때마다 동일한 학습/테스트용 데이터 세트를 생성하기 위해 주어지는 난수 값이다. random state 가 지정되지 않으면 train_test_split() 는 수행할 때마다 무작위로 다른 데이터를 생성한다.
  • train_test_split() 의 반환값은 튜플 형태이다. 순차적으로 학습용 데이터의 피처 데이터 세트, 테스트용 데이터의 피처 데이터 세트, 학습용 데이터의 레이블 데이터 세트, 테스트용 데이터의 레이블 데이터 세트가 반환된다.
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)
dt_clf.fit(X_train, y_train)
pred = dt_clf.predict(X_test)
print('예측 정확도: {0:.4f}'.format(accuracy_score(y_test,pred)))

output

예측 정확도: 0.9556

 

 

교차 검증

과적합 : 모델이 학습 데이터에만 과도하게 최적화되어, 실제 예측을 수행할 경우에 예측 성능이 과도하게 떨어지는 것

과적합 문제를 개선하기 위해, 교차 검증을 통해 더 다양한 학습과 평가를 수행한다.

데이터 편중을 막기 위해서 별도의 여러 세트로 구성된 학습 데이터 세트와 검증된 데이터 세트에서 학습과 평가를 수행하는 것이다. 각 세트에서 수행한 평가 결과에 따라 하이퍼 파라미터 튜닝 등의 모델 최적화를 더욱 손쉽게 할 수 있다.

대부분의 ML 모델의 성능 평가는 교차 검증 기반으로 1차 평가를 한 뒤에 최종적으로 테스트 데이터 세트에 적용해 평가하는 프로세스이다. ML에 사용되는 데이터 세트를 세분화해서 학습, 검증, 테스트 데이터 세트로 나눌 수 있다.

 

K 폴드 교차 검증

K 폴드 교차 검증은 가장 보편적으로 사용되는 교차 검증 기법이다. K개의 데이터 폴드 세트를 만들어서 K번만큼 각 폴드 세트에 학습과 검증 평가를 반복적으로 수행하는 방법이다.

K가 5인 5 폴드 교차 검증을 예로 들어보자.

데이터 세트를 5등분 (K등분) 한 뒤, 전체 5등분에서 5번을 검증 데이터 세트로 설정하고 나머지 1~4를 학습 데이터 세트로 설정한다.

학습 데이터 세트에서 학습을 수행하고, 검증 데이터 세트에서 평가를 수행한 후, 검증 데이터 세트를 바꿔가며 전체 과정을 반복 수행한다.

이 5개의 평가를 평균한 결과를 가지고 예측 성능을 평가한다.

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(random_state=156)

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

output

붓꽃 데이터 세트 크기: 150

 

KFold 의 split() 을 호출하면 학습용/검증용 데이터로 분할할 수 있는 인덱스를 반환한다. 따라서, 실제 학습용/검증용 데이터 추출은 반환된 인덱스를 기반으로 개발 코드에서 직접 수행해야 한다.

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

output

#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 교차 검증 정확도 :0.9667, 학습 데이터 크기: 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.8667, 학습 데이터 크기: 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.9

 

 

Stratified K 폴드

Stratified K 폴드는 불균형한(분포가 한쪽으로 치우칠 때; imbalanced) 분포도를 가진 레이블(결정 클래스) 데이터 집합을 위한 K 폴드 방식이다.

K 폴드가 가진 문제점을 알아보고, Stratified K 폴드로 개선해보자

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

output

2    50
1    50
0    50
Name: label, dtype: int64

레이블 값 0, 1, 2 모두 50개로 동일하다. 이슈가 발생하는 상황을 만들기 위해, KFold로 생성하고 학습/검증 레이블 데이터 값의 분포도를 확인해보자.

kfold = KFold(n_splits=3)
# kfold.split(X)는 폴드 세트를 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('## 교차 검증: {0}'.format(n_iter))
    print('학습 레이블 데이터 분포:\n', label_train.value_counts())
    print('검증 레이블 데이터 분포:\n', label_test.value_counts())

output

## 교차 검증: 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

위의 결과에서 볼 수 있듯이, 레이블이 고르지 않게 분포된다. 이러한 방식으로 데이터 세트를 분할하면 검증 예측 정확도가 떨어질 수 밖에 없다.

 

StratifiedKFold로 동일한 분할을 수행해보자. 사용하는 방법은 KFold와 비슷하나, split() 메서드에 인자로 피처 데이터 세트 뿐만 아니라 레이블 데이터 세트도 반드시 입력해야 한다.

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('## 교차 검증: {0}'.format(n_iter))
    print('학습 레이블 데이터 분포:\n', label_train.value_counts())
    print('검증 레이블 데이터 분포:\n', label_test.value_counts())

output

## 교차 검증: 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

위의 결과에서 볼 수 있듯이 각 레이블 값이 고르게 분포됐다.

 

다음 코드는 StratifiedKFold를 이용해 붓꽃 데이터를 교차 검증한 것이다.

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('\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)
    
# 교차 검증별 정확도 및 평균 정확도 계산 
print('\n## 교차 검증별 정확도:', np.round(cv_accuracy, 4))
print('## 평균 검증 정확도:', np.mean(cv_accuracy)) 

output

#1 교차 검증 정확도 :0.9804, 학습 데이터 크기: 99, 검증 데이터 크기: 51
#1 검증 세트 인덱스:[  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  50
  51  52  53  54  55  56  57  58  59  60  61  62  63  64  65  66 100 101
 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116]

#2 교차 검증 정확도 :0.9216, 학습 데이터 크기: 99, 검증 데이터 크기: 51
#2 검증 세트 인덱스:[ 17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  67
  68  69  70  71  72  73  74  75  76  77  78  79  80  81  82  83 117 118
 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133]

#3 교차 검증 정확도 :0.9792, 학습 데이터 크기: 102, 검증 데이터 크기: 48
#3 검증 세트 인덱스:[ 34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  84  85
  86  87  88  89  90  91  92  93  94  95  96  97  98  99 134 135 136 137
 138 139 140 141 142 143 144 145 146 147 148 149]

## 교차 검증별 정확도: [0.9804 0.9216 0.9792]
## 평균 검증 정확도: 0.9604

교차 검증한 결과 평균 검증 정확도가 약 96.04%로 측정됐다.

Stratified K Fold 는 원본 데이터의 레이블 분포도 특성을 반영한 학습 및 검증 데이터 세트를 만들 수 있으므로 왜곡된 레이블 세트에서는 반드시 Stratified K Fold 를 사용해야 한다.

일반적으로 분류(Classification)에서는 Stratified K 폴드로 분할해야 한다. 회귀(Regression)에서는 연속된 숫자값이기 때문에 결정값별로 분포를 정하는 의미가 없다. 따라서 회귀에서는 Stratified K Fold 가 지원되지 않는다.

 

 

*교차 검증을 보다 간편하게 - cross_val_score()*

KFold에서는
1. 먼저 폴드 세트를 설정하고
2. for 루프에서 반복으로 학습 및 테스트 데이터의 인덱스를 추출한 뒤
3. 반복적으로 학습과 예측을 수행하고 예측 성능을 반환
했다.

cross_val_score() 은 이러한 과정을 한꺼번에 수행해준다.

 

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

이 중 estimator, X, y, scoring, cv 가 주요 파라미터이다.

estimator는 사이킷런의 Clissifier 클래스 또는 Regressor 를 의미한다.

X는 피처 데이터 세트, y는 레이블 데이터 세트를 의미한다

scoring은 예측 성능 평가 지표를 기술하며, cv는 교차 검증 폴드 수를 의미한다.

cross_val_score() 수행 후 반환 값은 scoring 파라미터로 지정된 성능 지표 측정값을 배열 형태로 반환한다.

classifier가 입력되면 Stratified K 폴드로, Regressor의 경우는 K 폴드 방식으로 분할한다.

from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score , cross_validate
from sklearn.datasets import load_iris
import numpy as np

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 , scoring='accuracy',cv=3)
print('교차 검증별 정확도:',np.round(scores, 4))
print('평균 검증 정확도:', np.round(np.mean(scores), 4))

output

교차 검증별 정확도: [0.9804 0.9216 0.9792]
평균 검증 정확도: 0.9604

cross_val_score() 는 cv로 지정된 횟수만큼 scoring 파라미터로 지정된 평가 지표로 평가 결괏값을 배열로 반환한다. 그리고 일반적으로 이를 평균해 평가 수치로 사용한다.

비슷한 API로 cross_validate() 가 있다. cross_val_score() 는 하나의 평가 지표만 가능하지만, cross_validate() 는 여러 개의 평가 지표를 반환할 수 있다. 또한 학습 데이터에 대한 성능 평가 지표와 수행 시간도 같이 제공한다.

 

 

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

사이킷런은 GridSearchCV API를 이용해 Classifier나 Regressor와 같은 알고리즘에 사용되는 하이퍼 파라미터를 순차적으로 입력하면서 편리하게 최적의 파라미터를 도출할 수 있는 방안을 제공한다.

딕셔너리 형태로 입력된 하이퍼 파라미터에 따라, 하이퍼 파라미터를 변경하면서 교차 검증 데이터 세트의 수행 성능을 측정한다. CV가 3회이고 6개 조합의 파라미터를 입력했다면 총 3x6 = 18회의 학습이 이뤄진다.

GridSearchCV의 생성자로 들어가는 주요 파라미터는 다음과 같다.

  • estimator : classifier, regressor, pipeline이 사용될 수 있다.
  • param_grid : key + 리스트 값을 가지는 딕셔너리가 주어진다. estimator의 튜닝을 위해 파라미터명과 사용될 여러 파라미터 값을 지정한다.
  • scoring : 예측 성능을 측정할 평가 방법을 지정한다. 보통은 사이킷런의 성능 평자 지표를 지정하는 문자열로 지정하나, 별도의 성능 평가 지표 함수도 지정할 수 있다.
  • cv : 교차 검증을 위해 분할되는 학습/데이터 세트의 개수를 지정한다.
  • refit : 디폴트가 True이며 True로 생성 시 최적의 하이퍼 파라미터를 찾은 뒤 입력된 estimator 객체를 해당 하이퍼 파라미터로 재학습시킨다.
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.metrics import accuracy_score

# 데이터를 로딩하고 학습데이타와 테스트 데이터 분리
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()

### parameter 들을 dictionary 형태로 설정
parameters = {'max_depth':[1, 2, 3], 'min_samples_split':[2,3]}

 

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

GridSearchCV 객체의 fit 메서드를 수행하면, 학습 데이터를 cv에 입력된 폴딩 세트로 분할해, paramgrid에 입력된 하이퍼 파라미터를 순차적으로 변경하면서 학습/평가를 수행하고 그 결과를 cv_results 속성에 딕셔너리로 기록한다. 이를 Pandas의 DataFrame으로 변환하면 더 쉽게 내용을 볼 수 있다.

import pandas as pd

# param_grid의 하이퍼 파라미터들을 3개의 train, test set fold 로 나누어서 테스트 수행 설정.  
### refit=True 가 default 임. True이면 가장 좋은 파라미터 설정으로 재 학습 시킴.  
grid_dtree = GridSearchCV(dtree, param_grid=parameters, cv=3, refit=True, return_train_score=True)

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

# GridSearchCV 결과는 cv_results_ 라는 딕셔너리로 저장됨. 이를 DataFrame으로 변환
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']]

output

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 순위를 나타낸다. 1이 가장 뛰어난 순위이며, 이때 최적의 하이퍼 파라미터이다.
  • mean_test_score는 개별 하이퍼 파라미터별로 CV의 폴딩 테스트 세트에 대해 총 수행한 평가 평균값이다.

 

 

최적의 하이퍼 파라미터 값과 그때의 평가 결과는 각각 bestparams, bestscore 속성에 기록된다.

print('GridSearchCV 최적 파라미터:', grid_dtree.best_params_)
print('GridSearchCV 최고 정확도: {0:.4f}'.format(grid_dtree.best_score_))

output

GridSearchCV 최적 파라미터: {'max_depth': 3, 'min_samples_split': 2}
GridSearchCV 최고 정확도: 0.9750

이미 학습된 최적의 Estimator는 bestestimator를 통해 사용할 수 있다.

# GridSearchCV의 refit으로 이미 학습이 된 estimator 반환
estimator = grid_dtree.best_estimator_

# GridSearchCV의 best_estimator_는 이미 최적 하이퍼 파라미터로 학습이 됨
pred = estimator.predict(X_test)
print('테스트 데이터 세트 정확도: {0:.4f}'.format(accuracy_score(y_test,pred)))

output

테스트 데이터 세트 정확도: 0.9667

0개의 댓글