1 tidymodels와 XGBoost12

“Deep Learning in R” 책에서 François Chollet 와 JJ Allaire는 다음과 같이 XGBoost 의 가치를 평가했다. 즉, 직사각형 정형 데이터에는 XGBoost가 적합하고 비정형 데이터는 딥러닝 모형이 첫번째 데이터 과학자의 도구가 된다.

In 2016 and 2017, Kaggle was dominated by two approaches: gradient boosting machines and deep learning. Specifically, gradient boosting is used for problems where structured data is available, whereas deep learning is used for perceptual problems such as image classification. Practitioners of the former almost always use the excellent XGBoost library.

These are the two techniques you should be the most familiar with in order to be successful in applied machine learning today: gradient boosting machines, for shallow-learning problems; and deep learning, for perceptual problems. In technical terms, this means you’ll need to be familiar with XGBoost and Keras—the two libraries that currently dominate Kaggle competitions.

1.1 treesnip

treesnip 팩키지를 통해서 다음 의사결정나무모형을 활용할 수 있다.

  • tree 엔진: decision_tree()
  • catboost 엔진: boost_tree()
  • lightGBM 엔진: boost_tree()

1.2 Hyper Parameter

의사결정나무로 예측모형을 개발할 때 모형별로 Hyper Paramter를 튜닝하여 선정해야 한다.

1.2.1 decision_tree()

library(tidyverse)

tibble::tribble(
  ~ parsnip, ~tree, 
  "min_n", "minsize",
  "cost_complexity", "mindev"
) %>% knitr::kable()
parsnip tree
min_n minsize
cost_complexity mindev

1.2.2 boost_tree()

tibble::tribble(
  ~ parsnip, ~catboost, ~lightGBM,
  'mtry', 'rsm', 'feature_fraction',
  'trees', 'iterations', 'num_iterations',
  'min_n', 'min_data_in_leaf', 'min_data_in_leaf',
  'tree_depth', 'depth', 'max_depth',
  'learn_rate', 'learning_rate', 'learning_rate',
  'loss_reduction', kableExtra::cell_spec('Not found', color = 'red', bold = TRUE), 'min_gain_to_split',
  'sample_size', 'subsample', 'bagging_fraction'
) %>% knitr::kable(escape = FALSE) 
parsnip catboost lightGBM
mtry rsm feature_fraction
trees iterations num_iterations
min_n min_data_in_leaf min_data_in_leaf
tree_depth depth max_depth
learn_rate learning_rate learning_rate
loss_reduction Not found min_gain_to_split
sample_size subsample bagging_fraction

1.3 설치3

devtools::install_github("curso-r/treesnip") 명령어로 treesnip을 설치하여 parnsip에서 활용할 수 있도록 한다.

macOS에서 설치할 경우 Apple Clang의 내용을 참조한다.

1.3.1 Homebrew 설치

brew install lightgbm

1.3.2 GitHub 빌드

LightGBM builds 저장소를 참고한다. 먼저 맥 운영체제에 필수적인 두가지 도구를 먼저 설치한다. cmake, OpenMP를 설치하고 나서 빌드 과정을 거친다.

brew install cmake
brew install libomp

git clone --recursive https://github.com/microsoft/LightGBM
cd LightGBM
mkdir build 
cd build
cmake ..
make -j4

이제 준비가 되어 마지막으로 lightgbm R 팩키지를 설치힌다.

git clone --recursive https://github.com/microsoft/LightGBM
cd LightGBM
Rscript build_r.R

그리고 나서, 다음과 같이 treesnip을 설치한다.

install.packages(
  sprintf(
    "https://github.com/curso-r/lightgbm-build/releases/download/macos-r-4.0/lightgbm_3.0.0-1.tgz",
    getRversion()$major, getRversion()$minor
  ),
  repos = NULL
)
# remotes::install_github("curso-r/treesnip")

devtools::install_github("curso-r/rightgbm")
rightgbm::install_lightgbm()

2 작업흐름

직사각형 정형데이터를 기반으로 예측모형을 작성할 경우 일반적으로 Hyper Parameter를 갖는 모형이 우선 검토 대상이 되며 이를 실제 운영에 활용할 경우 Hyper Parameter를 교차검증 데이터에서 추론하여 가장 성능이 좋은 모형을 실제 운영계로 이관하게 된다. 이 과정에서 예측모형의 성능을 평가하는 내용도 필히 살펴봐야 된다.

  • 환경설정
    • 데이터와 팩키지 가져오기
  • 훈련/시험 데이터 나누기: rsample
  • 데이터 전처리, Feature Engineering: recipes
  • 모형 Hyper Parameters 특정
    • 교차검정(CV) 데이터 준비: rsample
    • 모형, 모형공식, 전처리를 포함한 작업흐름 생성: workflows
    • 모형 명세서 생성: parsnip, treesnip (XGBoost)
    • Hyper Parameter 탐색공간 격자 생성: dials
    • 모형 튜닝 실행:tune
  • 모형 튜닝 실행하여 최적 모형 선정
  • 최적 모형 시험(test) 데이터에 적합
  • 시험 데이터로 모형 성능 평가: yardstick

3 XGBoost 퍵균 성별 분류기

3.1 환경설정

tidymodelstidyverse와 마찬가지로 library(tidymodels) 명령어로 기계학습에 필요한 팩키지를 모두 가져올 수 있다. tidytuesdayR를 활용하여 필요한 데이터도 작업공간에 신속히 올려놓는다.

# 팩키지

library(tidyverse)
library(tidymodels)
library(treesnip)

# 데이터 

tuesdata <- tidytuesdayR::tt_load('2020-07-28')

    Downloading file 1 of 2: `penguins.csv`
    Downloading file 2 of 2: `penguins_raw.csv`
penguin <- tuesdata$penguins

3.2 데이터 전처리

간략하게 데이터 정제작업과 함께 범주형, 숫자형 변수에 대한 Feature Engineering도 함께 작업하고 나서, 훈련, 시험, 교차검증 데이터로 쪼개 후속 작업을 준비한다.

# 데이터 전처리
penguin_df <- penguin %>%
  filter(!is.na(sex)) %>%
  select(-year, -island) %>% 
  mutate_if(is.character, as.factor)

penguin_rec <- recipe(sex ~ ., data = penguin_df) %>%
  # update_role(species, new_role = "id") %>%
  update_role(sex, new_role = "outcome") %>% 
  update_role(
    species, bill_length_mm, bill_depth_mm, flipper_length_mm, body_mass_g,
    new_role = "predictor") %>% 
  # step_string2factor(all_nominal(), -all_outcomes()) %>%
  step_normalize(all_numeric()) %>% 
  # step_novel(all_nominal(), -all_outcomes()) %>%
  # step_dummy(all_nominal()) %>% 
  prep()

# 훈련, 시험, 교차검증 데이터
penguin_split <- initial_split(penguin_df, prop = 0.8, strata = sex)
penguin_train <- training(penguin_split)
penguin_test <- testing(penguin_split)

penguin_cv <- vfold_cv(penguin_train, v =10, repeats = 1) 

3.3 Hyper Parameter 반영 최적모형

workflow() 를 정의하기에 앞서 모형 명세작업과 Hyper Parameter 준비작업을 하고 나서 작업흐름을 완성한다.

3.3.1 모형 명세

예측모형을 lightgbm_spec 명세하여 실제 훈련에 사용된 엔진도 함께 명세한다.

lightgbm_spec <- boost_tree(
        mode = "classification",
        trees = 1000, 
        min_n = tune(), 
        tree_depth = tune()
        # loss_reduction = tune(), 
        # learn_rate = tune()
    ) %>%
    set_engine("lightgbm", objective = "binary:binary_logloss", verbose = -1)

3.3.2 Hyper Parameter 탐색공간정의

Hyper Parameter 탐색공간을 정의한다. 먼저 탐색할 Hyper Parameter를 정의하고 나서, grid_max_entropy() 크기를 size = 30으로 특정하여 빠른 시간내 탐색이 될 수 있도록 한다.

lightgbm_params <- dials::parameters(
        min_n(), 
        tree_depth()
    )

lightgbm_grid <- dials::grid_max_entropy(
        lightgbm_params, 
        size = 30)

lightgbm_grid
# A tibble: 30 x 2
   min_n tree_depth
   <int>      <int>
 1     6         13
 2     3         11
 3    10          7
 4    30          6
 5    29          2
 6    15          8
 7    19         13
 8    36          9
 9    13         12
10    21          1
# … with 20 more rows

3.3.3 workflow 흐름 작성

workflow 작업흐름을 생성하고 나서 명세된 모형, 레시피 feature engineering을 순차적으로 적어둔다.

lightgbm_wf <- workflows::workflow() %>%
  # add_formula(sex ~ .) %>%
  add_recipe(penguin_rec) %>% 
  add_model(lightgbm_spec) 

3.3.4 Hyper Parameter 탐색실행4

library(doParallel)
all_cores <- parallel::detectCores(logical = FALSE) 
registerDoParallel(cores = all_cores) 

tictoc::tic()

lightgbm_tuned <- tune::tune_grid(
    object = lightgbm_wf,
    resamples = penguin_cv,
    grid = lightgbm_grid,
    metrics = yardstick::metric_set(roc_auc, accuracy),
    control = tune::control_grid(verbose = TRUE)
)

tictoc::toc()
102.541 sec elapsed

3.3.5 탐색 결과 살펴보기

roc_auc 기준으로 가장 성능좋은 Hyper Paramter 조합을 살펴보자.

lightgbm_tuned %>% 
  tune::show_best(metric = "roc_auc")
# A tibble: 5 x 8
  min_n tree_depth .metric .estimator  mean     n std_err .config
  <int>      <int> <chr>   <chr>      <dbl> <int>   <dbl> <chr>  
1    21          1 roc_auc binary     0.934    10  0.0214 Model10
2    11          1 roc_auc binary     0.933    10  0.0221 Model19
3     4          1 roc_auc binary     0.931    10  0.0221 Model20
4    40          1 roc_auc binary     0.926    10  0.0223 Model16
5    25          2 roc_auc binary     0.916    10  0.0232 Model24

시각화를 통해 Hyper Paramter 탐색결과를 파악하자.

lightgbm_tuned %>%  
  tune::show_best(metric = "accuracy", n = 10) %>% 
  tidyr::pivot_longer(min_n:tree_depth, names_to="variable", values_to="value" ) %>% 
  ggplot(aes(value, mean)) + 
  geom_line(alpha=1/2)+ 
  geom_point()+ 
  facet_wrap(~variable,scales = "free")+
  labs(x="", title = "Best Paramters for Accuracy")

3.3.6 최종모형 선정 및 마무리

lightgbm_best_params <- lightgbm_tuned %>%
    tune::select_best("accuracy")

# lightgbm_best_params

lightgbm_model_final <- lightgbm_spec %>% 
  finalize_model(lightgbm_best_params)

lightgbm_model_final
Boosted Tree Model Specification (classification)

Main Arguments:
  trees = 1000
  min_n = 40
  tree_depth = 1

Engine-Specific Arguments:
  objective = binary:binary_logloss
  verbose = -1

Computational engine: lightgbm 

3.4 모형 평가

lightgbm_wf 작업흐름에 Hyper Parameter가 있어 이를 앞서 Hyper Parameter 탐색을 통해 확정한 최고 성능 Hyper Parameter로 넣어 최종 작업흐름을 완성한다.

lightgbm_wf_final <- lightgbm_wf %>% 
  finalize_workflow(lightgbm_best_params)

lightgbm_wf_final
══ Workflow ════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: boost_tree()

── Preprocessor ────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 Recipe Step

● step_normalize()

── Model ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Boosted Tree Model Specification (classification)

Main Arguments:
  trees = 1000
  min_n = 40
  tree_depth = 1

Engine-Specific Arguments:
  objective = binary:binary_logloss
  verbose = -1

Computational engine: lightgbm 

다음 단계로 last_fit()을 시험(test) 데이터에 넣어 예측모형 성능을 산출한다.

lightgbm_wf_final_fit <- lightgbm_wf_final %>% 
  last_fit(penguin_split)

lightgbm_wf_final_fit %>% 
  collect_metrics()
# A tibble: 2 x 3
  .metric  .estimator .estimate
  <chr>    <chr>          <dbl>
1 accuracy binary         0.892
2 roc_auc  binary         0.968

collect_predictions() 함수로 시험 데이터에 대한 예측 확률과 예측 결과를 담아낸다.

penguin_test_pred <- lightgbm_wf_final_fit %>% 
  collect_predictions()

예측확률과 결과가 있기 때문에 conf_mat()로 confusion matrix도 만들 수 있다.

penguin_test_pred %>% 
  conf_mat(truth = sex, estimate = .pred_class)
          Truth
Prediction female male
    female     27    2
    male        5   31

4 모형 배포5

fit() 함수를 사용해서 최종적으로 개발한 모형을 배포한다.

deploy_model <- fit(lightgbm_wf_final, penguin)
[LightGBM] [Info] Number of positive: 168, number of negative: 176
[LightGBM] [Warning] Auto-choosing col-wise multi-threading, the overhead of testing was 0.000096 seconds.
You can set `force_col_wise=true` to remove the overhead.
[LightGBM] [Info] Total Bins 267
[LightGBM] [Info] Number of data points in the train set: 344, number of used features: 5
[LightGBM] [Info] [binary:BoostFromScore]: pavg=0.488372 -> initscore=-0.046520
[LightGBM] [Info] Start training from score -0.046520
# deploy_model %>% 
#   write_rds("data/penguin_sex_model.rds")

deploy_model 
══ Workflow [trained] ══════════════════════════════════════════════════════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: boost_tree()

── Preprocessor ────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 Recipe Step

● step_normalize()

── Model ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
<lgb.Booster>
  Public:
    add_valid: function (data, name) 
    best_iter: -1
    best_score: NA
    current_iter: function () 
    dump_model: function (num_iteration = NULL, feature_importance_type = 0L) 
    eval: function (data, name, feval = NULL) 
    eval_train: function (feval = NULL) 
    eval_valid: function (feval = NULL) 
    finalize: function () 
    initialize: function (params = list(), train_set = NULL, modelfile = NULL, 
    lower_bound: function () 
    predict: function (data, start_iteration = NULL, num_iteration = NULL, 
    raw: NA
    record_evals: list
    reset_parameter: function (params, ...) 
    rollback_one_iter: function () 
    save: function () 
    save_model: function (filename, num_iteration = NULL, feature_importance_type = 0L) 
    save_model_to_string: function (num_iteration = NULL, feature_importance_type = 0L) 
    set_train_data_name: function (name) 
    to_predictor: function () 
    update: function (train_set = NULL, fobj = NULL) 
    upper_bound: function () 
  Private:
    eval_names: NULL
    get_eval_info: function () 
    handle: 6.94681240057486e-310
    higher_better_inner_eval: NULL
    init_predictor: NULL
    inner_eval: function (data_name, data_idx, feval = NULL) 
    inner_predict: function (idx) 
    is_predicted_cur_iter: list
    name_train_set: training
    name_valid_sets: list
    num_class: 1
    num_dataset: 1
    predict_buffer: list
    set_objective_to_none: FALSE
    train_set: lgb.Dataset, R6
    train_set_version: 1
    valid_sets: list

새로운 펭귄을 한 마리 구해서 lightgbm 으로 개발한 예측모형에 넣어 성별을 예측해보자.

new_penguin <- tribble(
   ~species, ~island, ~bill_length_mm, ~bill_depth_mm, ~flipper_length_mm, ~body_mass_g, ~year,
    "Adelie", "Torgersen",  39.1,  18.7, 181, 3750,  2007
)
predict(deploy_model, new_penguin) %>% 
  bind_cols(predict(deploy_model, new_penguin, type = "prob"))
# A tibble: 1 x 3
  .pred_class .pred_female .pred_male
  <fct>              <dbl>      <dbl>
1 male               0.339      0.661
 

데이터 과학자 이광춘 저작

kwangchun.lee.7@gmail.com