在随机森林中获取个体树的重要性

14

问题: 是否有一种方法可以从randomForest对象中提取每个单独的CART模型的变量重要性?

rf_mod$forest似乎没有这个信息,文档也没有提及。


在R的randomForest包中,整个CART模型森林的平均变量重要性可以通过importance(rf_mod)来获取。
library(randomForest)

df <- mtcars

set.seed(1)
rf_mod = randomForest(mpg ~ ., 
                      data = df, 
                      importance = TRUE, 
                      ntree = 200)

importance(rf_mod)

       %IncMSE IncNodePurity
cyl  6.0927875     111.65028
disp 8.7730959     261.06991
hp   7.8329831     212.74916
drat 2.9529334      79.01387
wt   7.9015687     246.32633
qsec 0.7741212      26.30662
vs   1.6908975      31.95701
am   2.5298261      13.33669
gear 1.5512788      17.77610
carb 3.2346351      35.69909

我们也可以使用getTree来提取单个树结构。这是第一棵树。
head(getTree(rf_mod, k = 1, labelVar = TRUE))
  left daughter right daughter split var split point status prediction
1             2              3        wt        2.15     -3   18.91875
2             0              0      <NA>        0.00     -1   31.56667
3             4              5        wt        3.16     -3   17.61034
4             6              7      drat        3.66     -3   21.26667
5             8              9      carb        3.50     -3   15.96500
6             0              0      <NA>        0.00     -1   19.70000

一种解决方法是多生成CARTs(即ntree = 1),获取每个树的变量重要性,并平均得到结果的%IncMSE

# number of trees to grow
nn <- 200

# function to run nn CART models 
run_rf <- function(rand_seed){
  set.seed(rand_seed)
  one_tr = randomForest(mpg ~ ., 
                        data = df, 
                        importance = TRUE, 
                        ntree = 1)
  return(one_tr)
}

# list to store output of each model
l <- vector("list", length = nn)
l <- lapply(1:nn, run_rf)

提取、平均和比较步骤。
# extract importance of each CART model 
library(dplyr); library(purrr)
map(l, importance) %>% 
  map(as.data.frame) %>% 
  map( ~ { .$var = rownames(.); rownames(.) <- NULL; return(.) } ) %>% 
  bind_rows() %>% 
  group_by(var) %>% 
  summarise(`%IncMSE` = mean(`%IncMSE`)) %>% 
  arrange(-`%IncMSE`)

    # A tibble: 10 x 2
   var   `%IncMSE`
   <chr>     <dbl>
 1 wt        8.52 
 2 cyl       7.75 
 3 disp      7.74 
 4 hp        5.53 
 5 drat      1.65 
 6 carb      1.52 
 7 vs        0.938
 8 qsec      0.824
 9 gear      0.495
10 am        0.355

# compare to the RF model above
importance(rf_mod)

       %IncMSE IncNodePurity
cyl  6.0927875     111.65028
disp 8.7730959     261.06991
hp   7.8329831     212.74916
drat 2.9529334      79.01387
wt   7.9015687     246.32633
qsec 0.7741212      26.30662
vs   1.6908975      31.95701
am   2.5298261      13.33669
gear 1.5512788      17.77610
carb 3.2346351      35.69909

我希望能够直接从randomForest对象中提取每个树的变量重要性,而不需要通过完全重新运行RF来实现可再现的累积变量重要性图,如此一类,以及下面显示的mtcars的图表。这里是最小示例
我知道单棵树的变量重要性并没有统计学意义,并且我也不想孤立地解释树。我需要它们来进行可视化和传达的信息是:随着森林中的树增加,变量重要性度量会在稳定之前跳动。

enter image description here


1
您可以检查treerpart是否接受由randomForest构建的树,如果是,则可以使用这些软件包中的变量重要性函数。 - lmo
你好,请问你是如何实现上面的交互式情节的呢?谢谢! - MarionEtp
3个回答

10
训练randomForest模型时,重要性得分是为整个森林计算并直接存储在对象中的。不保留树特定得分,因此无法直接从randomForest对象中检索。
不幸的是,您关于必须逐步构建森林的说法是正确的。好消息是,randomForest对象是自包含的,您不需要实现自己的run_rf。相反,您可以使用stats::update重新拟合随机森林模型,并使用randomForest::grow一次添加一个额外的树:
## Starting with a random forest having a single tree,
##   grow it 9 times, one tree at a time
rfs <- purrr::accumulate( .init = update(rf_mod, ntree=1),
                          rep(1,9), randomForest::grow )

## Retrieve the importance scores from each random forest
imp <- purrr::map( rfs, ~importance(.x)[,"%IncMSE"] )

## Combine all results into a single data frame
dplyr::bind_rows( !!!imp )
# # A tibble: 10 x 10
#      cyl  disp    hp  drat    wt   qsec    vs     am    gear  carb
#    <dbl> <dbl> <dbl> <dbl> <dbl>  <dbl> <dbl>  <dbl>   <dbl> <dbl>
#  1 0      18.8  8.63 1.05   0     1.17  0     0       0      0.194
#  2 0      10.0 46.4  0.561  0    -0.299 0     0       0.543  2.05 
#  3 0      22.4 31.2  0.955  0    -0.199 0     0       0.362  5.1
#  4 1.55   24.1 23.4  0.717  0    -0.150 0     0       0.272  5.28
#  5 1.24   22.8 23.6  0.573  0    -0.178 0     0      -0.0259 4.98
#  6 1.03   26.2 22.3  0.478  1.25  0.775 0     0      -0.0216 4.1
#  7 0.887  22.5 22.5  0.406  1.79 -0.101 0     0      -0.0185 3.56
#  8 0.776  19.7 21.3  0.944  1.70  0.105 0     0.0225 -0.0162 3.11
#  9 0.690  18.4 19.1  0.839  1.51  1.24  1.01  0.02   -0.0144 2.77
# 10 0.621  18.4 21.2  0.937  1.32  1.11  0.910 0.0725 -0.114  2.49

数据框显示每增加一棵树时特征重要性的变化。这是您绘图示例的右侧面板。左侧面板的树本身可以从最终的随机森林中检索,该随机森林由dplyr::last( rfs )给出。


1
优雅的解决方案 +1 - Maurits Evers
2
我在学习的过程中发现,如果不重写randomForest,这个问题是无法回答的。然而,这个解决方案是最有创意和最有前途的。感谢分享! - Rich Pauloo

4
免责声明:这不是真正的答案,但太长了无法作为评论发布。如果不适当将删除。
虽然我(认为我)理解您的问题,但老实说,我不确定您的问题是否从统计/机器学习的角度讲得通。以下内容基于我显然有限的RF和CART的理解。也许我的评论会带来一些见解。
让我们从一些关于随机森林(RF)变量重要性的一般理论开始,引用自Hastie, Tibshirani, Friedman, The Elements of Statistical Learning第593页(我加粗):
在每个树的每个分裂点上,分裂准则的改进是归因于分裂变量的重要性度量,并且针对每个变量在森林中的所有树中单独累积。[...] 随机森林还使用oob样本构建不同的变量重要性度量,显然是为了衡量每个变量的预测强度。
因此,RF中的变量重要性度量被定义为一个在所有树上累积的度量
在传统的单分类树(CART)中,变量重要性通过测量节点不纯度的基尼指数来描述(例如,参见如何在使用R中的rpart时测量/排名“变量重要性”?Carolin Strobl的博士论文)。
存在更复杂的衡量CART模型中变量重要性的方法;例如,在rpart中:
总体变量重要性的度量是每个分裂的拆分质量度量值的总和,其中它是主要变量的分裂,加上所有它是代理变量的分裂的拆分质量度量值乘以goodness *(调整一致性)。 在打印输出中,这些值按比例缩放为100,并显示四舍五入的值,省略任何比例小于1%的变量。
因此,这里的底线是:至少从单个分类树中比较变量测量值与应用于RF等基于集合的方法的变量重要性测量值之间的比较并不容易(在最坏的情况下,它们将毫无意义)。
这让我想问:为什么要从RF模型中提取单个树的变量重要性测量值?即使您提出了一种计算单个树的变量重要性的方法,我认为它们不会非常有意义,也不必“收敛”到集合累积值。

嗨@Maurits,感谢您的评论和对此的关注。我知道随机森林变量重要性只有作为聚合度量才有意义,因为任何一个树都会倾向于选择用于分割的预测子集。我的动机是创建可解释的可视化,显示随着森林中树的数量增加,变量重要性测量值会跳来跳去,然后稳定下来。这在我编辑的帖子上方已经展示了一个示例,并且可以在此处作为依据获得 - Rich Pauloo
1
randomForest中,每棵树的重要性似乎是在底层C代码中计算的。我不确定ranger或其他实现方式是否也是如此。randomForest的好处在于getTree函数,它返回森林中单个树的树结构。这可能比我有时间做的更多,但没关系,我只是想把这个问题提出来,看看SO的用户们是否能轻松解决。 - Rich Pauloo

3
我们可以简化它,方法是:
library(tidyverse)
out <- map(seq_len(nn),  ~ 
          run_rf(.x) %>% 
          importance) %>%
       reduce(`+`) %>% 
       magrittr::divide_by(nn)

你是否使用 load/save 等方法获取模型对象。如果是为了重复使用构建好的模型,则可以使用 save 存储模型,在评分时再使用 load 加载。如果你将其存储在 HDFS 上,可以通过 pickling/unpickling 使用 webHDFS 进行操作。我已经轻松地完成了这两个步骤,没有遇到太多问题。 - akrun
我想象人们提供一个 randomForest 对象,然后包函数获取该对象并计算 ntree 的累积 %IncMSE。不确定这是否回答了你的问题... - Rich Pauloo
@RichPauloo 我理解你的问题。以pickle /序列化对象形式存储的模型对象是通过反pickle /反序列化检索的,然后将函数传递以提取值。 - akrun
这完全正确,也是这个问题的动机所在!在第三个代码块中,我编写了run_rf函数来生成ntree = 1的RF,因为然后我可以在每棵树上使用importance。我的问题的核心是:我是否可以从代码块1中的rf_modntree = 200的RF)中获取这些“树对树”得分,而无需首先运行代码块3? - Rich Pauloo
1
首先,run_rf是一个函数而不是一个模型对象。因此,它是不可复制的。 - akrun
显示剩余5条评论

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