こんにちは,shun(@datasciencemore)です!!
今回はtidymodelsの{tune}と{dials}についてやっていきます.
なんで2つのパッケージを同時にやるかといいますと,両方ともハイパラチューニングに関するパッケージだからです.
厳密には,微妙に違うところもありますが両方同時に学習したほうがわかりやすいので2つ同時にやっていこうと思います!
0.{tune}と{dials}ってなに??
{tune}と{dials}は,ハイパーパラメータチューニングに関するパッケージだよ!!
チューニングするハイパラを指定する,チューニングするハイパラのグリッドを作成するなど,ハイパラに関する様々なことができるよ!!
{tune}と{dials}は,それぞれハイパーパラメータチューニングに特化したパッケージです.
両者の違いは
{tune}:チューニングするハイパラを最適化する.
{dials}:チューニングするハイパラに関するグリッドを作成する.
なのですが,あまり違いにこだわらず両者ともハイパラチューニングに関するパッケージだと認識すればOKだと思います.
ハイパラチューニングの手順は以下になります.
1.ハイパラ指定
2.探索範囲決定
3.グリッド作成
4.学習と評価
5.モデル選定
6.予測値算出
7.アンサンブル
それぞれ詳細に見ていきましょう!
1.ハイパラ指定
ハイパラチューニングをするためにまず最初にすることは,チューニングするハイパラを指定することです.
ハイパラを指定するのに使用する関数は,{tune}のtuneという関数です.
ややこしいですが,パッケージ{tune}の関数tuneということです
前回までハイパーパラメータは固定して,モデルの性能を確かめてきました.
しかし,そのやり方だと当たり前ですが,固定したハイパーパラメータに対するモデルの性能しか確認できません.
色々なパターンのハイパーパラメータを一度に評価できればとても便利ですよね!
そんな色々なパターンのハイパーパラメータを一度に評価するための関数がtuneなのです.
使い方はとっても簡単!
学習ルールを設定するところで,今まで固定していたハイパーパラメータにtuneを適用すればいいだけです.
そうすることでtuneを適用したハイパーパラメータについて,色々なパターンを試してくれるのです!
コーディングはこうなります.
1 2 3 4 |
rule = rand_forest(mtry = tune(), min_n = tune(), trees = tune()) %>% set_engine('ranger') %>% set_mode('classification') |
定義したruleを実行するとハイパーパラメータがtune()となった状態で学習ルールが出力されます.
1 2 3 4 5 6 7 8 |
Random Forest Model Specification (classification) Main Arguments: mtry = tune() trees = tune() min_n = tune() Computational engine: ranger |
2.探索範囲指定
前項で指定したハイパラに対し,どこからどこまでを探索するのかを指定する必要があります.
今回の例で言うと対象ハイパラは,mtry, trees, min_nの3種類です.
これらの探索範囲を決定していきましょう
特に何も設定しない最初の状態では,デフォルトの探索範囲が自動的に設定されています.
学習ルールにparameters,pull_dials_object(ハイパラ)を適用すると,指定したハイパラの探索範囲を確認することができます.
まずデフォルトの探索範囲を見てみましょう.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
rule %>% parameters() %>% pull_dials_object("mtry") # Randomly Selected Predictors (quantitative) Range: [1, ?] rule %>% parameters() %>% pull_dials_object("min_n") Minimal Node Size (quantitative) Range: [2, 40] rule %>% parameters() %>% pull_dials_object("trees") # Trees (quantitative) Range: [1, 2000] |
min_nとtreesはわかりやすいですね!
min_n :2~40
trees :1~2000
の範囲を探索するということですね
mtryはちょっと変な感じですね.
mtry :1~?
この?はまだ確定していないよって意味です.
mtryは,
各木に対する学習データの説明変数の数
のことでした.
説明変数は特徴量エンジニアリングによって増減するので,上限値はあえて未確定の状態を初期値である?と設定してあるのです.
よって最終的にはmtryの?に値を設定してあげなくてはなりません.
また,min_nやtreesは,初期値の範囲が狭すぎたり広すぎたりした場合は,調整することも可能です.
これらは,finalizeやupdateといった関数を使用することで実現することができます.
イメージは次の図のような感じです.
コーディングは,ワークフローの情報にハイパラの更新情報を上書きするように記述します.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# レシピ rec = recipe(Status ~ ., data = train) %>% step_impute_bag(all_predictors()) # 探索範囲更新 param = workflow() %>% add_model(rule) %>% add_recipe(rec) %>% parameters() %>% update(trees = trees(c(500, 2000))) %>% finalize(train) |
定義したparamに対し,pull_dials_object(ハイパラ)を適用するとtreesの範囲が500~2000に,mtryの範囲が1~14に更新されていることがわかります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# finalizeにより,?が指定したデータフレームの列数に更新される param %>% pull_dials_object("mtry") # Randomly Selected Predictors (quantitative) Range: [1, 14] # 何もしていないので初期値の範囲のまま param %>% pull_dials_object("min_n") Minimal Node Size (quantitative) Range: [2, 40] # updateにより500~2000に更新される param %>% pull_dials_object("trees") # Trees (quantitative) Range: [500, 2000] |
finalizeを使用してmtryの?を特徴量エンジニアリング後のデータフレームの説明変数の数にするには,次のようにする必要があります.
1 2 3 4 5 6 7 |
param = workflow() %>% add_model(rule) %>% add_recipe(rec) %>% parameters() %>% update(trees = trees(c(500, 2000))) %>% finalize(prep(rec) %>% bake(train) %>% select(-Status)) |
実際はこちらのほうが使うと思います.
おそらくfinalize(train)だけで説明変数の数を?に割り当てるほうが使い勝手がよいので,今後は仕様が変更されるかもしれませんね.
3.グリッド作成
以下の{dials}の関数を使用することで前項で指定した探索範囲のグリッドを作成することができます.
なお,grid_latin_hypercubeとgrid_max_entropyは,それぞれパラメータ空間全体をカバーするよう配置します.
両者の違いは,使用しているアルゴリズムが違うだけです.
難しいことは考えずに次に進みましょう!
コーディングはこんな感じ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# 1.グリッドサーチ param %>% grid_regular(levels = 2) # A tibble: 8 x 3 mtry trees min_n <int> <int> <int> 1 1 500 2 2 14 500 2 3 1 2000 2 4 14 2000 2 5 1 500 40 6 14 500 40 7 1 2000 40 8 14 2000 40 # 2.ランダムサーチ param %>% grid_random(size = 50) %>% with_seed(1234, .) # A tibble: 50 x 3 mtry trees min_n <int> <int> <int> 1 12 1881 7 2 10 1195 22 3 6 806 22 4 5 1401 33 5 12 1828 14 6 9 1744 18 7 5 1060 3 8 6 1836 7 9 4 635 3 10 2 1668 17 # … with 40 more rows # 3.latin_hypercube param %>% grid_latin_hypercube(size = 50) %>% with_seed(1234, .) # A tibble: 50 x 3 mtry trees min_n <int> <int> <int> 1 10 941 32 2 4 1887 22 3 2 977 9 4 1 1235 30 5 10 1140 9 6 13 648 6 7 9 1219 16 8 3 1453 30 9 1 1120 18 10 14 1610 12 # … with 40 more rows # 4.max_entropy param %>% grid_max_entropy(size = 50) %>% with_seed(1234, .) # A tibble: 50 x 3 mtry trees min_n <int> <int> <int> 1 8 1186 3 2 2 1637 9 3 4 714 13 4 10 1132 22 5 11 1521 9 6 13 610 3 7 2 717 38 8 2 588 25 9 6 1041 32 10 14 1977 2 # … with 40 more rows |
ベイズ最適化はグリッドを事前に作成する必要はありません.
ベイズ最適化時に自動的に最適なグリッドが作成されます.
4.学習と評価
前項で作成したグリッドの各行にあるハイパラに対し,学習と評価をすることで各ハイパラのモデル性能を評価することができます.
やりかたは,tune_gridという関数を使用します.
このような感じでワークフローにパイプをつないでtune_gridを適用します.
tune_gridで使用する主な引数は4点です.
①resamples:分割データ
②grid:グリッド
③control:様々なオプション(save_predをTRUEにすることをお勧めします.)
④metrics:評価指標
コーディングは次のようになります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# グリッドの定義 grid = param %>% grid_regular(levels = 2) # ワークフローの定義 wf = workflow() %>% add_recipe(rec) %>% add_model(rule) # 分割データの定義 stratified_splits = vfold_cv(train, v = 4, strata = "Status") %>% with_seed(1234, .) # 全ハイパラに対し,学習と評価 tune = wf %>% tune_grid( resamples = stratified_splits, grid = grid, control = control_grid(save_pred = TRUE), metrics = metric_set(accuracy) ) %>% with_seed(1234, .) |
tuneは次のように出力されます.
1 2 3 4 5 6 7 8 9 |
# Tuning results # 4-fold cross-validation using stratification # A tibble: 4 x 5 splits id .metrics .notes .predictions <list> <chr> <list> <list> <list> 1 <split [2672/891]> Fold1 <tibble [8 × 7]> <tibble [0 × 1]> <tibble [7,128 × 7]> 2 <split [2672/891]> Fold2 <tibble [8 × 7]> <tibble [0 × 1]> <tibble [7,128 × 7]> 3 <split [2672/891]> Fold3 <tibble [8 × 7]> <tibble [0 × 1]> <tibble [7,128 × 7]> 4 <split [2673/890]> Fold4 <tibble [8 × 7]> <tibble [0 × 1]> <tibble [7,120 × 7]> |
tuneに全ハイパラに対するモデルの評価情報が格納されています.
あとは,この中から良さげなハイパラを見つけましょう!
5.モデル選定
前項で作成したtuneには,全ハイパラの評価情報が格納されていました.
tuneから良さげなモデルを選定してあげましょう.
まず結果を可視化してみましょう.
可視化にはautoplotという便利な関数があります.
1 |
tune %>% autoplot() |
このコードを実行すると図が出力されます.
これは,各ハイパラの評価結果を示しています.
今回の指標はaccuracyだったので,値が大きいほうがいいです.
このように可視化すると傾向がわかりやすいですね!!
また,全結果から良い結果だけを抽出したい場合は,show_bestを使用します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
tune %>% show_best( metric = "accuracy", n = 5 ) # A tibble: 5 x 9 mtry trees min_n .metric .estimator mean n std_err .config <int> <int> <int> <chr> <chr> <dbl> <int> <dbl> <chr> 1 12 2000 40 accuracy binary 0.786 4 0.00381 Preprocessor1_Model8 2 12 500 2 accuracy binary 0.785 4 0.00343 Preprocessor1_Model2 3 12 2000 2 accuracy binary 0.784 4 0.00356 Preprocessor1_Model4 4 12 500 40 accuracy binary 0.781 4 0.00373 Preprocessor1_Model6 5 1 500 2 accuracy binary 0.772 4 0.00372 Preprocessor1_Model1 |
数値的に一番良い結果を抽出する場合,select_bestを使用するのが便利です.
1 2 3 4 5 6 |
tune %>% select_best(metric = "accuracy") # A tibble: 1 x 4 mtry trees min_n .config <int> <int> <int> <chr> 1 12 2000 40 Preprocessor1_Model8 |
これらの情報から良さげなモデルを選定するのです.
何をもってよさげとするのかは難しいです.
案件によっては精度が高いより簡潔なモデルを使用したいということも結構多いです.
また,数値だけでなく,標準誤差(簡単に言うとばらつきのこと)が小さいほうが安定した結果が期待できます.
アンサンブルすることを考えたら,多様性を持たせたほうがいいので,同じようなモデルを選定しないほうが無難です.
ということで分析の目的や算出された結果を合わせ,何が良いのかということを総合的に判断してモデル選定を実施しましょう!!
今回は,以下の2つを良さげなモデルとして選定しましょう.
No. | アルゴリズム | ハイパラ mtry | ハイパラ trees | ハイパラ min_n |
1 | ランダムフォレスト | 12 | 2000 | 40 |
2 | ランダムフォレスト | 12 | 500 | 2 |
環境によっては上記のハイパラがあまり良くない可能性もあります.その時は適宜,適したハイパラを選定してください.
6.予測値算出
前項で選定したモデルを評価データに適用し,予測値(予測クラス)を算出しましょう.
処理内容はこのような感じです.
①良さげなハイパラをワークフローにセット
②良さげなハイパラを用いて,モデル作成
③評価データにモデルを適用し,予測値算出
これらは次のようにコーディングすることができます.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# ①良さげなハイパラをワークフローにセット good_param_1 = tune %>% select_best(metric = "accuracy") good_param_2 = tune %>% show_best( metric = "accuracy", n = 5 ) %>% filter(mtry == 12, trees == 500, min_n == 2) final_wf_1 = wf %>% finalize_workflow(good_param_1) final_wf_2 = wf %>% finalize_workflow(good_param_2) # ②良さげなハイパラを用いて,モデル作成 final_model_1 = final_wf_1 %>% fit(train) %>% with_seed(1234, .) final_model_2 = final_wf_2 %>% fit(train) %>% with_seed(1234, .) # ③評価データにモデルを適用し,予測値算出 # モデル1に対する予測値 pred_1 = bind_cols( predict(final_model_1, new_data = test, type = "class"), predict(final_model_1, new_data = test, type = "prob") ) %>% set_names(str_c(names(.), "_1")) # モデル2に対する予測値 pred_2 = bind_cols( predict(final_model_2, new_data = test, type = "class"), predict(final_model_2, new_data = test, type = "prob") ) %>% set_names(str_c(names(.), "_2")) |
最後に作成したpred_1とpred_2は,次のようになります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# A tibble: 891 x 3 .pred_class_1 .pred_bad_1 .pred_good_1 <fct> <dbl> <dbl> 1 good 0.176 0.824 2 good 0.176 0.824 3 good 0.229 0.771 4 good 0.0749 0.925 5 good 0.124 0.876 6 good 0.382 0.618 7 bad 0.556 0.444 8 bad 0.680 0.320 9 good 0.292 0.708 10 bad 0.508 0.492 # … with 881 more rows |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# A tibble: 891 x 3 .pred_class_2 .pred_bad_2 .pred_good_2 <fct> <dbl> <dbl> 1 good 0.189 0.811 2 good 0.176 0.824 3 good 0.285 0.715 4 good 0.068 0.932 5 good 0.112 0.888 6 good 0.295 0.705 7 bad 0.656 0.344 8 bad 0.773 0.227 9 good 0.246 0.754 10 bad 0.676 0.324 # … with 881 more rows |
7.アンサンブル
前項までで終わりでもいいのですが,せっかくなのでアンサンブルもしてみましょう.
前項で算出したpred_1とpred_2をアンサンブルします.
やり方としては
① 予測クラスの多数決
② 予測確率の平均
が考えられますが,今回は②を採用しましょう.
※今回の場合,2つのモデルしか作成していないので①だと片方good,片方badだった場合,どちらにするか困りますしね笑
ということで,goodの確率を平均してそれが0.5以上だったらgood,0.5未満だったらbadという処理をコーディングすればOKですね.
今回は閾値を0.5としました.この閾値を調整することでさらに精度向上も狙えますが,今回は割愛します.
1 2 3 4 5 6 7 8 9 10 11 12 |
result = bind_cols(pred_1, pred_2) %>% mutate( .pred_good_all = (.pred_good_1 + .pred_good_2) / 2, .pred_bad_all = 1 - .pred_good_all, .pred_class_all = if_else( .pred_good_all >= 0.5, "good", "bad") %>% as.factor() ) %>% select(.pred_class_all, .pred_bad_all, .pred_good_all) |
resultの中身はこうなります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# A tibble: 891 x 3 .pred_class_all .pred_bad_all .pred_good_all <fct> <dbl> <dbl> 1 good 0.182 0.818 2 good 0.176 0.824 3 good 0.257 0.743 4 good 0.0715 0.929 5 good 0.118 0.882 6 good 0.339 0.661 7 bad 0.606 0.394 8 bad 0.727 0.273 9 good 0.269 0.731 10 bad 0.592 0.408 # … with 881 more rows |
いやー,長かったですね...
これにて最終予測値(予測クラス)が求まりました!!
まとめ
今回は,{tune}と{dials}についてやってきました.
{tune}と{dials}は,ハイパーパラメータチューニングに関するパッケージです.
以下の処理について,{tune}と{dials}の使い方を学習しました.
- ハイパラ指定
- 探索範囲指定
- グリッド作成
- 学習と評価
- モデル選定
- 予測値算出
- アンサンブル
相変わらず盛りだくさんでしたが,超重要なところなので頑張ってついてきてくださいね!!
それではお疲れさまでした!!