评估大型Random Forest小子集性能的方法及R实现
方法可行性判断
- 针对树的数量(num.trees) 这个参数,你提出的「先训练大模型再抽子集评估小规格模型性能」的思路完全成立,几乎没有偏差。随机森林的单棵树是独立同分布训练的,从1500棵已训练完成的树里无放回抽取500棵生成预测结果,和你从头单独训练500棵树的随机森林的性能期望完全一致。你在holdout集上多次重复无放回抽样取平均性能,得到的估计结果甚至比单次训练500棵树的结果更稳定,完全可以用来定位满足性能要求的最小树数,省掉多轮重训的成本。
注意不要用有放回抽样的方式选树,无放回抽样的结果才和从头训练对应规模模型的分布完全对齐。
- 针对max tree depth、minimum node size 这类控制单棵树生长规则的参数,这个思路完全不适用。这类参数是在树的训练分裂过程中生效的硬约束,你训练完成的深树已经走完了全部节点分裂流程,没法从已有的深树里裁剪出和「从头按指定max_depth训练出的浅树」结构、分裂逻辑完全一致的子树,硬剪枝得到的结果和真实约束下训练的模型性能偏差很大,估计结果不可靠。
- 这类树结构相关的参数调优成本其实很低:你只要先通过大模型抽子集的方法确定好「足够用的最小树数」,在这个固定树数的基础上,只需要测试3-5组常见的深度、最小节点大小参数即可,不需要反复遍历树数量维度,整体调参成本比全网格搜索低90%以上。
R语言实现示例
基于ranger包的原生实现
library(ranger) library(mlbench) # 加载示例分类数据集 data(Sonar) # 拆分训练集与holdout测试集 set.seed(123) test_idx <- sample(nrow(Sonar), size = round(0.2*nrow(Sonar))) train_data <- Sonar[-test_idx, ] test_data <- Sonar[test_idx, ] # 第一步:训练初始大模型,树数设到预估的足够大上限,不限制树深 big_rf <- ranger( formula = Class ~ ., data = train_data, num.trees = 1500, max.depth = NULL, min.node.size = 1, probability = FALSE ) # 第二步:编写子集评估函数,无放回抽树计算holdout集性能 eval_subset_rf <- function(rf_model, sample_tree_num, test_set, truth_col = "Class") { # 无放回抽取指定数量的树索引 sampled_tree_ids <- sample(rf_model$num.trees, size = sample_tree_num, replace = FALSE) # 调用ranger预测接口,指定使用抽中的树输出结果 pred_res <- predict( rf_model, data = test_set, tree.indices = sampled_tree_ids, type = "response" ) # 计算准确率,可替换为你需要的AUC、F1等其他指标 acc <- mean(pred_res$predictions == test_set[[truth_col]]) return(acc) } # 第三步:批量评估不同树规模的性能 tree_num_candidates <- seq(100, 1200, by = 100) repeat_times <- 20 # 每个树规模重复抽样20次计算稳定均值 perf_table <- data.frame() for (nt in tree_num_candidates) { rep_acc <- replicate(repeat_times, eval_subset_rf(big_rf, nt, test_data)) perf_table <- rbind(perf_table, data.frame( n_trees = nt, mean_accuracy = mean(rep_acc), sd_accuracy = sd(rep_acc) )) } # 查看perf_table即可找到性能不再显著提升的最小树数阈值
基于parsnip包的实现
parsnip是tidymodels生态的模型统一接口,训练完的大模型只需要提取底层的ranger对象,就可以复用上面的全部评估逻辑:
library(parsnip) # 定义大规格随机森林模型 big_rf_spec <- rand_forest( trees = 1500, min_n = 1 # 设为最小值保证树充分生长 ) %>% set_engine("ranger") %>% set_mode("classification") # 在训练集上拟合模型 set.seed(123) big_rf_parsnip <- fit(big_rf_spec, Class ~ ., data = train_data) # 提取底层ranger模型对象 big_rf_raw <- extract_fit_engine(big_rf_parsnip) # 后续直接调用之前写的eval_subset_rf函数,传入big_rf_raw即可完成评估
实操建议
- 训练初始大模型的时候,树的数量不用设得过于夸张,通常设为你日常经验收敛值的2-3倍即可(比如平时500棵树收敛,设1000-1500足够),避免第一次训练浪费过多时间。
- 初始大模型不要提前加深度、最小节点数、mtry的约束,先保证单棵树充分生长,等确定完最优树数之后,再测试树结构相关参数即可。
- 不要尝试用这个抽子集的方法评估mtry、分裂规则这类训练时生效的超参数,这类参数的变化会改变所有树的结构,训完的模型无法模拟不同参数下的训练结果。
内容的提问来源于stack exchange,提问作者Bryan Shalloway
相关产品推荐
相关产品推荐

