UPDATE: 2022-12-18 12:01:42
tidymodels
パッケージの使い方をいくつかのノートに分けてまとめている。tidymodels
パッケージは、統計モデルや機械学習モデルを構築するために必要なパッケージをコレクションしているパッケージで、非常に色んなパッケージがある。ここでは、今回はworkflows
というパッケージの使い方をまとめていく。モデルの数理的な側面や機械学習の用語などは、このノートでは扱わない。
下記の公式ドキュメントやtidymodels
パッケージに関する書籍を参考にしている。
workflows
パッケージの目的workflows
パッケージのワークフローを利用することで、前処理、モデリング、後処理をまとめることができる。基本的には、recipe
パッケージやparsnip
パッケージを組み合わせて、ワークフローを作成することになる。公式ドキュメントはこちら。
workflows
パッケージの実行例workflows
パッケージの基本的な利用方法を見る前に必要なオブジェクトを定義しておく。関数の使い方をまとめているので、ここでは動けば良い程度にレシピを作成しておく。
library(tidymodels)
library(tidyverse)
<- read_csv("https://raw.githubusercontent.com/SugiAki1989/statistical_note/main/note_TidyModels00/df_past.csv")
df_past set.seed(1989)
# rsample
<- df_past %>% initial_split(prop = 0.8, strata = "Status")
df_initial <- df_initial %>% training()
df_train <- df_initial %>% testing()
df_test
# parsnip
<- rand_forest(mtry = 5, trees = 1000) %>%
model1 set_engine("ranger", importance = "permutation") %>%
set_mode("classification")
# recipes
<- recipe(Status ~ ., data = df_train) %>%
recipe1 step_impute_bag(Income, impute_with = imp_vars(Marital, Expenses)) %>%
step_impute_bag(Assets, impute_with = imp_vars(Marital, Expenses, Income)) %>%
step_impute_bag(Debt, impute_with = imp_vars(Marital, Expenses, Income)) %>%
step_impute_bag(Home, impute_with = imp_vars(Marital, Expenses, Income)) %>%
step_impute_bag(Marital, impute_with = imp_vars(Marital, Expenses, Income)) %>%
step_impute_bag(Job, impute_with = imp_vars(Marital, Expenses, Income))
workflow
関数で、前処理、モデリングの情報をまとめておく。ワークフローには、レシピの内容、モデルの設定がまとめられていることがわかる。そのため、ワークフローを使えば、データに対して、どのような前処理を行うのか、そして、どのようなモデルを適用するのかがわかるので、訓練用、テスト用データを変えてもワークフローを使い回すことで、同じ処理を双方のデータに適用できる。
<- workflow() %>%
workflow1 add_recipe(recipe1) %>%
add_model(model1)
workflow1
## ══ Workflow ════════════════════════════════════════════════════════════════════
## Preprocessor: Recipe
## Model: rand_forest()
##
## ── Preprocessor ────────────────────────────────────────────────────────────────
## 6 Recipe Steps
##
## • step_impute_bag()
## • step_impute_bag()
## • step_impute_bag()
## • step_impute_bag()
## • step_impute_bag()
## • step_impute_bag()
##
## ── Model ───────────────────────────────────────────────────────────────────────
## Random Forest Model Specification (classification)
##
## Main Arguments:
## mtry = 5
## trees = 1000
##
## Engine-Specific Arguments:
## importance = permutation
##
## Computational engine: ranger
訓練データでモデルを学習するときは、ワークフローオブジェクトをfit
関数に渡せば良い。
<-
model_trained_workflow %>%
workflow1 fit(data = df_train)
model_trained_workflow
## ══ Workflow [trained] ══════════════════════════════════════════════════════════
## Preprocessor: Recipe
## Model: rand_forest()
##
## ── Preprocessor ────────────────────────────────────────────────────────────────
## 6 Recipe Steps
##
## • step_impute_bag()
## • step_impute_bag()
## • step_impute_bag()
## • step_impute_bag()
## • step_impute_bag()
## • step_impute_bag()
##
## ── Model ───────────────────────────────────────────────────────────────────────
## Ranger result
##
## Call:
## ranger::ranger(x = maybe_data_frame(x), y = y, mtry = min_cols(~5, x), num.trees = ~1000, importance = ~"permutation", num.threads = 1, verbose = FALSE, seed = sample.int(10^5, 1), probability = TRUE)
##
## Type: Probability estimation
## Number of trees: 1000
## Sample size: 3206
## Number of independent variables: 13
## Mtry: 5
## Target node size: 10
## Variable importance mode: permutation
## Splitrule: gini
## OOB prediction error (Brier s.): 0.1460313
テストデータでモデルの予測値を計算する場合は、ワークフローオブジェクトをpredict
関数に渡せば良い。
<-
model_predicted_workflow %>%
model_trained_workflow predict(df_test)
model_predicted_workflow
## # A tibble: 802 × 1
## .pred_class
## <fct>
## 1 bad
## 2 bad
## 3 bad
## 4 bad
## 5 good
## 6 bad
## 7 bad
## 8 good
## 9 bad
## 10 good
## # … with 792 more rows
これが基本的なworkflow
パッケージの使い方である。モデルとレシピをワークフローが束ねることで、モデルの訓練からモデルの予測までを効率よく行うことができる。
先程はクロスバリデーションを行わず、とりあえずworkflow
パッケージの使い方をまとめていたが、ここからはクロスバリデーションを行った場合にワークフローがどのように機能するのかまとめておく。データに分割を行い、先程と同じく、レシピとモデルを作成しておく。
set.seed(1989)
<-
df_train_stratified_splits vfold_cv(df_train, v = 5, strata = "Status")
<- rand_forest(mtry = 5, trees = 500, min_n = 10) %>%
model2 set_engine("ranger", importance = "permutation") %>%
set_mode("classification")
<- recipe(Status ~ ., data = df_train) %>%
recipe2 step_impute_bag(Income, impute_with = imp_vars(Marital, Expenses)) %>%
step_impute_bag(Assets, impute_with = imp_vars(Marital, Expenses, Income)) %>%
step_impute_bag(Debt, impute_with = imp_vars(Marital, Expenses, Income, Assets)) %>%
step_impute_bag(Home, impute_with = imp_vars(Marital, Expenses, Income, Assets, Debt)) %>%
step_impute_bag(Marital, impute_with = imp_vars(Marital, Expenses, Income, Assets, Debt, Home)) %>%
step_impute_bag(Job, impute_with = imp_vars(Marital, Expenses, Income, Assets, Debt, Home, Marital))
<- workflow() %>%
workflow2 add_recipe(recipe2) %>%
add_model(model2)
workflow2
## ══ Workflow ════════════════════════════════════════════════════════════════════
## Preprocessor: Recipe
## Model: rand_forest()
##
## ── Preprocessor ────────────────────────────────────────────────────────────────
## 6 Recipe Steps
##
## • step_impute_bag()
## • step_impute_bag()
## • step_impute_bag()
## • step_impute_bag()
## • step_impute_bag()
## • step_impute_bag()
##
## ── Model ───────────────────────────────────────────────────────────────────────
## Random Forest Model Specification (classification)
##
## Main Arguments:
## mtry = 5
## trees = 500
## min_n = 10
##
## Engine-Specific Arguments:
## importance = permutation
##
## Computational engine: ranger
データを分割しているので、先程のようにそのままワークフローを適用できない。tune
パッケージのfit_resamples
関数を使う必要がある。tune
パッケージは次回ノートにまとめるので、ここでは特に説明はしない。また、metric_set
関数はyardstick
パッケージの関数なので、同じく説明は他のノートでまとめる。
処理内容としては、分割されたデータが複数あるので、それらに対してワークフローで管理しているレシピとモデル内容を使って、評価指標はaccuracy
で、計算に使用した予測値は残す、ということをしている。このステップでは、クロスバリデーションしてもモデルを評価しているので、処理時間がかかる。これまでの部分は何をするかを決めていただけで、実際に手は動かしていない。
tune
パッケージを使用している事がわかるようにオブジェクトにはわかりやすいようにtuned
とつかておく。
<-
workflow_tuned %>%
workflow2 fit_resamples(
resamples = df_train_stratified_splits,
metrics = metric_set(accuracy),
control = control_resamples(save_pred = TRUE)
)
中身を確認すると、いくつかのカラムができており、内容は下記の通り。
splits
:
クロスバリデーションのために分割されたデータid
: 分割されたデータのフォールド番号.metrics
: 評価指標の値.notes
: エラー、ワーニングなどの情報.predictions
:
評価指標を計算するために利用したデータの観測値とモデルの予測値 workflow_tuned
## # Resampling results
## # 5-fold cross-validation using stratification
## # A tibble: 5 × 5
## splits id .metrics .notes .predictions
## <list> <chr> <list> <list> <list>
## 1 <split [2564/642]> Fold1 <tibble [1 × 4]> <tibble [0 × 3]> <tibble [642 × 4]>
## 2 <split [2564/642]> Fold2 <tibble [1 × 4]> <tibble [0 × 3]> <tibble [642 × 4]>
## 3 <split [2565/641]> Fold3 <tibble [1 × 4]> <tibble [0 × 3]> <tibble [641 × 4]>
## 4 <split [2565/641]> Fold4 <tibble [1 × 4]> <tibble [0 × 3]> <tibble [641 × 4]>
## 5 <split [2566/640]> Fold5 <tibble [1 × 4]> <tibble [0 × 3]> <tibble [640 × 4]>
splits
の中身は、訓練データの分割情報が記録されている。
%>%
workflow_tuned pluck("splits", 1)
## <Analysis/Assess/Total>
## <2564/642/3206>
.metrics
の中身は、モデルの分割データに対する評価情報が記録されている。この例だと、accuracy
が0.79ということになる。
%>%
workflow_tuned pluck(".metrics", 1)
## # A tibble: 1 × 4
## .metric .estimator .estimate .config
## <chr> <chr> <dbl> <chr>
## 1 accuracy binary 0.793 Preprocessor1_Model1
.predictions
の中身は、予測値.pred_class
、行番号.row
、観測値Status
、何番目のフォールドのモデルなのか.config
が記録されている。
%>%
workflow_tuned pluck(".predictions", 1)
## # A tibble: 642 × 4
## .pred_class .row Status .config
## <fct> <int> <fct> <chr>
## 1 bad 2 bad Preprocessor1_Model1
## 2 bad 11 bad Preprocessor1_Model1
## 3 bad 14 bad Preprocessor1_Model1
## 4 bad 18 bad Preprocessor1_Model1
## 5 good 19 bad Preprocessor1_Model1
## 6 good 24 bad Preprocessor1_Model1
## 7 good 26 bad Preprocessor1_Model1
## 8 good 30 bad Preprocessor1_Model1
## 9 good 35 bad Preprocessor1_Model1
## 10 bad 43 bad Preprocessor1_Model1
## # … with 632 more rows
collect_metrics
関数を使うことで、クロスバリデーションの結果を集計して表示してくれる。今回であれば、データを5つに分割しており、その各フォールドの結果がまとめられている。
%>%
workflow_tuned collect_metrics()
## # A tibble: 1 × 6
## .metric .estimator mean n std_err .config
## <chr> <chr> <dbl> <int> <dbl> <chr>
## 1 accuracy binary 0.785 5 0.00556 Preprocessor1_Model1
今回はパラメタチューニングを行っていないが、ここではパラメタチューニングの結果からこのモデルが良いモデルとなったと仮定する。ワークフローを使って学習データを再学習し、モデルを構築する。そして、モデルの予測値を計算する。
<-
model_trained_workflow2 %>%
workflow2 fit(df_train)
<-
model_predicted_workflow2 %>%
model_trained_workflow2 predict(df_test)
テストの観測値とモデルの予測値を計算したいのであれば、下記の通り、yardstick
パッケージのaccuracy
関数に渡せば良い。df_test
のStatus
の型が文字型なので、予測値の因子型にそろえてから評価している。
tibble(
obs = factor(df_test$Status, c(levels(model_predicted_workflow2$.pred_class))),
pred = model_predicted_workflow2$.pred_class
%>%
) ::accuracy(truth = obs, estimate = pred) yardstick
## # A tibble: 1 × 3
## .metric .estimator .estimate
## <chr> <chr> <dbl>
## 1 accuracy binary 0.791
下記はおまけ。最終的なモデルの変数重要度を可視化している。
library(vip)
<- model2 %>%
imp_model finalize_model(select_best(workflow_tuned)) %>%
set_engine("ranger", importance = "permutation")
workflow() %>%
add_recipe(recipe2) %>%
add_model(imp_model) %>%
fit(df_train) %>%
extract_fit_parsnip() %>%
vip(aesthetics = list(alpha = 0.8, fill = "#006E4F")) + theme_bw()