ラベル 機械学習 の投稿を表示しています。 すべての投稿を表示
ラベル 機械学習 の投稿を表示しています。 すべての投稿を表示

2020年6月2日火曜日

不均衡データのダウンサンプリングとキャリブレーション

ダウンサンプリングしたデータで 2 値分類を行う際に混入するバイアスの除去を検討する。
基本的には この論文ダウンサンプリングによる予測確率のバイアス を参考にしている。

データの読み込み


MNIST データを用いて特定のラベル(今回は 5)を特定する 2 値分類の問題を考える。
Kaggle のサイト から train.csv をダウンロードして取り込む。
library(tidyverse)

# 保存したファイルを読み込む
df.mnist <- readr::read_csv(
  file = "path/to/dir/train.csv",
  col_types = cols(
    .default = col_integer()
  )
) %>%

  dplyr::mutate(
    # ラベル(正解)が "5" かそれ以外かで分類しカテゴリ値に変換
    # 後の yardstick::mn_log_loss の仕様により正解(T)をカテゴリの最初の水準に指定する必要がある事に注意
    y = (label == 5) %>% factor(levels = c(T, F))
  ) %>%
  dplyr::select(-label)

ダウンサンプリング


recipes::step_downsample を用いて正解ラベル(y) が False であるレコードのダウンサンプリングを行う。
under_ratio = 1 の指定により正解ラベル(y)が「TRUE : FALSE = 1 : 1」となるようにサンプリングが実施される。
※詳細は ドキュメント を参照。
# 乱数 seed の固定
# rsample::initial_split 用
set.seed(1025)

lst.splitted <-

  # データを train/test の 2 つに分類
  rsample::initial_split(df.mnist, prop = 3/4, strata = "y") %>%

  {
    split <- (.)

    # ダウンサンプリング用の recipe を定義
    recipe <- rsample::training(split) %>%
      recipes::recipe(y ~ ., .) %>%
      recipes::step_downsample(y, under_ratio = 1, seed = 1025)

    list(
      train = rsample::training(split),
      train_sampled = recipes::prep(recipe, training = rsample::training(split)) %>% recipes::juice(),
      test  = rsample::testing(split)
    )
  }

学習の実施


モデルとして RandomForest を用いて学習を行う。
元データとサンプリング済データで学習時間に約 10 倍近い差が出ておりサンプリングによるメリットが大きく現れている。
  • 元データ: 約 220 秒 / 31,500 件
  • サンプリングデータ: 約 20 秒 / 5,646 件
model <- parsnip::rand_forest(
  mode = "classification",
  trees = 500,
  mtry = 300,
  min_n = 2
) %>%
  parsnip::set_engine(
    engine = "ranger",
    max.depth = 20,
    num.threads = 8,
    seed = 1025
  )

# 全データを用いた学習: 約 220 秒
system.time(
  fit.original <- model %>%
    parsnip::fit(y ~ ., lst.splitted$train)
)

# サンプリングデータを用いた学習:  約 20 秒
system.time(
  fit.downsampled <- model %>%
    parsnip::fit(y ~ ., lst.splitted$train_sampled)
)

キャリブレーション


キャリブレーションはオリジナルの予測確率を $p_{s}$ として下記で与えられる。 \begin{eqnarray} p = \frac{ p_{s} }{ p_{s} + \frac{ 1 - p_{s} }{ \beta } } \nonumber \end{eqnarray} ここで $\beta = p(s = 1 | −)$ により $\beta$ は負例におけるサンプリング割合であり、下記により $\beta \fallingdotseq 0.0984$ となる。
  • 訓練データ: 31,500 件
  • サンプリングデータ: 5,646 件
    • y = TRUE: 2,823 件
    • y = FALSE: 2,823 件
  • 非サンプリングデータ: 31,500 - 5,646 = 25,854 件
  • 2,823 / (2,823 + 25,854) ≒ 0.0984
# 訓練データ全体: 31,500
nrow(lst.splitted$train)

# サンプリング対象: 5,646
nrow(lst.splitted$train_sampled)

# サンプリング対象の内訳
# - T: 2,823
# - F: 2,823
lst.splitted$train_sampled %>% dplyr::count(y)

# beta: 0.09844126
beta <- 2823 / (2823 + 25854)

予測データ


この後の可視化および評価に備えて予測確率を正解ラベルと共にまとめておく。
正例と判定するしきい値はデフォルトの 0.5 を用いている。
# 各予測確率の一覧
# 正例のしきい値: 0.5
df.predicted <- tibble(
  # 正解ラベル
  actual = lst.splitted$test$y,

  # 元データによる学習と予測
  original.proba = predict(fit.original, lst.splitted$test, type = "prob")$.pred_TRUE,
  original.pred  = (original.proba > 0.5) %>% factor(levels = c(T, F)),

  # サンプリングデータによる学習と予測
  downsampled.proba = predict(fit.downsampled, lst.splitted$test, type = "prob")$.pred_TRUE,
  downsampled.pred  = (downsampled.proba > 0.5) %>% factor(levels = c(T, F)),

  # キャリブレーション適用済みの予測
  calibrated.proba = downsampled.proba / (downsampled.proba + (1 - downsampled.proba) / beta),
  calibrated.pred  = (calibrated.proba > 0.5) %>% factor(levels = c(T, F))
)

df.predicted
actual original.proba original.pred downsampled.proba downsampled.pred calibrated.proba calibrated.pred
FALSE 0.0006013 FALSE 0.0091151 FALSE 0.0009047 FALSE
FALSE 0.0024995 FALSE 0.0100703 FALSE 0.0010004 FALSE
FALSE 0.1053711 FALSE 0.2660000 FALSE 0.0344460 FALSE
FALSE 0.0001306 FALSE 0.0199857 FALSE 0.0020035 FALSE
FALSE 0.0003486 FALSE 0.0040777 FALSE 0.0004029 FALSE


予測確率の分布


各予測確率の分布を可視化してみる。
df.predicted %>%

  # wide-form => long-form
  dplyr::select(
    original.proba,
    downsampled.proba,
    calibrated.proba
  ) %>%
  tidyr::pivot_longer(cols = dplyr::everything(), names_to = "type", names_pattern = "(.+)\\.proba", , values_to = "prob") %>%

  # 可視化時の並び順(facet_grid)を指定
  dplyr::mutate(type = forcats::fct_relevel(type, "original", "downsampled", "calibrated")) %>%

  # 可視化
  ggplot(aes(prob)) +
    geom_histogram(aes(y = ..density..), position = "identity", binwidth = 0.010, boundary = 0, colour = "white", alpha = 1/2) +
    geom_density(aes(fill = type), colour = "white", alpha = 1/3) +
    geom_vline(
      data = function(df) { dplyr::group_by(df, type) %>% dplyr::summarise(avg = mean(prob)) },
      aes(xintercept = avg, colour = type),
      linetype = 2,
      size = 1,
      alpha = 1/2
    ) +
    guides(fill = F, colour = F) +
    labs(
      x = "Probability",
      y = NULL
    ) +
    facet_grid(type ~ ., scales = "free_y")

上から順に [ サンプリングなし(original) / サンプリングのみ(downsampled) / キャリブレーション適用(calibrated) ] の順で並んでいる。
サンプリングのみの予測確率(downsampled)は正例側(右側)に寄っており、これがダウンサンプリングにおけるバイアスの影響だと考えられる。



Calibration Curve


0.1 刻みでビン化した予測確率の区間毎に予測確率(横軸)と正例(縦軸)の平均を算出して可視化を行う。
df.predicted %>%

  # ビン化
  dplyr::mutate(
    bins.downsampled = cut(downsampled.proba, breaks = seq(0, 1, 0.1)),
    bins.calibrated  = cut(calibrated.proba,  breaks = seq(0, 1, 0.1))
  ) %>%

  # 集約処理
  {
    data <- (.)

    # サンプリングのみ(downsampled)
    df.downsampled <- data %>%
      dplyr::group_by(bins = bins.downsampled) %>%
      dplyr::summarise(
        n = n(),
        avg_proba = mean(downsampled.proba),
        ratio = mean(actual == "TRUE")
      ) %>%
      dplyr::mutate(type = "downsampled")

    # calibration 適用(calibrated)
    df.calibrated <- data %>%
      dplyr::group_by(bins = bins.calibrated) %>%
      dplyr::summarise(
        n = n(),
        avg_proba = mean(calibrated.proba),
        ratio = mean(actual == "TRUE")
      ) %>%
      dplyr::mutate(type = "calibrated")

    dplyr::bind_rows(
      df.downsampled,
      df.calibrated
    )
  } %>%

  # 可視化
  ggplot(aes(avg_proba, ratio)) +
    geom_point(aes(size = n, colour = type), show.legend = F) +
    geom_line(aes(colour = type)) +
    geom_abline(slope = 1, intercept = 0, linetype = 2, alpha = 1/2) +
    scale_size_area() +
    labs(
      x = "Predict Probability",
      y = "Positive Ratio",
      colour = NULL
    )

左下から右上へ対角線上に伸びる黒い点線に沿うほど信頼性が高いと考えられるが、今回の calibration による実現はない。
見た感じでは 2 つの曲線がグラフの中心 (x, y) = (0.5, 0.5) に対して点対称になっており、 downsampled 曲線が予測確率の高い範囲(1.0 付近)で黒点線に沿っているのに対し calibrated 曲線が予測確率の低い範囲(0.0 付近)で黒点線に沿うような状況になっている事から今回の calibration によって正例と負例のどちらを重視するのかが入れ替わっているものと考えられる (ただの感想なので正しい解釈が欲しい…)。
ダウンサンプリングによって少数である正例側へと生じたバイアスを取り除くという意味では妥当と考えて良いのかもしれない。

下記の図では丸の大きさによって該当するレコード数を表現しており、左下の負例の辺りに大きなサイズの丸が存在している。これは正例の比率が小さい(約10%)事から発生していると考えられるが、件数の多い範囲(0.0 付近)における予測確率の精度が向上するという事はデータ全体における精度の向上として現れるのではないかと考えられる。実際この後に calibration を実施した予測確率において Log Loss の低下(=精度向上)が見られる事を確認する。




Log Loss


評価指標としてまず Log Loss の値を確認する。
# LogLoss: サンプリングなし
yardstick::mn_log_loss(df.predicted, actual, original.proba) %>%
  dplyr::select(metric = .metric, original = .estimate) %>%

  # LogLoss: サンプリングのみ
  dplyr::left_join(
    yardstick::mn_log_loss(df.predicted, actual, downsampled.proba) %>%
      dplyr::select(metric = .metric, downsampled = .estimate),
    by = "metric"
  ) %>%

  # LogLoss: キャリブレーション適用
  dplyr::left_join(
    yardstick::mn_log_loss(df.predicted, actual, calibrated.proba) %>%
      dplyr::select(metric = .metric, calibrated = .estimate),
    by = "metric"
  )

各指標(行)ごとに最も良いスコアと最も悪いスコアをそれぞれ赤と青で色付けしている。

サンプリングのみ(downsampled)の場合と比較して calibration によって明らかに Log Loss の改善(0.12=>0.06)が見られている。
予測確率の分布で見たように calibration によって分布が 0 と 1 の付近に寄るようになった事と、多数側のサンプルである負例での精度が向上している事に依るものと思われる。
しかしながらサンプリングなし(original)で最も良い結果が出ている事から、今回のデータで Log Loss が評価指標になる場合は不均衡への対応を行わないという選択肢は十分にある(他の対応方法を否定するものではない)。

Log Loss
metric original downsampled calibrated
mn_log_loss 0.0464 0.1223 0.0651


混同行列


  • 縦方向の T/F: Actual
  • 横方向の T/F: Predict
  • しきい値: 0.5

# サンプリングなし
table(df.predicted[, c("actual", "original.pred")])
TRUE FALSE
TRUE 848 124
FALSE 12 9,516

# サンプリングのみ
table(df.predicted[, c("actual", "downsampled.pred")])
サンプリングなしと比較して予測が正例(左列)側に寄っており、バイアスの影響が見られる。
TRUE FALSE
TRUE 944 28
FALSE 241 9,287

# キャリブレーション適用
table(df.predicted[, c("actual", "calibrated.pred")])
サンプリングのみの場合と比較して予測が全体的に負例(右列)側に寄っており、ある程度バイアスの解消が出来ているものと思われる。
一方で、False Negative(右上) に該当するサンプル数が大幅に増加してしまっている。
TRUE FALSE
TRUE 700 272
FALSE 5 9,523

Accuracy/Precision/Recall/F


しきい値は 0.5
# 使用する指標を指定
metrics <- yardstick::metric_set(
  yardstick::accuracy,
  yardstick::precision,
  yardstick::recall,
  yardstick::f_meas
)

# サンプリングなし(original)
metrics(df.predicted, truth = actual, estimate = original.pred) %>%
  dplyr::select(metric = .metric, original = .estimate) %>%

  # サンプリングのみ(downsampled)
  dplyr::left_join(
    metrics(df.predicted, truth = actual, estimate = downsampled.pred) %>%
      dplyr::select(metric = .metric, downsampled = .estimate),
    by = "metric"
  ) %>%

  # キャリブレーション適用(calibrated)
  dplyr::left_join(
    metrics(df.predicted, truth = actual, estimate = calibrated.pred) %>%
      dplyr::select(metric = .metric, calibrated = .estimate),
    by = "metric"
  )
各指標(行)ごとに最も良いスコアと最も悪いスコアをそれぞれ赤と青で色付けしている。

総合的には F 値(f_meas)の最も高いサンプリングなし(original)の場合が最も良い予測であると考えられる。
一方で calibration を適用するとサンプリングのみ(downsampled)の場合よりも成績が悪化してしまっている。
metric original downsampled calibrated
accuracy 0.987 0.974 0.974
precision 0.986 0.797 0.993
recall 0.872 0.971 0.720
f_meas 0.926 0.875 0.835


ここで判定に用いているしきい値を変更する事を考えてみる。
予測確率の分布においてその平均値が 0.1〜0.2 付近である事を考えると、上記で用いたしきい値の 0.5 はもう少し小さい値を取る方が良いと思われる。


しきい値の変更


# しきい値を 0.0〜1.0 の範囲で変更して各指標を算出
df.evals <- purrr::map_dfr(seq(0, 1, 0.01), function(threshold) {

  # 予測値の算出
  df.predicted <- tibble(
    actual = lst.splitted$test$y,

    # サンプリングなし
    original.proba = predict(fit.original, lst.splitted$test, type = "prob")$.pred_TRUE,
    original.pred  = (original.proba > threshold) %>% factor(levels = c(T, F)),

    # サンプリングのみ
    downsampled.proba = predict(fit.downsampled, lst.splitted$test, type = "prob")$.pred_TRUE,
    downsampled.pred  = (downsampled.proba > threshold) %>% factor(levels = c(T, F)),

    # キャリブレーション適用
    calibrated.proba = downsampled.proba / (downsampled.proba + (1 - downsampled.proba) / beta),
    calibrated.pred  = (calibrated.proba > threshold) %>% factor(levels = c(T, F))
  )

  # 評価指標
  metrics <- yardstick::metric_set(
    yardstick::accuracy,
    yardstick::precision,
    yardstick::recall,
    yardstick::f_meas
  )

  # サンプリングなし
  metrics(df.predicted, truth = actual, estimate = original.pred) %>%
    dplyr::select(metric = .metric, original = .estimate) %>%

    # サンプリングのみ
    dplyr::left_join(
      metrics(df.predicted, truth = actual, estimate = downsampled.pred) %>%
        dplyr::select(metric = .metric, downsampled = .estimate),
      by = "metric"
    ) %>%

    # キャリブレーション適用
    dplyr::left_join(
      metrics(df.predicted, truth = actual, estimate = calibrated.pred) %>%
        dplyr::select(metric = .metric, calibrated = .estimate),
      by = "metric"
    ) %>%

    # しきい値の追加
    dplyr::mutate(threshold = threshold)
})


df.evals %>%

  # wide-form => long-form
  tidyr::pivot_longer(cols = c(original, downsampled, calibrated), names_to = "type", values_to = "score") %>%

  # 可視化時の並び順を指定
  dplyr::mutate(
    metric = forcats::fct_relevel(metric, "accuracy", "precision", "recall", "f_meas"),
    type = forcats::fct_relevel(type, "original", "downsampled", "calibrated")
  ) %>%

  # 可視化
  ggplot(aes(threshold, score)) +
    geom_line(aes(colour = type)) +
    geom_vline(xintercept = c(0.15), linetype = 2, alpha = 1/2) +
    scale_x_continuous(breaks = seq(0, 1, 0.1)) +
    labs(
      x = "Threshold",
      y = NULL,
      colour = NULL
    ) +
    facet_grid(metric ~ .)

下の図に示すようにしきい値の値によって各指標は大きく変動する。
サンプリングのみの場合とそれ以外では F 値(f_meas)において逆の傾向が出ている。
しきい値として 0.15 を採用するとキャリブレーションを適用した予測値の F 値(f_meas)が極大となる。


まとめ


  • ダウンサンプリングにより学習時間が大幅(今回は約 1/10)に削減される
  • ダウンサンプリングにより正例寄りにバイアスが発生する
  • キャリブレーションによりダウンサンプリングによるバイアスは(ある程度)解消される
  • ダウンサンプリングにより Log Loss は悪化する
  • キャリブレーションによりダウンサンプリングによる Log Loss の悪化は改善可能
  • 少なくとも今回のデータではしきい値の調整は必須
  • しきい値の調整によりキャリブレーション適用後の各指標をサンプリングなしと同程度まで改善可能

しきい値: 0.15
metric original downsampled calibrated
accuracy 0.977 0.801 0.984
precision 0.813 0.318 0.898
recall 0.976 0.999 0.939
f_meas 0.887 0.482 0.918
mn_log_loss 0.046 0.122 0.065

2019年6月22日土曜日

Tidymodels による機械学習モデル構築

俺様コードによる tidymodels を用いた機械学習モデル構築のサンプルを記載する。
※基礎的な集計の段階は終了しているという前提で話を進めていく

使用したパッケージの一覧



データの分割(訓練/テスト)


データを訓練用とテスト用に分割する。
strata 引数に目的変数を指定する事で目的変数の分布を変えずにデータを分割する事が可能。
library(tidyverse)
library(tidymodels)

# 元データを訓練用とテスト用に分割
# strata を指定する事でクラスの分布を保持したまま分割
lst.splitted <- rsample::initial_split(iris, prop = 0.75, strata = "Species") %>% {
  list(
    train = rsample::analysis(.),
    test  = rsample::assessment(.)
  )
}

クロスバリデーション用データの分割


クロスバリデーション用にデータを分割する。
strata に目的変数を指定しているのは前述した rsample::initial_split と同じ理由。
# クロスバリデーション用の分割を定義
df.cv <- rsample::vfold_cv(
  lst.splitted$train,
  v = 5,
  strata = "Species"
)

前処理の定義


事前に実施した基礎集計の結果やドメイン知識を元にデータの前処理を行う。
いわゆる特徴量エンジニアリングを行う箇所。

※ここで定義している処理はサンプルとして適当に行っているものであり、実施によって精度は下がっていると思われるw
# 前処理レシピの定義
recipe <- recipes::recipe(Species ~ ., lst.splitted$train) %>%

  # Sepal.Width を削除
  recipes::step_rm(Sepal.Width) %>%

  # Sepal.Length を対数変換
  recipes::step_log(Sepal.Length) %>%

  # 説明変数を基準化
  recipes::step_center(all_predictors()) %>%
  recipes::step_scale(all_predictors())

モデルの定義


今回は RandomForest でモデルを定義する。
各ハイパーパラメータに parsnip::varying を指定する事で後から複数の値を設定可能。今回はグリッドサーチでパラメータを指定する(後述)。

parsnip::set_engine では指定した engine パッケージ特有のパラメータをセットする事ができる。
下記の例では ranger::ranger 関数の num.threads パラメータ(並列処理に用いるコア数)を指定している。
# モデルの定義
model <- parsnip::rand_forest(
  mode = "classification",
  mtry = parsnip::varying(),
  min_n = parsnip::varying(),
  trees = parsnip::varying()
) %>%
  parsnip::set_engine(engine = "ranger", num.threads = 4)

グリッドサーチの定義


3 x 3 x 3 = 27 パターンのハイパーパラメータの組み合わせを定義。
今回はグリッドサーチを行っているが dials::grid_random を用いてランダムサーチを行う事も可能。
# グリッドサーチ用の組み合わせパターンを定義
df.grid.params <- dials::grid_regular(
  dials::mtry  %>% dials::range_set(c(1, 3)),
  dials::min_n %>% dials::range_set(c(2, 6)),
  dials::trees %>% dials::range_set(c(500, 1500)),
  levels = 3
)

モデルの学習と評価


処理の構造としては下記の 2 重ループとなっている。
  1. ハイパーパラメータ一覧のループ
  2. クロスバリデーションによる分割のループ
上記 2 重ループの内部で下記の各ステップを実施。
  1. モデルの学習
  2. 学習済モデルによる予測
  3. モデルの評価
上記ループが終了したら 1 段目のループ(ハイパラ一覧のループ)の内部にて下記を実施。
  • CV 毎の評価スコアを平均してハイパーパラメータ毎の評価スコアを算出

上記によって得られる結果を評価スコアでソートする事で最も性能の良いハイパーパラメータを特定する。
df.results <-

  # ハイパーパラメータをモデルに適用
  # ※2020.05 時点で下記のコードは動かない
  merge(df.grid.params, model) %>%
  # 下記のコードに改修(2020.05.19)
  purrr::pmap(df.grid.params, set_args, object = model) %>%

  # ハイパーパラメータの組み合わせごとにループ
  purrr::map(function(model.applied) {

    # クロスバリデーションの分割ごとにループ
    purrr::map(df.cv$splits, model = model.applied, function(df.split, model) {

      # 前処理済データの作成
      df.train <- recipe %>%
        recipes::prep() %>%
        recipes::bake(rsample::analysis(df.split))
      df.test <- recipe %>%
        recipes::prep() %>%
        recipes::bake(rsample::assessment(df.split))

      model %>%

        # モデルの学習
        {
          model <- (.)

          parsnip::fit(model, Species ~ ., df.train)
        } %>%

        # 学習済モデルによる予測
        {
          fit <- (.)

          list(
            train = predict(fit, df.train, type = "class")[[1]],
            test  = predict(fit, df.test,  type = "class")[[1]]
          )
        } %>%

        # 評価
        {
          lst.predicted <- (.)

          # 評価指標の一覧を定義
          metrics <- yardstick::metric_set(
            yardstick::accuracy,
            yardstick::precision,
            yardstick::recall,
            yardstick::f_meas
          )

          # train データでモデルを評価
          df.result.train <- df.train %>%
            dplyr::mutate(
              predicted = lst.predicted$train
            ) %>%
            metrics(truth = Species, estimate = predicted) %>%
            dplyr::select(-.estimator) %>%
            dplyr::mutate(
              .metric = stringr::str_c("train", .metric, sep = "_")
            ) %>%
            tidyr::spread(key = .metric, value = .estimate)

          # test データでモデルを評価
          df.result.test <- df.test %>%
            dplyr::mutate(
              predicted = lst.predicted$test
            ) %>%
            metrics(truth = Species, estimate = predicted) %>%
            dplyr::select(-.estimator) %>%
            dplyr::mutate(
              .metric = stringr::str_c("test", .metric, sep = "_")
            ) %>%
            tidyr::spread(key = .metric, value = .estimate)

          dplyr::bind_cols(
            df.result.train,
            df.result.test
          )
        }
    }) %>%

      # CV 分割全体の平均値を評価スコアとする
      purrr::reduce(dplyr::bind_rows) %>%
      dplyr::summarise_all(mean)
  }) %>%

  # 評価結果とパラメータを結合
  purrr::reduce(dplyr::bind_rows) %>%
  dplyr::bind_cols(df.grid.params) %>%

  # 評価スコアの順にソート(昇順)
  dplyr::arrange(
    desc(test_accuracy)
  ) %>%

  dplyr::select(
    mtry,
    min_n,
    trees,

    train_accuracy,
    train_precision,
    train_recall,
    train_f_meas,

    test_accuracy,
    test_precision,
    test_recall,
    test_f_meas
  )

上記で得られる df.results のサンプル。
mtry min_n trees train_accuracy train_precision train_recall train_f_meas test_accuracy test_precision test_recall test_f_meas
2 2 500 1.000 1.000 1.000 1.000 0.946 0.949 0.946 0.946
2 4 500 0.991 0.991 0.991 0.991 0.946 0.949 0.946 0.946
3 4 500 0.987 0.987 0.987 0.987 0.946 0.949 0.946 0.946
2 6 500 0.985 0.985 0.985 0.985 0.946 0.949 0.946 0.946
3 6 500 0.982 0.983 0.982 0.982 0.946 0.949 0.946 0.946


ベストモデルの構築


最も性能の良い(=評価スコア最大)モデルを構築する。
この段階では訓練データの全体を用いて学習を行う事に注意。
# 訓練データに前処理を適用
df.train.baked <- recipe %>%
  recipes::prep() %>%
  recipes::bake(lst.splitted$train)

# 最も性能の良いハイパーパラメータを用いたモデルを構築
best_model <- update(
  model,
  mtry  = df.results[1,]$mtry,
  min_n = df.results[1,]$min_n,
  trees = df.results[1,]$trees
) %>%

  # 訓練データ全体を用いてモデルの学習を行う
  parsnip::fit(Species ~ ., df.train.baked)

テストデータによる検証


テストデータを用いた評価を行い、構築したモデルの汎化性能を検証する。
# テストデータに前処理を適用
df.test.baked <- recipe %>%
  recipes::prep() %>%
  recipes::bake(lst.splitted$test)  

# 汎化性能を検証
df.test.baked %>%

  # ベストモデルを用いて予測
  dplyr::mutate(
    predicted = predict(best_model, df.test.baked)[[1]]
  ) %>%

  # 精度(Accuracy)を算出
  yardstick::accuracy(Species, predicted)

まとめ


必要になる度に前に書いたコードを思い出したりコピペしたりでノウハウをまとめられていなかったので、今回は良い機会になった。
機械学習周りは scikit-learn 一択かなと思ってた頃もあったけど tidymodels がいい感じなのでぜひ使う人が増えてくれると嬉し。
他の人がどんな感じでやってるのかも知りたいところ。