R(tydymodels)でAutoMLする

## さいしょに
Rのtidymodelsを使ってAutoMLをする方法を紹介します

## モチベーション
最近、機械学習のためにtidymodelsを触っています。すごく使いやすいと感じているのですが、日本語の記事も少ないので、自分が調べたことをシェアしておきたいと思います。間違っている点がありましたらコメントしていただけたら大変助かります。

## パッケージの紹介
`tidymodels`, `h2o`, `agua` を使います。

### tidymodels?
`tidymodels`は、`tidyverse`のような書き方で機械学習を行うパッケージの集まりです。`tidyverse`と同じように、様々なパッケージを集めたものであり、前処理を行う`recipe`、モデルを作成する`parsnip`などが含まれています。

もともとは、例えばLassoをするパッケージとRandomforest Regressionをするパッケージが違うため、それぞれコードの書き方や前処理の違いを覚えて書き分ける必要がありました。それを、`tidyverse`的な書き方で統一できるようにした、というのが`tidymodels`です。（私の認識では）

https://www.tidymodels.org/

https://qiita.com/kenkenvw/items/053ed45b4ddeeb1cb8ba

### h2o?
`h2o`は様々な機械学習を行うプラットフォーム、と紹介されています。その中でも、今回はAutoMLを使って、自動でモデルを作って評価してもらいます。PythonやRから実行することが出来ますが、このパッケージだけでは`tydyverse`的な書き方は出来ません。

https://docs.h2o.ai/h2o/latest-stable/h2o-docs/faq/r.html

### agua!
そこで、`h2o`を`tidymodels`的な書き方で使えるようにしてくれるのが、`agua`というパッケージです！

https://agua.tidymodels.org/articles/auto_ml.html

## コード
irisを分類分けするコードをサンプルとして下に示します。
```r
library(tidyverse)
library(tidymodels)
library(agua)
library(doParallel)

# h2oを使う場合はここから始める。
h2o_start()

# data split
# strataで指定した列の比率を元データと同じようにします。
# 具体的には、irisだとsetosaなど3種類がそれぞれ均等に33%ですが、この割合を保持するようにデータを分割します。
iris_split <- initial_split(iris, strata = Species)
iris_train <- training(iris_split)
iris_test <- testing(iris_split)

# pre-process
# 数値型の説明変数をすべて正規化します。
# AutoMLではいろいろなアルゴリズムを試すことになるので、とりあえず正規化しておいたほうが無難だと思います。
iris_recipe <- recipe(Species ~ ., data = iris_split) %>%
  step_normalize(all_numeric_predictors())

# set machine-learning spec
# ここでAutoMLをh2oでやるよ、と指定します。
auto_spec <-
  auto_ml() %>%
  set_engine("h2o", max_runtime_secs = 120, seed = 1) %>%
  set_mode("classification")

# set workflow
auto_wflow <-
  workflow() %>%
  add_model(auto_spec) %>%
  add_recipe(iris_recipe)

# set parallel processing
# 時間がかかるので並列処理します
cl <- makePSOCKcluster(parallel::detectCores() - 1)
registerDoParallel(cl)

# fitting
auto_fit <- fit(auto_wflow, data = iris_train)

# confirm model
# autoplot()でモデルの良し悪しを確認します。
autoplot(auto_fit,
         type = "rank",
         metric = c("accuracy", "auc")) +
  theme(legend.position = "none")

pred <- bind_cols(iris_test, predict(auto_fit, iris_test))
conf_mat(pred, truth = Species, estimate = .pred_class)
```
結果として、以下の出力が得られます。
```r
> conf_mat(pred, truth=Species, estimate=.pred_class)
            Truth
Prediction   setosa versicolor virginica
  setosa         13          0         0
  versicolor      0         12         1
  virginica       0          1        12
```
autoplotで出力されるグラフについては、
> The lower the average ranking, the more likely the model type suits the data.

とaguaのページ書かれています。この`autoplot()`の出力は、自動で作成して評価したモデルにおけるランキングのばらつき示していて、ランキングの数値が小さいほうがデータに対してより合っているという意味だと思います。（自分の認識では、、、要勉強）

そう考えると、今回はglmで良くね？とも思ったりしますね。

![plot_zoom_png.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3526485/f62d84ee-c542-3a7d-3ec1-546bab64030e.png)

さらに、

```r
auto_fit %>%
  extract_fit_parsnip() %>%
  member_weights() %>%
  unnest(importance) %>%
  filter(type == "scaled_importance") %>%
  ggplot() +
  geom_boxplot(aes(value, algorithm)) +
  scale_x_sqrt() +
  labs(y = NULL, x = "scaled importance", title = "Member importance in stacked ensembles")
```
としたときのグラフを以下に示します。

![plot_zoom_png.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3526485/462e24ba-01c1-eb46-d664-2b7aec71ef86.png)

> member_weights() computes member importance for all stacked ensemble models. （中略） Here, we show the scaled contribution of different algorithms in stacked ensembles.

と書かれているように、スタッキングモデルの中での貢献度を確認することが出来ます。スタッキングモデルでやるしかないでしょ！となった場合は、このような形でスタッキングモデルの内訳を確認しておく必要があるのかと思います。

## 感想

難しいことを考えずに機械学習を行い、スコアの良いモデルを立てることが出来ました。ただ、お仕事とかで使うか？と言われると今のところはそんなに積極的には使わないかなあ、と思います。
 
一つ目の理由は学習コストの高さ、もう一つは単に高いスコアのモデルがそこまで必要ではなくない？と思っているからです。
 
一つ目の理由として、今回紹介したコードを手元のノートパソコンで動かすと、irisの分類分けだけもまあまあの時間がかかります。実際のお仕事ではもっと大きいデータを取り扱うと思いますし、デカいデータを強いコンピューターで計算したらどうなるか、までは試していませんが、LightGBMを単体で動かすことと比べると圧倒的にコストが大きいので、なんか大変そうだなあという印象があります。
 
二つ目の理由として、そこまで高いスコアを求めていない、というのがあります。これは目的に寄ると思いますが、私の場合は変数重要度や変数同士の関係を調べるような事が多いので、「アンサンブルモデルでROCを最大にしました！」と言われても、「そう、、、」となってしまうかなあと思ったためです。（私自身データサイエンティストではなく、別に専門を持ちつつ機械学習のアプローチも使うメーカー勤務人間なので、理解が浅いというのもあるかと思います）

ただ、まだ自分の勉強不足もあるので、いい感じに動かす方法はあるかもしれないと思うので、時間を見てドキュメントは読んでいきたいところです。

また、
```r
h2o_start()
```
の部分を
```r
h2o::h2o.init()
```
と変えるとKaggleのnotebookで動くので、「今週GPU余ってるわ」みたいな日に動かしてみるのも良いかもしれません。House Pricesなどでは意外といいスコアをたたき出し、複雑な気分にさせてくれます。