【问题标题】:GBM R function: get variable importance separately for each classGBM R函数:分别获取每个类的变量重要性
【发布时间】:2015-06-20 15:24:07
【问题描述】:

我正在使用 R(gbm 包)中的 gbm 函数来拟合多类分类的随机梯度提升模型。我只是想分别获得每个预测变量对每个类的重要性,就像这张来自Hastie book (the Elements of Statistical Learning) (p. 382) 的图片一样。

但是,函数 summary.gbm 仅返回预测变量的总体重要性(它们的重要性在所有类中平均)。

有谁知道如何获取相对重要性值?

【问题讨论】:

  • @germcd ??我不明白这会如何改变问题......
  • @germcd 您是否建议为需要预测的目标变量的每个类别建立不同的模型?我真的不明白这是怎么回事。
  • 感谢您提供这本书的链接 - 看起来很有趣。
  • 看来这个库可以提供一种解决方法(python):github.com/marcotcr/lime

标签: r machine-learning classification data-mining gbm


【解决方案1】:

我认为简短的回答是,在第 379 页,Hastie 提到他使用了 MART,这似乎只适用于 Splus。

我同意 gbm 包似乎不允许看到单独的相对影响。如果您对多类问题感兴趣,您可能会通过为每个类构建一个一对多的 g​​bm,然后从每个模型中获取重要性度量来获得非常相似的东西。

假设你的班级是 a、b、c 和 d。您对 a 与其他模型进行建模,并从该模型中获取重要性。然后,您对 b 与其他模型进行建模,并从该模型中获取重要性。等等。

【讨论】:

  • 快3年过去了,但至今没有答案。除了您在此答案中提出的有效解决方法之外,您还有其他提示吗?
  • 不幸的是,这些年来我没有深入研究它,我主要是在 Python 中工作
  • python是否提供这种解决方案?
  • @Tchotchke 您如何看待我对每棵树使用错误减少的方法(请参阅下面的答案)?我在我的工作中使用它,如果您有任何想法,我将不胜感激。
【解决方案2】:

希望此功能对您有所帮助。例如,我使用了 ElemStatLearn 包中的数据。该函数确定列的类是什么,将数据拆分为这些类,对每个类运行 gbm() 函数并绘制这些模型的条形图。

# install.packages("ElemStatLearn"); install.packages("gbm")
library(ElemStatLearn)
library(gbm)

set.seed(137531)

# formula: the formula to pass to gbm()
# data: the data set to use
# column: the class column to use
classPlots <- function (formula, data, column) {
    
    class_column <- as.character(data[,column])
    class_values <- names(table(class_column))
    class_indexes <- sapply(class_values, function(x) which(class_column == x))
    split_data <- lapply(class_indexes, function(x) marketing[x,])
    object <- lapply(split_data, function(x) gbm(formula, data = x))
    rel.inf <- lapply(object, function(x) summary.gbm(x, plotit=FALSE))
    
    nobjs <- length(class_values)
    for( i in 1:nobjs ) {
        tmp <- rel.inf[[i]]
        tmp.names <- row.names(tmp)
        tmp <- tmp$rel.inf
        names(tmp) <- tmp.names
        
        barplot(tmp, horiz=TRUE, col='red',
                xlab="Relative importance", main=paste0("Class = ", class_values[i]))
    }
    rel.inf
}

par(mfrow=c(1,2))
classPlots(Income ~ Marital + Age, data = marketing, column = 2)

`

【讨论】:

  • 这个例子的解释是年龄对男性收入的影响很大,婚姻状况对女性收入的影响很大
  • 非常感谢您提供这个有用的答案。在我接受答案/奖励赏金之前,让我详细玩一下你的命令。另外,从理论的角度来看,我想知道比较变量对两个独立模型的影响是否有效......
  • 其实是同一个模型,只是在数据的两个子集上。为什么这会无效?
  • 我们在这两种情况下都使用相同的算法,当然,但最终我们获得了两个不同的模型,因为数据集不同。如果您比较最终方程(Boosting 类似于广义加法模型),它们将不一样。因此,我们并不是在比较变量在预测给定独特模型的每个类别时的相对重要性。
  • 同意 - 当我在上面提出这个解决方案时,它是您正在寻找的解决方案的近似值 - 我不认为它与 Hastie 所做的完全一样,但它可能已经足够接近(并且是我能想到的在 R 中开箱即用的最简单的事情)
【解决方案3】:

我对 gbm 包如何计算重要性做了一些研究,它基于 ErrorReduction,它包含在结果的 trees 元素中,可以通过 pretty.gbm.trees() 访问。通过对每个变量的所有树求此 ErrorReduction 的总和来获得相对影响。对于多类问题,模型中实际上有 n.trees*num.classes 树。因此,如果有 3 个类,您可以计算每三棵树上每个变量的 ErrorReduction 的总和,以获得一个类的重要性。我编写了以下函数来实现这一点,然后绘制结果:

按类获取变量重要性

RelInf_ByClass <- function(object, n.trees, n.classes, Scale = TRUE){
  library(dplyr)
  library(purrr)
  library(gbm)
  Ext_ErrRed<- function(ptree){
    ErrRed <- ptree %>% filter(SplitVar != -1) %>% group_by(SplitVar) %>% 
      summarise(Sum_ErrRed = sum(ErrorReduction))
  }
  trees_ErrRed <- map(1:n.trees, ~pretty.gbm.tree(object, .)) %>% 
    map(Ext_ErrRed)

  trees_by_class <- split(trees_ErrRed, rep(1:n.classes, n.trees/n.classes)) %>% 
    map(~bind_rows(.) %>% group_by(SplitVar) %>% 
          summarise(rel_inf = sum(Sum_ErrRed)))
  varnames <- data.frame(Num = 0:(length(object$var.names)-1),
                         Name = object$var.names)
  classnames <- data.frame(Num = 1:object$num.classes, 
                           Name = object$classes)
  out <- trees_by_class %>% bind_rows(.id = "Class") %>%  
    mutate(Class = classnames$Name[match(Class,classnames$Num)],
    SplitVar = varnames$Name[match(SplitVar,varnames$Num)]) %>%
    group_by(Class) 
  if(Scale == FALSE){
    return(out)
    } else {
    out <- out %>% mutate(Scaled_inf = rel_inf/max(rel_inf)*100)
    }
}

按类绘制变量重要性

在我的实际用途中,我有 40 多个特征,因此我提供了一个选项来指定要绘制的特征数量。如果我想为每个类单独排序图,我也不能使用分面,这就是我使用gridExtra 的原因。

plot_imp_byclass <- function(df, n) {
  library(ggplot2)
  library(gridExtra)
  plot_imp_class <- function(df){
    df %>% arrange(rel_inf) %>% 
      mutate(SplitVar = factor(SplitVar, levels = .$SplitVar)) %>% 
      ggplot(aes(SplitVar, rel_inf))+
      geom_segment(aes(x = SplitVar, 
                       xend = SplitVar, 
                       y = 0, 
                       yend = rel_inf))+
      geom_point(size=3, col = "cyan") + 
      coord_flip()+
      labs(title = df$Class[[1]], x = "Variable", y = "Importance")+
      theme_classic()+
      theme(plot.title = element_text(hjust = 0.5))
  }

  df %>% top_n(n, rel_inf) %>% split(.$Class) %>% 
    map(plot_imp_class) %>% map(ggplotGrob) %>% 
    {grid.arrange(grobs = .)}
}

试试看

gbm_iris <- gbm(Species~., data = iris)
imp_byclass <- RelInf_ByClass(gbm_iris, length(gbm_iris$trees), 
                              gbm_iris$num.classes, Scale = F)
plot_imp_byclass(imp_byclass, 4)

如果对所有类的结果求和,似乎给出的结果与内置的 relative.influence 函数相同。

relative.influence(gbm_iris)
# n.trees not given. Using 100 trees.
# Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
# 0.00000     51.88684   2226.88017    868.71085 

imp_byclass %>% group_by(SplitVar) %>% summarise(Overall_rel_inf = sum(rel_inf))
# A tibble: 3 x 2
# SplitVar     Overall_rel_inf
# <fct>                  <dbl>
#   1 Petal.Length          2227. 
# 2 Petal.Width            869. 
# 3 Sepal.Width             51.9

【讨论】:

  • 谢谢,我会在接下来的几周内仔细查看。同时 +1 分享您的代码! gbm 包已被孤立,似乎最新版本位于 GitHub 上,为 gbm3:github.com/gbm-developers/gbm3
  • 太棒了!我在 gbm3 页面上打开了一个 github issue 所以希望他们会在新版本中添加这个功能
  • @Antoine 你有机会看看这个吗?我已经开始在我的工作中使用它,所以如果您发现任何问题,我将不胜感激!
  • 我刚结束两周的假期。短期内我没有时间看这个,但我会尽快尝试
猜你喜欢
  • 2018-05-16
  • 2017-07-03
  • 2017-12-23
  • 2015-12-06
  • 2018-07-20
  • 2016-05-23
  • 2014-07-17
  • 1970-01-01
  • 2015-06-22
相关资源
最近更新 更多