교차검증 수행하기 - K 폴드, Stratified K 폴드, cross_val_score
일반적으로 기계학습을 진행할 때는 Hold Out 과정에서 Train 데이터와 Test 데이터를 나누게 된다. 사이킷런 Model Selection 모듈의 train_test_split()
을 이용해 Train / Test 데이터로 나눌 수 있는데, 이 과정만 수행한다면 여전히 과적합 문제가 존재할 수 있다. 이는 고정된 Train 데이터와 Test 데이터로 평가를 하다보면 Test 데이터에만 최적의 성능을 발휘하도록 편향되게 모델을 유도할 수 있기 때문이다. 이러한 문제점을 해결하기 위해 교차검증이 필요하다.
1. K 폴드 교차검증
K 폴드 교차검증은 K개의 데이터 폴드 세트를 만들어 K번만큼 각 폴드 세트에 학습과 검증 평가를 반복적으로 수행하는 방법이다. 사이킷런에서는 Model Selection 모듈의 KFold
클래스를 이용해 K 폴드 교차검증을 수행할 수 있다.
다음 예시에서 데이터는 사이킷런에서 기본적으로 제공되는 iris 데이터를 사용했으며, Estimator로는 DecisionTreeClassifier를 사용했다.
from sklearn.datasets import load_iris
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 = []
n_iter = 0
# KFold객체의 split() 호출하면 폴드별 학습용, 검증용 각각의 로우 인덱스를 array로 반환한다.
for train_index, test_index in kfold.split(features):
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))
.
.
.
#5 교차 검증 정확도 :0.8, 학습 데이터 크기: 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.9199999999999999
2. Stratified K 폴드
데이터 레이블의 분포가 불균형할 경우, 원본 데이터와 유사한 레이블 값의 분포를 학습/테스트 세트에도 유지할 필요가 있다. 이렇게 분포를 유지하게 만드는 것을 stratify로 수행할 수 있으며, 특히 데이터의 개수가 적을 시에 이 과정이 필요하다.
예를 들어, Train / Test 데이터를 분리할 때 사용하는 train_test_split()
에서도 다음과 같이 stratify를 적용해볼 수 있다.
from sklearn.datasets import load_iris
import pandas as pd
from sklearn.model_selection import train_test_split
iris = load_iris()
iris_df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
iris_df['label'] = iris.target
train, test = train_test_split(iris_df, stratify=iris_df.label)
K 폴드 교차검증에서도 stratify를 사용할 수 있는데, 이를 Stratified K 폴드라고 한다. 일반적으로 분류(Classification)에서의 교차검증은 Stratified K 폴드로 수행한다.
회귀(Regression)에서는 Stratified K 폴드가 지원되지 않는다.
iris 데이터의 경우 레이블 값은 0(Setosa), 1(Versicolor), 2(Virginica) 모두 50개로 동일한 개수를 가지고 있다. 전체 데이터 개수가 충분하지 않은(150개) 이 데이터에 일반적인 K 폴드를 적용할 경우 학습용 레이블이나 검증용 레이블 데이터 분포가 원본 데이터의 분포를 제대로 반영하지 못하는 문제가 발생한다. (KFold(n_split=3)
을 적용하여 value_counts()
로 확인해보면 이러한 문제가 극명하게 드러난다.)
이 문제를 해결하기 위해 Stratified K 폴드를 사용해보자.
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())
.
.
.
## 교차 검증: 3
학습 레이블 데이터 분포:
0 34
2 33
1 33
Name: label, dtype: int64
검증 레이블 데이터 분포:
2 17
1 17
0 16
Name: label, dtype: int64
레이블 데이터 세트의 분포가 고르게 분리된 것을 확인할 수 있다. 이를 활용하여 교차검증하는 코드를 다시 작성해보자.
dt_clf = DecisionTreeClassifier()
skfold = StratifiedKFold(n_splits=5)
cv_accuracy=[]
n_iter=0
# StratifiedKFold의 split() 호출시 반드시 레이블 데이터 셋도 추가 입력 필요
for train_index, test_index in skfold.split(features, label):
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))
.
.
.
#5 교차 검증 정확도 :1.0, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#5 검증 세트 인덱스:[ 40 41 42 43 44 45 46 47 48 49 90 91 92 93 94 95 96 97
98 99 140 141 142 143 144 145 146 147 148 149]
## 교차 검증별 정확도: [0.9667 0.9667 0.9 0.9667 1. ]
## 평균 검증 정확도: 0.9600200000000001
3. cross_val_score()
사이킷런에선는 cross_val_score()
로 보다 간편하게 교차검증을 수행할 수 있다. cross_val_score()
는 분류에서는 classifier가 입력되면 Stratified K 폴드 방식으로 레이블값의 분포에 따라 학습/테스트 세트를 분할하며, 회귀에서는 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()
data = iris_data.data
label = iris_data.target
# 성능 지표는 정확도(accuracy) , 교차 검증 세트는 5개
scores = cross_val_score(dt_clf , data , label , scoring='accuracy',cv=5)
print('교차 검증별 정확도:',np.round(scores, 4))
print('평균 검증 정확도:', np.mean(scores))
교차 검증별 정확도: [0.9667 0.9667 0.9 0.9667 1. ]
평균 검증 정확도: 0.9600000000000002
Leave a comment