35  重采样

library(tidymodels)
tidymodels_prefer()
theme_set(theme_bw())

35.1 重采样的流程

{#sec-resampling-process-and-terminology}

图 35.1 展示了一套引入重采样操作的标准数据分配方案。完成初始数据划分后,重采样可生成多组不同的训练子集:

我们先假设数据中的每一行都是独立的,在后续章节中(小节 35.7, 小节 35.8, 小节 35.9) 再展示非独立数据的示例。
  • 分析集(analysis set)/训练集:用于估算、预处理、模型训练及后处理的子集,通常占原始数据集的 60%~80%。
  • 评估集(assessment set)/验证集:仅用于模型预测、性能评估及模型选择的子集,通常占原始数据集的 20%~40%。在模型开发过程中,评估集不参与模型训练。
图 35.1: 基于重抽样的通用数据划分方案。图中棕褐色区域代表模型训练数据集,淡紫色区域代表用于模型评估的独立数据集。

35.2 重采样的目标

重采样的目标是通过多次重复的训练/评估过程,旨在有限数据集上更稳健地评估模型性能。在模型开发过程中,重采样可用于:

  • 模型性能的稳健评估
  • 模型参数的调优
  • 模型选择

小节 27.1 中,我们介绍了偏差与方差平衡的概念。重采样可用于评估模型的偏差和方差,帮助我们选择合适的模型。可以简单的理解为:

  • 偏差衡量模型的准确性,模型预测值与真实值之间的误差,偏差越大,预测越不准确。
  • 方差衡量模型的精度,模型预测值之间的离散程度,方差越大,数据的分布越分散。

各类重采样方法均基于原始训练集生成重抽样样本。下面以基础自助重采样为例,介绍核心工具与函数,再对其余重采样方法展开论述。

# load and spend data
set.seed(82)
concrete_split <- initial_split(concrete)
concrete_tr <- training(concrete_split)
concrete_te <- testing(concrete_split)

# define resampling method
set.seed(380)
concrete_rs <- rsample::bootstraps(concrete, times = 5) # 创建5个自助法重采样样本
concrete_rs
# Bootstrap sampling 
# A tibble: 5 × 2
  splits             id        
  <list>             <chr>     
1 <split [1030/355]> Bootstrap1
2 <split [1030/408]> Bootstrap2
3 <split [1030/369]> Bootstrap3
4 <split [1030/375]> Bootstrap4
5 <split [1030/378]> Bootstrap5

concete_rs 是一个 bootstraps 对象,包含了 5 个自助法重采样样本。

  • 该重抽样对象除tibble外,还包括bootstrapsrset两个类,将其与标准的tibble对象区分开来。bootstraps类表示该对象是一个自助法重采样对象,rset类表示该对象是一个重采样对象。
  • id列:每个重采样样本的唯一标识符。
  • splits列:每个重采样样本的训练集和评估集的划分信息。

35.3 验证集

  • 验证集是用于模型评估和选择的独立数据集。在模型开发过程中,验证集不参与模型训练,但用于评估模型的性能,并帮助我们选择最佳的模型。
  • 使用验证集的一个要点是,在我们对模型流程做出最终判定后该如何处理它。通常情况下,完整训练集会被用于最终模型拟合。
  • 当训练数据集较小时,验证集的使用可能会导致训练数据不足,从而影响模型的性能。在这种情况下,我们可以使用交叉验证(小节 35.4小节 35.5) 等方法来充分利用数据。
  • 可以把验证集简单的理解为1折交叉验证。在数据量非常大的情况下,使用验证集就可以了;在数据量较小的情况下,使用交叉验证可以更好地利用数据。
# 创建验证集
concrete_split <- initial_validation_split(concrete, prop = c(0.8, 0.1)) # 80%训练集,10%验证集,10%测试集
concrete_tr <- training(concrete_split)
concrete_vl <- validation(concrete_split)
concrete_ts <- testing(concrete_split)

# 定义验证集rset
concrete_rs_val <- rsample::validation_set(concrete_split)
concrete_rs_val
# A tibble: 1 × 2
  splits            id        
  <list>            <chr>     
1 <split [824/103]> validation

35.4 蒙特卡洛 交叉验证

图 35.2 展示了基于 30 个初始数据点生成的三个蒙特卡洛交叉验证重抽样示意图。每个重采样样本都包含了不同的训练集和评估集划分,且每个样本的训练集和评估集都是独立的。

  • 在精确性层面(方差),将超过 70% 的训练集划入评估集几乎不会带来增益。
  • 同时,尽管增加重采样次数总能提升效果,但当重采样次数超过 50 或 60 次时,增益幅度会逐步递减。
  • 针对偏差指标,当留存训练集比例接近 80% 时,偏差的下降速度会有所放缓。因此:
    • 75% 至 80% 左右的留出比例或许可作为合理经验取值。
    • 50 次左右的重采样次数或许可作为合理经验取值。
    • 在实际应用中,建议可根据数据集的大小和模型的复杂度进行调整。
图 35.2: 基于 30 个初始数据点生成的三个蒙特卡洛交叉验证重抽样示意图
# 创建蒙特卡洛交叉验证重采样对象
set.seed(380)
rsample::mc_cv(concrete_tr, times = 3, prop = 0.9) # 创建3个蒙特卡洛交叉验证重采样样本,每个样本包含90%的训练集和10%的评估集
# Monte Carlo cross-validation (0.9/0.1) with 3 resamples 
# A tibble: 3 × 2
  splits           id       
  <list>           <chr>    
1 <split [741/83]> Resample1
2 <split [741/83]> Resample2
3 <split [741/83]> Resample3

35.5 V折交叉验证

图 35.3 展示了三次 3 折交叉验证的迭代流程。每一轮迭代选取不同子集作为评估集,剩余两部分子集合并构成训练集,所有样本仅参与一次模型评估。

研究推荐优先选取折数 \(V=10\)。10 折交叉验证在偏差、方差优化层面均具备优良表现。5 折交叉验证对偏差的改善效果有限,而增加重复次数能够进一步提升 10 折交叉验证的估计精度。

图 35.3: 基于 30 个初始数据点生成的三个 V 折交叉验证重抽样示意图
# 创建 V 折交叉验证重采样对象
set.seed(380)
rsample::vfold_cv(concrete_tr, repeats = 2) # 创建 2 次重复的 V=10 折交叉验证重采样样本
#  10-fold cross-validation repeated 2 times 
# A tibble: 20 × 3
   splits           id      id2   
   <list>           <chr>   <chr> 
 1 <split [741/83]> Repeat1 Fold01
 2 <split [741/83]> Repeat1 Fold02
 3 <split [741/83]> Repeat1 Fold03
 4 <split [741/83]> Repeat1 Fold04
 5 <split [742/82]> Repeat1 Fold05
 6 <split [742/82]> Repeat1 Fold06
 7 <split [742/82]> Repeat1 Fold07
 8 <split [742/82]> Repeat1 Fold08
 9 <split [742/82]> Repeat1 Fold09
10 <split [742/82]> Repeat1 Fold10
11 <split [741/83]> Repeat2 Fold01
12 <split [741/83]> Repeat2 Fold02
13 <split [741/83]> Repeat2 Fold03
14 <split [741/83]> Repeat2 Fold04
15 <split [742/82]> Repeat2 Fold05
16 <split [742/82]> Repeat2 Fold06
17 <split [742/82]> Repeat2 Fold07
18 <split [742/82]> Repeat2 Fold08
19 <split [742/82]> Repeat2 Fold09
20 <split [742/82]> Repeat2 Fold10

35.6 自助法

自助法(Bootstrap)是一种重抽样统计推断技术:从原始样本中有放回地抽取与原始样本大小相同的样本(自助样本),重复多次(如 2000 次),通过计算每个自助样本上的统计量来估计该统计量的抽样分布,从而获得标准误、置信区间、偏差等,而无需假设总体分布。图 35.4 展示了从初始 30 个数据点中生成的三个自助法重采样。

图 35.4: 从初始 30 个数据点中生成的三个自助法重采样示意图
# 创建自助法重采样对象
set.seed(380)
rsample::bootstraps(concrete, times = 5, apparent = TRUE) # 创建 5 个自助法重采样样本
# Bootstrap sampling with apparent sample 
# A tibble: 6 × 2
  splits              id        
  <list>              <chr>     
1 <split [1030/355]>  Bootstrap1
2 <split [1030/408]>  Bootstrap2
3 <split [1030/369]>  Bootstrap3
4 <split [1030/375]>  Bootstrap4
5 <split [1030/378]>  Bootstrap5
6 <split [1030/1030]> Apparent  
  • apparent = TRUE:除自助样本外,额外附加原始全量数据作为第一个“重抽样样本”(称为 Apparent 样本),方便与真实训练集对比。
注记常见的重抽样方法及其适用场景
方法 原理 数据利用率 偏差 方差 计算成本 典型用途
简单拆分(Hold‑out) 随机划分训练/测试集(如 80%/20%) 低(部分数据仅用于评估) 较高(受单次拆分影响) 高(随机拆分波动大) 极低 快速基线评估、大数据
k 折交叉验证(k‑fold CV) 将数据分为 k 组,轮流以 k‑1 组训练,1 组测试,取平均 中等(所有数据均被用于训练和测试,但每次模型训练时仍只使用部分) 较低(k 较大时更接近全数据) 中等 中等(k 次训练) 模型选择、超参数调优
留一交叉验证(LOOCV) k= n 的特殊 k 折交叉验证 高(每次训练用 n‑1 个样本) 极低 极高(训练集高度重复,导致预测值强相关) 极高(n 次训练,小样本时可用) 小样本、线性模型
重复 k 折交叉验证 多次重复 k 折(每次随机重新划分) 较高 低(平均后方差显著降低) 较高(k×重复次数) 追求稳定评估
自助法(Bootstrap) 有放回抽取 n 个样本,多次(B 次) 中等(每个自助样本约含 63.2% 独特观测) 略乐观(训练集与原始集重叠) 较低 高(B 次训练) 模型性能分布、置信区间、小样本统计推断

35.7 时间序列数据

TODO:

35.8 空间数据

小节 28.6 中,我们介绍了空间数据的核心特征:空间自相关性。空间数据中的观测值通常受地理位置影响,导致相邻位置的数据点更相似(如气温、污染水平等),而远距离的数据点差异较大。这种空间相关性违反了统计独立性的假设,需在数据划分时加以考虑。

与时序数据类似,空间数据类型所采用的重采样方案模拟了初始数据的划分流程。我们可以采用以下方法为训练集构建分组:

  • 聚类方法:这类方法对数据进行划分,使得拥有空间坐标的数据点在同一聚类内彼此间距离,小于其与其他聚类中点的距离。
  • 基于网格的方法:构建由大小均等区块组成的网格(矩形或六边形),使其覆盖所有训练集样本点。

在分组的基础上,就可以使用与之前介绍的重采样方法相同的方式进行训练集的划分。

每种方法都可以通过添加缓冲区来增强,以排除过于邻近的样本被使用。
  • 留一组外重采样:该方法生成与分组数量相同的重采样样本。在每一次重采样中,某一组的数据点被用作评估集,其余分组用于训练模型。该流程针对每一组重复执行。
  • V 折交叉验证:该方法将每个分组分配至 V 个元分组(即折)中的某一个。在 V 轮迭代的每一轮中,留出其中一折作为评估集,剩余折用于训练模型。
  • 重复 V 折交叉验证:该方法更换随机数种子重新执行分组流程,从而实现多轮 V 折交叉验证。
library(sf) # 用于处理空间数据
library(spatialsample) # 用于空间数据的重采样
library(tidysdm) # 用于空间数据的初始划分和分组

ames_sf <- ames |>
  st_as_sf(coords = c("Longitude", "Latitude"), crs = 4326) # 将数据框转换为空间数据框

# 空间数据的初始划分
set.seed(318)
ames_block_buff_split <- tidysdm::spatial_initial_split(
  ames_sf,
  prop = 0.2, # 20%数据用于测试,80%数据用于训练
  strategy = spatial_block_cv, # 设定策略
  method = "continuous", # 连续型变量
  n = 25, # 划分为25个空间块
  square = FALSE, # 使用非方形块-可以让块的形状更贴合真实的地理分布
  buffer = 250 # 设置训练集和测试集之间的缓冲区为250米,可以进一步降低相邻位置样本相互泄漏信息的风险
)

ames_tr <- training(ames_block_buff_split)
ames_te <- testing(ames_block_buff_split)

# 空间数据重采样与数据分割的参数基本相同
set.seed(652)
ames_rs <- spatialsample::spatial_block_cv(
  ames_tr,
  v = 10, # V折交叉验证
  method = "continuous", # 连续型变量
  n = 25, # 划分为25个空间块
  square = FALSE, # 使用非方形块-可以让块的形状更贴合真实的地理分布
  buffer = 250 # 设置训练集和测试集之间的缓冲区为250米,可以进一步降低相邻位置样本相互泄漏信息的风险
)

# 展示空间数据重采样对象的图形网格
autoplot(ames_rs, cex = 1, show_grid = T)

# 绘制单次划分结果,从而查看分析集与评估集的空间分布情况
autoplot(ames_rs$splits[[1]], cex = 1)

35.9 分组或多层数据

小节 28.5 中,我们介绍了分组或多层数据的核心特征:组内相关性。分组或多层数据中的观测值通常受组别影响,导致同一组别的数据点更相似,而不同组别的数据点差异较大。这种组内相关性违反了统计独立性的假设,需在数据划分时加以考虑。

假设训练集包含10000条样本,对应100名顾客,我们可以采用 V 折交叉验证确定哪些顾客划入分析集、哪些划入评估集。

和初始划分规则保持一致,属于特定顾客的所有样本行都会被划分至同一个分区(即分析集或者评估集)。

# load and spend data
data(Orthodont, package = "nlme")
set.seed(93)
orth_split <- group_initial_split(Orthodont, group = Subject, prop = 2 / 3)
orth_tr <- training(orth_split)
orth_te <- testing(orth_split)

# define resampling method
dplyr::n_distinct(orth_tr$Subject) # 计算训练集中的唯一组数
[1] 18
set.seed(714)
# 核心逻辑:生成分组交叉验证计划后,检查每个折的评估集中包含多少个不同的 Subject,用于验证分组的正确性或了解数据分布。
orth_rs <-
  group_vfold_cv(orth_tr, group = Subject, v = 10) |> # 创建10折交叉验证重采样样本,确保同一Subject的所有数据点都在同一数据集中
  mutate(
    # 对 splits 中的每一个重抽样划分对象 x,执行匿名函数 \(x) ...
    num_subjects = map_int(
      splits,
      # assessment(x) 提取评估集(测试集合)。
      \(x) dplyr::n_distinct(assessment(x)$Subject)
    )
  )

orth_rs
# Group 10-fold cross-validation 
# A tibble: 10 × 3
   splits         id         num_subjects
   <list>         <chr>             <int>
 1 <split [64/8]> Resample01            2
 2 <split [64/8]> Resample02            2
 3 <split [64/8]> Resample03            2
 4 <split [64/8]> Resample04            2
 5 <split [64/8]> Resample05            2
 6 <split [64/8]> Resample06            2
 7 <split [64/8]> Resample07            2
 8 <split [64/8]> Resample08            2
 9 <split [68/4]> Resample09            1
10 <split [68/4]> Resample10            1

35.10 性能评估

在进行重采样时,我们通常会生成多个训练/测试分割,然后在每个分割上训练模型并评估其性能。这种做法有助于我们更全面地了解模型在不同数据子集上的表现。

tidymodels提供了一系列函数来简化性能评估的过程。我们可以使用fit_resamples()函数在每个重采样分割上训练模型,并使用collect_metrics()函数收集性能指标。

fit_resamples()函数:

  • 前两个参数分别是模型对象(model specification)和预处理器(preprocessor)。
  • 其中,第一个参数可以是一个workflow对象,包含了模型和预处理步骤。

我们使用混凝土数据,创建10折交叉验证重采样对象,并在每个分割上训练线性回归模型,最后收集性能指标。

# 创建10折交叉验证重采样对象
set.seed(426)
concrete_split <- initial_split(concrete, prop = 3 / 4)
concrete_tr <- training(concrete_split)
concrete_te <- testing(concrete_split)

concrete_rs <- vfold_cv(concrete_tr, v = 10)

# 使用简单线性回归模型进行性能评估
lm_spec <- linear_reg() |> set_engine("lm") |> set_mode("regression") # 定义线性回归模型规范

# 使用`fit_resamples()`函数在每个重采样分割上训练模型
concrete_res <- fit_resamples(
  lm_spec,
  compressive_strength ~ ., # 目标变量为Sale_Price,使用所有其他变量作为预测变量
  resamples = concrete_rs, # 使用10折交叉验证重采样对象
  # control = control_resamples(save_pred = TRUE) # 保存每个分割的预测结果
)
concrete_res
# Resampling results
# 10-fold cross-validation 
# A tibble: 10 × 4
   splits           id     .metrics         .notes          
   <list>           <chr>  <list>           <list>          
 1 <split [694/78]> Fold01 <tibble [2 × 4]> <tibble [0 × 4]>
 2 <split [694/78]> Fold02 <tibble [2 × 4]> <tibble [0 × 4]>
 3 <split [695/77]> Fold03 <tibble [2 × 4]> <tibble [0 × 4]>
 4 <split [695/77]> Fold04 <tibble [2 × 4]> <tibble [0 × 4]>
 5 <split [695/77]> Fold05 <tibble [2 × 4]> <tibble [0 × 4]>
 6 <split [695/77]> Fold06 <tibble [2 × 4]> <tibble [0 × 4]>
 7 <split [695/77]> Fold07 <tibble [2 × 4]> <tibble [0 × 4]>
 8 <split [695/77]> Fold08 <tibble [2 × 4]> <tibble [0 × 4]>
 9 <split [695/77]> Fold09 <tibble [2 × 4]> <tibble [0 × 4]>
10 <split [695/77]> Fold10 <tibble [2 × 4]> <tibble [0 × 4]>

以上输出看起来很像我们的重采样对象。其中新增了一些列:

  • .metrics 包含数据框,其中存储了特定重采样样本的性能统计信息。
  • .notes 列则包含模型产生的任何警告或错误信息,这 10 个模型拟合均未产生此类信息。
  • 值得注意的是,如果出现错误, fit_resamples() 不会停止计算。

在得到性能评估结果后,我们可以使用collect_**()系列函数收集所有重采样分割的性能指标。

  • collect_metrics():收集所有重采样分割的性能指标,并返回一个数据框,其中包含每个指标的平均值和标准误。
  • collect_note():收集所有重采样分割的警告或错误信息,并返回一个数据框,其中包含每个分割的警告或错误信息。
# 收集性能指标
concrete_metrics <- collect_metrics(concrete_res) # 总体评估结果
concrete_metrics
# A tibble: 2 × 6
  .metric .estimator  mean     n std_err .config        
  <chr>   <chr>      <dbl> <int>   <dbl> <chr>          
1 rmse    standard   6.50     10  0.239  pre0_mod0_post0
2 rsq     standard   0.852    10  0.0117 pre0_mod0_post0
concrete_metrics_by_resample <- collect_metrics(concrete_res, summarize = FALSE) # 按重采样分割收集性能指标
concrete_metrics_by_resample
# A tibble: 20 × 5
   id     .metric .estimator .estimate .config        
   <chr>  <chr>   <chr>          <dbl> <chr>          
 1 Fold01 rmse    standard       6.76  pre0_mod0_post0
 2 Fold01 rsq     standard       0.846 pre0_mod0_post0
 3 Fold02 rmse    standard       6.93  pre0_mod0_post0
 4 Fold02 rsq     standard       0.804 pre0_mod0_post0
 5 Fold03 rmse    standard       6.65  pre0_mod0_post0
 6 Fold03 rsq     standard       0.830 pre0_mod0_post0
 7 Fold04 rmse    standard       7.17  pre0_mod0_post0
 8 Fold04 rsq     standard       0.863 pre0_mod0_post0
 9 Fold05 rmse    standard       5.48  pre0_mod0_post0
10 Fold05 rsq     standard       0.916 pre0_mod0_post0
11 Fold06 rmse    standard       7.14  pre0_mod0_post0
12 Fold06 rsq     standard       0.857 pre0_mod0_post0
13 Fold07 rmse    standard       6.45  pre0_mod0_post0
14 Fold07 rsq     standard       0.867 pre0_mod0_post0
15 Fold08 rmse    standard       6.38  pre0_mod0_post0
16 Fold08 rsq     standard       0.819 pre0_mod0_post0
17 Fold09 rmse    standard       7.17  pre0_mod0_post0
18 Fold09 rsq     standard       0.814 pre0_mod0_post0
19 Fold10 rmse    standard       4.92  pre0_mod0_post0
20 Fold10 rsq     standard       0.901 pre0_mod0_post0

35.10.1 fit_resample()中的控制参数

35.10.1.1 并行处理

  • futuretidymodels使用future包进行并行计算。通过设置不同的计划(plan()),可以在本地或远程计算资源上运行模型训练和评估。
  • windows系统中,默认的并行处理方式是multisession,也只能使用这种方法。
  • 一旦我们确定了并行处理的计划,就可以在运行可并行执行的操作之前,执行启动并行计算的代码。
* mirai包也提供了一个统一的接口,用于在不同的计算环境中运行并行计算任务,实际我们使用future就好。
# future
library(future)
parallelly::availableCores() # 查看可用的CPU核心数
system 
    12 
plan(multisession, workers = 4) # 设置并行处理计划,使用4个工作进程

35.10.1.2 其他选项

  • control_resamples():用于控制fit_resamples()的行为。常用参数 save_pred表示是否保存每个重采样分割的预测结果,默认为FALSE
  • fit_resamples()函数还可以接受其他参数,如metrics用于指定性能指标,可以是单一指标,也可以是多个指标集。
ctrl <- control_resamples(save_pred = TRUE) # 保存每个分割的预测结果
reg_metrics <- metric_set(rmse, rsq, mae) # 定义性能指标集,包括均方根误差(RMSE)、决定系数(R²)、Lin's Concordance Correlation Coefficient (CCC)和平均绝对误差(MAE)

concrete_opts_res <- fit_resamples(
  lm_spec,
  compressive_strength ~ .,
  resamples = concrete_rs,
  metrics = reg_metrics, # 使用自定义的性能指标集
  control = ctrl # 使用自定义的控制参数
)
concrete_opts_res
# Resampling results
# 10-fold cross-validation 
# A tibble: 10 × 5
   splits           id     .metrics         .notes           .predictions     
   <list>           <chr>  <list>           <list>           <list>           
 1 <split [694/78]> Fold01 <tibble [3 × 4]> <tibble [0 × 4]> <tibble [78 × 4]>
 2 <split [694/78]> Fold02 <tibble [3 × 4]> <tibble [0 × 4]> <tibble [78 × 4]>
 3 <split [695/77]> Fold03 <tibble [3 × 4]> <tibble [0 × 4]> <tibble [77 × 4]>
 4 <split [695/77]> Fold04 <tibble [3 × 4]> <tibble [0 × 4]> <tibble [77 × 4]>
 5 <split [695/77]> Fold05 <tibble [3 × 4]> <tibble [0 × 4]> <tibble [77 × 4]>
 6 <split [695/77]> Fold06 <tibble [3 × 4]> <tibble [0 × 4]> <tibble [77 × 4]>
 7 <split [695/77]> Fold07 <tibble [3 × 4]> <tibble [0 × 4]> <tibble [77 × 4]>
 8 <split [695/77]> Fold08 <tibble [3 × 4]> <tibble [0 × 4]> <tibble [77 × 4]>
 9 <split [695/77]> Fold09 <tibble [3 × 4]> <tibble [0 × 4]> <tibble [77 × 4]>
10 <split [695/77]> Fold10 <tibble [3 × 4]> <tibble [0 × 4]> <tibble [77 × 4]>
# 收集性能指标
concrete_opts_metrics <- collect_metrics(concrete_opts_res)
concrete_opts_metrics
# A tibble: 3 × 6
  .metric .estimator   mean     n std_err .config        
  <chr>   <chr>       <dbl> <int>   <dbl> <chr>          
1 mae     standard    8.22     10  0.267  pre0_mod0_post0
2 rmse    standard   10.5      10  0.307  pre0_mod0_post0
3 rsq     standard    0.623    10  0.0257 pre0_mod0_post0
  • concrete_opts_metrics的输出中,.metric列表示性能指标,.predictions列表示每个重采样分割的预测结果,要提取每个分割的预测结果,可以使用collect_predictions()函数或augment()函数。
  • 提取预测结果后,我们就可以进行进一步分析,以了解模型还可以在哪些方面进行改进。
    • probably包提供了一个简单的接口,用于获取观测值与预测值的图表(即回归“校准”图):
# collect_predictions()函数用于收集每个重采样分割的预测结果,并返回一个数据框,其中包含每个分割的预测结果和实际值。
heldout_predictions <- collect_predictions(concrete_opts_res) # 收集每个重采样分割的预测结果
heldout_predictions
# A tibble: 772 × 5
   .pred id     compressive_strength  .row .config        
   <dbl> <chr>                 <dbl> <int> <chr>          
 1  40.6 Fold01                40.8      8 pre0_mod0_post0
 2  29.3 Fold01                29.2     14 pre0_mod0_post0
 3  32.7 Fold01                37.4     27 pre0_mod0_post0
 4  24.1 Fold01                20.7     38 pre0_mod0_post0
 5  61.6 Fold01                55.2     53 pre0_mod0_post0
 6  12.3 Fold01                 9.74    71 pre0_mod0_post0
 7  38.2 Fold01                59.1     83 pre0_mod0_post0
 8  12.5 Fold01                15.0    119 pre0_mod0_post0
 9  13.9 Fold01                14.2    123 pre0_mod0_post0
10  37.8 Fold01                29.6    125 pre0_mod0_post0
# ℹ 762 more rows
# augment()函数用于将每个重采样分割的预测结果与原始数据框进行合并,并返回一个数据框,其中包含每个分割的预测结果、实际值和其他相关信息。
augment(concrete_opts_res)
# A tibble: 772 × 11
   compressive_strength .pred   .resid cement blast_furnace_slag fly_ash water
                  <dbl> <dbl>    <dbl>  <dbl>              <dbl>   <dbl> <dbl>
 1                 32.8 45.5  -12.7      168                42.1    164.  122.
 2                 53.7 54.8   -1.13     190               190        0   228 
 3                 32.8 32.8    0.0740   160               188      146   203 
 4                 12.6  9.69   2.86     190.                0      125.  167.
 5                 37.3 47.4  -10.1      219.                0      124.  158.
 6                  7.4 16.9   -9.53     169.               42.2    124.  158.
 7                 10.4  8.13   2.22     123.              184.       0   204.
 8                 40.8 40.6    0.205    380                95        0   228 
 9                 45.9 36.3    9.60     214.                0      174.  155.
10                 29.4 28.8    0.580    156               178      187   221 
# ℹ 762 more rows
# ℹ 4 more variables: superplasticizer <dbl>, coarse_aggregate <dbl>,
#   fine_aggregate <dbl>, age <int>
# probably包提供了一个简单的接口,用于获取观测值与预测值的图表(即回归“校准”图)
library(probably)
cal_plot_regression(concrete_opts_res)

上图显示,模型的结果不算太差,但存在相当大的异常值,且模型在预测较大值时表现不佳。我们可以进一步分析这些异常值,找出模型的不足之处,并尝试改进模型。

35.11 常见问题及解答

  1. 每次重采样得到的结果不同是否不好?

重采样的目的是观察当数据发生变化时模型流程会产生何种变动。很有可能部分关键估计值在不同重采样样本之间存在差异。例如,当采用算法筛选预测变量时,你可能会得到 B 组不同的预测变量清单,这属于正常现象。这能够让你感知到模型流程中该环节存在多大的噪声。

  1. 训练出的 b 个模型会怎样?应该保留哪一个?

重采样过程中构建的 B 组模型仅用于评估模型的数据拟合性能,完成评估后无需继续留存;但可保留该批模型用于模型诊断。依托多组重复模型分析模型各模块的波动特征具备较高参考价值。

  1. 什么是嵌套重抽样?

这是一种额外增加一重重采样的方案(小节 36.4 中有详细说明)。举例来说,假设你正在使用十折交叉验证。在十次迭代的每一轮内部,你可能额外执行二十次自助抽样迭代(该操作会对分析集重采样)。该过程最终会训练出两百个互不相同的模型。

嵌套重抽样通过两层独立的重抽样分离调参与评估:

  • 外层循环(Outer loop):只用于最终评估模型性能(产生一个无偏的泛化误差估计)。
  • 内层循环(Inner loop):在外层训练集内部再进行一个独立的重抽样,用于选择最优超参数(或特征、模型等),并将这个“最优配置”在外层测试集上测试。+

嵌套重抽样常用于:

  • 比较多种模型类型(如随机森林 vs. 提升树 vs. 线性模型),再报告最终获胜模型的实际泛化能力。
  • 特征工程包含自动化选择(如 step_* 调优),需要一次完整的“端到端”评估。
  • 论文或学术环境下要求无偏估计,不满足于单一交叉验证。

对于日常的工程型调参且数据充足,使用单一交叉验证 + 最终留出测试集通常足够;嵌套重抽样的主要价值在于提供严格的无偏泛化误差估计,防止模式选择过拟合。