[ML] 교차검증 - KFold, StratifiedKFold, cross_val_score, GridSearchCV
과적합
과적합 : 모델이 학습 데이터에만 과도하게 최적화되어, 실제 예측을 다른 데이터로 수행할 경우에는 예측 성능이 과도하게 떨어지는 것을 말합니다.
교차검증
교차검증 : 데이터 편중을 막기 위해서 별도의 세트로 구성된 학습 데이터 세트와 검증 데이터 세트에서 학습과 평가를 수행하는 것입니다.
- 대부분의 ML 모델의 성능 평가는 교차 검증 기반으로 1차 평가를 한 뒤에 최종적으로 테스트 데이터 세트에 적용해 평가하는 프로세스 입니다.
1. KFold
- 가장 보편적으로 사용되는 교차 검증 기법
- 먼저 K개의 데이터 폴드 세트를 만들어서 K번만큼 각 폴드 세트에 학습과 검증 평가를 1 반복적으로 수행하는 방법
학습 데이터 세트와 검증 데이터 세트를 점진적으로 변경하면서 마지막 5번째(K번째)까지 학습과 검증을 수행하는 것이 바로 K 폴드 교차 검증
from sklearn.model_selection import KFold from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import accuracy_score import numpy as np iris = load_iris() features = iris.data label = iris.target dtc = DecisionTreeClassifier(random_state=156) # 5개의 폴드 세트로 분리하는 KFold 객체와 폴드 세트별 정확도를 담을 리스트 객체 생성 kfold = KFold(n_splits=5) cv_accuracy=[] print('붓꽃 데이터 세트 크기:', features.shape[0])
- KFold(n_splits = 5)로 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] # 학습 및 예측 dtc.fit(X_train, y_train) pred = dtc.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))
KFold는 순서대로 나누고 있는 것을 볼 수 있습니다. 순서대로 나누는 것이 안 좋을 수도 있다? 맞습니다. 예를 들어 대출사기 데이터 세트는 1억 건이고 수십 개의 피처와 대출 사기 여부를 뜻하는 레이블(대출 사기:1, 정상 대출 : 0)이 있다고 가정해 봅시다. 데이터의 대부분은 정상 대출일 것입니다. 그리고 대출 사기가 약 1000건 있다고 한다면 전체의 0.0001%의 아주 작은 확률로 대출 사기 레이블이 존재합니다. 이렇게 작은 비율로 1 레이블 값이 있다면 KFold로 랜덤 하게 학습 및 테스트 세트의 인덱스를 고르더라도 레이블 값인 0과 1의 비율을 제대로 반영하지 못하는 경우가 쉽게 발생합니다.
-- 이런 경우에는 어떻게 해야 할까? --
이런 경우를 해결할 수 있는 것이StratifiedKFold입니다
StratifiedKFold : 불균형한 분포도를 가진 레이블 데이터 집합을 위한 KFold 방식입니다.
- 특정 레이블 값이 특이하게 많거나, 매우 적어서 값의 분포가 한쪽으로 치우치는 경우
먼저, iris의 데이터로 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]
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())
- 이런 경우는 모델이 아주 잘못되었음을 알 수 있습니다.
그럼 이제 StratifiedKFold로 확인해보겠습니다.
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())
잘 나눠진 것을 확인할 수 있습니다.
그럼 KFold와 StratifiedKFold는 언제 써야 할까요?
- KFold
- 회귀 문제
- 회귀의 결정 값은 이산값 형태의 레이블이 아니라 연속된 숫자값이기 때문에 결정값 별로 분포를 정하는 의미가 없기 때문
- StratifedKFold
- 레이블 데이터가 왜곡됐을 경우 반드시.
- 일반적으로 분류에서의 교차 검증
교차 검증을 보다 간편하게 - cross_val_score()
먼저 코드를 보면
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score, cross_validate
from sklearn.datasets import load_iris
iris = load_iris()
dtc = DecisionTreeClassifier(random_state=156)
data = iris.data
label = iris.target
# 성능 지표는 정확도(accuracy), 교차 검증 세트는 3개
scores = cross_val_score(dtc, data, label, scoring='accuracy', cv=3)
print('교차 검증별 정확도:', np.round(scores,4))
print('평균 검증 정확도:', np.round(np.mean(scores)))
전에 것들에 비하면 참 간단하죠. 그래서 많이 쓰이기도 합니다.
- 참고로 내부에서 Estimator를 학습, 예측, 평가까지 시켜주므로 간단하게 교차 검증을 수행할 수 있습니다.
- 앞의 예제인 StratifiedKFold의 교차 검증 별 정확도와 평균 검증 정확도가 모두 동일함을 알 수 있는데 이유가 뭘까요?
바로 cross_val_score()은 내부적으로 StratifiedKFold를 이용하기 때문입니다.
그럼 이제 마지막 교차 검증인 GridSearchCV에 대해 알아봅시다!
GridSearchCV - 교차 검증과 최적 하이퍼 파라미터 튜닝을 한 번에
먼저 *하이퍼 파라미터란?
: 최적의 훈련 모델을 구현하기 위해 모델에 설정하는 변수
참고로 하이퍼 파라미터는 머신러닝 알고리즘을 구성하는 주요 구성 요소이며, 이 값을 조정해 알고리즘의 예측 성능을 개선할 수 있습니다 먼저 튜닝할 파라미터를 정하겠습니다.
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, iris.target, test_size=0.2, random_state=121
)
dtc = DecisionTreeClassifier()
params = {
'max_depth': [1,2,3],
'min_samples_split' : [2,3]
}
- 이제는 GridSearchCV가 어떻게 작동되는지 알아보겠습니다.
import pandas as pd
# param_grid의 하이퍼 파라미터를 3개의 train, test set fold로 나누어 테스트 수행 설정
### refit=True가 default임. True이면 가장 좋은 파라미터 설정으로 재학습시킴
grid_dtc = GridSearchCV(dtc, params, cv=3)
# 붓꽃 학습 데이터로 param_grid의 하이퍼 파라미터를 순차적으로 학습/평가
grid_dtc.fit(X_train, y_train)
# GridSearchCV 결과를 추출해 DataFrame으로 변환
scores_df = pd.DataFrame(grid_dtc.cv_results_)
scores_df[['params', 'mean_test_score','rank_test_score',
'split0_test_score', 'split1_test_score', 'split2_test_score']]
cv = 3 : 3개의 폴딩 세트에서 각각 테스트한다는 뜻 refit = True : GridSearchCV가 최적 성능을 나타내는 하이퍼 파라미터로 Estimator를 학습해 bestestimator로 저장
- 총 6개의 결과를 볼 수 있으며, max_depth가 3일 때,min_samples_split이 2일 때 1등인 것을 확인할 수 있습니다
참고로 max_depth가 3이고 min_samples_split이 3일 때도 공동 1등입니다.