決策樹算法是一種監督式學習算法,它簡單好用,易於解釋,在金融科技,數字健康,教育服務,消費互聯網等許多領域發揮着積極作用。決策樹算法學習的結果,類似下圖結構:
本文首先介紹決策樹的原理,然后基於tidymodels框架設計和執行決策樹算法以解決實際問題。
一、決策樹算法原理
決策樹算法的理解,可以參考下面的算法偽代碼(來源:數據挖掘概念與技術)
決策樹算法需要解決關鍵問題
1 如何選擇特征做拆分?
主要采用這些度量方法
1)信息增益
最大化變量的信息增益,確定變量的拆分以及先后順序
2)增益率
增益率用於優化信息增益偏向於具有變量值分布不一致所導致的問題。
3)Gini 指數
2 如何對樹的結構進行裁剪?
目的:防止學習的模型過擬合(對訓練集效果好,而測試集上效果不佳)
使用統計測量刪除不可靠的分支或者有少量樣本組成的分支。實際操作中,可以通過設置樹生成的一些超參數來控制樹的結構,比方說:
1)樹的最大深度max_depth
2)樹的最小划分樣本數min_samples_split
3)數的葉子節點最小樣本數min_samples_leaf
通過裁剪技術,可以讓樹更加簡潔,容易理解,也可提提升模型的泛化性能。
決策樹算法的優點:
-
簡單可解釋
-
可以處理各種數據
-
非參數模型
-
穩健
-
快速
決策樹算法的缺點:
-
過度擬合問題
-
不穩定問題
-
偏差問題
-
優化問題
二、決策樹算法應用案例
利用決策樹算法預測Scooby Doo monsters是否真實?
第一步:數據理解與准備
options(warn = -1)
library(tidyverse)
# 數據獲取
scooby_raw <- read_csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2021/2021-07-13/scoobydoo.csv")
scooby_raw %>%
filter(monster_amount > 0) %>%
count(monster_real)
第二步:從不同維度做洞察
時間維度
scooby_raw %>%
filter(monster_amount > 0) %>%
count(
year_aired = 10 * ((lubridate::year(date_aired) + 1) %/% 10),
monster_real
) %>%
mutate(year_aired = factor(year_aired)) %>%
ggplot(aes(year_aired, n, fill = monster_real)) +
geom_col(position = position_dodge(preserve = "single"), alpha = 0.8) +
labs(x = "Date aired", y = "Monsters per decade", fill = "Real monster?")
imdb評分維度
scooby_raw %>%
filter(monster_amount > 0) %>%
mutate(imdb = parse_number(imdb)) %>%
ggplot(aes(imdb, after_stat(density), fill = monster_real)) +
geom_histogram(position = "identity", alpha = 0.5) +
labs(x = "IMDB rating", y = "Density", fill = "Real monster?")
第三步:決策樹模型構建
數據集划分
訓練集,用於訓練模型
測試集,用於評價模型性能
訓練集中利用bootstraps策略用於做超參數選擇和優化
library(tidymodels)
set.seed(123)
scooby_split <- scooby_raw %>%
mutate(
imdb = parse_number(imdb),
year_aired = lubridate::year(date_aired)
) %>%
filter(monster_amount > 0, !is.na(imdb)) %>%
mutate(
monster_real = case_when(
monster_real == "FALSE" ~ "fake",
TRUE ~ "real"
),
monster_real = factor(monster_real)
) %>%
select(year_aired, imdb, monster_real, title) %>%
initial_split(strata = monster_real)
scooby_train <- training(scooby_split)
scooby_test <- testing(scooby_split)
set.seed(234)
scooby_folds <- bootstraps(scooby_train, strata = monster_real)
scooby_folds
決策樹模型設計
# 設計決策樹模型
tree_spec <-
decision_tree(
cost_complexity = tune(),
tree_depth = tune(),
min_n = tune()
) %>%
set_mode("classification") %>%
set_engine("rpart")
tree_spec
tree_grid <- grid_regular(cost_complexity(), tree_depth(), min_n(), levels = 4)
tree_grid
doParallel::registerDoParallel()
set.seed(345)
tree_rs <-
tune_grid(
tree_spec,
monster_real ~ year_aired + imdb,
resamples = scooby_folds,
grid = tree_grid,
metrics = metric_set(accuracy, roc_auc, sensitivity, specificity)
)
tree_rs
第四步:模型性能評價
# 模型評估和理解
show_best(tree_rs)
# 超參數可視化
autoplot(tree_rs) + theme_light(base_family = "IBMPlexSans")
# 基於所關注的指標選擇最佳模型的超參數
simpler_tree <- select_by_one_std_err(tree_rs,
-cost_complexity,
metric = "roc_auc"
)
# 根據最佳參數重構模型
final_tree <- finalize_model(tree_spec, simpler_tree)
final_fit <- fit(final_tree, monster_real ~ year_aired + imdb, scooby_train)
final_rs <- last_fit(final_tree, monster_real ~ year_aired + imdb, scooby_split)
collect_metrics(final_rs)
第五步:模型結果可視化
# 決策樹執行決策的可視化
library(parttree)
scooby_train %>%
ggplot(aes(imdb, year_aired)) +
geom_parttree(data = final_fit, aes(fill = monster_real), alpha = 0.2) +
geom_jitter(alpha = 0.7, width = 0.05, height = 0.2, aes(color = monster_real))
參考資料:
1 Understanding the Mathematics Behind Decision Trees | by Nikita Sharma | Heartbeat (fritz.ai)
2 https://juliasilge.com/blog/scooby-doo/
3 https://github.com/grantmcdermott/parttree