使用插入符号训练多个模型时使用相同的 trainControl 对象进行交叉验证是否可以进行准确的模型比较?

Does using the same trainControl object for cross-validation when training multiple models with caret allow for accurate model comparison?

我最近一直在研究 R 包 caret,并且有一个关于训练期间模型的再现性和比较的问题,我还没有完全确定。

我的意图是每个 train 调用以及每个结果模型都使用相同的交叉验证拆分,以便交叉验证的初始存储结果与样本外估计具有可比性在构建过程中计算的模型。

我见过的一种方法是您可以在每次 train 调用之前指定种子:

set.seed(1)
model <- train(..., trControl = trainControl(...))
set.seed(1)
model2 <- train(..., trControl = trainControl(...))
set.seed(1)
model3 <- train(..., trControl = trainControl(...))

但是,在 train 调用之间共享一个 trainControl 对象是否意味着它们通常使用相同的重采样和索引,或者我是否必须显式地将 seeds 参数传递给功能。列车控制对象在使用时是随机函数还是在声明时设置?

我目前的方法是:

set.seed(1)
train_control <- trainControl(method="cv", ...)
model1 <- train(..., trControl = train_control)
model2 <- train(..., trControl = train_control)
model3 <- train(..., trControl = train_control)

这些列车呼叫是否会使用相同的分组并具有可比性,还是我必须采取进一步的措施来确保这一点?即在创建 trainControl 对象时指定种子,或者在每次训练之前调用 set.seed?或者两者兼而有之?

希望这是有道理的,而不是一堆垃圾。任何帮助


可以找到我查询的代码项目here。阅读它可能更容易,您就会明白。

除非使用我推荐的 index 参数明确说明,否则在定义 trainControl 期间不会创建 CV 折叠。这些可以使用专门的 caret 函数之一创建:

createFolds
createMultiFolds
createTimeSlices
groupKFold

也就是说,在 trainControl 定义之前使用特定种子不会导致相同的 CV 折叠。

示例:

library(caret)
library(tidyverse)

set.seed(1)
trControl = trainControl(method = "cv",
                         returnResamp = "final",
                         savePredictions = "final")

创建两个模型:

knnFit1 <- train(iris[,1:4], iris[,5],
                 method = "knn",
                 preProcess = c("center", "scale"),
                 tuneLength = 10,
                 trControl = trControl)

ldaFit2 <- train(iris[,1:4], iris[,5],
                 method = "lda",
                 tuneLength = 10,
                 trControl = trControl)

检查相同的索引是否在相同的折叠中:

knnFit1$pred %>%
  left_join(ldaFit2$pred, by = "rowIndex") %>%
  mutate(same = Resample.x == Resample.y) %>%
  {all(.$same)}
#FALSE

如果您在每次 train 调用之前设置相同的种子

set.seed(1)
knnFit1 <- train(iris[,1:4], iris[,5],
                 method = "knn",
                 preProcess = c("center", "scale"),
                 tuneLength = 10,
                 trControl = trControl)

set.seed(1)
ldaFit2 <- train(iris[,1:4], iris[,5],
                 method = "lda",
                 tuneLength = 10,
                 trControl = trControl)


set.seed(1)
rangerFit3 <- train(iris[,1:4], iris[,5],
                 method = "ranger",
                 tuneLength = 10,
                 trControl = trControl)


knnFit1$pred %>%
  left_join(ldaFit2$pred, by = "rowIndex") %>%
  mutate(same = Resample.x == Resample.y) %>%
  {all(.$same)}

knnFit1$pred %>%
  left_join(rangerFit3$pred, by = "rowIndex") %>%
  mutate(same = Resample.x == Resample.y) %>%
  {all(.$same)}

折叠中将使用相同的索引。但是,在使用并行计算时我不会依赖这种方法。因此,为了确保使用相同的数据拆分,最好使用 index/indexOut 参数到 trainControl.

手动定义它们

当您手动设置索引参数时,折叠将是相同的,但这并不能确保使用相同方法制作的模型是相同的,因为大多数方法都包含某种随机过程。因此,为了完全可重现,建议在每次列车调用之前也设置种子。当 运行 并行获得完全可重现的模型时,需要设置 trainControlseeds 参数。