Home 4 결정 트리 하이퍼파라미터 튜닝
Post
Cancel

4 결정 트리 하이퍼파라미터 튜닝

하이퍼 파라미터는 파라미터와 다른것이다.

머신러닝에서 파라미터는 모델이 튜닝될 때 조정된다. 예를 들어 선형회귀와 로지스틱 회귀의 가중치가 오차를 최소화하는 단계에서 조정되는 파라미터이다. 이와 다르게 하이퍼파라미터는 훈련 단계 이전에 미리 선택된다. 하이퍼 파라미터를 선택하지 않으면 기본값이 사용된다.

4.1 결정 트리 회귀 모델


하이퍼 파라미터는 실제로 여러가지 때려박아보는게 답이다. 다양한 하이퍼 파라미터 선택에 관한 이론들이 있지만 실전이 이론보다 앞선다고 한다. 데이터 셋마다 성능을 높일 수 있는 하이퍼파라미터 값이 다른다.

하이퍼 파라미터를 선택하기 전에 DecisionTreeRegressor 클래스와 cross_val_score() 함수로 기준 점수를 확인해본다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import pandas as pd

# --------------preprocessing------------------
df_bikes = pd.read_csv('./bike_rentals_cleaned.csv')
X_bikes = df_bikes.iloc[:, :-1]
y_bikes = df_bikes.iloc[:, -1]

from sklearn.linear_model import LinearRegression

X_train, X_test, y_train, y_test = train_test_split(X_bikes, y_bikes,
                                                    random_state=1117)

# ------------linear regression model-------------
from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import cross_val_score

reg = DecisionTreeRegressor(random_state=1117)
scores = cross_val_score(reg, X_bikes, y_bikes, scoring='neg_mean_squared_error',
                         cv=5)
rmse = np.sqrt(-scores)
print(f'\n... RMSE 평균:{rmse.mean():.2f} ...\n')

… RMSE 평균:1248.51 …

좋지 못한 1248.51의 RMSE가 나왔다.

분산이 너무 높아 모델이 데이터에 과대적합된 것인가? 훈련 세트에 대한 결정 트리의 성능을 확인하여 위 질문에 답을 얻을 수 있다.

1
2
3
4
5
6
7
8
reg = DecisionTreeRegressor()
reg.fit(X_train, y_train)
y_pred = reg.predict(X_train)

from sklearn.metrics import mean_squared_error
reg_mse = mean_squared_error(y_train, y_pred)
reg_rmse = np.sqrt(reg_mse)
reg_mse

0.0

RMSE가 0이면 100% 싹다 맞췄다는 얘기이다. 이 점수와 교차 검증 결과인 1248.51을 함께 생각하면 결정 트리가 과대 적합되어 분산이 크다는 것이 확실하다. 훈련 세트는 완벽하게 맞췄지만 테스트 세트에서는 큰 차이가 발생해버린 것이다.

하이퍼 파라미터는 이런 상황을 바로 잡을 수 있다.

4.2 하이퍼 파라미터


max_depth

max_depth는 트리의 깊이를 정의한다. 깊이는 분할 횟수를 결정한다. max_depth의 기본값은 None으로 제한이 없다. 따라서 수백이나 수천번 분할이 일어날 수 있으며 과대적합을 만든다. max_depth를 작은 값으로 제한하면 분산이 줄어들고 모델이 새로운 데이터에 잘 일반화 된다.

최선의 max_depth를 어떻게 선택할 수 있는가?

GridSearchCV

GridSearchCV는 교차 검증을 사용해 최선의 결과를 만드는 매개변수 조합을 찾는다.

GridSearchCV 클래스는 사이킷런의 다른 머신러닝 알고리즘처럼 동작한다. 즉 훈련 세트에서 훈련하고 테스트 세트에서 점수를 계산한다. 다른 모델과 주요한 차이점은 GridSearchCV가 최종모델을 선택하기 전에 모든 매개변수를 검사한다는 점이다.

GridSearchCV의 핵심은 매개변수 값의 딕셔너리를 정의한다는 것이다. 올바른 조합이란 것은 없다. 한 가지 방법은 가장 작은 값과 갖아 큰 값 사이에서 일정 간격을 선택하는 것이다. 과대적합을 줄여야 하기 때문에 max_depth값을 줄여서 시도해보는 것이 좋다.

GridSearchCV를 임포트하고 max_depth 파라미터의 리스트를 다음처럼 정의한다.

1
2
from sklearn.model_selection import GridSearchCV
params = {'max_depth': [None, 2, 3, 4, 6, 8, 10, 20]}

일반적으로 max란 이름이 붙은 매개변수는 감소시키고 min이 붙은 매개변수는 증가시키면 분산이 줄어들고 과대적합이 방지된다

그 다음 DecisionTreeRegressor 객체를 만들고 GridSearchCV에 params 딕셔너리와 평가지표를 함께 전달한다.

1
2
3
4
5
reg = DecisionTreeRegressor(random_state=1117)
grid_reg = GridSearchCV(reg, params, scoring='neg_mean_squared_error',
                        cv=5, return_train_score=True,
												jobs=-1) # CPU 풀가동
grid_reg.fit(X_train, y_train)

데이터를 GridSearchCV에 fitting시켰으므로 이제 최상의 매개변수를 확인한다.

1
2
best_params = grid_reg.best_params_
print(f'\n... best params : {best_params} ...\n')

… best params : {‘max_depth’: 8} …

max_depth=8일 때 훈련 세트에서 최상의 교차 검증 점수를 만든다.

훈련점수는 best_score_에 저장되어 있다.

1
2
best_score = np.sqrt(-grid_reg.best_score_)
print(f'\n... train score : {best_score:.3f} ...\n')

… train score : 821.074 …

테스트 점수는 다음과 같이 출력한다.

1
2
3
4
5
6
7
8
9
best_model = grid_reg.best_estimator_

y_pred = best_model.predict(X_test)

from sklearn.metrics import mean_squared_error

rmse_test = mean_squared_error(y_test, y_pred)**.5

print(f'\n... test score : {rmse_test:.3f} ...\n')

… train score : 821.074 …

1034로 확실히 분산이 줄어들었다.

min_samples_leaf

min_samples_leaf는 리프 노드가 가질 수 있는 최소 샘플의 개수를 제한한다. max_depth와 마찬가지로 min_samples_leaf는 과적합을 방지한다.

min_samples_leaf의 기본값은 1로 제한이 없을 때 리프 노드는 하나의 샘플로 구성할 수 있다.(과적합되기 쉽다). min_samples_leaf를 증가시키면 분산을 줄일 수 있다. 예를 들어 min_samples_leaf=8 이면 모든 리프 노드는 최소한 8개 이상의 샘플을 담고 있어야 한다.

min_samples_leaf의 값을 테스트하는 과정은 이전과 동일하다. 복붙을 하는 대신 DecisionTreeRegressor(random_state=1117)를 reg 객체에 할당하고 GridSearchCV르 사용해서 최상의 매개변수, 훈련 점수, 테스트 점수를 출력하는 함수를 작성하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def grid_search(params, reg=DecisionTreeRegressor(random_state=1117),
                X_train=X_train, X_test=X_test, y_train=y_train, y_test=y_test):
  grid_reg = GridSearchCV(reg, params, scoring='neg_mean_squared_error', cv=5,
                          n_jobs=-1)
  grid_reg.fit(X_train, y_train)

  best_params = grid_reg.best_params_
  best_score = np.sqrt(-grid_reg.best_score_)
  print(f'\n... best params: {best_params} ...\n')
  print(f'\n... train score: {best_score:.3f} ...\n')

  y_pred = grid_reg.best_estimator_.predict(X_test)
  rmse_test = mean_squared_error(y_test, y_pred)**.5
  print(f'\n... test score: {rmse_test:.3f} ...\n')

하이퍼 파라미터의 범위를 선택할 때 훈련 세트의 크기를 아는 것이 도움된다.

1
X_train.shape

… train score : 821.074 …

훈련 세트가 548개 행을 가지고 있기 때문에 적절한 min_samples_leaf 값을 결정할 수 있다. grid_search의 입력으로 [1, 2, 4, 8, 20, 30]을 시도해본다.

1
grid_search(params={'min_samples_leaf': [1, 2, 4, 6, 8, 10, 20, 30]})

… best params: {‘min_samples_leaf’: 2} …

… train score: 855.425 …

… test score: 898.941 …

min_samples_leaf와 max_depth를 같이 넣어보자.

1
2
grid_search(params={'max_depth': [None, 2, 3, 4, 6, 8, 10, 20],
                    'min_samples_leaf': [1, 2, 4, 6, 8, 10, 20, 30]})

… best params: {‘max_depth’: 8, ‘min_samples_leaf’: 1} …

… train score: 821.074 …

… test score: 1034.835 …

어처구니가 없게도 훈련점수는 좋아졌지만 테스트점수는 나빠졌다(과적합).

이전 예제에서 분산을 줄였던 것처럼 min_samples_leaf를 3보다 크게 설정해보자.

1
2
grid_search(params={'max_depth': [None, 6, 7, 8, 9, 10],
                    'min_samples_leaf': [3, 5, 7, 9]})

… best params: {‘max_depth’: 8, ‘min_samples_leaf’: 5} …

… train score: 849.492 …

… test score: 793.616 …

결과에서 볼 수 있듯이 테스트 점수가 향상되었다.

max_leaf_nodes

max_leaf_nodes는 min_samples_leaf와 비슷하다. 리프 노드 하나당 샘플 개수의 하한을 지정하는 대신, 전체 트리의 리프 노드 개수의 상한을 지정한다. 예를 들어, max_leaf_nodes=10 으로 지정하면 트리 리프 노드가 최대 10개를 넘을 수 없다.

max_features

max_features는 분산을 줄이는데 효과적인 매개변수이다. 분할마다 모든 feature를 고려하지 않고 매번 지정된 개수의 feature 중에서 선택한다.

max_features의 옵션은 다음과 같다.

  • None(default)와 ‘auto’는 전체 feature를 사용한다.
  • ‘sqrt’는 전체 feature 개수의 제곱근을 사용한다.
  • ‘log2’는 전체 log_2(feature 개수)를 사용한다. ex) 전체 feature 개수가 32개 ⇒ 분할 당 5개의 feature만 고려.

min_samples_split

분할을 제한하는 또 다른 방법은 min_samples_split이다. 이름에서 알 수 있듯이 1회 분할하기 위해 필요한 최소 sample 개수를 제한한다. 기본값은 2이다. 예를 들어, 이 값을 5로 설정하면 5개 보다 적은 노드는 더 이상 분할 하지 않는다.

splitter

splitter 매개 변수에는 ‘random’과 ‘best’ 두개의 옵션이 있다. 분할기는 노드를 분할하기 위한 feature 선택 방법이다. 기본값은 ‘best’로 정보이득(information gain)이 가장 큰 특성을 선택한다 (criterion으로 지정된 평가지표를 가장 줄이는 feature). 이와 달리 ‘random’은 랜덤하게 노드를 분할한다.

splitter=’random’으로 하면 과대적합을 막고 다양한 트리를 만드는 효과가 있다.

criterion

결정 트리 회귀 모델과 분류 모델의 criterion값이 다르다. criterion은 분할 품질을 측정할 수 있는 방법을 제공한다. criterion에 지정한 함수를 가능한 분할마다 계산하여 비교한다. 가장 좋은 점수를 얻은 분할이 선택된다.

회귀 모델일 경우 ‘squared_error’(평균 제곱 오차), ‘friedman_mse’(프리드만 MSE), ‘absolute error’, ‘poisson’(포이송 편차)가 있다. 기본값은 ‘squared_error’이다.

분류 모델일 경우 앞서 언급한 ‘gini’(기본값)과 ‘entropy’가 있다. 일반적으로 두 옵션은 비슷한 결과를 만든다.

min_impurity_decrease

min_impurity_decrease는 분할하기 위한 최소 불순도 감소를 지정한다.

불순도는 각 노드의 예측이 얼마나 순수한지를 측정한다. 100%의 정확도를 가진 트리의 불순도는 0.0이다. 80% 정확도를 가진 트리의 평균적인 불순도는 0.20일 것이다.

불순도는 결정 트리에서 중요한 개념이다. 트리를 성장시키는 과정에서 불순도는 지속적으로 감소되어야 한다. 각 노드에거 가장 크게 불순도를 감소시키는 분할이 선택된다.

기본값은 0.0이다. 이 값을 증가시키면 임곗값에 도달할 때 트리의 성장이 멈춘다.

min_weight_fraction_leaf

min_weight_fraction_leaf는 리프 노드가 되기 위한 전체 가중치의 최소 비율이다. sample_weight를 지정해주지 않으면 샘플은 모두 동일한 가중치를 가진다.

min_weight_fraction_leaf는 분산을 줄이고 과대적합을 막을 수 있는 또 다른 하이퍼파라미터이다. 기본값은 0.0이다. 500개의 샘플이 있고 가중치가 동일하다면, 이 매개변수를 0.01로 지정할 때 리프 노드가 되기 위한 최소 샘플 개수는 5개이다.

cca_alpha

cca_alpha 매개변수는 트리를 만든 후 가지치기(prunning)를 하는 기능으로 여기서 설명하지는 않을 것이다. 자세한 내용은 최소 비용복잡도 가지치기 (minimal cost-complexity prunning)에 대해 알아볼 것.

4.3 정리


하이퍼 파라미터 튜닝을 할 때 몇 가지 고려사항이 있다.

  • 소요 시간
  • 하이퍼파라미터 개수
  • 원하는 소수점 정확도

소요 시간, 튜닝할 하이퍼 파라미터 개수, 원하는 정확도는 데이터셋과 프로젝트에 따라 다르다. 하이퍼파라미터는 서로 연관되어 있기 때문에 모두 수정할 필요는 없다. 작은 범위에서 튜닝하면 더 좋은 결과를 만들 수 있다.

This post is licensed under CC BY 4.0 by the author.

3 분산(variance)과 편향(bias)

5 심장 질환 예측하기