K-Fold Target Encoding
はじめに
ここではK-Fold Target Encodingについてまとめておく。下記のサイトでは、K-Fold Target Encodingの説明とPythonでの実装が乗っているので、それを参考にRで雑に書き直してみた。いつの日か関数化しよう…。
K-Fold Target Encoding
One-hot EncodingやDummy Encodingは、カテゴリ変数のカーディナリティが多くなると、データセットの次元が大きくなるため、役に立たない場合があるらしい。そこで、Target Encodingなどが役立つが、なにも考えずにTarget Encodingすると、リークしてしまい、モデルの汎化性能が良くなくなる。それを回避しつつ、Target Encodingするために、K-Fold Target Encodingを行う。
K-Fold Target Encodingとは、クロスバリデーションを行いながら、ターゲットの変数の値を、カテゴリカル特徴量のグループごとの平均などでエンコードすることで、カテゴリカルな特徴量を新たな特徴量に変換するエンコード方法。クロスバリデーションしないと行けない理由としては、単純に全データを使ってTarget Encodingしてしまうと、リークする可能性がでてくるため。なので、Target Encodingする際には、エンコードされた値を付与したいレコード群(フォールド群)には、自身のレコードのターゲットの値を含めず、アウトオブフォールド群で計算された平均値を付与する。それをクロスバリデーションの要領で行うことで、トレーニング用のモデルの特徴量として、モデルを構築する。テストデータに対するTarget Encodingの値は、テストデータに付与したK-Fold Target Encodingの値をカテゴリ毎に平均して付与する。
サンプルデータ
K-Fold Target Encodingの値と一致しているかを確認するため、サンプルデータ自体もK-Fold Target Encodingのデータを再現させていただいている。
library(cvTools) df <- tibble(id = 1:25, feature = c("A","B","B","B","B","A","B","A","A","B", "A","A","B","A","A","B","B","B","A","A","B","B","B","A","A"), target = c(1,0,0,1,1,1,0,0,0,0,1,0,1,0,1,0,0,0,1,1,NA,NA,NA,NA,NA), flg = rep(c("train", "test"), c(20, 5))) train_df <- df %>% dplyr::filter(flg == "train") test_df <- df %>% dplyr::filter(flg == "test")
K-Fold Target Encodingの雑な実装
フォールドのインデックスとクロスバリデーション回数は、参考サイトの図解にあわせて、k=5で、それにあわせてフォールドは連番で作成している。これはあくまでも解説のためなので、本番のデータの状態にあわせて変更する。たぶん、{purrr}
で書き直せばもっとすっきりしそう。
# type = c("random", "consecutive", "interleaved") k <- 5 folds <- cvFolds(nrow(train_df), K = k, type = "consecutive") train_df <- train_df %>% dplyr::bind_cols(tibble(folds = folds$which)) res <- tibble() for (i in 1:k) { fold_data <- train_df %>% dplyr::filter(folds == i) out_fold_data <- train_df %>% dplyr::filter(folds != i) tmp <- out_fold_data %>% dplyr::mutate(folds = i) %>% dplyr::group_by(feature, folds) %>% dplyr::summarise(cv_target_encoding = mean(target, na.rm = TRUE)) res <- res %>% dplyr::bind_rows(tmp) } res # A tibble: 10 x 3 feature folds cv_target_encoding <chr> <int> <dbl> 1 A 1 0.556 2 B 1 0.286 3 A 2 0.625 4 B 2 0.25 5 A 3 0.714 6 B 3 0.333 7 A 4 0.625 8 B 4 0.25 9 A 5 0.5 10 B 5 0.375
あとはこれをtrain_df
にもどし、そこからTarget Encodingの値を訓練データから平均して求め、テストデータに返す。left_join()
のところでサブクエリっぽくなっている部分で訓練データから平均を計算している。雑ですんません。
train_df <- train_df %>% dplyr::left_join(x = ., y = res, by = c("feature", "folds")) test_df <- test_df %>% dplyr::left_join(x = ., y = train_df %>% dplyr::group_by(feature) %>% dplyr::summarise(cv_target_encoding = mean(cv_target_encoding)), by = c("feature")) df2 <- train_df %>% dplyr::bind_rows(test_df) df2 %>% # 表示のため as.data.frame() id feature target flg folds cv_target_encoding 1 1 A 1 train 1 0.5555556 2 2 B 0 train 1 0.2857143 3 3 B 0 train 1 0.2857143 4 4 B 1 train 1 0.2857143 5 5 B 1 train 2 0.2500000 6 6 A 1 train 2 0.6250000 7 7 B 0 train 2 0.2500000 8 8 A 0 train 2 0.6250000 9 9 A 0 train 3 0.7142857 10 10 B 0 train 3 0.3333333 11 11 A 1 train 3 0.7142857 12 12 A 0 train 3 0.7142857 13 13 B 1 train 4 0.2500000 14 14 A 0 train 4 0.6250000 15 15 A 1 train 4 0.6250000 16 16 B 0 train 4 0.2500000 17 17 B 0 train 5 0.3750000 18 18 B 0 train 5 0.3750000 19 19 A 1 train 5 0.5000000 20 20 A 1 train 5 0.5000000 21 21 B NA test NA 0.2940476 22 22 B NA test NA 0.2940476 23 23 B NA test NA 0.2940476 24 24 A NA test NA 0.6198413 25 25 A NA test NA 0.6198413