본문 바로가기

ML

[ML] 교차 검증

※ 본 글은 '혼자 공부하는 머신러닝 + 딥러닝' 교재를 토대로 작성한 글입니다. ※

 

검증 세트 validation set

검증 세트는 말 그대로 모델을 검증하기 위해 사용되는 데이터 세트이다.

이때 '훈련세트가 있는데 검증세트가 왜 필요하지?' 라는 의문을 가질 수 있지만,

모델 검증(validation)과 모델 평가(evaluation)는 분리하여 생각해야 한다.

모델 검증은 모델 성능을 평가하고, 그 결과를 토대로 모델을 튜닝하는 작업을 진행한다.

반면 모델 평가는 최종적으로 '이 모델이 실전에서 이만큼 성능을 낼 것이다!' 라는 것을 확인하는 단계이다.

그렇기 때문에 모델 검증 단계와 모델 평가 단계에서 사용하는 데이터셋은 분리되어야한다.

정리하면, Test Set는 모델 튜닝을 모두 마치고 실전에 투입하기 전에 딱 1번(모델 평가 단계)만 사용하는 것이 좋다.

그래서 모델을 튜닝할 때는 별도의 Validation Set을 이용하고, 이는 훈련 세트를 조금 떼어서 만든다.

설명 참고 : https://hyjykelly.tistory.com/49


실습

검증 세트를 분리하기 위해서 사이킷런에서 제공하는 train_test_split 함수를 사용할 수 있다.

# 판다스로 csv 데이터 읽어오기
import pandas as pd
wine = pd.read_csv('https://bit.ly/wine_csv_data')

# csv 데이터 head 를 기준으로 데이터(=X), 타겟(=Y) 분류
data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()

 

# 훈련 세트, 테스트 세트 분류
from sklearn.model_selection import train_test_split

# test_size=0.2로 전체 데이터에서 테스트 세트 비율 설정
train_input, test_input, train_target, test_target = train_test_split(
    data, target, test_size=0.2, random_state=42)

# train_set : test_set = 0.8 : 0.2 임을 확인
print("train_input :\t",train_input.shape,
      "\ntest_input :\t",test_input.shape,
      "\ntrain_target :\t",train_target.shape,
      "\ntest_target :\t",test_target.shape)
train_input  :	 (5197, 3) 
test_input   :	 (1300, 3) 
train_target :	 (5197,) 
test_target  :	 (1300,)
 
# 훈련세트를 다시 sub_set과 val_set으로 분류 - train_test_split 함수 이용
sub_input, val_input, sub_target, val_target = train_test_split(
    train_input, train_target, test_size=0.2, random_state=42)
# sub : val : test = 0.64 : 0.16 : 0.2
print("sub_input :\t",sub_input.shape,
      "\nval_input :\t",val_input.shape,
      "\nsub_target :\t",sub_target.shape,
      "\nval_target :\t",val_target.shape)
sub_input :	 (4157, 3) 
val_input :	 (1040, 3) 
sub_target :	 (4157,) 
val_target :	 (1040,)

 

# 결정 트리 모델 이용
from sklearn.tree import DecisionTreeClassifier

# 모델 훈련
dt = DecisionTreeClassifier(random_state=42)
dt.fit(sub_input, sub_target)

# sub 점수
print(dt.score(sub_input, sub_target))

# val 점수
print(dt.score(val_input, val_target))
0.9971133028626413
0.864423076923077

validation set로 검증해본 결과,

sub(훈련세트)의 평가 점수보다 val(검증세트)의 평가점수가 더 낮으므로

훈련세트에 대해 과대적합이 일어났다는 것을 알 수 있다.

 


교차 검증 cross validation

머신러닝 모델은 학습 데이터가 많을 수록 정교하게 학습하는데,

훈련 세트의 일부를 검증 세트로 이용하면 학습에 사용할 수 있는 데이터가 줄어들기 때문에

위와 같은 문제(훈련 데이터에 대해 과적합)가 발생할 수 있다.

또 고정된 검증 세트를 이용하면 실제 데이터가 아닌 검증 세트에만 과대 적합될 수 있다는 문제도 발생한다.

이를 해결하기 위해 교차 검증을 이용한다.

교차 검증은 검증세트를 떼어 평가하는 과정을 여러 번 반복한다.

대표적으로 훈련세트를 k등분하여 교차검증을 수행하는 k-폴드 방식이 있다.

교차 검증을 이용하면 대부분의 데이터를 훈련에 사용할 수 있다는 장점이 있지만,

그만큼 오랜 시간이 걸린다는 단점이 있다.

참고 : https://en.wikipedia.org/wiki/Cross-validation_(statistics)#/media/File:LOOCV.gif

사이킷 런은 교차 검증 라이브러리 cross_validation을 제공한다.

cross_validation 객체를 생성할 때 매개변수로 (모델 객체, 훈련 인풋, 훈련 타겟)을 넣어주면

디폴트로 5-폴드 교차 검증을 진행하며 각각의 검증에 걸린 훈련 시간, 검증 시간, 검증의 점수을 딕셔너리로 반환한다.

# 사이킷 런에서 제공하는 교차 검증 라이브러리 - cross_validation
from sklearn.model_selection import cross_validate

# cross_validate(모델 객체, 훈련 인풋, 훈련 타겟)
scores = cross_validate(dt, train_input, train_target)
print(scores)
{'fit_time': array([0.00889111, 0.00710058, 0.00845313, 0.00752854, 0.00723028]), 'score_time': array([0.00088072, 0.0006938 , 0.00089931, 0.0007596 , 0.00072026]), 'test_score': array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}

교차 검증의 최종 점수는 반환된 딕셔너리에서 'test_score'의 평균으로 구할 수 있다.

import numpy as np

print(np.mean(scores['test_score']))
0.855300214703487

cross_validate 자체로는 훈련세트를 섞지 않고 데이터를 분할하기 때문에

cross_validate 객체에 'cv = StratifiedKFold()' 옵션을 추가하여 섞어줘야 한다.

앞에서 실행한 k-fold는 이미 train_test_split로 섞인 데이터를 이용했기 때문에 이 과정을 생략했지만,

대부분의 경우 아래와 같이 cv = StratifiedKFold()를 이용해 훈련데이터를 섞어준다.

from sklearn.model_selection import StratifiedKFold

scores = cross_validate(dt, train_input, train_target, cv=StratifiedKFold())
print(np.mean(scores['test_score']))
0.855300214703487
# 훈련세트를 섞은 후 10-폴드 교차검증
splitter = StratifiedKFold(n_splits=10, shuffle=True, random_state=42)
scores = cross_validate(dt, train_input, train_target, cv=splitter)
print(np.mean(scores['test_score']))
0.8574181117533719

'ML' 카테고리의 다른 글

통계적 관점에서의 머신러닝  (0) 2023.01.29
[ML] 데이터 증강 (Data Augmentation)  (0) 2022.03.27
[ML] Fashion MNIST  (0) 2022.02.13
[ML] 타이타닉 생존자 예측하기  (0) 2022.01.30
[ML] CNN  (0) 2022.01.23