예측을 하기 위해 모델을 사용하는 경우 모델에 존재하는 파라미터는 예측하기 전에 추정되어야 합니다. OLS 회귀모델의 회귀계수처럼 일부 파라미터는 Training set을 통해 직접 추정될 수 있습니다. 하지만 튜닝파라미터(Tuning parameter)나 하이퍼파라미터(Hyper-Parameter)는 모델을 적합하기 전에 미리 명시가 되어있어야 하므로 Training set을 통해 직접 추정될 수 없습니다. 예측이나 추론에 중요한 영향을 끼치지만 훈련데이터로부터 직접 추정될 수 없기 때문에 기존 방식과는 다르게 접근해야 합니다.
챕터 12에서는 튜닝파라미터의 예시를 제공하고 tidymodels 함수가 어떻게 튜닝 파라미터를 다루는지 보여주며 챕터 13에서는 그리드서치(Grid Search), 챕터 14에서는 Iterative Search 방식을 통해 파라미터를 튜닝하는 방식에 대해 다루고 있습니다.
12 Model Tuning and the Dangers of Overfitting | Tidy Modeling with R
The tidymodels framework is a collection of R packages for modeling and machine learning using tidyverse principles. This book provides a thorough introduction to how to use tidymodels, and an outline of good methodology and statistical practice for phases
www.tmwr.org
1. Model Parameters
단순회귀분석에서 모델에는 절편을 나타내는 $\beta_0$와 기울기를 나타내는 $\beta_1$라는 두 개의 파라미터가 존재합니다.
$$y_i = \beta_0 + \beta_1x_i + \epsilon_i$$
회귀분석을 공부해봤으면 아래와 같이 최소제곱법을 통해 절편과 기울기를 추정할 수 있는데요!
$$\widehat{\beta_1} = \frac{cov(X,Y)}{var(X)}, \quad \widehat{\beta_0} = \overline{y} - \widehat{\beta_1}\overline{x}$$
반면 K-Nearest Neigborhood (KNN, K-최근접이웃) 모델에서는 예측을 위한 방정식은 아래와 같습니다.
$$\widehat{y} = \frac{1}{K}\sum_{l=1}^{K}x^*_l$$
K는 추정을 위해 사용하는 이웃 데이터의 수이며 $x^*_l$은 주어진 값에서 $l$번째로 가까운 값을 의미합니다.
KNN 모델 같은 경우에는 회귀모델과 다르게 K라는 파라미터 값을 사전에 입력해야 예측할 수 있습니다. 이러한 특성으로 인해 K에 대한 (최적)방정식을 구할 수 없게 됩니다.
최근접 이웃의 K값은 모델 구성에 큰 영향을 끼치지만 데이터에서 직접 추정할 수 없는 하이퍼파라미터(Hyper-Parameter)에 해당합니다.
2. Tuning Parameters for Different Types of Models
하이퍼파라미터의 예시는 아래와 같이 전처리와 통계모델 및 머신러닝 모델에 많이 존재합니다.
- Imputation : Method of Imputations
- PCA : Number of extracted components
- Boosting : Number of boosting iterations
- Neural-Network : Depth of layers, Number of units, Type of activation function & optimizer
- Gradient-Descent : Learning rates, Number of optimization iterations
- Logistic Regression : Type of Link-Function
파라미터를 튜닝하는 것이 부적절한 경우는 베이지안 분석에 필요한 사전 분포입니다. 반면에 본문에서 하이퍼파라미터지만 튜닝할 필요가 없는 경우로 랜덤포레스트와 부스팅 모델에서의 트리의 수를 소개하고 있습니다. (대신 안정성 있는 결과가 나올 수 있을 만큼 크게 선택해야 하며 랜덤포레스트는 수천개 배깅에서는 50~100개 선택을 권장)
3. What do we optimize?
모델 방식과 모델의 목적에 따라 하이퍼파라미터를 최적화하는 방식이 달리질 수 있습니다. 만약 하이퍼파라미터의 통계적 특성이 다루기 쉽다면 이러한 통계적 특성을 이용해 최적화할 수 있습니다. 대표적으로 로지스틱 회귀에서 Link-Function의 선택 기준으로 가능도함수(likelihood)의 최대화로 선택할 수 있을 것입니다. 하지만 통계적 특성을 이용해 하이퍼-파라미터를 최적화할 때 결과는 정확성을 기준으로 하는 방식의 최적화 결과와 동일하지 않을 수도 있습니다. (예시는 본문에 자세하게 나와있습니다!)
4. The Consequences of poor parameters estimates
많은 하이퍼파라미터는 모델의 복잡성을 결정하는 경향이 있는데 일반적으로 복잡성이 증가할수록 데이터에 존재하는 패턴을 모델이 더욱 유연하게 학습할 수 있습니다. 만약 실제 패턴이 복잡한 경우 유연하게 학습하는 것은 도움이 되지만, 실제 패턴이 복잡하지 않은 경우에는 과적합이 발생하는 문제가 될 수 있습니다.
과적합은 모델이 훈련 데이터에 있는 패턴을 과도하게 학습한 경우로 실제 패턴을 학습하지 못하고 훈련 데이터에서만 존재하는 패턴을 학습하여 훈련데이터에서는 성과가 좋지만 검증데이터에서는 성능이 지나치게 떨어지게 됩니다.
따라서 하이퍼파라미터를 적합할 때, 검증 데이터를 사용하여 과적합이 발생하는지 확인하는 것이 중요합니다.
5. Two General Strategies for Optimiztion
하이퍼파라미터의 최적화 방법에는 아래와 같이 grid search와 iterative search가 존재합니다.
- Grid Search : 하이퍼파라미터의 값을 미리 설정하고 제일 성과지표가 좋은 값을 사용하는 방법
- Iterative Search : 이전 결과를 기반으로 새로운 하이퍼파라미터의 값을 순차적으로 탐색하는 방법
Grid Search는 비교적 간단하고 구현하기 쉽지만 최적화가 필요한 하이퍼파리미터가 여러 개인 경우 조합의 수가 기하급수적으로 증가하는 문제가 있습니다.
반면에 Iterative Search는 최적의 위치에 가능한 가까워 지려고 하지만 많은 계산을 필요로 합니다.
두가지 방식을 합친 방식도 있는데 Grid Search를 통해 구한 성능이 제일 좋은 하이퍼파라미터의 값을 Iterative Search의 초기값으로 설정해서 최적화할 수 있습니다.
6. Tuning Parameters in Tidymodels
Parsnip 모델 사양으로는 크게 두가지 종류의 파라미터 인자가 존재합니다. 우선 Main arguments는 다양한 엔진에서 사용할 수 있고 우수한 성과지표를 얻기 위해 주로 최적화됩니다. 예를 들면, 회귀모델 linear_reg에서 penalty 인자, 랜덤포레스트 모델 rand_forest에서 trees, min_n, mtry 인자를 main arguments라고 할 수 있겠습니다. 반면에 Engine arguments는 특정 엔진에서만 사용할 수 있는 인자로 set_engine 함수 내부의 ...을 통해서 전달할 수 있습니다. (Chapter 6 참고)
그렇다면 Tidymodels에서는 어떻게 특정 파라미터가 튜닝되어야 하는지 알 수 있을까요? 정답은 최적화되어야 하는 파라미터(Main arguments & Engine arguments)에 tune() 함수를 작성하면 됩니다. (Parsnip, Recipe)
(* 참고로, tune( ) 함수는 특정 값을 대입해 실행하는 것이 아니라 R expression을 반환하며 임베딩 시 내부적으로 최적화 대상임을 명시하는데 사용되고 있습니다.)
랜덤포레스트 모델에서 사용 변수의 수를 나타내는 mtry 파라미터를 최적화 하기 위해 아래와 같이 나타낼 수 있습니다.
rand_forest(trees=5000, mtry = tune()) %>%
set_engine(engine = "ranger") %>%
set_mode(mode = "regression")
또한, NN model에서 은닉층의 수를 튜닝하려면 다음과 같이 작성할 수 있습니다.
neural_net_model <-
mlp(hidden_units = tune()) %>%
set_engine(engine = "keras") %>%
set_mode(mode = "regression")
tune 함수가 지정된 워크플로, 레시피, 파스닙 객체에서 튜닝 파라미터를 확인하기 위해서
extract_parameter_dials 또는 extract_parameter_set_dials 함수를 사용할 수 있습니다.
rf_model %>% extract_parameter_set_dials()
neural_net_model %>% extract_parameter_set_dials()
extract_parameter_dials는 아래 예시와 같이 문자열을 받아들여 해당 이름을 가지는 튜닝파라미터를 반환합니다.
ames_rec <- recipe(Sale_Price ~ Neighborhood + Gr_Liv_Area + Year_Built + Bldg_Type + Latitude + Longitude, data = ames_train) %>%
step_log(Gr_Liv_Area, base=10) %>%
step_other(Neighborhood, threshold = tune()) %>%
step_dummy(all_nominal_predictors()) %>%
step_interact(~ Gr_Liv_Area : starts_with("Bldg_Type_")) %>%
step_ns(Longitude, deg_free = tune("Longitude df")) %>%
step_ns(Latitude, deg_free = tune("Latitude df"))
ames_rec %>% extract_parameter_set_dials()
ames_rec %>% extract_parameter_dials("Latitude df")
ames_rec %>% extract_parameter_dials("Longitude df")
모델과 recipe 객체를 추가한 workflow 객체에 extract_parameter_set_dials를 사용한 경우 모델과 레시피에 있는 모든 튜닝파라미터를 반환합니다.
mlp_workflow <- workflow() %>%
add_recipe(ames_rec) %>%
add_model(neural_net_model)
mlp_workflow %>% extract_parameter_set_dials()
mlp_workflow %>% extract_parameter_dials("threshold")
mlp_workflow %>% extract_parameter_dials("hidden_units")
mlp_workflow %>% extract_parameter_dials("Longitude df")
tune( ) 함수를 사용하게 되면 대응하는 함수를 가지게 되는데, 대부분은 파라미터 인자와 동일한 이름의 함수가 생성됩니다. 예를 들어, ames_rec 객체를 만들기 위해서 threshold = tune()을 통해 임계치를 튜닝의 대상으로 지정할 때 threshold( )라는 함수가 생성되게 됩니다.
반면에 다른 이름을 가질 수 있는데 step_ns(var, deg_free = tune())와 같이 자유도를 튜닝의 대상으로 지정한다면 deg_free( )라는 함수가 생성되는 것이 아닌 spline_degree( )를 통해 접근이 가능합니다.
threshold()
spline_degree()
제일 중요한 점은 이렇게 생성된 함수 내부에 range를 지정함으로써 튜닝 파라미터의 값을 조정할 수 있다는 것 입니다.
업데이트 전 튜닝의 대상인 mtry는 1 이상의 범위를 가지는 튜닝 파라미터였지만 업데이트 후 1~70 사이의 값을 가지는 것을 확인할 수 있습니다.
또한, 행이나 피쳐의 수가 늘어나는 레시피를 적용하여 range의 범위를 설정할 수 없는 경우 unknown 함수를 지정할 수 있으며, 추후 finalize 함수를 통해 자동으로 데이터에 맞게 설정할 수 있습니다. (https://moogie.tistory.com/99 레시피에서 사용코드 참고)
rf_model <-
rand_forest(mtry = tune()) %>%
set_engine("ranger", regularization.factor = tune("regularization")) %>%
set_mode(mode = "regression")
rf_model %>% extract_parameter_dials("mtry")
rf_model <- rf_model %>% extract_parameter_set_dials() %>%
update(mtry = mtry(range = c(1, 70)))
rf_model %>% extract_parameter_dials("mtry")
전처리를 위해 step 함수가 recipe 객체에 추가되면서 mtry(parsnip)가 가져야 하는 값의 범위가 계속해서 변할 수 있다면 위의 예시처럼 range로 고정된 값을 지정하는 것이 아닌 finalize함수를 통해 데이터의 차원을 참고할 수 있도록 해야합니다.
pca <- recipe(Sale_Price ~ ., data = ames_train) %>%
step_normalize(contains("SF")) %>%
step_pca(contains("SF"), threshold = 0.95)
update_param <-
workflow() %>%
add_model(rf_model) %>%
add_recipe(pca_recipe) %>%
extract_parameter_set_dials() %>%
finalize(ames_train)
update_param
update_param %>% extract_parameter_dials("mtry")
'Data Science > Modeling' 카테고리의 다른 글
[Tidy Modeling With R] 14. Iterative Search with XGBoost (2) | 2023.12.26 |
---|---|
[Tidy Modeling With R] 13. Grid Search with XGBoost (2) | 2023.12.22 |
[Tidy Modeling with R] 11. Model Comparison (모델 비교) (0) | 2023.10.09 |
[Tidy Modeling with R] 10. Resampling (0) | 2023.10.01 |
[Tidy Modeling with R] 9. Performance Metrics (모델 성과 지표) (0) | 2023.09.27 |