이 포스팅은 혼자 공부하는 머신러닝 + 딥러닝을 공부하고 정리한 것입니다.
지금까지 훈련세트에서 모델을 훈련하고 테스트 세트에서 모델을 평가했다. 테스트 점수를 통해 일반화 성능을 가늠해 볼 수 있었다. 그런데 테스트 세트를 사용해 자꾸 성능을 확인하다 보면 점점 테스트 세트에 맞추게 된다.
이전 까지는 문제를 간단히 하려고 테스트 세트를 사용했다. 하지만 테스트 세트로 일반화 성능을 올바르게 예측하려면 가능한 한 테스트 세트를 사용하지 말아야 한다. 모델을 만들고 나서 마지막에 딱 한번만 사용하는 것이 좋다.
검증 세트
테스트 세트를 사용하지 않으면 모델이 과대적합인지 과소적합인지 판단하기 어렵다. 테스트 세트를 사용하지 않고 이를 측정하는 간단한 방법은 훈련 세트를 또 나누는 것이다. 이 데이터를 검증세트(validation set) 이라고 부른다.
훈련 세트에서 모델을 훈련하고 검증 세트로 모델을 평가한다. 이런 식으로 테스트하고 싶은 매개변수를 바꿔가며 가장 좋으 모델을 고른다. 그다음 이 매개변수를 사용해 훈련 세트와 검증 세트를 함쳐 전체 훈련 데이터에서 모델을 다시 훈련한다. 그리고 마지막에 테스트 세트에서 최종 점수를 평가한다. 아마도 실전에 투입했을 때 테스트 세트의 점수와 비슷한 성능을 기대할 수 있을 것이다.
import pandas as pd
wine=pd.read_csv('https://bit.ly/wine-date')
data=wine[['alcohol','sugar','pH']].to_numpy()
target=wine['class'].to_numpy()
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)
print(sub_input.shape,val_input.shape)
#(4157, 3) (1040, 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))
교차 검증
검정 세트를 만들어서 훈련 세트가 줄었다. 보통 많은 데이터를 훈련에 사용할수록 좋은 모델이 만들어진다. 이럴 때 교차검증(cross validation)을 이용하면 안정적인 검증 점수를 얻고 훈련에 더 많은 데이터를 사용할 수 있다.
교차 검증은 검증 세트를 떼어 내어 평가하는 과정을 여러 번 반복한다. 그다음 이 점수를 평균하여 최종 검증 점수를 얻는다. 다음은 3-폴드 교차 검증 그림이다.
보통 5,10-폴드 교차 검정을 많이 사용한다. 이렇게 하면 데이터의 80~90% 까지 훈련에 사용할 수 있다. 검증 세트가 줄어들지만 각 폴드에서 계산한 검증 점수를 평균하기 때문에 안정된 점수로 생각할 수 있다.
사이킷런에는 cross_validate() 라는 교차 검증 함수가 있다. 먼저 평가할 모델 객체를 첫 번째 매개변수로 전달한다. 그다음 앞에서처럼 직접 검증 세트를 떼어 내지 않고 훈련 세트 전체를 cross_validate() 함수에 전달한다.
from sklearn.model_selection import cross_validate
scores=cross_validate(dt,train_input,train_target)
print(scores)
처음 2개의 키는 각각 모델을 훈련하는 시간과 검증하는 시간을 의미한다. corss_validate() 함수는 기본적으로 5-폴드 교차 검증을 수행한다. cv매개변수에서 폴드 수를 바꿀 수도 있다.
교차 검증의 최종 점수는 test_score 키에 담긴 5개의 점수를 평균하여 얻을 수 있다. 이름은 test_score지만 검증폴드의 점수이다. 혼동하면 안된다.
print(np.mean(scores['test_score']))
교차 검증을 수행하면 입력한 모델에서 얻을 수 있는 최상의 검증 점수를 가늠해 볼 수 있다.
한가지 주의할 점은 cross_validate()는 훈련 세트를 섞어 폴드를 나누지 않는다. 앞서 우리는 train_test_split() 함수로 전체 데이터를 섞은 후 훈련 세트를 준비했기 때문에 따로 섞을 필요가 없다. 하지만 교차검증을 할 때 훈련 세트를 섞으려면 분할기(splitter)를 지정해야 한다.
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']))
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']))
하이퍼파라미터 튜닝
하이퍼파라미터는 사용자가 지정해야 하는 파라미터이다. 이런 하이퍼파라미터를 튜닝하는 작업은 먼저 라이브러리가 제공하는 기본값을 그대로 사용해 모델을 훈련하고, 검증 세트의 점수나 교차 검증을 통해서 매개변수를 조금씩 바꿔 본다. 모델마다 적게는 1~2개에서, 많게는 5~6개의 매개변수를 제공한다. 매개변수들을 동시에 바꿔가며 최적의 값을 찾아야 한다.
사이킷런에서 제공하는 그리드 서치(Grid Search)를 사용한다. 사이킷런의 GridSearchCV 클래스는 하이퍼파라미터 탐색과 교차 검증을 한 번에 수행한다. 별도로 cross_validate() 함수를 호출할 필요가 없다.
기본 매개변수를 사용한 결정 트리 모델에서 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)
그 다음 일반 모델을 훈련한는 것 처럼 gs객체에 fit() 메서드를 호출한다. 이 메서드를 호출하면 그리드 서치 객체는 결정 트리 모델 min_inpurity_decrease 값을 바꿔가며 총 5번 실행한다.
GridSearchCV의 cv 매개변수 기본값은 5이다. 즉 25개의 모델을 훈련한다. n_jobs=-1은 시스템에 있는 모든 코어를 사용한다는 의미이다.
gs.fit(train_input,train_target)
교차 검증에서 최적의 하이퍼파라미터를 찾으면 전체 훈련 세트로 모델을 다시 만들어야 했는데 사이킷런의 그리드 서치는 훈련이 끝나면 25개의 모델 중에서 검증 점수가 가장 높은 모델의 매개변수 조합으로 전체 훈련 세트에서 자동으로 다시 모델을 훈련한다. 이 모델은 gs 객체의 best_estimator_ 속성에 저장되어 있다. 이 모델을 일반 결정 트리 처럼 똑같이 사용할 수 있다.
dt=gs.best_estimator_
print(gs.best_params_)
print(dt.score(train_input,train_target))
print(gs.cv_results_['mean_test_score'])
best_index=np.argmax(gs.cv_results_['mean_test_score'])
print(gs.cv_results_['params'][best_index])
과정을 정리해보면
1. 먼저 탐색할 매개변수를 지정한다.
2. 훈련 세트에서 그리드 서치를 수행하여 최상의 평균 검증 점수가 나오는 매개변수 조합을 찾는다. 이 조합은 그리드 서치 객체에 저장된다.
3. 그리드 서치는 최상의 매개변수에서 (교차 검증에 사용한 훈련 세트가 아니라) 전체 훈련 세트를 사용해 최종 모델을 훈련 한다. 이 모델도 그리드 서치 객체에 저장된다.
더 복잡한 매개변수 조합을 탐색해 보자. 결정 트리에서 min_inpurity_decrease는 노드를 분할하기 위한 불순도 감소 최소량(어떤 노드의 정보 이득 x (노드의 샘플 수) /(전체 샘플 수) )을 지정한다. 여기에다 max_depth로 트리의 깊이를 제한하고 min_samples_split으로 노드를 나누기 위한 최소 샘플 수도 골라보겠다.
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']))
GridSearchCV 클래스를 사용하니 매개변수를 일일이 바꿔가며 교차 검증을 수행하지 않고 원하는 매개변수 값을 나열하면 자동으로 교차 검증을 수행해서 최상의 매개변수를 찾을 수 있다. 그런데 아쉬운 점이 있다. 앞에서 탐색할 매개변수 간격을 0.0001 혹은 1로 설정했는데 이렇게 간격을 둔 것에 특별한 근거가 없다. 이보다 더 좁거나 넓은 간격으로 시도해 볼 수 있지 않을까?
랜덤서치
매개변수 값이 수치일 때 값의 범위나 간격을 미리 정하기 어려울 수 있다. 또 너무 많은 매개변수 조건이 있어 그리드 서치 수행 시간이 오래 걸릴 수 있다. 이럴 때 랜덤서치(Random Search)를 사용하면 좋다.
랜덤 서치에는 매개변수 값의 목록을 전달하는 것이 아니라 매개변수를 샘플링 할 수 있는 확률 분포객체를 전달한다.
from scipy.stats import uniform,randint
uniform과 randint 클래스 모두 주어진 범위에서 고르게 값을 뽑는다. 이를 균등 분포에서 샘플링 한다 라고 말한다.
난수 발생기랑 유사하게 생각하면 된다. 랜덤 서치에 randint 와 uniform 클래스 객체를 넘겨주고 총 몇 번을 샘플링해서 최적의 매개변수를 찾으라고 명령할 수 있다. 그럼 탐색할 매개변수의 딕셔너리를 만들어 보겠다. 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의 n_iter 매개변수에 지정한다.
from sklearn.model_selection import RandomizedSearchCV
gs=RandomizedSearchCV(DecisionTreeClassifier(random_state=42),params,
n_iter=100,n_jobs=-1,random_state=42)
gs.fit(train_input,train_target)
params에 정의된 매개변수 범위에서 총 100번(n_iter 매개변수)을 샘플링하여 교차 검증을 수행하고 최적의 매개변수 조합을 찾는다. 앞서 그리드 서치보다 훨씬 교차 검증 수를 줄이면서 넓은 영역을 효과적으로 탐색할 수 있다.
print(gs.best_params_)
print(np.max(gs.cv_results_['mean_test_score']))
최적의 모델은 이미 전체 훈련 세트(train_input, train_target)로 훈련되어 best_estimator_속성에 저장되어 있다. 이 모델을 최종 모델로 결정하고 테스트 세트의 성능을 확인해 보자.
dt=gs.best_estimator_
print(dt.score(test_input,test_target))
테스트 세트 점수는 검증 세트에 대한 점수보다 조금 작은것이 일반적이다.
결론
레드 와인과 화이트 와인을 선별하는 작업의 성능을 끌어올리기 위해 결정 트리의 다양한 하이퍼파라미터를 시도해 봐야 한다. 이런 과정에서 테스트 세트를 사용하면 결국 테스트 세트에 맞춰 모델을 훈련하는 효과를 만든다. 테스트 세트는 최종 모델을 선택할 때까지 사용하지 말아야 한다. 테스트 세트를 사용하지 않고 모델을 평가하려면 또 다른 세트가 필요하다. 이를 검증 세트라고 한다
검증 세트가 크지 않다면 교차 검증을 한다. 보통 훈련 세트를 5,10등분한뒤 최종 검정 점수는 모든 폴드의 검증 점수를 평균하여 계산한다. 교차 검증을 사용해 다양한 하이퍼파라미터를 탐색한다.
매개변수 값이 수치형이고 특히 연속적인 실숫값이라면 싸이파이의 확률 분포 객체를 전달하여 특정 범위 내에서 지정된 횟수만큼 매개변수 후보 값을 샘플링 하여 검증을 시도할 수 있다.
다음 시간에는 결정 트리를 확장하여 머신러닝 계를 제패한 앙상블 모델에 대해 알아보겠다.
'Machine Learning > Basic' 카테고리의 다른 글
[5-3-(2)] 트리의 앙상블 - 엑스트라 트리, 그레이디언트 부스팅, 히스토그램 기반 그레이디언트 부스팅 (0) | 2021.04.10 |
---|---|
[5-3-(1)] 트리의 앙상블 - 랜덤 포레스트 (0) | 2021.04.10 |
[5-1] 결정 트리 (0) | 2021.04.07 |
[4-2] 확률적 경사 하강법 (0) | 2021.04.07 |
[4-1] 로지스틱 회귀 (0) | 2021.04.06 |