sim0609 2023. 2. 17. 23:32

교차 검증과 그리드 서치

지금까지 훈련 세트에서 모델을 훈련하고 테스트 세트에서 모델을 평가했다. 하지만 그렇게 되면 테스트 세트를 계속 사용할수록 성능이 테스트 세트에 맞춰져 높아진다. 그렇기 때문에 일반화 성능을 올바르게 예측하기 위해 테스트 세트를 이용하면 안된다. 

 

테스트 세트 대신에 검증 세트를 이용하면 된다. 검증 세트는 훈련 세트에서 다시 쪼개져 사용되는 데이터이다. 또한, 교차 검증을 통해 검증 세트가 적어 검증 점수가 들쭉날쭉한 걸 막을 수 있다. (교차 검증은 검증 세트를 떼어 내어 평가하는 과정을 여러 번 반복한다.)

 

그리고 이러한 교차 검증을 사용해 다양한 파라미터를 탐색할 수 있다.

 

소스 코드

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_validate
import numpy as np
from sklearn.model_selection import StratifiedKFold
from sklearn.model_selection import GridSearchCV
from scipy.stats import uniform, randint
from sklearn.model_selection import RandomizedSearchCV

wine = pd.read_csv('https://bit.ly/wine_csv_data')

# 데이터 프레임을 넘파이 배열로 바꾸기
data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()

# 훈련 세트와 테스트 세트로 나누기
train_input, test_input, train_target, test_target = train_test_split(data, target, test_size = 0.2, random_state = 42)

# 훈련 세트를 다시 훈련 세트와 검증 세트로 나누기
sub_input, valid_input, sub_target, valid_target = train_test_split(train_input, train_target, test_size = 0.2, random_state = 42)
print(sub_input.shape, valid_input.shape)

# 의사 결정 트리 훈련시키기
dt = DecisionTreeClassifier(random_state = 42)
dt.fit(sub_input, sub_target)
print(dt.score(sub_input, sub_target))
print(dt.score(valid_input, valid_target))

# 안정적인 검증 점수를 얻고 훈련에 더 많은 데이터를 사용할 수 있는 교차 검증 이용하기
# 5-폴드 교차 검증
scores = cross_validate(dt, train_input, train_target)
print(scores)

# test_score는 검증 폴드의 점수이므로 test_score의 평균으로 교차 검증의 최종 점수를 얻을 수 있다
print(np.mean(scores['test_score']))

# 교차 검증에서 폴드를 어떻게 나눌지 결정해주는 분할기를 지정해야함
# 분류 모델인 경우: 타깃 클래스를 골고루 나누기 위한 StratifiedKFold를 사용한다
scores = cross_validate(dt, train_input, train_target, cv = StratifiedKFold())
print(np.mean(scores['test_score']))

# 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']))

# 하이퍼 파라미터 탐색과 교차 검증을 한 번에 수행하는 클래스를 이용하자
# cv의 기본값은 5이므로 5-폴드 교차 검증을 수행함
# 5(5-폴드 교차 검증) * 5(5개의 하이퍼 파라미터를 설정해줌) = 25, 총 25개의 모델을 훈련하는 것임
params = {'min_impurity_decrease': [0.0001, 0.0002, 0.0003, 0.0004, 0.0005]}
gs = GridSearchCV(DecisionTreeClassifier(random_state = 42), params, n_jobs = -1) 

# 그리디 서치는 훈련 후 25개의 모델 중에서 검증 점수가 가장 높은 모델의 하이퍼 파라미터 조합으로 전체 훈련 세트에서 자동으로 다시 모델을 훈련함
# gs.best_estimator_: 검증 점수가 가장 높은 모델의 하이퍼 파라미 조합이 저장됨
# gs.best_params_: 최적의 하이퍼 파라미터가 저장됨
gs.fit(train_input, train_target)
dt = gs.best_estimator_
print(dt.score(train_input, train_target))
print(gs.best_params_)

# 각 하이퍼 파라미터에서 수행한 교차 검증의 평균 점수(5번의 교차 검증으로 얻은 점수의 평균)
print(gs.cv_results_['mean_test_score'])

# gs.best_params_를 찾는 또다른 방법
best_index = np.argmax(gs.cv_results_['mean_test_score'])
print(gs.cv_results_['params'][best_index])

# 하이퍼 파라미터 조합을 좀 더 복잡하게 설정해보자
# min_impurity_decrease: 노드를 분할하기 위한 불순도 감소 최소량 지정 - 9개 
# max_depth: 트리의 깊이 제한 - 15개 
# min_samples_split: 노드를 나누기 위한 최소 샘플 수 고르기 - 10개
# 교차 검증 수: 9 * 15 * 10 = 1350
params = {'min_impurity_decrease': np.arange(0.0001, 0.001, 0.0001),
          'max_depth': range(5, 20, 1),
          'min_samples_split': range(2, 100, 10)}
gs = GridSearchCV(DecisionTreeClassifier(random_state = 42), params, n_jobs = -1)
gs.fit(train_input, train_target)
print(gs.best_params_)
print(np.max(gs.cv_results_['mean_test_score']))

# 하이퍼 파라미터를 샘플링 할 수 있는 확률 분포 객체를 전달하는 랜덤 서치를 이용하자 
# randint는 정수값을 뽑고 uniform은 실수값을 뽑음
rgen = randint(0, 10)
rgen.rvs(10)
np.unique(rgen.rvs(1000), return_counts = True)
ugen = uniform(0, 1)
ugen.rvs(10)

# 하이퍼 파라미터 범위 지정
# min_samples_leaf는 리프 노드가 되기 위한 최소 샘플의 개수
params = {'min_impurity_decrease': uniform(0.0001, 0.001),
          'max_depth': randint(20, 50),
          'min_samples_split': randint(2, 25),
          'min_samples_leaf': randint(1, 25)
          }
          
# 샘플링 횟수는 랜덤 서치 클래스인 RandomizedSearchCV의 매개변수에 지정
# 총 100번 샘플링해 교차 검증을 수행하고 최적의 하이퍼 파라미터 조합을 찾음
gs = RandomizedSearchCV(DecisionTreeClassifier(random_state = 42), params, n_iter = 100, n_jobs = -1, random_state = 42)
gs.fit(train_input, train_target)
print(gs.best_params_)
print(np.max(gs.cv_results_['mean_test_score']))
dt = gs.best_estimator_
print(dt.score(test_input, test_target))