3 분 소요

❗ 문제점 ❗

chap 5-1에서 max_depth를 3으로 하고 끝내버렸다. 하지만 최적의 max_depth를 찾지는 못했다.

🔮 sol 1. 검증 세트

기존에는 데이터들을 80%/20%의 비율로 훈련 세트와 데이터 세트로 나누었다. 이럴 경우 검증을 못한다. 검증을 하기 위해서는 여러 번의 훈련을 하면 되지만, 같은 데이터로 여러번 훈련을 하면 안된다. 따라서 이 문제를 검증 세트 를 통해서 해결했다. 검증 세트는 기존의 데이터를 60%/20%/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()

# 트리는 딱히 전처리 과정이 필요없으니까 pass
# 훈련 세트 & 테스트 세트 분리

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)
  • sub_input, sub_target : 60%의 훈련 세트
  • val_input, val_target : 20%의 검증 세트
  • test_input, test_target : 20%의 테스트 세트

📍 점수 확인

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 # 검증 세트 점수

🔮 sol 2. 교차 검증

검증 세트를 통해서 훈련시킬 경우 검증 세트를 만드느라 훈련 세트가 줄었다. 이 문제를 해결하기 위해서 교차 검증 을 사용한다. 교차 검증이란 검증 세트를 떼어 내어 평가하는 과정을 여러 번 반복하는 것이다. 여기서 80%의 데이터를 k개로 나누어서 검증 세트를 뽑아내는 과정을 k-폴드 교차 검증 이라고 한다.
사이킷 런에는 cross_validate() 라는 교차 검증 함수가 있다.

from sklearn.model_selection import cross_validate
scores = cross_validate(dt, train_input, train_target)
print(scores)

>>> 출력값
{'fit_time': array([0.0117085 , 0.01046944, 0.01088619, 0.01039219, 0.01086831]),
'score_time': array([0.0014801 , 0.00232983, 0.00132895, 0.00125623, 0.00124264]),
'test_score': array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}
  • fit_time : 모델을 훈련하는 시간
  • score_time : 검증하는 시간
  • test_score : 검증 폴드의 점수

cross_validate() 함수는 기본적으로 5-폴드 교차 검증을 수행하고, cv 매개변수를 통해서 폴드 수를 수정할 수 있다.

import numpy as np
print(np.mean(scores['test_score'])) # test_score 키에 담긴 5개의 점수의 평균

>>> 0.855300214703487

지금 데이터들의 분리 과정에서 train_test_split() 함수를 통해서 분리하였으므로 따로 섞을 필요가 없다. 이 경우는 딱 80%/20% 중에서 20%는 훈련 세트로 떼어 놓고 교차 검증을 한 경우! 하지만 만약 교차 검증을 할 때 훈련 세트를 섞으려먼 분할기를 지정해야 한다.

📍 폴드 수정하는 방법

회귀 모델일 경우 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

🔮 그리드 서치

검증 세트, 교차 검증을 통해서 보다 확실하게 모델을 검증하는 방법에 대해서 배웠다. 이번 절부터는 이를 활용하여 최적의 max_depth를 구하는 방법을 알아보자.

  • 모델 파라미터 : 머신러닝 모델이 학습하는 파라미터
  • 하이퍼파라미터 : 모델이 학습할 수 없어서 사용자가 지정해야만 하는 파라미터 like 교차 검증


min*samplessplit이 바뀌면 최적의 max_depth도 바뀐다. 그렇다면 지금 매개 변수가 2개라는 말인데, 지금은 비록 2개지만 더 많아지면 문제가 복잡해진다. -> sol. **_GridSearch**

📍 매개변수 직접 지정

from sklearn.model_selection import GridSearchCV
params = {'min_impurity_decrease':[0.0001, 0.0002, 0.0003, 0.0004, 0.0005]} # 값을 바꿔가면서 최적의 min_impurity_decrease 결정
gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1) # GridSearchCV의 cv기본값은 5.

gs.fit(train_input, train_target)
dt = gs.best_estimator_
print(dt.score(train_input, train_target)) # 서치로 찾은 최적의 매개변수는 best_params_ 속성에 저장!

print(gs.best_params_)
# {'min_impurity_decrease': 0.0001}

print(gs.cv_results_['mean_test_score'])
# [0.86819297 0.86453617 0.86492226 0.86780891 0.86761605]

best_index = np.argmax(gs.cv_results_['mean_test_score'])
print(gs.cv_results_['params'][best_index])
# {'min_impurity_decrease': 0.0001}
  • min_impurity_decrease : 얘의 최적값을 찾는 것이 목표
  • GridSearchCV 클래스 : 탐색 대상 모델, params 변수 전달을 통해서 그리드 서치 객체를 만듬
  • bestestimator : ❓
  • best_params : 그리드 서치로 찾은 최적의 매개변수
  • cvresults[‘mean_test_score’] : min_impurity_decrease의 값마다 5-폴드 교차 검증을 수행하여 나온 점수의 평균

🌗 최적의 max_depth 구하기

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_)
# {'max_depth': 14, 'min_impurity_decrease': 0.0004, 'min_samples_split': 12}

print(np.max(gs.cv_results_['mean_test_score'])) # 0.8683865773302731

🔮 랜덤 서치

매개변수의 값이 수치일 때 값의 범위나 간격을 미리 정하기 어려울 경우가 있다. 이럴 때 랜덤 서치를 사용한다.

from scipy.stats import uniform, randint # uniform, randint 클래스는 모두 주어진 범위에서 고르게 뽑는다.
rgen = randint(0, 10)
rgen.rvs(10)
# array([9, 2, 0, 8, 1, 2, 6, 6, 4, 0])

np.unique(rgen.rvs(1000), return_counts=True)
# (array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), array([ 99, 119,  98,  83, 104, 104,  99, 102,  94,  98]))

ugen = uniform(0, 1)
ugen.rvs(10)
# array([0.66538712, 0.75439689, 0.09605709, 0.36154309, 0.78712722, 0.54217307, 0.81125253, 0.90118702, 0.1911896 , 0.4142776 ])
  • randint : 랜덤으로 정수값 뽑기
  • uniform : 랜덤으로 실숫값 뽑기

  • rgen = randint(0, 10) : 0부터 10까지의 범위를 갖는 객체 생성
  • rgen.rvs(10) : rgen 객체에서 10개를 뽑기
  • np.unique(rgen.rvs(1000), return_counts=True) : 1000개 뽑아서 해당 숫자와 개수 리턴

  • ugen = uniform(0, 1) : rgen과 동일하게 0부터 1까지의 범위를 갖는 객체 생성
  • ugen.rvs(10) : 10개 뽑기
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)
print(gs.best_params_)
print(np.max(gs.cv_results_['mean_test_score']))

dt = gs.best_estimator_
print(dt.score(test_input, test_target))

>>>
{'max_depth': 39, 'min_impurity_decrease': 0.00034102546602601173, 'min_samples_leaf': 7, 'min_samples_split': 13}
0.8695428296438884 # 훈련 세트의 최고 점수
0.86 # 테스트 세트 점수

댓글남기기