<04. Model selection 모듈 소개>
사이킷런의 model_selection 모듈은 학습 데이터와 테스트 데이터 세트를 분리하거나 교차 검증 분할 및 평가, 그리고 Estimator의 하이퍼 파라미터를 튜닝하기 위한 다양한 함수와 클래스를 제공한다.
[학습/테스트 데이터 세트 분리 - train_test_split()]
먼저 테스트 데이터를 이용하지 않고 학습 데이터 세트로만 학습하고 예측하면 어떤 문제가 생기는지 보자
from sklearn.datasets import load_iris #붓꽃 데이터 호출
from sklearn.tree import DecisionTreeClassifier #tree 기반 머신러닝, DecisionTreeClassifier 호출 -> 분류 구현 클래스
from sklearn.metrics import accuracy_score #정확도 측정
iris = load_iris()
dt_clf = DecisionTreeClassifier() #의사결정트리 클래스를 객체로 생성
train_data = iris.data #iris.data는 Iris 데이터 세트에서 피처(feature)만으로 된 데이터를 numpy 형태로 가짐
train_label = iris.target #iris.target은 붓꽃 데이터 세트에서 레이블(결정값)데이터를 numpy 형태로 가짐
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()을 통해 원본 데이터 세트에서 학습 및 테스트 데이터 세트를 쉽게 분리할 수 있다.
붓꽃 데이터 세트를 학습,테스트 데이터 세트로 분리해보자
먼저 sklearn.model_selection 모듈에서 trian_test_split()을 로드한다.
(*sklearn.model_selection 모듈은 학습, 검증, 예측 데이터로 데이터를 분리하거나 최적의 하이퍼 파라미터로 평가하기 위한 다양한 모듈의 모임)
train_test_split() 의 첫 번째 파라미터로 피처 데이터 세트, 두 번째 파라미터로 레이블 데이터 세트를 입력받는다.
그리고 선택적으로 다음 파라미터를 입력받는다.
- test_size : 전체 데이터에서 테스트 데이터 세트의 크기를 얼마로 샘플링할 것인가를 결정한다. (디폴트 0.25, 25%)
- train_size : 전체 데이터에서 학습용 데이터 세트 크기를 얼마로 샘플링할 것인가를 결정한다. (잘 사용되지 않음)
- shuffle : 데이터를 분리하기 전에 데이터를 미리 섞을지를 결정한다. 데이터를 분산시켜서 좀 더 효율적인 학습 및 데이터 세트를 만듦(디폴드 True)
- random_state : 동일한 학습/테스트용 데이터 세트를 생성하기 위해 주어지는 난수값, train_test_split()은 호출 시 무작위로 데이터를 분리하므로 random_state를 지정하지 않으면 수행할 때마다 다른 학습/테스트 용 데이터를 생성한다.
- train_test_split()의 반환값은 튜플 형태이다. 순차적으로 학습용-피처데이터, 테스트용-피처데이터, 학습용-레이블데이터, 테스트용-레이블데이터 세트가 반환된다.
붓꽃 데이터 세트를 train_test_split()을 이용해 테스트 데이터 세트 30%, 학습 데이터 세트 70%로 분리해보자
from sklearn.datasets import load_iris #붓꽃 데이터 호출
from sklearn.tree import DecisionTreeClassifier #tree 기반 머신러닝, DecisionTreeClassifier 호출 -> 분류 구현 클래스
from sklearn.metrics import accuracy_score #정확도 측정
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)
학습 데이터를 기반으로 DecisionTreeClassifier을 학습하고 이 모델을 이용해 예측 정확도를 측정해보자
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
테스트 데이터로 예측을 수행한 결과 정확도가 약 95.56%이다.
붓꽃 데이터는 150개의 데이터로 데이터 양이 크지 않아 전체의 30% 정도인 테스트 데이터는 45개 정도밖에 되지 않으므로 이를 통해 알고리즘의 예측 성능을 판단하기에는 그리 적절하지 않다.
학습을 위한 데이터의 양을 일정 수준 이상으로 보장하는것도 중요하지만, 학습된 모델에 대해 다양한 데이터를 기반으로 예측 성능을 평가해보는 것도 중요하다.
[교차 검증]
과적합(Overfitting)이란 모델이 학습 데이터에만 과도하게 최적화되어, 실제 예측을 다른 데이터로 수행할 경우에는 예측 성능이 과도하게 떨어지는 것을 말한다.
이러한 문제점을 개선하기 위해 교차 검증을 이용해 더 다양한 학습과 평가를 수행한다.
교차검증은 데이터의 편중을 막기 위해서 별도의 여러 세트로 구성된 학습 데이터 세트와 검증 데이터 세트에서 학습과 평가를 수행하는 것이다.
그리고 각 세트에서 수행한 평가 결과에 따라 하이퍼 파라미터 튜닝 등의 모델 최적화를 더욱 손쉽게 할 수 있다.
대부분의 ML 모델의 성능 평가는 교차 검증 기반으로 1차 평가를 한 뒤에 최종적으로 테스트 데이터 세트에 적용해 평가하는 프로세스이다.
ML에 사용되는 데이터 세트를 세분화 해서 학습, 검증, 테스트 데이터 세트로 나눌 수 있다.
이 별도의 검증 데이터 세트가 최종 평가 이전에 학습된 모델을 다양하게 평가하는 데 사용되는 것이다.
1. K 폴드 교차 검증
K 폴드 교차 검증은 가장 보편적으로 사용되는 교차 검증 기법이다.
K개의 데이터 폴드 세트를 만들어서 K번 만큼 각 폴드 세트에 학습과 검증 평가를 반복적으로 수행하는 방법이다.
5 폴드 교차 검증 (K가 5)의 경우, 5개의 폴드된 데이터 세트를 학습과 검증을 위한 데이터 세트로 변경하면서 5번 평가를 수행한 뒤, 이 5개의 평가를 평균한 결과를 가지고 예측 성능을 평가한다.
먼저 데이터 세트를 k등분, 즉 5등분한다.
그리고 첫 번째 반복에서는 처음부터 4개 등분을 학습 데이터 세트, 마지막 5번째 등분 하나를 검증 데이터 세트로 설정하고 학습 데이터 세트에 학습 수행, 검증 데이터 세트에서 평가를 수행한다.
두 번째 반복에서 다시 비슷한 학습과 평가 작업을 수행한다.
단, 이번에는 처음부터 3개, 마지막 5번째 등분을 학습 데이터 세트로, 4번째 등분 하나를 검증 데이터 세트로 결정한다.
이렇게 5개의 예측 평가를 구현했으면 이를 평균해서 K 폴드 평가 결과로 반영하면 된다.
사이킷런에서는 K 폴드 교차 검증 프로세스를 구현하기 위해 KFold와 StratifiedKFold 클래스를 제공한다.
먼저 Kold 클래스를 이용해 붓꽃 데이터 세트를 교차 검증하고 에측 정확도를 알아보자
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()을 호출해 전체 붓꽃 데이터를 5개의 폴드 데이터 세트로 분리한다.
전체 붓꽃 데이터는 150개이다.
따라서 학습용 데이터는 4/5 인 120개, 테스트용 데이터는 1/5인 30개로 분할된다.
split() 메서드를 호출하게 되면 학습용/테스트용 데이터로 분할할 수 있는 인덱스를 반환한다.
n_iter = 0
#KFold 객체의 split()을 호출하면 폴드별 학습용, 검증용 테스트 로우 인덱스를 array로 반환
for train_index, test_index, in kfold.split(features): #split()의 파라미터는 붓꽃 피처에 대한 값을 받는다
#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 = 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
5번 교차 검증 결과 평균 검증 정확도는 0.9이다. 그리고 교차 검증 시마다 검증 세트의 인덱스가 달라짐을 알 수 있다.
또한 인덱스를 보면 교차 검증 시마다 split()함수가 어떻게 인덱스를 할당하는지 알 수 있다.
각 횟수마다 30개의 검증 세트 인덱스를 생성했고, 이를 기반으로 검증 세트를 추출하게 된다.
2. Stratified K 폴드
Stratified K 폴드는 불균형한 분포도를 가진 레이블(결정 클래스)데이터 집합을 위한 K 폴드 방식이다.
Stratified K 폴드는 K 폴드가 레이블 데이터 집합이 원본 데이터 집합의 레이블 분포를 학습 및 테스트 세트에 제대로 분배하지 못하는 경우의 문제를 해결해준다.
이를 위해 Stratified K 폴드는 원본 데이터의 레이블 분포를 먼저 고려한 뒤 이 분포와 동일하게 학습과 검증 데이터 세트를 분배한다.
먼저 K 폴드가 어떤 문제를 가지고 있는지 확인해 보고 이를 사이킷런의 StratifiedKFold클래스를 이용해 개선해보자.
이를 위해 붓꽃 데이터 세트를 간단하게 DataFrame 으로 생성하고 레이블 값의 분포도를 먼저 확인해본다.
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개로 동일하다. 즉 Setosa, Versicolor, Virginica 품종 모두 50개인 것이다.
이슈가 발생하는 현상을 도출하기 위해 3개의 폴드 세트를 KFold로 생성하고, 각 교차 검증 시마다 생성되는 학습/검증 레이블 데이터 값의 분포도를 확인해보자
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] #iloc: 위치기반인덱싱 iloc[행위치정수값, 열위치정수값]
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
교차 검증 시마다 3개의 폴드 세트로 만들어지는 학습 레이블과 검증 레이블이 완전히 다른 값으로 추출되었다.
예를 들어, 첫 번째 교차 검증의 경우 학습 레이블이 1,2 밖에 없으므로 0의 경우는 전혀 학습하지 못한다.
반대로 검증 레이블은 0밖에 없으므로 학습 모델은 절대 0을 예측하지 못한다.
이런 유형으로 교차 검증 데이터 세트를 분할하면 검증 예측 정확도는 0이 될 수 밖에 없다.
이번에는 동일한 데이터 분할을 StratifiedKFold로 수행하고 학습 검증 레이블 데이터의 분포도를 확인해보자
StratifiedKFold를 사용하는 방법은 KFold와 거의 동일하지만 단 하나 큰 차이는 StratifiedKFold는 레이블 데이터 분포도에 따라 학습/검증 데이터를 나누기 때문에 split()메서드에 인자로 피처 데이터 세트 뿐만 아니라 레이블 데이터 세트도 반드시 필요하다.
폴드 세트는 3개로 설정해서 이를 구현해보자
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 = 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 34
1 33
0 33
Name: label, dtype: int64
검증 레이블 데이터 분포:
1 17
0 17
2 16
Name: label, dtype: int64
##교차 검증:2
학습 레이블 데이터 분포:
1 34
2 33
0 33
Name: label, dtype: int64
검증 레이블 데이터 분포:
2 17
0 17
1 16
Name: label, dtype: int64
##교차 검증:3
학습 레이블 데이터 분포:
0 34
2 33
1 33
Name: label, dtype: int64
검증 레이블 데이터 분포:
2 17
1 17
0 16
Name: label, dtype: int64
출력 결과를 보면 학습 레이블과 검증 레이블 데이터 값의 분포도가 거의 동일하게 할당됐음을 알 수 있다.
이렇게 분할이 되어야 0,1,2 를 모두 학습할 수 있고 이에 기반해 검증을 수행할 수 있다.
다음 코드는 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 = 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.round(np.mean(cv_accuracy),4))
[output]
1교차 검증 정확도 :0.98, 학습 데이터 크기: 100, 검증 데이터 크기:50
#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]
2교차 검증 정확도 :0.94, 학습 데이터 크기: 100, 검증 데이터 크기:50
#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 116 117 118
119 120 121 122 123 124 125 126 127 128 129 130 131 132]
3교차 검증 정확도 :0.98, 학습 데이터 크기: 100, 검증 데이터 크기:50
#3 검증 세트 인덱스:[ 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 83 84
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 133 134 135
136 137 138 139 140 141 142 143 144 145 146 147 148 149]
## 교차 검증별 정확도: [0.98 0.94 0.98]
## 평균 검증 정확도: 0.9667
3개의 Stratified K 폴드로 교차 검증한 결과 평균 검증 정확도각 약 96.67%로 측정되었다.
일반적으로 분류에서의 교차 검증은 K 폴드가 아니라 Stratified K 폴드로 분할돼야한다.
회귀의 결정값은 이산값 혀애의 레이블이 아니라 연속된 숫자값이기 때문에 회귀에서는 Stratified K 폴드가 지원되지 않는다.
3. 교차검증을 보다 간편하게 - cross_val_score()
사이킷런은 교차 검증을 좀 더 편리하게 수행할 수 있게 해주는 API를 제공한다.
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, 또는 회귀 알고리즘 클래스인 Regressor을 의미
- X - 피처 데이터 세트
- y - 레이블 데이터 세트
- scoring - 예측 성능 평가 지표
- cv - 교차 검증 폴드 수
cross_val_score() 수행 후 반환 값은 scoring 파라미터로 지정된 성능 지표 측정값을 배열 형태로 반환한다.
cross_val_score() 는 classifier 가 입력되면 Stratified K 폴드 방식으로 레이블값 분포에 따라 학습/테스트 세트를 분할한다. (회귀인 경우에는 K폴드 방식으로)
다음 코드에서 cross_val_score()의 자세한 사용법을 살펴보자
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
# 성능 지표(scoring)는 정확도(accuracy), 교차 검증 세트(cv)는 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.98 0.94 0.98]
평균 검증 정확도: 0.9667
교차 검증별 정확도를 보면 cross_val_score()는 cv로 지정된 횟수만큼 scoring 파라미터로 지정된 평가 지표로 평가 결괏값을 배열(array)형태로 반환하는 것을 확인할 수 있다.
그리고 일반적으로 이를 평균해 평균 수치로 사용한다.
cross_val_score()가 내부적으로 StartifiedKFold를 이용하기 때문에 이는 학습, 예측, 평가를 통해 간단하게 검증을 수행할 수 있는 것이다.
[GirdSeachCV - 교차 검증과 최적 하이퍼 파라미터 튜닝을 한 번에]
하이퍼 파라미터는 머신러닝 알고리즘을 구성하는 주요 구성 요소이며, 이 값을 조정해 알고리즘의 예측 성능을 개선할 수 있다.
사이킷런은 GridSearchCV API를 이용해 Classifier 나 Regressor와 같은 알고리즘에 사용되는 하이퍼 파라미터를 순차적으로 입력하면서 편리하게 최적의 파라미터를 도출할 수 있는 방안을 제공한다.
예를 들어 결정 트리 알고리즘의 여러 하이퍼 파라미터를 순차적으로 변경하면서 최고 성능을 가지는 파라미터 조합을 찾고자 한다면 다음과 같이 하이퍼 파라미터의 집합을 만들고 이를 순차적으로 적용하면서 최적화를 수행할 수 있다.
grid_parameters = {'max_depth': [1,2,3],
'min_samples_split': [2,3]
}
총 6회에 걸쳐 파라미터를 순차적으로 바꿔 실행하면서 최적의 파라미터와 수행결과를 도출할 수 있다.
for 루프로 모든 파라미터를 번갈아 입력하면서 학습시키는 방법을 좀 더 유연하게 API레벨에서 제공한 것이다.
GirdSearchCV는 교차검증을 기반으로 이 하이퍼 파라미터의 최적값을 찾게 해준다.
즉, 데이터 세트를 cross-validation을 위한 학습/테스트 세트로 자동으로 분할한 뒤에 하이퍼 파라미터 그리드에 기술된 모든 파라미터를 순차적으로 적용해 최적의 파라미터를 찾을 수 있게 해준다.
하지만 이는 수행시간이 상대적으로 오래 걸리는 것에 유념해야한다.
위의 경우 순차적으로 6회에 걸쳐 파라미터를 변경하면서 교차 검증데이터 세트에 수행 결과를 측정한다.
CV가 3회라면 총 CV 3회 x 6개의 파라미터 조합 = 18회의 학습/평가가 이루어진다.
GridSearchCV 클래스의 주요 파라미터는 다음과 같다.
- estimator : classifier, regressor, pipline이 사용될 수 있다.
- param_grid : key + 리스트값을 가지는 딕셔너리가 주어진다. estimator의 튜닝을 위해 파라미터명과 사용될 여러 파라미터 값을 지정한다.
- scoring : 예측 성능을 측정할 평가 방법을 지정한다. 보통은 사이킷런의 성능평가 지표를 지정하는 문자열(예: 정확도의 경우 'accuracy')로 지정하나 별도의 성능 평가 지표 함수도 지정할 수 있다.
- cv : 교차 검증을 위해 분할되는 학습/테스트 세트의 개수를 지정한다.
- refit: 디폴트가 True이며, True로 생성 시 가장 최적의 하이퍼 파라미터를 찾은 뒤 입력된 estimator 객체를 해당 하이퍼 파라미터로 재학습시킨다.
예시로 결정 트리 알고리즘의 여러가지 최적화 파라미터를 순차적으로 적용해 붓꽃 데이터를 예측 분석하는데 GridSearchCV를 이용해보자
trian_test_split()을 이용해 학습/테스트 데이터를 분리하고 학습 데이터에 GridSearchCV를 이용해 최적 하이퍼 파라미터를 추출해본다.
DecisionTreeClassifier의 중요 하이퍼 파라미터인 max_depth와 min_sample_split의 값을 변화시키면서(순차적으로 입력) 최적화를 진행한다.
테스트할 하이퍼 파라미터 세트는 딕셔너리 형태로 하이퍼 파라미터의 명칭은 문자열 key 값으로, 하이퍼 파라미터의 값은 리스트형으로 설정한다.
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import train_test_split
# 데이터를 로딩하고 학습 데이터와 테스트 데이터 분리 -train_test_split()
iris_data = 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(parameters)에 기술된 하이퍼 파라미터를 순차적으로 변경하면서 학습/평가를 수행하고 그 결과를 cv_results_속성에 기록한다.
cv_results_는 gridsearchcv의 결과 세트로서 딕셔너리 형태로 key값과 리스트 형태의 value값을 가진다.
이를 판다스의 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)
#붓꽃 학습 데이터로 param_grid의 하이퍼 파라미터를 순차적으로 학습/평가
grid_dtree.fit(X_train, y_train)
#GridSearchCV 결과를 추출해 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]
위의 결과에서 총 6개의 결과를 볼 수 있으며, 이는 하이퍼 파라미터 max_depth와 mmin_samples_split을 순차적으로 6번 변경하면서 학습 및 평가를 수행했음을 의미한다.
위 결과의 'params'칼럼은 수행할 때 마다 적용된 하이퍼 파라미터 값을 갖고 있다.
인덱스 4, 5번을 보면 'rank_test_score'칼럼 값이 1이다. 이는 해당 하이퍼 파라미터 조합인 max_depth:3, min_samples_split:2로 평가한 결과 예측 성능이 공동 1위라는 의미이다.
그때의 mean_test_score 칼럼값을 보면 0.975000으로 가장 높다.
split0, split1, split2_test_score는 CV가 3인 경우, 즉 3개의 폴딩세트에서 각각 테스트한 성능 수치이다.
mean_test_score는 이 세 개성능 수치를 평균한 것이다.
주요 칼럼별 의미를 정리해보자
- params 칼럼에는 수행할 때 마다 적용된 개별 하이퍼 파라미터 값을 나타낸다.
- rank_test_score는 하이퍼파라미터 별 성능이 좋은 score순위를 나타낸다.
- mean_test_scroe는 개별 하이퍼 파라미터별로 CV의 폴딩 테스트 세트에 대해 총 수행한 평가 평균값이다.
GridSearchCV 객체에 fit()을 수행하면 최고 성능을 나타내는 하이퍼 파라미터 값과 그때의 평과 결과 값이 각각 best_params_, best_score_ 속성에 기록된다.
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
또한 디폴트 값인 refit=True이면 GridSearchCV가 최적 성능을 나타내는 하이퍼 파라미터로 Estimator를 학습해 best_estimator_로 저장한다.
이미 학습된 best_estimator_을 이용해 앞에서 train_test_split()으로 분리한 테스트 데이터 세트에 대해 예측하고 성능을 평가해보자
#GridSearchCV의 refit으로 이미 학습된 estimator 반환
estimator = grid_dtree.best_estimator_
#GridSearch의 best_estimator는 이미 최적 학습이 됐으므로 별도의 학습이 필요없음
pred = estimator.predict(X_test)
print('테스트 데이터 세트 정확도:{0:.4f}'.format(accuracy_score(y_test,pred)))
[output]
테스트 데이터 세트 정확도:0.9667
일반적으로 학습 데이터를 GridSearchCV를 이용해 최적 하이퍼 파라미터 튜닝을 수행한 뒤에 별도의 테스트 세트에서 이를 평가하는 것이 일반적인 머신러닝 모델 적용 방법이다.