获取 RandomForest 中单个树的重要性

Posted

技术标签:

【中文标题】获取 RandomForest 中单个树的重要性【英文标题】:Obtain importance of individual trees in a RandomForest 【发布时间】:2019-09-22 21:56:47 【问题描述】:

问题:有没有办法从randomForest 对象中提取每个单独的 CART 模型的变量重要性?

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


在 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

一种解决方法是增长许多 CART(即 - 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 以便于重现cumulative variable importance plots like this one,下面显示的是mtcars。 Minimal example here。

我知道一棵树的变量重要性在统计上没有意义,我也无意孤立地解释树。我希望它们用于可视化和传达随着森林中树木的增加,可变重要性度量在稳定之前会跳来跳去。

【问题讨论】:

您可以检查treerpart 是否会接受randomForest 构建的树,如果是,请使用这些包中的变量重要性函数。 【参考方案1】:

在训练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 我一路上了解到,如果不重写 randomForest,就无法回答这个问题。然而,这个解决方案是最有创意和最有前途的。感谢分享!【参考方案2】:

免责声明:这不是一个真正的答案,但作为评论发布太长了。如果认为不合适,将删除。

虽然我(认为我)理解您的问题,但老实说,我不确定您的问题从统计/机器学习的角度是否有意义。以下是基于我对 RF 和 CART 的明显有限的理解。也许我的评论帖子会带来一些见解。

让我们从Hastie, Tibshirani, Friedman, The Elements of Statistical Learning, p. 中关于变量重要性的一些一般随机森林 (RF) 理论开始。 593(黑体字):

在每棵树的每次分裂中,分裂标准的改进是 归因于分裂变量的重要性度量,并且 是累积的 为每个变量分别遍历森林中的所有树木。 [...] 随机森林还使用 oob 样本来构建不同的变量重要性度量,显然是为了衡量每个变量的预测强度。

因此,RF 中的变量重要性度量被定义在所有树上累积的度量


在传统的单分类树 (CART) 中,变量重要性通过衡量节点杂质的基尼指数来表征(参见例如 How to measure/rank “variable importance” when using CART? (specifically using rpart from R) 和 Carolin Strobl's PhD thesis)

在类似 CART 的模型中存在更复杂的衡量变量重要性的方法;例如rpart:

变量重要性的总体衡量标准是拆分优度的总和 测量作为主要变量的每个拆分,加上优度*(调整后 协议)适用于它作为代理的所有拆分。在打印输出中,这些被缩放为总和 到 100 并显示四舍五入的值,忽略比例较小的任何变量 不到 1%。


因此,这里的底线如下:至少,将来自单个分类树的可变度量与应用于集成的可变重要性度量进行比较并不容易(在最坏的情况下也没有意义)基于 RF 的方法。

这让我问:为什么你想从 RF 模型中提取单个树的可变重要性度量?即使您想出了一种从单个树计算变量重要性的方法,我相信它们也不会很有意义,并且它们不必“收敛”到整体累积值。

【讨论】:

嗨@Maurits,感谢您对此的评论和兴趣。我熟悉这样一个事实,即 RF 的可变重要性仅作为一种聚合度量才有意义,因为任何一棵树都会偏向为拆分选择的预测变量子集。我的动机是创建可解释的可视化,显示随着森林中树木数量的增加,变量重要性度量会跳来跳去,然后稳定下来。上面我编辑的帖子中显示了一个示例,available as a gist here。 randomForest 中,似乎每棵树的重要性是在底层C 代码中计算的。我不确定ranger 或其他实现。 randomForest 的好处是 getTree 函数返回森林中单个树的树结构。这可能比我有时间做的工作更多,没关系,我只是想把它扔给 SO 的居民,以防它实际上很容易。【参考方案3】:

我们可以简化为

library(tidyverse)
out <- map(seq_len(nn),  ~ 
          run_rf(.x) %>% 
          importance) %>%
       reduce(`+`) %>% 
       magrittr::divide_by(nn)

【讨论】:

这是对代码清晰度的明显提升。谢谢!但是,我想制作一个包,供人们提供 randomForest 对象。这种方法仍然需要从头开始重新运行模型...我不确定randomForest 是否支持我的要求。 你是用load/save等获取模型对象吗?如果是构建模型后的重用目的,将模型存储为save,然后在评分时,load .如果您将其存储在 hdfs 上,您可以使用webHDFS 通过酸洗/解酸来完成此操作。我已经轻松完成了这两项工作,没有太多问题 我想象人们提供了一个randomForest 对象,包函数采用该对象并计算%IncMSE 作为ntree 的函数。不确定这是否能回答您的问题... @RichPauloo 我明白你的问题。存储为腌制/序列化对象的模型对象通过 unpickling/unserialized 检索,然后传递函数以提取值 首先,run_rf 是一个函数而不是模型对象。所以,它是不​​可重现的

以上是关于获取 RandomForest 中单个树的重要性的主要内容,如果未能解决你的问题,请参考以下文章

如何访问 Spark RandomForest 中的单个预测?

Scikit learn + Random Forest - 单棵树的特征

将随机森林变成决策树 - 在 R 中使用 randomForest 包

如何使用 Ranger 按类别获取特征重要性?

有没有办法在 R 中获取用于随机森林树分类的实例?

sklearn 的 RandomForest 中如何计算特征重要性?