【问题标题】:How to remove training data from party:::ctree models?如何从party:::ctree 模型中删除训练数据?
【发布时间】:2011-11-01 06:28:23
【问题描述】:

我创建了几个我想要经常评估的 ctree 模型(大约 40 到 80 个)。

一个问题是模型对象非常大(40 个模型需要超过 2.8G 的内存),而且在我看来,它们存储了训练数据,可能是 modelname@data 和 modelname@responses,而不仅仅是与预测新数据相关的信息。

大多数其他 R 学习包都有可配置的选项,是否将数据包含在模型对象中,但我在文档中找不到任何提示。我还尝试通过

分配空的 ModelEnv 对象
modelname@data <- new("ModelEnv")

但对相应 RData 文件的大小没有影响。

任何人都知道 ctree 是否真的存储了训练数据,以及如何从 ctree 模型中删除与新预测无关的所有数据,以便我可以将其中的许多数据放入内存中?

非常感谢,

斯蒂芬


感谢您的反馈,这已经很有帮助了。

我用dputstr深入观察了对象,发现模型中没有包含训练数据,但是有一个responses槽,里面好像有训练标签和行名.无论如何,我注意到每个节点对于每个训练样本都有一个权重向量。在检查了一段时间代码后,我在谷歌上搜索了一下,在party NEWS 日志中找到了以下评论:

         CHANGES IN party VERSION 0.9-13 (2007-07-23)

o   update `mvt.f'

o   improve the memory footprint of RandomForest objects
    substancially (by removing the weights slots from each node).

事实证明,party包中有一个C函数可以删除这些权重,称为R_remove_weights,定义如下:

SEXP R_remove_weights(SEXP subtree, SEXP removestats) {
    C_remove_weights(subtree, LOGICAL(removestats)[0]);
    return(R_NilValue);
}

它也可以正常工作:

# cc is my model object

sum(unlist(lapply(slotNames(cc), function (x)  object.size(slot(cc, x)))))
# returns: [1] 2521256
save(cc, file="cc_before.RData")

.Call("R_remove_weights", cc@tree, TRUE, PACKAGE="party")
# returns NULL and removes weights and node statistics

sum(unlist(lapply(slotNames(cc), function (x)  object.size(slot(cc, x)))))
# returns: [1] 1521392
save(cc, file="cc_after.RData")

如您所见,它大大减小了对象大小,从大约 2.5MB 减少到 1.5MB。

不过,奇怪的是,相应的 RData 文件异常庞大,而且对它们没有任何影响:

$ ls -lh cc*
-rw-r--r-- 1 user user 9.6M Aug 24 15:44 cc_after.RData
-rw-r--r-- 1 user user 9.6M Aug 24 15:43 cc_before.RData

解压文件显示 2.5MB 的对象占用了将近 100MB 的空间:

$ cp cc_before.RData cc_before.gz
$ gunzip cc_before.gz 
$ ls -lh cc_before*
-rw-r--r-- 1 user user  98M Aug 24 15:45 cc_before

任何想法,可能是什么原因造成的?

【问题讨论】:

  • 如果您可以将dput 的结果发布在一个小样本对象上会有所帮助。如果您使用str,您可以查看对象中是否存储了训练数据。
  • 注意:我重新标记了问题并删除了“派对”标签,因为它在 SO 上没有明确定义,当然不限于 R 包。我添加了内存管理,因为这似乎是主要动机。

标签: r memory-management classification decision-tree


【解决方案1】:

我找到了手头问题的解决方案,所以如果有人遇到同样的问题,我会写下这个答案。我将描述我的过程,所以可能有点漫无边际,请耐心等待。

在毫无头绪的情况下,我想到了对插槽进行核化并移除权重以使对象尽可能小并至少节省一些内存,以防找不到修复。所以我删除了@data@responses 作为开始,没有它们,预测仍然很好,但对.RData 文件大小没有影响。

我反过来创建并清空 ctree 模型,只是将树插入其中:

> library(party)

## create reference predictions for the dataset
> predictions.org <- treeresponse(c1, d)

## save tree object for reference
save(c1, "testSize_c1.RData")

检查原始对象的大小:

$ ls -lh testSize_c1.RData 
-rw-r--r-- 1 user user 9.6M 2011-08-25 14:35 testSize_c1.RData

现在,让我们创建一个空的 CTree 并只复制树:

## extract the tree only 
> c1Tree <- c1@tree

## create empty tree and plug in the extracted one 
> newCTree <- new("BinaryTree")
> newCTree@tree <- c1Tree

## save tree for reference 
save(newCTree, file="testSize_newCTree.RData")

这个新的树对象现在更小了:

$ ls -lh testSize_newCTree.RData 
-rw-r--r-- 1 user user 108K 2011-08-25 14:35 testSize_newCTree.RData

但是不能用来预测:

## predict with the new tree
> predictions.new <- treeresponse(newCTree, d)
Error in object@cond_distr_response(newdata = newdata, ...) : 
  unused argument(s) (newdata = newdata)

我们没有设置@cond_distr_response,这可能会导致错误,所以把原来的也复制一遍,然后再次尝试预测:

## extract cond_distr_response from original tree
> cdr <- c1@cond_distr_response
> newCTree@cond_distr_response <- cdr

## save tree for reference 
save(newCTree, file="testSize_newCTree_with_cdr.RData")

## predict with the new tree
> predictions.new <- treeresponse(newCTree, d)

## check correctness
> identical(predictions.org, predictions.new)
[1] TRUE

这很好用,但现在 RData 文件的大小又回到了原来的值:

$ ls -lh testSize_newCTree_with_cdr.RData 
-rw-r--r-- 1 user user 9.6M 2011-08-25 14:37 testSize_newCTree_with_cdr.RData

只需打印插槽,将其显示为绑定到环境的函数:

> c1@cond_distr_response
function (newdata = NULL, mincriterion = 0, ...) 
{
    wh <- RET@get_where(newdata = newdata, mincriterion = mincriterion)
    response <- object@responses
    if (any(response@is_censored)) {
        swh <- sort(unique(wh))
        RET <- vector(mode = "list", length = length(wh))
        resp <- response@variables[[1]]
        for (i in 1:length(swh)) {
            w <- weights * (where == swh[i])
            RET[wh == swh[i]] <- list(mysurvfit(resp, weights = w))
        }
        return(RET)
    }
    RET <- .Call("R_getpredictions", tree, wh, PACKAGE = "party")
    return(RET)
}
<environment: 0x44e8090>

所以最初问题的答案似乎是对象的方法将环境绑定到它,然后将其与对象一起保存在相应的 RData 文件中。这也可以解释为什么读取 RData 文件时会加载几个包。

因此,要摆脱环境,我们无法复制方法,但没有它们我们也无法预测。比较“肮脏”的解决方案是模拟原始方法的功能并直接调用底层 C 代码。在对源代码进行了一些挖掘之后,这确实是可能的。正如上面复制的代码所示,我们需要调用get_where,它确定输入到达的树的终端节点。然后我们需要调用R_getpredictions 来确定来自该终端节点对每个输入样本的响应。棘手的部分是我们需要以正确的输入格式获取数据,因此必须调用 ctree 中包含的数据预处理:

## create a character string of the formula which was used to fit the free
## (there might be a more neat way to do this)
> library(stringr)
> org.formula <- str_c(
                   do.call(str_c, as.list(deparse(c1@data@formula$response[[2]]))),
                   "~", 
                   do.call(str_c, as.list(deparse(c1@data@formula$input[[2]]))))

## call the internal ctree preprocessing 
> data.dpp <- party:::ctreedpp(as.formula(org.formula), d)

## create the data object necessary for the ctree C code
> data.ivf <- party:::initVariableFrame.df(data.dpp@menv@get("input"), 
                                           trafo = ptrafo)

## now call the tree traversal routine, note that it only requires the tree
## extracted from the @tree slot, not the whole object
> nodeID <- .Call("R_get_nodeID", c1Tree, data.ivf, 0, PACKAGE = "party")

## now determine the respective responses
> predictions.syn <- .Call("R_getpredictions", c1Tree, nodeID, PACKAGE = "party")

## check correctness
> identical(predictions.org, predictions.syn)
[1] TRUE

我们现在只需要保存提取的树和公式字符串就可以预测新数据:

> save(c1Tree, org.formula, file="testSize_extractedObjects.RData")

我们可以进一步删除上面更新问题中描述的不必要的权重:

> .Call("R_remove_weights", c1Tree, TRUE, PACKAGE="party")
> save(c1Tree, org.formula, file="testSize_extractedObjects__removedWeights.RData")

现在让我们再看看文件大小:

$ ls -lh testSize_extractedObjects*
-rw-r--r-- 1 user user 109K 2011-08-25 15:31 testSize_extractedObjects.RData
-rw-r--r-- 1 user user  43K 2011-08-25 15:31 testSize_extractedObjects__removedWeights.RData

最后,代替(压缩)9.6M,只需要 43K 即可使用该模型。我现在应该能够在我的 3G 堆空间中容纳任意数量的空间。万岁!

【讨论】:

    【解决方案2】:

    您正在寻找的是删除插槽。提醒一句:考虑到 party 函数如何与对象一起工作,这可能相当危险。

    不过,请查看slotNames(yourModel)。您也可以尝试object.size(slot(yourModel), slotNameOfInterest) 来检查不同插槽的大小。您可以轻松地创建一个排序表来确定每个槽中对象的大小。

    无论如何,data 的插槽是ModelEnvFormula(我将称之为“MEF”)对象。您可以创建一个虚拟 MEF:dummyMEF &lt;- ModelEnvFormula(1 ~ 1),然后将其分配给 dataslot(yourModel, "data") &lt;- dummyMEF

    这将摧毁那个特定的插槽。您应该看看是否有其他插槽在存储方面引起头痛 - object.size() 功能将提供帮助。我同意能够从模型对象中省略训练数据是件好事。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-11-04
      • 1970-01-01
      • 1970-01-01
      • 2023-01-08
      • 2020-01-30
      • 1970-01-01
      • 2021-01-16
      • 2014-06-25
      相关资源
      最近更新 更多