解读
14 Iterative Search | Tidy Modeling with R
1️ 数据与模型准备
我们先搭建一个基本的 SVM 工作流:
library(tidymodels)
tidymodels_prefer()
library(finetune) # 模拟退火需要用到
# 数据
data(cells)
cells <- cells %>% select(-case)
set.seed(1304)
cell_folds <- vfold_cv(cells)
roc_res <- metric_set(roc_auc)
# 数据预处理
svm_rec <-
recipe(class ~ ., data = cells) %>%
step_YeoJohnson(all_numeric_predictors()) %>%
step_normalize(all_numeric_predictors())
# 模型:径向基核 SVM
svm_spec <-
svm_rbf(cost = tune(), rbf_sigma = tune()) %>%
set_engine("kernlab") %>%
set_mode("classification")
svm_wflow <-
workflow() %>%
add_model(svm_spec) %>%
add_recipe(svm_rec)两个需要调的超参数:
- cost:控制“分界线”的复杂度。越大,越不允许分错,模型越复杂。
- rbf_sigma:决定高斯核的“宽度”。越大,每个数据点影响的范围越广。
2️ 网格搜索:像在棋盘上踩点
首先用最传统的 网格搜索。它的思想很简单:在参数空间里,像棋盘一样布点,然后把每个点都试一遍。
svm_param <-
svm_wflow %>%
extract_parameter_set_dials() %>%
update(rbf_sigma = rbf_sigma(c(-7, -1))) # 定义参数的大致范围
set.seed(1401)
start_grid <-
svm_param %>%
update(
cost = cost(c(-6, 1)),
rbf_sigma = rbf_sigma(c(-6, -4))
) %>%
grid_regular(levels = 2) # 生成一个非常小的、均匀的网格
set.seed(1402)
svm_initial <-
svm_wflow %>%
tune_grid(resamples = cell_folds, grid = start_grid, metrics = roc_res)
collect_metrics(svm_initial)- 优点:简单粗暴,绝对不会漏掉网格上的任何点。
- 缺点:非常“笨”且“浪费”。它不会利用前一次的结果去指导下一次的选择,就像闭着眼睛把地图上的格子一个个踩一遍。如果参数多、范围大,计算量会爆炸式增长,而且很可能最好的参数组合根本不在你设定的网格点上。
3️ 贝叶斯优化
既然 svm_initial 已经搜索过一些点,为什么不能更聪明地利用这些信息呢? 于是就有了贝叶斯优化。
想象你在一个巨大的美食城里,目标是找到最好吃的餐馆。
- 网格搜索的做法是:按地图坐标,每隔100米选一家,不管口碑好坏,全都吃一遍。费时费钱。
- 贝叶斯优化的做法是:
- 先验:你随机吃了几家(这就是
svm_initial的作用),对美食城的口味有了初步印象。 - 建模型:你根据吃过的餐馆(位置、口味),大脑里画出了一张 “预测美食地图” 。这张图会预测哪里可能好吃(利用),哪里因为没去过而充满不确定性(探索)。
- 决策:你决定下一家店要么去“预测很好吃”的地方(抓住当前最佳),要么去一个“完全没去过”的区域(说不定有惊喜)。这个权衡由一个叫 “采集函数” 的规则来决定。
- 更新:吃完后,你的体验会更新你的“预测美食地图”,让它更准。如此循环,用最少的次数,快速找到最美味的餐馆。
- 先验:你随机吃了几家(这就是
核心就是在 利用已知好结果 和 探索未知区域 之间找到智能的平衡。
ctrl <- control_bayes(verbose = TRUE) # 设置控制参数,verbose=TRUE看过程
set.seed(1403)
svm_bo <-
svm_wflow %>%
tune_bayes(
resamples = cell_folds,
metrics = roc_res,
initial = svm_initial, # 基于网格搜索结果
param_info = svm_param, # 告诉算法参数的完整范围
iter = 25, # 在初始点之后,再尝试25组参数
control = ctrl
)
show_best(svm_bo) # 查看最佳结果
autoplot(svm_bo, type = "performance") # 查看性能随迭代提升的过程
autoplot(svm_bo, type = "parameters") # 查看参数是如何被探索的4️ 模拟退火
贝叶斯优化很聪明,但它需要维护一个概率模型,计算量有时也大。模拟退火 是另一种思路,它模仿金属冶炼中退火的物理过程。
- 你的策略:
- 高温阶段(大胆探索):一开始,你“精力旺盛”(高温),可以大踏步地随机走向任何方向。即使新地点的金子比当前位置的少一点,你也有一定概率接受它跳过去。这是为了能跳出当前的小山丘,避免困在局部最优(一个铜矿很多但金矿少的小山包),从而有机会到达全局最优(真正的金山)。
- 降温阶段(精细收敛):随着时间推移(迭代),你慢慢“累了冷静了”(温度下降)。你的步伐越来越小,并且越来越不愿意接受更差的结果。最终,你会在认为最好的那个点(金山顶)附近小范围仔细勘探,直到确认找到最好的位置。
它的核心是:先大胆跳跃逃离局部最优,再逐渐冷静精细收敛。
ctrl_sa <- control_sim_anneal(verbose = TRUE, no_improve = 10L)
# no_improve = 10L 表示连续10次没有更好结果就停止
set.seed(1404)
svm_sa <-
svm_wflow %>%
tune_sim_anneal(
resamples = cell_folds,
metrics = roc_res,
initial = svm_initial, # 同样需要一个起点
param_info = svm_param,
iter = 50, # 总迭代次数
control = ctrl_sa
)
show_best(svm_sa)
autoplot(svm_sa, type = "performance") # 你会看到曲线有时会下降,这正是其特点!