이전 글에서 결정 트리를 이용하여 와인을 분류해보았다. 결정 트리는 어떤 식으로 분류가 진행되었는지 쉽게 알 수 있었다. 지금까지는 훈련 세트를 이용하여 테스트 세트의 점수를 확인했다. 하지만 테스트 세트를 사용하여 성능을 확인하다 보면 모델을 테스트 세트에 맞추게 되는 셈이다. 가능한 한 테스트 세트를 사용하지 말고 모델을 설계한 뒤에 한 번만 사용하는 것이 좋다. 이번 글에서는 결정 트리의 매개 변수들을 테스트해보자.
검증 세트(Validation Set)
테스트 세트를 사용하지 않고 과소/과대 적합을 판단하는 방법이 무엇일까?
훈련 세트를 조금 떼서 사용해보면 되지않을까? 너무 단순하지만 실제로 많이 사용하는 방법이라고 한다. 그리고 이렇게 훈련 세트에서 20~30% 정도 떼어 놓은 세트를 검증 세트(Validation Set)라고 한다.
훈련 세트에서 모델을 훈련하고 검증 세트로 모델을 평가한 뒤에 훈련 세트와 검증 세트를 합쳐 전체 훈련 데이터에서 모델을 다시 훈련한 다음 마지막에 테스트 세트에서 최종 점수를 확인한다. 실전에 투입했을 때 테스트 세트의 점수와 비슷한 성능을 기대할 수 있을 것이다.
이전 글에서 사용했던 데이터로 검증 세트를 만들어보자. 일단 데이터를 훈련 세트와 테스트 세트로 나누자. 20%를 테스트 세트로 만든다.
import pandas as pd
wine = pd.read_csv('https://bit.ly/wine_csv_data')
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
)
훈련 세트를 또 다른 훈련 세트와 검증 세트로 만들자. 여기서도 20%를 사용하고 크기를 출력해보자.
sub_input, val_input, sub_target, val_target = train_test_split(
train_input, train_target, test_size=0.2, random_state=42
)
훈련 세트는 4157개 검증 세트는 1040개가 되었다. 훈련 세트와 검증 세트로 모델을 만들고 점수를 확인해보자.
훈련 세트에 과대 적합되어 있다. 매개변수를 바꿔서 더 좋은 모델을 만들기 전에 검증 세트에 대해 더 알아보자.
교차 검증(Cross Validation)
보통 많은 데이터를 훈련에 사용할수록 좋은 모델이 나오는데 검증 세트 때문에 훈련 세트가 줄었으니 그렇게 좋은 모델이 나올 것 같지 않다. 이때 교차 검증(Cross Validation)을 사용하면 안정적인 검증 점수를 얻고 훈련에 더 많은 데이터를 사용할 수 있다.
교차 검증은 검증 세트를 떼서 평가하는 과정을 반복한다. 그 다음 이 점수들을 평균하여 최정 검증 점수를 얻는다.
흔히 K-폴드 교차 검증(K-fold cross validation)이라고 불린다. k가 5이면 훈련 세트를 다섯 부분으로 나눠서 각 부분별로 검증 세트로 만든 다음 평가를 5번 진행한다. 그리고 평가한 점수를 평균하여 검증 점수 평균을 낸다.
cross_validate() 함수를 사용하여 교차 검증을 해보자. 평가할 모델 객체를 첫 번째 매개변수로 전달하고 검증 세트가 아닌 훈련 세트 전체를 전달해주면 된다.
from sklearn.model_selection import cross_validate
scores = cross_validate(dt, train_input, train_target)
scores의 출력 값은 다음과 같다.
{'fit_time': array([0.01210165, 0.00786185, 0.00757599, 0.00772381, 0.00728822]), 'score_time': array([0.00086761, 0.00080395, 0.00074649, 0.00078464, 0.00071526]), 'test_score': array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}
이 함수는 fit_time, score_time, test_score 키를 가진 딕셔너리를 반환한다. 처음 2개의 키는 각각 모델을 훈련하는 시간과 검증하는 시간을 의미한다. 각 키마다 5개의 숫자가 담겨있는데, 이는 cross_validate() 함수가 기본적으로 5-폴드 교차 검증을 수행한다는 뜻이다. cv 매개변수로 폴드 수를 바꿀 수 있다.
교차 검증의 최종 점수는 test_score 키에 담긴 5개의 점수를 평균하여 얻을 수 있다. 이름은 test_score지만 검증 폴드의 점수이다. 평균은 다음과 같다.
주의할 점은 cross_validate() 함수는 훈련 세트를 섞어서 폴드를 나누지 않는다. 앞에서 train_test_split() 함수로 전체 데이터를 섞어서 훈련 세트를 준비했기 때문에 따로 섞을 필요가 없지만, 만약 섞으려면 분할기(splitter)를 지정해야한다.
사이킷런의 분할기는 교차 검증에서 폴드를 어떻게 나눌지를 결정해 준다. cross_validate() 함수는 회귀 모델일 때는 KFOLD 분할기, 분류 모델일 때는 타깃 클래스를 골고루 나누기 위해 StratifiedKFold를 사용한다. 위에서 수행한 교차 검증은 다음 코드와 같다.
훈련 세트를 섞은 뒤에 10-폴드 교차 검증을 수행하려면 다음과 같이 코드를 짜면 된다.
n_splits 매개변수는 얼마의 폴드 교차 검증을 할 것인지 정한다. KFold도 같은 방식으로 사용하면 된다.
하이퍼 파라미터 튜닝
하이퍼 파라미터란 모델이 학습할 수 없어서 사용자가 지정해야 하는 파라미터를 의미한다. 사이킷런과 같은 머신러닝 라이브러리를 사용할 때 이런 하이퍼 파라미터는 모두 클래스나 메서드의 매개변수로 표현된다.
그렇다면 하이퍼파라미터는 어떻게 튜닝할까? 먼저 기본값으로 모델을 훈련한 뒤 검증 세트의 점수나 교차 검증을 통해서 매개변수를 조금씩 바꿔본다. 모델마다 적게는 1~2개, 많게는 5~6개의 매개변수가 있다. 이 매개변수를 바꿔가면서 모델을 훈련하고 교차 검증을 수행해야 한다.
결정 트리 모델에서 최적의 max_depth 값을 찾았다고 해보자. 그다음 max_depth를 고정하고 min_samples_split 매개변수를 바꿔가면 최적의 값을 찾았다고 하자. 이렇게 매개변수의 최적의 값을 하나씩 찾아서 고정해도 될까? 아쉽지만 max_depth의 최적 값은 min_samples_split의 값이 바뀌면 함께 달라진다. 우리는 이 두 매개변수를 동시에 바꿔가면서 최적의 값을 찾아야 한다.
사이킷런에서 제공하는 그리드 서치(Grid Search)를 사용하면 하이퍼 파라미터 탐색과 교차 검증을 한 번에 수행할 수 있다. cross_validate() 함수를 호출할 필요가 없다. 결정 트리 모델에서 min_impurity_decrease 매개변수의 최적 값을 찾아보자. GridSearchCV 클래스를 import 하고 탐색할 매개변수와 탐색할 값의 리스트를 딕셔너리로 만들어야 한다.
from sklearn.model_selection import GridSearchCV
params = {'min_impurity_decrease': [0.0001, 0.0002, 0.0003, 0.0004, 0.0005]}
0.0001~0.0005까지의 값을 이용해보자.
GridSearchCV 클래스에 탐색 대상 모델과 params 변수를 전달하여 그리드 서치 객체를 만든다.
gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
결정 트리 클래스의 객체를 생성하자마자 바로 전달했다. 일반 모델처럼 gs 객체에 fit() 메서드를 호출하여 훈련한다. 이 메서드를 호출하면 그리드 서치 객체는 결정 트리 모델 min_impurity_decrease 값을 바꿔가면서 5번 실행한다.
GridSearchCV의 cv매개변수 기본값은 5이다. 따라서 min_impurity_decrease 값마다 5-폴드 교차 검증을 수행한다. 즉 25개의 모델을 훈련하는 것이다. 많은 모델을 훈련하기 때문에 n_jobs 변수에서 병렬 실행에 사용할 CPU 코어 수를 지정하는 것이 좋다. 기본값은 1이지만 -1로 지정하면 시스템에 있는 모든 코어를 사용한다.
교차 검증에서 최적의 하이퍼 파라미터를 찾으면 전체 훈련 세트로 모델을 다시 만들어야 하는데 그리드 서치는 훈련이 끝나면 훈련한 25개의 모델 중에서 검증 점수가 가장 높은 모델의 매개변수 조합으로 전체 훈련 세트에서 자동으로 다시 모델을 훈련한다. 이 모델은 gs객체의 best_estimator_ 속성에 저장되어 있다. 이 모델을 일반 결정 트리처럼 똑같이 사용할 수 있다.
그리드 서치로 찾은 최적의 매개변수는 best_params_ 속성에 저장된다.
각 매개변수에서 수행한 교차 검증의 평균 점수는 cv_results_ 속성의 mean_test_score 키에 저장되어 있다.
첫 번째 값이 가장 큰 것을 확인할 수 있다.
정리하자면,
1. 먼저 탐색할 매개변수를 지정한다.
2. 훈련 세트에서 그리드 서치를 수행하여 최상의 평균 검증 점수가 나오는 매개변수 조합을 찾는다. 이 조합은 그리드 서치 객체에 저장된다.
3. 그리드 서치는 최상의 매개변수에서 교차 검증에 사용한 훈련 세트가 아니라 전체 훈련 세트를 사용해서 최종 모델을 훈련한다.
조금 더 복잡한 매개변수 조합을 탐색해보자.
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)}
min_impurity_decrease 9개, max_depth 15개, min_samples_split 10개를 모두 곱한 총 1350개의 교차 검증을 수행한다.
기본적으로 5-폴드 교차 검증을 수행하기 때문에 6750개의 모델이 만들어진다. n_jobs=-1로 꼭 설정해야겠다.
gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
gs.fit(train_input, train_target)
최적의 매개변수들과 최상의 교차 검증 점수도 확인할 수 있다.
랜덤 서치
앞에서 탐색할 매개변수의 간격을 0.0001 혹은 1로 설정했다. 특별히 의미가 없는 범위다. 자동으로 범위 같은 것을 설정해줄 수는 없을까? 이럴 때 랜덤 서치(Random Search)를 사용하면 좋다.
랜덤 서치에는 매개변수 값의 목록을 전달하는 것이 아니라 매개변수를 샘플링할 수 있는 확률 분포 객체를 전달한다.
싸이파이(scipy)는 적분, 보간, 선형 대수, 확률 등을 포함한 수치 계산 전용 라이브러리이다. 싸이파이를 이용하여 2개의 확률 분포 클래스를 import 해보자.
from scipy.stats import uniform, randint
uniform, randint 클래스는 모두 주어진 범위에서 고르게 값을 뽑는다. 이를 '균등 분포에서 샘플링한다'라고 한다.
randint는 정숫값을, uniform은 실숫값을 뽑는다.
uniform도 사용법은 같다.
그렇다면 랜덤 서치에 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)
}
최적의 매개 변숫값은 {'max_depth': 39, 'min_impurity_decrease': 0.00034102546602601173, 'min_samples_leaf': 7, 'min_samples_split': 13}이다.
최고의 교차 검증 점수도 출력해보자.
최적의 모델은 이미 전체 훈련 세트로 훈련되어 best_estimator_ 속성에 저장되어있으므로 이 모델을 이용해서 테스트 세트의 성능을 확인해보자.
테스트 세트 점수는 검증 세트에 대한 점수보다 조금 작은 것이 일반적이다.
'AI > 머신 러닝(ML)' 카테고리의 다른 글
군집 알고리즘 (Clustering) (0) | 2021.08.24 |
---|---|
트리의 앙상블 (0) | 2021.08.21 |
결정 트리 (Decision Tree) (0) | 2021.08.16 |
확률적 경사 하강법 (Stochastic Gradient Descent) (0) | 2021.08.14 |
로지스틱 회귀 (Logistic Regression) (0) | 2021.08.11 |