파이썬 머신러닝 교차검증 - KFold, StratifiedKFold, cross_val_score, GridSearchCV

오현승·2022년 3월 1일
0

MLstart

목록 보기
1/1

과적합

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

이번 블로그는 과적합을 방지할 수 있는 교차검증 에 대해 얘기하려합니다.

교차검증

교차검증 : 데이터 편중을 막기 위해서 별도의 세트로 구성된 학습 데이터 세트와 검증 데이터 세트에서 학습과 평가를 수행하는 것입니다.

  • 대부분의 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])
  1. KFold(n_splits = 5)로 KFold 객체를 생성합니다.
  2. 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등입니다.

이상 교차검증에 대한 얘기는 여기까지입니다

감사합니다~

profile
Comeandplay-~

0개의 댓글