[혼공머신] 교차검증, 그리드 서치

강민우·2022년 2월 9일
1
post-thumbnail

[혼자 공부하는 머신러닝+딥러닝] 책에 기반한 정리글입니다.
전체 소스코드는 아래 Github 링크에서 확인할 수 있습니다.

Github 링크

0. 개요

모델의 하이퍼파라미터 튜닝을 위해서 모델을 여러 번 평가해야 하는데, 일반화 성능을 올바르게 예측하려면 가급적 테스트 세트를 사용하지 말아야 한다.
이것을 위한 검증 세트교차검증, 그리드서치랜덤서치를 통한 최선의 하이퍼파라미터 튜닝을 알아본다.

1. 검증 세트

테스트 세트를 사용하지 않으면 모델이 과대적합인지, 과소적합인지 알기 어렵다. 이를 위해서 훈련세트를 한번 더 나누고, 이 데이터를 검증 세트라고 한다.

훈련세트에서 20%정도를 떼어 내어 검증 세트로 만든다.

1-1. 데이터 준비하기

csv파일을 읽어 data, target numpy 배열을 준비한다.

import pandas as pd
wine = pd.read_csv("http://bit.ly/wine_csv_data")

data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()

1-2. 훈련·테스트·검증세트로 나누기

from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(data, target, test_size=0.2, random_state=42)

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

test_size 매개변수를 0.2로 정해 훈련세트의 20% 정도를 검증세트로 만든다.

1-3. 검증세트로 모델 평가하기

from sklearn.tree import DecisionTreeClassifier

dt = DecisionTreeClassifier(random_state=42)
dt.fit(sub_input, sub_target)

print(dt.score(sub_input, sub_target))
print(dt.score(val_input, val_target))
출력
0.9971133028626413
0.864423076923077

훈련세트에 과대적합된 것을 확인할 수 있다.

2. 교차 검증

위의 방식으로 검증 세트를 만든다면 훈련세트가 줄어든다.
많은 데이터를 훈련에 사용하면 좋은 모델이 만들어지지만, 검증세트를 너무 조금 떼어놓는다면 검증점수가 들쑥날쑥하다.
교차 검증을 사용한다면 안정적인 점수를 얻고 훈련에 더 많은 데이터를 사용할 수 있다.

교차검증은 검증세트를 떼어 평가하는 과정을 여러 번 반복한다.
이후 이 점수를 평균화하여 최종 검증 점수를 얻는다.

훈련 세트를 k개의 부분으로 나누어 교차검증을 수행하는 것을 k-폴드 교차 검증이라고 한다.

cross_validate() 교차 검증 함수를 사용한다.
기본적으로 5-폴드 교차 검증을 수행하며, cv매개변수에서 폴드 수를 바꿀 수 있다.

from sklearn.model_selection import cross_validate
scores = cross_validate(dt, train_input, train_target)
print(scores)
출력
{'fit_time': array([0.00827432, 0.00717115, 0.00755119, 0.00725603, 0.00695348]), 'score_time': array([0.0008502 , 0.00073886, 0.00070643, 0.00070453, 0.00066519]), 'test_score': array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}

이 함수는 fit_time, score_time, test_score 키를 가진 딕셔너리를 반환한다.

교차 검증의 최종 점수는 test_score 키에 담긴 5개의 점수를 평균하여 얻는다.

import numpy as np
print(np.mean(scores['test_score']))
출력 0.855300214703487

교차 검증으로모델에서 얻을수 있는 최선의 검증 점수를 가늠할 수 있다.

여기서는 train_test_split로 훈련세트를 섞었지만,
cross_validate() 함수는 훈련세트를 섞어 폴드를 나누지 않는다.

교차검증을 할 때 훈련세트를 섞으려면 분할기를 이용한다.
cross_validate()는 회귀 모델이라면 KFold분할기를, 분류모델이라면 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

3. 하이퍼파라미터 튜닝

머신러닝 모델이 파라미터를 학습할 수 없어 사용자가 지정해야만 하는 파라미터를 하이퍼파라미터라고 한다.

매개변수가 여러 개가 있다면 이 여러 개의 매개변수를 동시에 바꿔가며 최적의 값을 찾아야 한다.

사이킷런에서는 이를 해결하기 위한 그리드서치를 제공한다.

3-1. 그리드 서치

사이킷런의 GridSearchCV는 하이퍼파라미터 탐색과 교차검증을 한번에 수행한다.

결정 트리 모델에서 min_impurity_decrease 매개변수의 최적값을 찾아본다.

from sklearn.model_selection import GridSearchCV

params = {'min_impurity_decrease' : [0.0001, 0.0002, 0.0003, 0.0004, 0.0005]}
gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1) 
#모델과 파라미터와 사용할 cpu코어 개수 전달(전부 사용)

gs.fit(train_input, train_target)
#cv 매개변수 기본값은 5, 5폴드 교차 검증 수행, 총 25번 계산

그리드서치는 훈련이 끝나면 25개의 모델 중 검증 점수가 가장 높은 매개변수 조합으로 모델을 다시 훈련한다.

이 모델은 gs객체의 best_estimator_ 속성에 저장된다.
이 모델을 일반 결정 트리처럼 사용할 수 있다.

dt = gs.best_estimator_
print(dt.score(train_input, train_target))
출력 0.9615162593804117

최적의 매개변수는 best_params_에,
각 매개변수에서의 교차검증의 평균점수는 cv_result_속성의 mean_test_score에 저장된다.

print(gs.best_params_)
print(gs.cv_results_['mean_test_score'])
출력
{'min_impurity_decrease': 0.0001}
[0.86819297 0.86453617 0.86492226 0.86780891 0.86761605]

넘파이 argmax() 함수로 가장 큰 값의 인덱스를 추출하여 매개변수를 출력할 수 있다.

best_index = np.argmax(gs.cv_results_['mean_test_score']) # 가장 큰 값의 인덱스 추출
print(gs.cv_results_['params'][best_index])
출력 {'min_impurity_decrease': 0.0001}

더 복잡한 매개변수 조합도 탐색할 수 있다.

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)
          }

이 매개변수로 수행할 교차 검증 횟수는 9 x 15 x 10 = 1350 개 이다.
5-폴드 교차 검증을 수행하므로 만들어지는 모델은 6750 개 이다.

gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
gs.fit(train_input, train_target)
print(gs.best_estimator_)
print(np.max(gs.cv_results_['mean_test_score'])) # 최상의 교차 점수
출력
DecisionTreeClassifier(max_depth=14, min_impurity_decrease=0.0004,
                       min_samples_split=12, random_state=42)
0.8683865773302731

3-2. 랜덤 서치

매개변수의 값이 수치일 때, 값의 범위나 간격을 미리 정하기 어렵거나, 너무 많은 매개변수 조건이 있어 그리드 서치를 오래 해야된다면 랜덤서치를 사용하면 좋다.

랜덤 서치에는 매개변수를 샘플링할 수 있는 확률 분포 객체를 전달한다.
싸이파이에서 확률 분포 클래스를 임포트한다.

uniform(), randint()로 실수값과 정수값을 샘플링한다.

from scipy.stats import uniform, randint
from sklearn.model_selection import RandomizedSearchCV

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),
          }

n_iter 매개변수로 샘플링 횟수를 지정할 수 있다.

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']))
출력
{'max_depth': 39, 'min_impurity_decrease': 0.00034102546602601173, 'min_samples_leaf': 7, 'min_samples_split': 13}
0.8695428296438884

최적의 모델로 테스트세트의 성능을 확인할 수 있다.

dt = gs.best_estimator_
print(dt.score(test_input, test_target))
출력 0.86
profile
어제보다 성장한 오늘

0개의 댓글