クイープ訳, 福島真太朗 監訳の「Python機械学習プログラミング: 達人データサイエンティストによる理論と実践(第三版)」(インプレス, 2016) ISBN: 978-4-295-01007-4 をtidymodelsで実装してみる。
- 著者: Sebastian Raschka and Vahid Mirjalili
- 書名: Python Machine Learning
- 出版社: Packt Publishing
- 出版年: 2019
- リポジトリ: https://github.com/rasbt/python-machine-learning-book-3rd-edition (コードライセンス: MIT)
── Attaching packages ────────────────────────────────────── tidymodels 1.0.0 ──
✔ broom 1.0.0 ✔ recipes 1.0.1
✔ dials 1.0.0 ✔ rsample 1.0.0
✔ dplyr 1.0.9 ✔ tibble 3.1.8
✔ ggplot2 3.3.6 ✔ tidyr 1.2.0
✔ infer 1.0.2 ✔ tune 1.0.0
✔ modeldata 1.0.0 ✔ workflows 1.0.0
✔ parsnip 1.0.0 ✔ workflowsets 1.0.0
✔ purrr 0.3.4 ✔ yardstick 1.0.0
── Conflicts ───────────────────────────────────────── tidymodels_conflicts() ──
✖ purrr::discard() masks scales::discard()
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
✖ recipes::step() masks stats::step()
• Learn how to get started at https://www.tidymodels.org/start/
# rsample, recipes, parsnip, yardstick, workflows
6.1 パイプラインによるワークフローの効率化
6.1.1 Breast Cancer Wisconsinデータセットを読み込む
Breast Cancer WisconsinデータセットはUCIから提供されている。 Rではmlbenchパッケージによってこのデータを利用可能になる。
# Breast Cancer Wisconsinデータセットの読み込み
library(mlbench)
data("BreastCancer")
# 変数の数に注意
dim(BreastCancer)
tbl_BreastCancer <-
BreastCancer |>
tibble::as_tibble() |>
janitor::clean_names() |>
mutate(across(.cols = c(contains("_"), "mitoses"), .fns = as.numeric, .names = "{.col}"))
# 書籍とは件数が異なるので注意(書籍では569件)
nrow(tbl_BreastCancer)
# class変数に悪性腫瘍(malignant)か良性腫瘍(benign)かのラベルが割り当てられる
levels(tbl_BreastCancer$class)
データセットの分割はrsampleパッケージを使って行う。
# 訓練データ80%、テストデータ20%
set.seed(123)
cancer_split <-
initial_split(tbl_BreastCancer, prop = 0.8, strata = class)
cancer_train <-
training(cancer_split)
cancer_test <-
testing(cancer_split)
6.1.2 パイプラインで変換器と推定器を結合する
tidymodelsでのパイプラインは前処理とモデルの実行・評価を分けて実行する。 前処理にはrecipes、モデルの定義と実行はparsnip、評価はyardstickパッケージの関数をそれぞれ利用する。 また、これらのパッケージや処理をワーフフローとしてまとめるためにworkflowパッケージが役に立つ。
pca_rec <-
recipe(class ~ ., data = cancer_train) |>
# id列の識別
update_role(id, new_role = "id") |>
# 標準化
step_normalize(all_numeric()) |>
# 欠損を含む行の除外
step_naomit(all_numeric()) |>
# PCA
step_pca(all_numeric(), num_comp = 2) |>
prep()
# GLM
glm_spec <-
logistic_reg() |>
set_mode("classification") |>
set_engine("glm")
# ワークフローの定義
cancer_wflow <-
# 宣言
workflow() %>%
# 前処理レシピ
add_recipe(pca_rec) |>
# モデルの仕様
add_model(glm_spec)
# workflow()関数に直接レシピとモデルを指定しても良い
# workflow(preprocessor = pca_rec, spec = glm_spec)
# 学習データをモデルに当てはめてモデルの訓練を行う
cancer_fit <-
cancer_wflow %>%
# 与えるデータは前処理適用前の学習データ
fit(data = cancer_train)
# テストデータ(評価データ)を使った予測
# predict(cancer_fit, cancer_test)
cancer_aug <-
augment(cancer_fit, cancer_test)
# yardstickパッケージの関数による性能評価
cancer_aug %>%
accuracy(class, .pred_class)
# A tibble: 1 × 3
.metric .estimator .estimate
<chr> <chr> <dbl>
1 accuracy binary 0.985
6.2 k分割交差検証を使ったモデルの性能の評価
6.2.2 k分割交差検証
rsamplesパッケージでのk分割交差検証を実行する関数はvfold_cv()
。 分割する
set.seed(123)
# 学習データに対して、さらに分割データセットを作成する
cancer_kfold <-
cancer_train |>
# classによる層化
vfold_cv(v = 10, strata = class)
cancer_kfold
# 10-fold cross-validation using stratification
# A tibble: 10 × 2
splits id
<list> <chr>
1 <split [501/57]> Fold01
2 <split [501/57]> Fold02
3 <split [502/56]> Fold03
4 <split [502/56]> Fold04
5 <split [502/56]> Fold05
6 <split [502/56]> Fold06
7 <split [503/55]> Fold07
8 <split [503/55]> Fold08
9 <split [503/55]> Fold09
10 <split [503/55]> Fold10
交差検証によるリサンプリングデータに対してワークフローを適用するには、tune::fit_resamples()
関数を使う また、交差検証用のデータからモデルの評価指標を得るにはtune::collect_metrics()
を用いる。
set.seed(123)
glm_fit_fold <-
cancer_wflow %>%
fit_resamples(cancer_kfold,
control = control_resamples(
save_pred = TRUE))
# glm_fit_fold |>
# nplyr::nest_filter(.metrics, .metric == "accuracy") |>
# tidyr::unnest(.metrics)
# 各foldで求められた評価指標の平均値を最終的な指標として採用する
collect_metrics(glm_fit_fold)
# A tibble: 2 × 6
.metric .estimator mean n std_err .config
<chr> <chr> <dbl> <int> <dbl> <chr>
1 accuracy binary 0.965 10 0.00426 Preprocessor1_Model1
2 roc_auc binary 0.994 10 0.00172 Preprocessor1_Model1
tidymodelsの枠組みにおいて、前処理の手順、複数のモデルを管理するには workflowsetsパッケージを利用すると便利。 このパッケージでは、recipes、parsnipによるレシピとモデルの仕様を組み合わせたモデルの管理・運用が可能となる。
6.3 学習曲線と検証曲線によるアルゴリズムの検証
6.3.1 学習曲線を使ってバイアスとバリアンスの問題を診断する
6.3.2 検証曲線を使って過学習と学習不足を明らかにする
6.4 グリッドサーチによる機械学習モデルのチューニング
6.4.1 グリッドサーチを使ったハイパーパラメータのチューニング
# bare_nuclei
std_rec <-
recipe(class ~ .,
data = cancer_train) |>
update_role(id, new_role = "id") |>
step_rm(bare_nuclei) |>
step_normalize(all_predictors())
spec_rbf_svm <-
svm_rbf() |>
set_engine("kernlab", scaled = TRUE) |>
set_mode("classification")
# svm_linear_spec <-
# svm_poly(degree = 1) %>%
# set_mode("classification") %>%
# set_engine("kernlab")
# spec_rbf_svm |>
# fit(class ~ ., data = cancer_train)
# spec_rbf_svm |>
# set_args(cost = 0.1) |>
# fit(class ~ ., data = cancer_train)
spec_rbf_svm_tune <-
spec_rbf_svm |>
set_args(cost = tune())
# param_grid <-
# grid_regular(cost(), levels = 10)
#
#
cancer_tune_wflow <-
workflow(preprocessor = std_rec, spec = spec_rbf_svm_tune)
tune_res <-
tune_grid(
cancer_tune_wflow,
metrics = metric_set(accuracy, roc_auc),
resamples = cancer_kfold)
collect_metrics(tune_res)
# A tibble: 20 × 7
cost .metric .estimator mean n std_err .config
<dbl> <chr> <chr> <dbl> <int> <dbl> <chr>
1 0.0767 accuracy binary 0.939 10 0.0106 Preprocessor1_Model01
2 0.0767 roc_auc binary 0.972 10 0.00576 Preprocessor1_Model01
3 12.2 accuracy binary 0.945 10 0.0109 Preprocessor1_Model02
4 12.2 roc_auc binary 0.967 10 0.00781 Preprocessor1_Model02
5 0.0289 accuracy binary 0.939 10 0.0108 Preprocessor1_Model03
6 0.0289 roc_auc binary 0.977 10 0.00573 Preprocessor1_Model03
7 0.00321 accuracy binary 0.656 10 0.00146 Preprocessor1_Model04
8 0.00321 roc_auc binary 0.976 10 0.00552 Preprocessor1_Model04
9 1.25 accuracy binary 0.952 10 0.00797 Preprocessor1_Model05
10 1.25 roc_auc binary 0.973 10 0.00584 Preprocessor1_Model05
11 0.00203 accuracy binary 0.656 10 0.00146 Preprocessor1_Model06
12 0.00203 roc_auc binary 0.974 10 0.00591 Preprocessor1_Model06
13 8.02 accuracy binary 0.950 10 0.00968 Preprocessor1_Model07
14 8.02 roc_auc binary 0.970 10 0.00610 Preprocessor1_Model07
15 2.14 accuracy binary 0.953 10 0.00716 Preprocessor1_Model08
16 2.14 roc_auc binary 0.973 10 0.00569 Preprocessor1_Model08
17 0.0186 accuracy binary 0.893 10 0.0404 Preprocessor1_Model09
18 0.0186 roc_auc binary 0.975 10 0.00581 Preprocessor1_Model09
19 0.219 accuracy binary 0.943 10 0.00900 Preprocessor1_Model10
20 0.219 roc_auc binary 0.973 10 0.00575 Preprocessor1_Model10
show_best(tune_res, metric = "accuracy")
# A tibble: 5 × 7
cost .metric .estimator mean n std_err .config
<dbl> <chr> <chr> <dbl> <int> <dbl> <chr>
1 2.14 accuracy binary 0.953 10 0.00716 Preprocessor1_Model08
2 1.25 accuracy binary 0.952 10 0.00797 Preprocessor1_Model05
3 8.02 accuracy binary 0.950 10 0.00968 Preprocessor1_Model07
4 12.2 accuracy binary 0.945 10 0.0109 Preprocessor1_Model02
5 0.219 accuracy binary 0.943 10 0.00900 Preprocessor1_Model10
show_best(tune_res, metric = "roc_auc")
# A tibble: 5 × 7
cost .metric .estimator mean n std_err .config
<dbl> <chr> <chr> <dbl> <int> <dbl> <chr>
1 0.0289 roc_auc binary 0.977 10 0.00573 Preprocessor1_Model03
2 0.00321 roc_auc binary 0.976 10 0.00552 Preprocessor1_Model04
3 0.0186 roc_auc binary 0.975 10 0.00581 Preprocessor1_Model09
4 0.00203 roc_auc binary 0.974 10 0.00591 Preprocessor1_Model06
5 0.219 roc_auc binary 0.973 10 0.00575 Preprocessor1_Model10
svm_mod <-
svm_rbf(cost = tune(), rbf_sigma = tune()) %>%
set_engine("kernlab") %>%
set_mode("classification")
cancer_tune_wflow2 <-
workflow(preprocessor = std_rec, spec = svm_mod)
svm_res <-
tune_grid(
cancer_tune_wflow2,
resamples = cancer_kfold,
grid = 7)
show_best(svm_res, metric = "accuracy")
# A tibble: 5 × 8
cost rbf_sigma .metric .estimator mean n std_err .config
<dbl> <dbl> <chr> <chr> <dbl> <int> <dbl> <chr>
1 0.0595 2.87e- 2 accuracy binary 0.952 10 0.00743 Preprocessor1_Model3
2 0.203 9.96e- 1 accuracy binary 0.941 10 0.00990 Preprocessor1_Model6
3 0.0117 5.59e-10 accuracy binary 0.656 10 0.00146 Preprocessor1_Model1
4 0.00229 1.70e- 4 accuracy binary 0.656 10 0.00146 Preprocessor1_Model2
5 2.43 5.07e- 7 accuracy binary 0.656 10 0.00146 Preprocessor1_Model4
ctrl <- control_grid(verbose = FALSE, save_pred = TRUE)
svm_tune_res <-
tune_grid(
cancer_tune_wflow2,
resamples = cancer_kfold,
control = ctrl)
show_best(svm_tune_res, metric = "accuracy")
# A tibble: 5 × 8
cost rbf_sigma .metric .estimator mean n std_err .config
<dbl> <dbl> <chr> <chr> <dbl> <int> <dbl> <chr>
1 10.3 0.00484 accuracy binary 0.962 10 0.00494 Preprocessor1_Mode…
2 0.178 0.0343 accuracy binary 0.957 10 0.00607 Preprocessor1_Mode…
3 26.8 0.000100 accuracy binary 0.952 10 0.00743 Preprocessor1_Mode…
4 0.0722 0.120 accuracy binary 0.952 10 0.00896 Preprocessor1_Mode…
5 0.00265 0.0000816 accuracy binary 0.656 10 0.00146 Preprocessor1_Mode…
select_best(svm_tune_res, metric = "accuracy")
# A tibble: 1 × 3
cost rbf_sigma .config
<dbl> <dbl> <chr>
1 10.3 0.00484 Preprocessor1_Model05
6.4.2 入れ子式の交差検証によるアルゴリズムの選択
6.5 さまざまな性能評価指標
6.5.1 混同行列を解釈する
cm <-
cancer_aug |>
conf_mat(class, .pred_class)
# cm <-
# collect_predictions(glm_fit_fold) |>
# conf_mat(class, .pred_class)
cm
Truth
Prediction benign malignant
benign 87 1
malignant 1 48
# autoplot(cm, type = "mosaic")
autoplot(cm, type = "heatmap")
6.5.2 分類モデルの適合率と再現率を最適化する
cancer_aug |>
f_meas(class, .pred_class)
# A tibble: 1 × 3
.metric .estimator .estimate
<chr> <chr> <dbl>
1 f_meas binary 0.989
collect_predictions(glm_fit_fold) |>
f_meas(class, .pred_class)
# A tibble: 1 × 3
.metric .estimator .estimate
<chr> <chr> <dbl>
1 f_meas binary 0.973
set.seed(123)
cancer_wflow %>%
fit_resamples(cancer_kfold,
metrics = metric_set(f_meas, precision)) |>
collect_metrics()
# A tibble: 2 × 6
.metric .estimator mean n std_err .config
<chr> <chr> <dbl> <int> <dbl> <chr>
1 f_meas binary 0.973 10 0.00325 Preprocessor1_Model1
2 precision binary 0.978 10 0.00790 Preprocessor1_Model1
collect_predictions(glm_fit_fold) |>
group_by(id == "Fold01") |>
roc_curve(class, .pred_class) |>
autoplot()
6.6 クラスの不均衡に対処する
tbl_BreastCancer |>
count(class)
# A tibble: 2 × 2
class n
<fct> <int>
1 benign 458
2 malignant 241
# 不均衡なデータの作成
# 良性腫瘍のすべてのデータと悪性腫瘍40件分のデータを結合
tbl_BreastCancer_imbalance <-
tbl_BreastCancer |>
filter(class == "benign") |>
bind_rows(
tbl_BreastCancer |>
filter(class == "malignant") |>
slice_head(n = 40))
tbl_BreastCancer_imbalance |>
count(class)
# A tibble: 2 × 2
class n
<fct> <int>
1 benign 458
2 malignant 40
cancer_split_imb <-
tbl_BreastCancer_imbalance |>
initial_split(strata = class)
cancer_train_imb <-
training(cancer_split_imb)
cancer_test_imb <-
training(cancer_split_imb)
cancer_train_imb |>
count(class)
# A tibble: 2 × 2
class n
<fct> <int>
1 benign 342
2 malignant 31
# アップサンプリング
# 多い方のクラス件数となるまで、少ないクラスのデータを復元抽出する
up_rec <-
cancer_train_imb |>
recipe(class ~ .) |>
themis::step_upsample(class) |>
prep()
# ダウンサンプリング
# 少ないクラス件数に寄せる
dw_rec <-
cancer_train_imb |>
recipe(class ~ .) |>
themis::step_downsample(class) |>
prep()
up_rec |>
bake(new_data = NULL) |>
count(class)
# A tibble: 2 × 2
class n
<fct> <int>
1 benign 342
2 malignant 342
dw_rec |>
bake(new_data = NULL) |>
count(class)
# A tibble: 2 × 2
class n
<fct> <int>
1 benign 31
2 malignant 31