[머신러닝] 5-2. 교자 검증과 그리드 서치
검증 세트가 필요한 이유를 이해하고 교차 검증에 대해 배운다. 그리드 서치와 랜덤 서치를 이용해 최적의 성능을 내는 하이퍼파라미터를 찾자
검증 세트
테스트 세트를 사용하지 않으면 모델이 과대적합이닞 과소적합인지 알기 어렵다.테스트 세트를 사용하지 않고 측정하는 방법은 훈련 세트를 또 나누면 된다 이 데이터를 검증 세트라고 한다.
1
2
import pandas as pd
wine=pd.read_csv('https://bit.ly/wine-date')
1
2
3
4
#class열을 타깃으로 하고 나머지 열은 특성 배열에 저장하기
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)
train_input 과 train_target을 다시 train_test_split()함수에 넣어 훈련 세트 sub_input, sub_target과 검증 세트 val_input, val_target을 만든다.
1
sub_input, val_input, sub_target, val_target=train_test_split(train_input, train_target, test_size=0.2, random_state=42)
1
2
print(sub_input.shape, val_input.shape)
#(4157, 3) (1040, 3)
원래 5197개의 훈련세트가 4157개, 검증 세트는 1040개가 되었다.
1
2
3
4
5
6
7
8
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, valu_target))
#0.9971
#0.8644
이 모델은 과대적합이다. 매개변수를 바꿔서 다시 훈련시켜야 한다. 그 전에 교차검증을 알아보자
교차 검증
검증 세트만든다고 훈련 세트를 줄였다. 데이터가 많으면 좋은 모델이 만들어진다. 그렇다고 또 검증 세트를 조금만 떼어놓자니 검증 점수가 불안정할 것이다. 이렇게 교차 검증이라는 것을 이용하면 안정적인 검증 점수를 얻고 훈련에 더 많은 데이터를 사용할 수 있을 것이다.
검증 세트를 떼어 내어 평가하는 과정을 여러번 반복하고 점수를 평균해서 최종 검증 점수를 얻는것이다.
위는 3-폴드 교차검증이다. 훈련 세트를 세 부분으로 나눠서 교차 검증을 수행하는 것이다.
k-폴드 교차 검증(k-fold cross validation)=k-겹 교차 검증이라고 하며 훈련 세트를 몇 부분으로 나누냐에 따라 다르게 부른다.
보통 5-폴드 교차 검증, 10-폴드 교차 검증을 많이 사용한다. 이렇게 하면 데이터 80에서 90%까지 훈련에 사용할 수 있다. 검증 세트가 줄어들지만 각 폴드에서 계산한 검증 점수를 평균하기 때문에 안정된 점수로 생각할 수 있다
cross_validate()라는 교차 검증 함수가 있다. 먼저 평가할 모델 객체를 첫 번째 매개변수로 전달한다. 그다음 검증 세트를 떼어내지 않고 훈련 세트 전체를 cross_validate() 함수에 전달한다.
1
2
3
4
5
from sklean.model_selection import cross_validate
scores=cross_validate(dt, train_input, train_target)
print(scores)
#{'fit_time': array([0.013, 0.011, 0.007, 0,007, 0,007]), 'score_time':array([0.0008, 0.0006, 0.0006, 0.0006, 0.0006]), 'test_score':array([0.869, 0.846, 0.876, 0.848, 0.835])}
처음 2개 키는 모델을 훈련하는 시간과 검증하는 시간을 의미한다. 각 키마다 5개의 숫자가 담겨 있다. cross_validate()는 기본적으로 5-폴드 교차 검증을 수행한다. cv 매개변수에서 폴드 수를 바꿀 수도 있다.
test_score 키에 5개의 점수를 평균하여 최종 점수를 얻는다. 이름만 test_score이지 검증 폴드의 점수다!
1
2
3
4
import numpy as np
print(np.mean(scores['test_score']))
#0.855
앞서 train_test_split() 으로 전체 데이터를 섞은 후 훈련 세트를 준비했기 때문에 cross_validate()는 훈련 세트를 섞어 폴드를 나누지 않는다.
하지만 만약 교차 검증을 할 때 훈련 세트를 섞으려면 분할기(splitter)를 지정한다.
어떻게 나눌지 결정해준다. cross_validate()함수는 기본적으로 회귀 모델일 경우 KFold 분할기를 사용하고 분류 모델일 경우 타깃 클래스를 골고루 나누기 위해 StandardKFold 를 사용한다.
1
2
3
from sklearn.model_selection import StatifiedKFold
scores=cross_validate(dt, train_input, cv=StatifiedFold())
print(np.mean(scores['test_score']))
훈련 세트를 섞고 10-폴드 교차 검증을 해보자. n_splits로 몇 폴드 교차 검증을 할지 정한다.
1
2
3
splitter=StratifiedFold(n_split=10, suffle=True, random_state=42)
scores=cross_validate(dt, train_input, train_target, cv=splitter)
print(np.mean(scores['test_score']))
테스트 세트를 사용하지 않고 교차 검증을 통해서 좋은 모델을 고르면 된다
하이퍼파라미터 튜닝
머신러닝 모델이 학습하는 파라미터는 모델 파라미터
모델이 학습할 수 없어서 사용자가 지정해야만 하는 파라미터를 하이퍼파라미터라고 했다.
중요한 것! 결정 트리 모델에서 최적의 max_depth 값을 찾았다고 가정해보자. 그다음 max_depth를 최적의 값으로 고정하고 min_samples_split을 바꿔가며 최적의 값을 찾는다. 아니 안된다!! max_depth의 최적값은 min_samples_split 매개변수의 값이 바뀌면 함께 달라진다. 두 매개변수를 동시에 찾아야한다!!
매개변수가 많아지면.. 복잡하다. 파이썬 for 반복문으로 할 수도 있지만 이미 만들어진 것이 있지!! 그리드 서치!
사이킷런 GridSearchCV 클래스는 하이퍼파라미터 탐색과 교차 검증을 한 번에 수행한다. cross_validate()함수를 호출할 필요가 없다.
기본 매개변수를 사용한 결정 트리 모델에서 min_impurity_decrease 매개변수 최적값을 찾아보자
GridSearchCV 클래스를 임포트하고 탐색할 매개변수와 탐색할 값의 리스트를 딕셔너리로 만들자.
1
2
from sklearn.model_selecion import GridSearchCV
params={'min_impurity_decrease': [0.001, 0.002,0.003,0.004,0.005]}
GridSearchCV클래스에 탐색 대상 모델과 params 변수를 전달하여 그리드 서치 객체를 만든다.
1
gs=GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
fit()메서드를 호출하면 그리드 서치 객체는 결정 트리 모델 min_impurity_decrease값을 바꿔가며 총 5번 실행한다.
GridSearchCV의 cv매개변수 기본값은 5이다. min_impurity_decrease 값마다 5-폴드 교차 검증을 수행하여 25개의 모델을 훈련한다.
n_jobs 매개변수로 병렬 실행에 사용할 CPU 코어 수를 지정하는 것이 좋다. 이 매개변수 기본 값은 1이다. -1로 하면 시스템에 있는 모든 코어를 사용한다.
1
gs.fit(train_input, train_target)
교차검증에서 최적의 하이퍼파라미터를 찾으면 모델을 다시 만들어야 했지만 사이킷런의 그리드 서치는 점수가 가장 높은 모델의 매개변수 조합으로 전체 훈련 세트에서 자동으로 다시 모델을 훈련한다. 이 모델은 gs 객체의 best_estimator_속성에 있다. 이 모델을 일반 결정 트리처럼 똑같이 사용할 수 있다.
1
2
3
4
dt=gs.best_estimator_
print(dt.score(train_input, train_target))
#0.961
그리드 서치로 찾은 최적의 매개변수는 best_params_속성에 있다
1
2
3
print(gs.best_params_)
#{'min_impurity_decrease':0.0001}
교차 검증의 평균 점수는 cv_result_속성의 ‘mean_test_socre’ 키에 저장되어 있다.
1
print(gs.cv_results_['mean_test_socre'])
argmax() 함수를 사용하면 가장 큰 값의 인덱스를 추출할 수 있다. 그 인덱스로 params 키에 저장된 매개 변수를 출력할 수 있다. 이 값이 최상의 검증 점수로 만든 매개변수 조합이다. 앞에서 출력한 gs.best_params_와 동일한지 보자
1
2
3
4
best_index=np.argmax(gs.cv_results_['mean_test_score'])
print(gs.cv_results_['params'][best_index])
#{'min_impurity_decrease':0.001}
- 탐색할 매개변수 지정하기
- 훈련 세트에서 그리드 서치를 수행하여 최상의 평균 검증 점수가 나오는 매개변수 조합 찾기(이 조합은 그리드 서치 객체에 저장된다)
- 그리드 서치는 최상의 매개변수에서 (교차 검증에 사용한 훈련 세트가 아니라) 전체 훈련 세트를 사용해 최종 모델을 훈련하다. 이 모델도 그리드 서치 객체에 저장된다
min_impurity_decrease는 노드를 분할 하기 위한 불순도 감소 최소량을 지정한다. max_depth로 트리의 깊이를 제한하고 min_samples_split으로 노드를 나누기 위한 최소 샘플 수도 골라 보자
1
2
3
4
params={'min_impurity_decrease': np.arange(0.0001, 0.001,0.0001)#1
, 'max_depth': range(5,20, 1)#2
, 'min_samples_split':range(2,100,10)#2
}
#1 arange()함수는 0.0001에서 0.001이 되기 전까지 0.0001을 계속 더한 배열
#2 range()함수는 정수만 가능하다. max_depth를 5에서 20까지 1씩 증가하면서 값을 만든다. min_samples_split은 2에서 100까지 10씩 증가한다.
1
2
3
4
5
6
gs=GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
gs.fit(train_input, train_target)
print(gs.best_params_)
#{'max_depth':14, 'min_impurity_decrease': 0.0004, 'min_samples_split': 12}
최상의 매개변수 조합
1
2
print(np.max(gs.cv_results_['mean_test_score']))
#0.8683
최상의 교차 검증 점수!
약간 아쉬운게 있다면 간격 설정 근거가 없다.
랜덤 서치
범위나 간격을 미리 정하기 어렵다… 너무 많은 매개변수 조건이 있어 그리드 서치 수행 시간이 오래 걸릴 수 있다. 이 때 랜덤 서치를 사용하자
랜덤 서치는 매개변수 값의 목록 전달이 아닌 매개변수 샘플링할 수 있는 확률 분포 객체를 전달한다.
1
from scipy.stats import uniform, randint
싸이파이의 stats 서브 패키지에 있는 uniform과 rading 클래스는 모두 주어진 범위에서 고르게 뽑아준다. 이거를 균등 분포에서 샘플링한다고 하지
1
2
3
4
5
rgen=randint(0,10)#정수값 뽑기
rgen.rvs(10)
#0에서 10 사이의 범위의 randing 객체를 만들고 10개의 숫자를 샘플링
#array([6,4,2,2,7,7,0,0,5,4])
- uniform은 실수값 뽑기
샘플링 숫자를 늘리면 고르개 수가 뽑혔다는 것을 볼 수 있다.
1
2
3
np.unique(rgen.rvs(1000), return_counts=True)
#(array([0,1,2,3,4,5,6,7,8,9]), array([98,94,99,93,93,92,111,118,105,97])
탐색할 매개변수 딕셔너리 만들기! min_samples_leaf 매개변수를 탐색 대상에 추가하자. 리프 노드가 되기 위한 최소 샘플의 수이다. 만약 분할해서 만들어질 자식 노드 샘플 수가 이 값보다 작다면? 분할하지 않는다
1
2
3
4
5
6
7
#탐색 매개변수 범위
params = {'min_impurity_decrease': uniform(0.0001, 0.001), #0.0001에서 0.001사이의 실숫값 샘플링
'max_depth':randint(20,50), #20에서 50 사이의 정수
'min_samples_split':randint(2,25), #2에서 25사이
'min_samples_leaf': randint(1,25),#1에서 25사이
}
1
2
3
from sklearn.model_selection import RandomizesdSearchCV
gs=RandomizesSearchCV(DecisionTreeClassifier(random_state=42), params, n_iter=100, n_jobs=-1, random_state=42)
gs.fit(train_input, train_target)
정의된 매개 변수 범위에서 100번(n_iter=100)을 샘플링하여 교차 검증을 수행하고 최적의 매개변수 조합을 찾는다. 그리드 서치보다 효율적이다
1
2
3
4
5
#최적의 매개변수 조합 출력
print(gs.best_params_)
#{'max_depth':39, 'min_impurity_decrease':0.00034105, 'min_samples_leaf':7, 'min_samples_split':13}
1
2
3
4
5
#최고의 교차 검증 점수 확인
print(np.max(gs.cv_results_['meantest_score']))
#0.869
최적의 모델은 이미 전체 훈련 세트로 훈련되어 best_estiomator_속성에 저장되어 있다.
1
2
3
4
5
6
#테스트 성능 확인
dt-gs.best_estimator_
print(dt.score(test_input, test_target))
#0.86
테스트 세트 점수가 만족스럽지는 않지만 다양한 매개변수를 테스트했다는 것에 의미를 두자
앞으로 수동으로 매개변수를 바꾸지 말고 그리드 서치나 랜덤 서치를 사용하자
마무리
키워드
검증 세트 교차 검증 그리드 서치 랜덤 서치
핵심 패키지와 함수
scikit-learn
cross_validate() GridSearchCV RandomizedSearchCV


