为什么Caret训练会占用这么多内存?

21

当我只使用 glm 进行训练时,一切正常,并且我甚至没有接近耗尽内存。但是当我运行 train(..., method='glm') 时,我会用完内存。

这是因为 train 在交叉验证的每次迭代(或者其他 trControl 过程)中存储了大量数据吗?我查看了 trainControl,但无法找到如何防止这种情况...有什么提示吗?我只关心性能摘要和可能的预测响应。

(我知道这与存储参数调整网格搜索的每个迭代的数据无关,因为我相信 glm 没有网格。)


1
能否提供一个小的可重现的示例让其他人尝试一下? - Roman Luštrik
这已经是7年前的事情了,很可能这个问题在之前的版本中就已经被修复了。请问这个问题发生在哪个版本中?能否确认一下哪个版本解决了这个问题? - smci
3个回答

39
问题有两个方面。第一,train不仅通过glm()来拟合模型,而且它将对该模型进行自助抽样。即使使用默认值,train()也会执行25个bootstrap样本,这与问题二结合起来是您的问题的原因之一。第二,train()只是使用glm()函数及其默认值。而这些默认值将存储模型框架(?glm的参数model=TRUE),其中包括数据的一个模型框架样式的副本。由train()返回的对象已经在$trainingData中存储了数据的副本,$finalModel中的"glm"对象也有实际数据的副本。
此时,通过简单地使用train()运行glm()将产生25个完全展开的model.frame和原始数据的副本,这些都需要在重采样过程中保存在内存中。无论是并发还是连续保存,从代码的快速查看中并不清楚重采样是如何进行的,因为重采样在lapply()调用中进行。还会有25份原始数据的副本。
一旦重采样完成,返回的对象将包含2份原始数据和一个完整的model.frame的副本。如果您的训练数据相对于可用RAM较大或包含要在model.frame中展开的许多因素,则只是在携带数据副本时,就可能使用大量内存。
如果您在训练调用中添加model=FALSE,可能会有所改善。以下是在?glm中使用clotting数据的示例:
clotting <- data.frame(u = c(5,10,15,20,30,40,60,80,100),
                       lot1 = c(118,58,42,35,27,25,21,19,18),
                       lot2 = c(69,35,26,21,18,16,13,12,12))
require(caret)
那么。
> m1 <- train(lot1 ~ log(u), data=clotting, family = Gamma, method = "glm", 
+             model = TRUE)
Fitting: parameter=none 
Aggregating results
Fitting model on full training set
> m2 <- train(lot1 ~ log(u), data=clotting, family = Gamma, method = "glm",
+             model = FALSE)
Fitting: parameter=none 
Aggregating results
Fitting model on full training set
> object.size(m1)
121832 bytes
> object.size(m2)
116456 bytes
> ## ordinary glm() call:
> m3 <- glm(lot1 ~ log(u), data=clotting, family = Gamma)
> object.size(m3)
47272 bytes
> m4 <- glm(lot1 ~ log(u), data=clotting, family = Gamma, model = FALSE)
> object.size(m4)
42152 bytes

返回的对象大小不同,训练期间的内存使用会降低。降低程度取决于train()内部是否在重采样过程中保留所有model.frame的副本。

train()返回的对象也比glm()返回的对象大得多 - 正如@DWin在评论中提到的那样。

要进一步了解,请仔细研究代码或给caret的维护者Max Kuhn发送电子邮件,以了解减少内存占用的选项。


1
好答案(像您通常的一样,Gavin)。只需添加glm对象的大小:`> m3 = glm(lot1〜log(u),data = clotting,family = Gamma)
object.size(m3) 47272字节`
- IRTFM
@Dwin 谢谢,说得好。我会将该输出添加到答案中,并注明出处。 - Gavin Simpson
谢谢,我已经请Max在这里添加答案了。 - Yang

32

Gavin的回答很好。我建立这个函数是为了方便使用,而不是为了速度或效率 [1]

首先,在有很多预测变量时,使用公式接口可能会出现问题。这是R核心可以解决的问题;公式方法需要保留一个非常大但稀疏的terms()矩阵,而R有一些软件包可以有效地处理这个问题。例如,当n = 3,000,p = 2,000时,使用公式接口的3-Tree随机森林模型对象大小增加了1.5倍,执行时间增加了23倍(282秒对比12秒)。

其次,您不必保留训练数据(请参见trainControl()中的returnData参数)。

此外,由于R没有任何真正的共享内存基础设施,Gavin关于保留在内存中的数据副本数量是正确的。基本上,为每个重新采样创建一个列表,并使用lapply()处理该列表,然后仅返回重新采样的估计值。另一种选择是顺序地制作数据的一个副本(用于当前重新采样),进行所需的操作,然后重复进行剩余的迭代。那里的问题是I/O和无法进行任何并行处理。 [2]

如果您有大型数据集,我建议使用非公式界面(即使实际模型,如glm,最终也会使用公式)。此外,对于大型数据集,train()保存了重新采样指数以供resamples()和其他函数使用。您可能也可以将它们删除。

Yang - 通过str(data)了解更多关于数据的信息将会很有帮助,这样我们就可以理解其维度和其他方面(例如,许多级别的因素等)。

希望这有所帮助,

Max

[1] 我应该指出,我们尽可能少地拟合模型。"子模型"技巧用于许多模型,例如 pls、gbm、rpart、earth 等等。此外,当一个模型具有公式和非公式接口时(例如 lda()earth()),我们默认使用非公式接口。

[2] 偶尔我会有重新启动 train() 函数的疯狂冲动。使用 foreach 可以解决其中的一些问题。


欢迎来到SO @Max,感谢您提供有用的答案。我很高兴您编写了train()以便于使用;最近我一直在使用它进行随机梯度提升,并且自己编写了一些调整代码,但是转换到carettrain()后真是大开眼界! - Gavin Simpson
我正在提供自己的模型矩阵和响应向量(必须这样做才能使用findCorrelation),因此我不使用任何模型的公式接口。什么是子模型技巧? - Yang
1
你提到的那些包是用来解决公式内存使用问题的吗?"R有一些包可以有效地解决这个问题"。 - Eduardo
正确。特别是公式接口部分可能会导致内存问题。 - Filippo Mazza

7
我认为上面的回答有点过时了。现在 caret 和 caretEnsemble 包在 trainControl 中包括一个额外的参数“trim”。Trim 最初设置为 FALSE,但将其更改为 TRUE 将显著减小模型大小。你应该将其与 returnData=FALSE 结合使用,以使模型尺寸最小化。如果你正在使用模型集成,你还应该在贪心/堆叠集成 trainControl 中指定这两个参数。
对于我的情况,在集成控制中使用这些参数后,一个 1.6GB 的模型缩小到了约 500MB,进一步使用贪心集成控制中的参数,它缩小到了约 300MB。
Ensemble_control_A9 <- trainControl(trim=TRUE, method = "repeatedcv", number = 3, repeats = 2, verboseIter = TRUE, returnData = FALSE, returnResamp = "all", classProbs = TRUE, summaryFunction = twoClassSummary, savePredictions = TRUE, allowParallel = TRUE, sampling = "up")


Ensemble_greedy_A5 <- caretEnsemble(Ensemble_list_A5, metric="ROC",  trControl=trainControl(number=2, trim=TRUE, returnData = FALSE, summaryFunction=twoClassSummary, classProbs=TRUE))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接