2.7 모델 세부 튜닝

2.6 모델 선택과 훈련 | 목차 | 2.8 론칭, 모니터링, 그리고 시스템 유지 보수

 

가능성 있는 모델들을 추렸다고 가정하겠습니다. 이제 이 모델들을 세부 튜닝해야 합니다. 그 방법을 몇 개 살펴봅시다.

 

2.7.1 그리드 탐색

가장 단순한 방법은 만족할 만한 하이퍼파라미터 조합을 찾을 때까지 수동으로 하이퍼파라미터를 조정하는 것입니다. 이는 매우 지루한 작업이고 많은 경우의 수를 탐색하기에는 시간이 부족할 수도 있습니다.

대신 사이킷런의 GridSearchCV를 사용하는 것이 좋습니다. 탐색하고자 하는 하이퍼파라미터와 시도해볼 값을 지정하기만 하면 됩니다. 그러면 가능한 모든 하이퍼파라미터 조합에 대해 교차 검증을 사용해 평가하게 됩니다. 예를 들어 다음 코드는 RandomForestRegressor에 대한 최적의 하이퍼파라미터 조합을 탐색합니다.

from sklearn.model_selection import GridSearchCV

param_grid = [
        {'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]},
        {'bootstrap': [False], 'n_estimators': [3, 10], 'max_features': [2, 3, 4]},
    ]
forest_reg = RandomForestRegressor()

grid_search = GridSearchCV(forest_reg, param_grid, cv=5,
                           scoring='neg_mean_squared_error',
                           return_train_score=True)

grid_search.fit(housing_prepared, housing_labels)
TIP 어떤 하이퍼파라미터 값을 지정해야 할지 모를 때는 연속된 10의 거듭제곱 수로 시도해보는 것도 좋습니다(더 세밀하게 탐색하려면 위 예제의 n_estimators 하이퍼파라미터처럼 더 작은 값을 지정합니다).

param_grid 설정에 따라 사이킷런이 먼저 첫 번째 dict에 있는 n_estimators와 max_features 하이퍼파라미터의 조합인 3 × 4 = 12개를 평가하고(지금은 이 하이퍼파라미터가 무엇을 의미하는지 걱정하지 않아도 됩니다. 7장에서 설명하겠습니다), 그런 다음 두 번째 dict에 있는 하이퍼파라미터의 조합인 2 × 3 = 6개를 시도합니다. 하지만 두 번째는 bootstrap 하이퍼파라미터를 True (기본값)가 아니라 False로 설정합니다.

모두 합하면 그리드 탐색이 RandomForestRegressor 하이퍼파라미터 값의 12 + 6 = 18개 조합을 탐색하고, 각각 다섯 번 모델을 훈련시킵니다(5-겹 교차 검증을 사용하기 때문에). 다시 말해 전체 훈련 횟수는 18 × 5 = 90이 됩니다! 이는 시간이 꽤 오래 걸리지만 다음과 같이 최적의 조합을 얻을 수 있습니다.

>>> grid_search.best_params_
{'max_features': 8, 'n_estimators': 30}
TIP 8과 30은 탐색 범위의 최댓값이기 때문에 계속 점수가 향상될 가능성이 있으므로 더 큰 값으로 다시 검색해야 합니다.

최적의 추정기에 직접 접근할 수도 있습니다.

>>> grid_search.best_estimator_
RandomForestRegressor(bootstrap=True, criterion='mse', max_depth=None,
        max_features=8, max_leaf_nodes=None, min_impurity_split=1e-07,
        min_samples_leaf=1, min_samples_split=2,
        min_weight_fraction_leaf=0.0, n_estimators=30, n_jobs=1,
        oob_score=False, random_state=42, verbose=0, warm_start=False)
NOTE_ GridSearchCV가 (기본값인) refit=True로 초기화되었다면 교차 검증으로 최적의 추정기를 찾은 다음 전체 훈련 세트로 다시 훈련시킵니다. 일반적으로 데이터가 많을수록 성능이 향상되므로 좋은 방법
입니다.

물론 평가 점수도 확인할 수 있습니다.

>>> cvres = grid_search.cv_results_
>>> for mean_score, params in zip(cvres["mean_test_score"], cvres["params"]):
...     print(np.sqrt(-mean_score), params)
...
63825.0479302 {'max_features': 2, 'n_estimators': 3}
55643.8429091 {'max_features': 2, 'n_estimators': 10}
53380.6566859 {'max_features': 2, 'n_estimators': 30}
60959.1388585 {'max_features': 4, 'n_estimators': 3}
52740.5841667 {'max_features': 4, 'n_estimators': 10}
50374.1421461 {'max_features': 4, 'n_estimators': 30}
58661.2866462 {'max_features': 6, 'n_estimators': 3}
52009.9739798 {'max_features': 6, 'n_estimators': 10}
50154.1177737 {'max_features': 6, 'n_estimators': 30}
57865.3616801 {'max_features': 8, 'n_estimators': 3}
51730.0755087 {'max_features': 8, 'n_estimators': 10}
49694.8514333 {'max_features': 8, 'n_estimators': 30}
62874.4073931 {'max_features': 2, 'n_estimators': 3, 'bootstrap': False}
54561.9398157 {'max_features': 2, 'n_estimators': 10, 'bootstrap': False}
59416.6463145 {'max_features': 3, 'n_estimators': 3, 'bootstrap': False}
52660.245911 {'max_features': 3, 'n_estimators': 10, 'bootstrap': False}
57490.0168279 {'max_features': 4, 'n_estimators': 3, 'bootstrap': False}
51093.9059428 {'max_features': 4, 'n_estimators': 10, 'bootstrap': False}

이 예에서는 max_features 하이퍼파라미터가 8, n_estimators 하이퍼파라미터가 30일 때 최적의 솔루션입니다. 이때 RMSE 점수가 49,694로 앞서 기본 하이퍼파라미터 설정으로 얻은 52,564점보다 조금 더 좋습니다. 축하합니다. 성공적으로 최적의 모델을 찾았습니다.

TIP 데이터 준비 단계를 하나의 하이퍼파라미터처럼 다룰 수 있습니다. 예를 들면 그리드 탐색이 확실하지 않은 특성을 추가할지 말지 자동으로 정할 수 있습니다(예를 들어 CombinedAttributesAdder 변환기의 add_bedrooms_per_room 하이퍼파라미터를 사용하여 특성을 추가할지 결정합니다). 비슷하게 이상치나 값이 빈 특성을 다루거나 특성 선택 등을 자동으로 처리하는 데 그리드 탐색을 사용합니다.51

 

2.7.2 랜덤 탐색

그리드 탐색 방법은 이전 예제와 같이 비교적 적은 수의 조합을 탐구할 때 괜찮습니다. 하지만 하이퍼파라미터 탐색 공간이 커지면 RandomizedSearchCV를 사용하는 편이 더 좋습니다.52 RandomizedSearchCV는 GridSearchCV와 거의 같은 방식으로 사용하지만 가능한 모든 조합을 시도하는 대신 각 반복마다 하이퍼파라미터에 임의의 수를 대입하여 지정한 횟수만큼 평가합니다. 이 방식의 주요 장점은 다음 두 가지입니다.

  • 랜덤 탐색을 1,000회 반복하도록 실행하면 하이퍼파라미터마다 각기 다른 1,000개의 값을 탐색합니다(그리드 탐색에서는 하이퍼파라미터마다 몇 개의 값만 탐색합니다).
  • 단순히 반복 횟수를 조절하는 것만으로 하이퍼파라미터 탐색에 투입할 컴퓨팅 자원을 제어할 수 있습니다.

 

2.7.3 앙상블 방법

모델을 세밀하게 튜닝하는 또 다른 방법은 최상의 모델을 연결해보는 것입니다. (결정 트리의 앙상블인 랜덤 포레스트가 결정 트리 하나보다 더 성능이 좋은 것처럼) 모델의 그룹(또는 앙상블emsemble)이 최상의 단일 모델보다 더 나은 성능을 발휘할 때가 많습니다. 특히 개개의 모델이 각기 다른 형태의 오차를 만들 때 그렇습니다. 이 주제는 7장에서 자세히 살펴보겠습니다.

 

2.7.4 최상의 모델과 오차 분석

최상의 모델을 분석하면 문제에 대한 좋은 통찰을 얻는 경우가 많습니다. 예를 들어R andomForestRegressor가 정확한 예측을 만들기 위한 각 특성의 상대적인 중요도를 알려줍니다.

>>> feature_importances = grid_search.best_estimator_.feature_importances_
>>> feature_importances
array([ 7.33442355e-02, 6.29090705e-02, 4.11437985e-02,
        1.46726854e-02, 1.41064835e-02, 1.48742809e-02,
        1.42575993e-02, 3.66158981e-01, 5.64191792e-02,
        1.08792957e-01, 5.33510773e-02, 1.03114883e-02,
        1.64780994e-01, 6.02803867e-05, 1.96041560e-03,
        2.85647464e-03])

중요도 다음에 그에 대응하는 특성 이름을 표시해보겠습니다.

>>> extra_attribs = ["rooms_per_hhold", "pop_per_hhold", "bedrooms_per_room"]
>>> cat_one_hot_attribs = list(encoder.classes_)
>>> attributes = num_attribs + extra_attribs + cat_one_hot_attribs
>>> sorted(zip(feature_importances, attributes), reverse=True)
[(0.36615898061813418, 'median_income'),
 (0.16478099356159051, 'INLAND'),
 (0.10879295677551573, 'pop_per_hhold'),
 (0.073344235516012421, 'longitude'),
 (0.062909070482620302, 'latitude'),
 (0.056419179181954007, 'rooms_per_hhold'),
 (0.053351077347675809, 'bedrooms_per_room'),
 (0.041143798478729635, 'housing_median_age'),
 (0.014874280890402767, 'population'),
 (0.014672685420543237, 'total_rooms'),
 (0.014257599323407807, 'households'),
 (0.014106483453584102, 'total_bedrooms'),
 (0.010311488326303787, '<1H OCEAN'),
 (0.0028564746373201579, 'NEAR OCEAN'),
 (0.0019604155994780701, 'NEAR BAY'),
 (6.0280386727365991e-05, 'ISLAND')]

이 정보를 바탕으로 덜 중요한 특성들을 제외할 수 있습니다(예를 들어 ocean_proximity 카테고리 중 하나만 실제로 유용하므로 다른 카테고리는 제외할 수 있습니다).

시스템이 특정한 오차를 만들었다면 왜 그런 문제가 생겼는지 이해하고 문제를 해결하는 방법이 무엇인지 찾아야 합니다(추가 특성을 포함시키거나, 반대로 불필요한 특성을 제거하거나, 이상치를 제외하는 등).

 

2.7.5 테스트 세트로 시스템 평가하기

어느 정도 모델을 튜닝하면 마침내 만족할 만한 모델을 얻게 됩니다. 그럼 이제 테스트 세트에서 최종 모델을 평가할 차례입니다. 이 과정에 특별히 다른 점은 없습니다. 테스트 세트에서 예측 변수와 레이블을 얻은 후 full_pipeline을 사용해 데이터를 변환하고(fit_transform()이 아니라 transform()을 호출해야 합니다!) 테스트 세트에서 최종 모델을 평가합니다.

final_model = grid_search.best_estimator_

X_test = strat_test_set.drop("median_house_value", axis=1)
y_test = strat_test_set["median_house_value"].copy()

X_test_prepared = full_pipeline.transform(X_test)

final_predictions = final_model.predict(X_test_prepared)

final_mse = mean_squared_error(y_test, final_predictions)
final_rmse = np.sqrt(final_mse) # => 47,766.0 출력

하이퍼파라미터 튜닝을 많이 했다면 교차 검증을 사용해 측정한 것보다 조금 성능이 낮은 것이 보통입니다(우리 시스템이 검증 데이터에서 좋은 성능을 내도록 세밀하게 튜닝되었기 때문에 새로운 데이터셋에는 잘 작동하지 않을 가능성이 큽니다). 이 예제에서는 성능이 낮아지진 않았지만, 이런 경우가 생기더라도 테스트 세트에서 성능 수치를 좋게 하려고 하이퍼파라미터를 튜닝하려 시도해서는 안 됩니다. 그렇게 향상된 성능은 새로운 데이터에 일반화되기 어렵습니다. 이제 프로젝트 론칭 직전 단계에 왔습니다. (학습한 것, 한 일과 하지 않은 일, 수립한 가정, 시스템 제한사항 등을 강조하면서) 솔루션과 문서를 출시하고, 깔끔한 도표와 기억하기 쉬운 제목으로(예를 들면 ‘수입의 중간값이 주택 가격 예측의 가장 중요한 지표다’) 멋진 발표 자료를 만들어야 합니다.

 


 

51 옮긴이_ 데이터 준비 단계와 모델을 연결한 파이프라인을 그리드 탐색에 적용할 때 데이터 준비 단계를 캐싱하면 탐색 시간을 줄일 수있습니다. 파이프라인 캐싱에 대한 간단한 예는 옮긴이의 블로그를 참고하세요(https://goo.gl/cq9Nyb).
52 옮긴이_ 특히 규제처럼 설정값이 연속형인 경우 랜덤 탐색이 권장됩니다. 랜덤 탐색을 10회 반복하여 찾은 최적의 하이퍼파라미터는 n_estimators가 180, max_features가 7입니다. 깃허브의 주피터 노트북에서 랜덤 탐색 예제를 확인할 수 있습니다.

 

2.6 모델 선택과 훈련 | 목차 | 2.8 론칭, 모니터링, 그리고 시스템 유지 보수

 

이 글은 한빛미디어에서 출간한  “핸즈온 머신러닝“의 1장과 2장입니다. 이 책의 저작권은 한빛미디어(주)에 있으므로 무단 복제 및 무단 전제를 금합니다.

댓글 남기기

이 사이트는 스팸을 줄이는 아키스밋을 사용합니다. 댓글이 어떻게 처리되는지 알아보십시오.