缘由
艺多不压身
碎碎念
我们继续飞行在云层和雾气之间,飞行在一个无光无影的空洞世界中。 ——《风沙星辰》
Ref
10 Resampling for Evaluating Performance | Tidy Modeling with R
解读
从简单回归到交叉验证:tidymodels 模型评估与解释
一、准备数据
使用 Ames 房价数据集,并对房价取对数变换:
library(tidymodels)
library(modeldata)
tidymodels_prefer()
data(ames)
ames <- mutate(ames, Sale_Price = log10(Sale_Price))
set.seed(502)
ames_split <- initial_split(ames, prop = 0.80, strata = Sale_Price)
ames_train <- training(ames_split)
ames_test <- testing(ames_split)这里我们把数据分成 训练集 (80%) 和 测试集 (20%),并且按照房价分层抽样,保证训练/测试数据的分布一致。
二、最简单的线性回归
我们先用车库面积来预测房价:
lm_model <- linear_reg() %>% set_engine("lm")
lm_fit <- fit(lm_model, Sale_Price ~ Garage_Area, data = ames_train)
ames_test_res <- predict(lm_fit, new_data = ames_test) %>%
bind_cols(ames_test %>% select(Sale_Price))这里只用一个特征(Garage_Area),模型简单但可解释性很强,缺点是可能欠拟合。
三、随机森林:建更强的模型
尝试随机森林,使用更多特征:
rf_model <-
rand_forest(trees = 1000) %>%
set_engine("ranger") %>%
set_mode("regression")
rf_wflow <-
workflow() %>%
add_formula(
Sale_Price ~ Neighborhood + Gr_Liv_Area + Year_Built +
Bldg_Type + Latitude + Longitude) %>%
add_model(rf_model)
rf_fit <- rf_wflow %>% fit(data = ames_train)- 这里选择 1000 棵树,避免过高的方差。
- 相比线性回归,随机森林能建模非线性关系,但也可能学习到噪音。
四、评估函数:统一比较模型
我们写一个函数来计算 RMSE 和 R²:
estimate_perf <- function(model, dat) {
cl <- match.call()
obj_name <- as.character(cl$model)
data_name <- gsub("ames_", "", as.character(cl$dat))
reg_metrics <- metric_set(rmse, rsq)
model %>%
predict(dat) %>%
bind_cols(dat %>% select(Sale_Price)) %>%
reg_metrics(Sale_Price, .pred) %>%
select(-.estimator) %>%
mutate(object = obj_name, data = data_name)
}
estimate_perf(rf_fit, ames_train)
estimate_perf(lm_fit, ames_train)
estimate_perf(lm_fit, ames_test)这样就能直接比较 不同模型、不同数据集 下的表现。
五、交叉验证:稳健的性能评估
单次划分可能不稳定,因此引入交叉验证:
set.seed(1001)
ames_folds <- vfold_cv(ames_train, v = 10) # 10 折交叉验证
ames_folds其他方法:
- 重复交叉验证:
vfold_cv(..., repeats = 5) - 留一法 (LOOCV):每次只留一个样本做验证
- 蒙特卡洛采样:随机采样 9/10 的数据,重复多次
- 自助法 (Bootstrap):对训练集有放回抽样
这些方法能帮助我们更全面地估计模型的泛化能力。
六、在交叉验证中保存预测结果
keep_pred <- control_resamples(save_pred = TRUE, save_workflow = TRUE)
set.seed(1003)
rf_res <- rf_wflow %>%
fit_resamples(resamples = ames_folds, control = keep_pred)
collect_metrics(rf_res)
assess_res <- collect_predictions(rf_res)-
control_resamples():控制 resampling(重采样/交叉验证)的行为 。save_pred = TRUE:作用:保存交叉验证中每折的预测结果(.pred列)。可以后续画图(比如预测 vs 实际),而不仅仅是最终的 RMSE、R² 之类的指标。save_workflow = TRUE:保存整个 workflow 对象,也就是预处理 recipe + 模型。你可以回头检查到底在交叉验证中用的是什么配方、什么参数的模型(非常方便调试或解释)。
-
fit_resamples()这是执行交叉验证的主力函数。resamples = ames_foldsames_folds是之前用vfold_cv()生成的 10 折交叉验证数据。fit_resamples()会自动循环:在每一折中用训练子集拟合模型,用验证子集做预测。
可视化预测效果:
assess_res %>%
ggplot(aes(x = Sale_Price, y = .pred)) +
geom_point(alpha = .15) +
geom_abline(color = "red") +
coord_obs_pred() +
ylab("Predicted")如果点接近红色对角线,说明模型预测较准。
七、配方 Recipe:系统化特征工程
在实际问题中,数据预处理步骤很多。Recipe 能把它们组织起来:
ames_rec <- recipe(
Sale_Price ~ Neighborhood + Gr_Liv_Area + Year_Built +
Bldg_Type + Latitude + Longitude, data = ames_train) %>%
step_other(Neighborhood, threshold = 0.01) %>% # 合并稀有类别
step_dummy(all_nominal_predictors()) %>% # 独热编码
step_interact(~ Gr_Liv_Area:starts_with("Bldg_Type_")) %>% # 交互项
step_ns(Latitude, Longitude, deg_free = 20) # 平滑拟合经纬度这样一来,整个预处理流程可以无缝嵌入 workflow,保证训练和预测阶段一致。
八、提取模型与解释
配方加模型放进 workflow:
lm_wflow <- workflow() %>%
add_recipe(ames_rec) %>%
add_model(linear_reg() %>% set_engine("lm"))
lm_fit <- lm_wflow %>% fit(data = ames_train)提取模型系数:
get_model <- function(x) {
extract_fit_parsnip(x) %>% tidy()
}在交叉验证中保存模型:
ctrl <- control_resamples(extract = get_model)
lm_res <- lm_wflow %>%
fit_resamples(resamples = ames_folds, control = ctrl)
# 查看第一折的模型系数
lm_res$.extracts[[1]][[1]]这样我们不仅能得到模型性能,还能查看 每一折的模型参数,对比其稳定性,从而进行更深入的解释。