树模型是如何计算特征重要性的

AI助手已提取文章相关产品:

前言

在特征的选择过程中,如果学习器(基学习器)是树模型的话,可以根据特征的重要性来筛选有效的特征。本文是对Random Forest、GBDT、XGBoost如何用在特征选择上做一个简单的介绍。

各种模型的特征重要性计算

Random Forests

  • 袋外数据错误率评估 
    RF的数据是boostrap的有放回采样,形成了袋外数据。因此可以采用袋外数据(OOB)错误率进行特征重要性的评估。 
    袋外数据错误率定义为:袋外数据自变量值发生轻微扰动后的分类正确率与扰动前分类正确率的平均减少量。 
    (1)对于每棵决策树,利用袋外数据进行预测,将袋外数据的预测误差记录下来,其每棵树的误差为vote1,vote2,…,voteb 
    (2)随机变换每个预测变量,从而形成新的袋外数据,再利用袋外数据进行验证,其每个变量的误差是votel1,votel2,…votelb

  • Gini系数评价指标 (和GBDT的方法相同)

GBDT

在sklearn中,GBDT和RF的特征重要性计算方法是相同的,都是基于单棵树计算每个特征的重要性,探究每个特征在每棵树上做了多少的贡献,再取个平均值。 
利用随机森林对特征重要性进行评估写的比较清楚了,但是还是有一点小的问题,比较ensemble模型 零碎记录中对源代码的解析可以看出,前者计算中丢失了weighted_n_node_samples。

  • 利用Gini计算特征的重要性 
    单棵树上特征的重要性定义为:特征在所有非叶节在分裂时加权不纯度的减少,减少的越多说明特征越重要。 
    沿用参考博客里的符号,我们将变量重要性评分(variable importance measures)用VIMVIM来表示,将Gini指数用GIGI来表示 
    节点m的Gini指数的计算公式为: 

    GIm=1−∑k=1|K|p2mkGIm=1−∑k=1|K|pmk2


    其中,K表示有K个类别,pmkpmk表示节点m中类别k所占的比例。直观地说,就是随便从节点m中随机抽取两个样本,其类别标记不一致的概率。 
    特征XjXj在节点mm的重要性可以表示为加权不纯度的减少 

    VIMGinijm=Nm×GIm−Nl×GIl−Nr×GIrVIMjmGini=Nm×GIm−Nl×GIl−Nr×GIr


    其中,GIlGIl和GIrGIr分别表示分枝后两个新节点的Gini指数。NmNm、NlNl、NrNr表示节点m、左孩子节点l和右孩子节点r的样本数。 
    如果,特征XjXj在决策树i中出现的节点在集合M中,那么XjXj在第i颗树的重要性为 

    VIMij=∑m∈MVIMjmVIMij=∑m∈MVIMjm

~~如果这样还不是很清晰的话,我们来举个例子(李航统计学习方法表5.1)

import numpy as np
from sklearn.tree import DecisionTreeClassifier
from sklearn.externals.six import StringIO
from sklearn import tree
import pydotplus

clf = DecisionTreeClassifier()
x = [[1,1,1,1,1,2,2,2,2,2,3,3,3,3,3],
     [1,1,2,2,1,1,1,2,1,1,1,1,2,2,1],
     [1,1,1,2,1,1,1,2,2,2,2,2,1,1,1],
     [1,2,2,1,1,1,2,2,3,3,3,2,2,3,1]
     ]
y =  [1,1,2,2,1,1,1,2,2,2,2,2,2,2,1]
x = np.array(x)
x = np.transpose(x)
clf.fit(x,y)
print clf.feature_importances_
feature_name = ['A1','A2','A3','A4']
target_name = ['1','2']
dot_data = StringIO()
tree.export_graphviz(clf,out_file = dot_data,feature_names=feature_name,
                     class_names=target_name,filled=True,rounded=True,
                     special_characters=True)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_pdf("WineTree.pdf")
print('Visible tree plot saved as pdf.')

可以得到树的划分过程图 
这里写图片描述 
特征A3的重要性为 0.48×15−0.4444×9−0=3.20040.48×15−0.4444×9−0=3.2004 
特征A2的重要性为 0.4444×9−0−0=3.99960.4444×9−0−0=3.9996 
特征A1和A4的重要性都为0 
所以该棵树上所有节点总的加权不纯度减少量为 3.2004+3.9996=7.33.2004+3.9996=7.3 
对其进行归一化操作可以得到A1、A2、A3、A4的特征重要性为

[ 0. 0.55555556 0.44444444 0. ]

这是单棵树上特征的计算方法,推广到n棵树 

VIMj=∑i=1nVIMijVIMj=∑i=1nVIMij


最后,把所有求得的重要性评分做一个归一化处理即可。 

VIMj=VIMj∑i=1cVIMiVIMj=VIMj∑i=1cVIMi


其中cc为特征的总个数

 

XGBoost

关于XGBoost中特征重要性计算相关代码出现在xgboost/core.py L1203

    def get_score(self, fmap='', importance_type='weight'):
        """Get feature importance of each feature.
        Importance type can be defined as:
            'weight' - the number of times a feature is used to split the data across all trees.
            'gain' - the average gain of the feature when it is used in trees
            'cover' - the average coverage of the feature when it is used in trees
        Parameters
        ----------
        fmap: str (optional)
           The name of feature map file
        """

        if importance_type not in ['weight', 'gain', 'cover']:
            msg = "importance_type mismatch, got '{}', expected 'weight', 'gain', or 'cover'"
            raise ValueError(msg.format(importance_type))

        # if it's weight, then omap stores the number of missing values
        if importance_type == 'weight':
            # do a simpler tree dump to save time
            trees = self.get_dump(fmap, with_stats=False)

            fmap = {}
            for tree in trees:
                for line in tree.split('\n'):
                    # look for the opening square bracket
                    arr = line.split('[')
                    # if no opening bracket (leaf node), ignore this line
                    if len(arr) == 1:
                        continue

                    # extract feature name from string between []
                    fid = arr[1].split(']')[0].split('<')[0]

                    if fid not in fmap:
                        # if the feature hasn't been seen yet
                        fmap[fid] = 1
                    else:
                        fmap[fid] += 1

            return fmap

        else:
            trees = self.get_dump(fmap, with_stats=True)

            importance_type += '='
            fmap = {}
            gmap = {}
            for tree in trees:
                for line in tree.split('\n'):
                    # look for the opening square bracket
                    arr = line.split('[')
                    # if no opening bracket (leaf node), ignore this line
                    if len(arr) == 1:
                        continue

                    # look for the closing bracket, extract only info within that bracket
                    fid = arr[1].split(']')

                    # extract gain or cover from string after closing bracket
                    g = float(fid[1].split(importance_type)[1].split(',')[0])

                    # extract feature name from string before closing bracket
                    fid = fid[0].split('<')[0]

                    if fid not in fmap:
                        # if the feature hasn't been seen yet
                        fmap[fid] = 1
                        gmap[fid] = g
                    else:
                        fmap[fid] += 1
                        gmap[fid] += g

            # calculate average value (gain/cover) for each feature
            for fid in gmap:
                gmap[fid] = gmap[fid] / fmap[fid]
            return gmap

在XGBoost中提供了三种特征重要性的计算方法:

‘weight’ - the number of times a feature is used to split the data across all trees. 
‘gain’ - the average gain of the feature when it is used in trees 
‘cover’ - the average coverage of the feature when it is used in trees

简单来说 
weight就是在所有树中特征用来分割的节点个数总和; 
gain就是特征用于分割的平均增益 
cover 的解释有点晦涩,在[R-package/man/xgb.plot.tree.Rd]有比较详尽的解释:(https://github.com/dmlc/xgboost/blob/f5659e17d5200bd7471a2e735177a81cb8d3012b/R-package/man/xgb.plot.tree.Rd):the sum of second order gradient of training data classified to the leaf, if it is square loss, this simply corresponds to the number of instances in that branch. Deeper in the tree a node is, lower this metric will be。实际上coverage可以理解为被分到该节点的样本的二阶导数之和,而特征度量的标准就是平均的coverage值。

还是举李航书上那个例子,我们用不同颜色来表示不同的特征,绘制下图 
这里写图片描述

import xgboost as xgb
import numpy as np
x = [[1,1,1,1,1,2,2,2,2,2,3,3,3,3,3],
     [1,1,2,2,1,1,1,2,1,1,1,1,2,2,1],
     [1,1,1,2,1,1,1,2,2,2,2,2,1,1,1],
     [1,2,2,1,1,1,2,2,3,3,3,2,2,3,1]
     ]
y =  [0,0,1,1,0,0,0,1,1,1,1,1,1,1,0]
x = np.array(x)
x = np.transpose(x)

params = {
    'max_depth': 10,
    'subsample': 1,
    'verbose_eval': True,
    'seed': 12,
    'objective':'binary:logistic'
}
xgtrain = xgb.DMatrix(x, label=y)
bst = xgb.train(params, xgtrain, num_boost_round=10)
fmap = 'weight'
importance = bst.get_score(fmap = '',importance_type=fmap)
print importance
print bst.get_dump(with_stats=False)
fmap = 'gain'
importance = bst.get_score(fmap = '',importance_type=fmap)
print importance
print bst.get_dump(with_stats=True)
fmap = 'cover'
importance = bst.get_score(fmap = '',importance_type=fmap)
print importance
print bst.get_dump(with_stats=True)

logs :

0:[f2<1.5] yes=1,no=2,missing=1,gain=3.81862,cover=3.75
    1:[f3<1.5] yes=3,no=4,missing=3,gain=1.4188,cover=2.25
        3:leaf=-0.3,cover=1
        4:leaf=0.0666667,cover=1.25
    2:leaf=0.36,cover=1.5

0:[f2<1.5] yes=1,no=2,missing=1,gain=2.69365,cover=3.67888
    1:leaf=-0.119531,cover=2.22645
    2:leaf=0.30163,cover=1.45243

0:[f1<1.5] yes=1,no=2,missing=1,gain=2.4414,cover=3.5535
    1:leaf=-0.107177,cover=2.35499
    2:leaf=0.302984,cover=1.19851

0:[f2<1.5] yes=1,no=2,missing=1,gain=1.92691,cover=3.49546
    1:leaf=-0.10337,cover=2.16893
    2:leaf=0.259344,cover=1.32653

0:[f1<1.5] yes=1,no=2,missing=1,gain=1.79698,cover=3.3467
    1:leaf=-0.095155,cover=2.24952
    2:leaf=0.263871,cover=1.09718

0:[f3<1.5] yes=1,no=2,missing=1,gain=1.56711,cover=3.26459
    1:leaf=-0.165662,cover=1.02953
    2:[f2<1.5] yes=3,no=4,missing=3,gain=0.084128,cover=2.23506
        3:leaf=0.0508745,cover=1.20352
        4:leaf=0.220771,cover=1.03154

0:[f1<1.5] yes=1,no=2,missing=1,gain=1.31036,cover=3.12913
    1:leaf=-0.0852405,cover=2.12169
    2:leaf=0.227708,cover=1.00744

0:[f2<1.5] yes=1,no=2,missing=1,gain=1.25432,cover=3.0361
    1:leaf=-0.0915171,cover=1.94381
    2:leaf=0.214414,cover=1.09229

0:[f0<2.5] yes=1,no=2,missing=1,gain=0.440551,cover=2.89962
    1:leaf=-0.0431823,cover=1.87075
    2:leaf=0.142726,cover=1.02887

0:leaf=0.0379022,cover=2.86568

使用weight的结果为

{‘f0’: 1, ‘f1’: 3, ‘f2’: 5, ‘f3’: 2}

使用gain的结果为

{‘f0’: 0.440551, ‘f1’: 1.8495799999999998, ‘f2’: 1.9555256, ‘f3’: 1.492955}

使用cover的结果为

{‘f0’: 2.89962, ‘f1’: 3.34311, ‘f2’: 3.2390999999999996, ‘f3’: 2.757295}

可以看出,不同的特征重要性度量方法得出的结果也是不尽相同的。 
这里有个疑惑,究竟哪种度量方法更为合理呢?

To do list : LightGBM

您可能感兴趣的与本文相关内容

<think> 由于用户的问题是关于决策树模型特征重要性为负数的原因及解决方法,我将结合系统指令提供的引用内容进行回答。需要注意的是,在决策树模型中(如使用scikit-learn库),特征重要性通常是非负的,因为它们是基于不纯度减少(如基尼不纯度或信息增益)的累积计算的。不过,在某些特殊情况下,特征重要性可能出现负值,这可能是由于实现方式、计算方式或特定库的特性造成的。 根据引用[2][5]: - 特征重要性通常是通过计算每个特征在分裂节点时带来的不纯度减少的总,然后进行归一化得到的(引用[2])。 - 在XGBoost或LightGBM等梯度提升树中,特征重要性有时可以通过“增益”(gain)来评估,而增益可能是负的(引用[5])。 因此,特征重要性为负数的情况可能出现在梯度提升树(如XGBoost)中,而不是在单棵决策树中。 原因分析: 1. **在梯度提升树中**:特征重要性的一种计算方式是平均增益(gain)。增益是指分裂节点时损失函数的减少量。在梯度提升中,如果一次分裂没有改善模型(即损失函数没有减少,反而增加),那么这次分裂的增益就是负的。如果一个特征被多次用于分裂,且这些分裂的平均增益为负,那么该特征重要性可能就是负数(引用[5])。 2. **实现差异**:不同的库对特征重要性计算方式不同。例如,scikit-learn的决策树(`DecisionTreeClassifier``DecisionTreeRegressor`)的特征重要性总是非负的,因为它是基于不纯度减少量的总(引用[3])。而在XGBoost中,特征重要性有三种计算方式:'weight'(特征被用于分裂的次数)、'gain'(平均增益)'cover'(平均覆盖度)。其中,'gain'方式可能产生负数。 3. **模型过拟合**:在训练过程中,如果模型在某个特征上的分裂导致了验证集上损失函数的增加(即产生了负面影响),那么该特征的平均增益可能为负。 4. **正则化效果**:有时,模型可能会使用一些特征来抵消过拟合,这些特征可能会被赋予负的重要性,以表示它们对模型性能的负面影响。 解决方法: 1. **检查所使用的模型特征重要性计算方法**:如果是使用XGBoost,并使用了'gain'作为重要性指标,则出现负值是可能的。可以尝试使用其他重要性类型,如'weight'(特征被使用的次数)或'cover'(特征被用于分裂时覆盖的样本数),这些通常是非负的。 2. **特征选择**:如果特征重要性为负,说明这个特征可能对模型有负面影响,可以考虑移除该特征,重新训练模型。 3. **模型调优**:通过调整正则化参数(如XGBoost中的lambda或gamma)来减少过拟合,可能使得特征的增益不再为负。 4. **数据问题**:检查该特征是否存在噪声或异常值,这可能导致分裂时出现负面影响。进行数据预处理,如清洗异常值、进行特征变换或分桶处理。 5. **交叉验证**:使用交叉验证来评估特征重要性,避免单次训练中的随机性影响。 根据引用[3][4]中的代码示例,scikit-learn的决策树特征重要性是非负的,因此如果用户是在单棵决策树中观察到负的特征重要性,可能是使用了其他库或自定义的计算方法。 因此,回答将围绕上述原因解决方法展开,并提醒用户检查所使用的库特征重要性计算方法。 下面按照要求生成回答,包括相关问题。 </think> ### 决策树特征重要性为负数的原因及解决方法 #### 一、特征重要性为负的原因分析 1. **基于增益(gain)的计算方法缺陷** 当特征重要性基于**分裂带来的平均增益**计算时(多见于XGBoost/LightGBM),若某特征的分裂导致模型损失函数增大(即增益为负),其特征重要性可能为负值[^5]。 $$ \text{Gain} = \mathcal{L}_{\text{before}} - \mathcal{L}_{\text{after}} $$ 其中 $\mathcal{L}$ 为损失函数,若 $\mathcal{L}_{\text{after}} > \mathcal{L}_{\text{before}}$,则增益为负。 2. **过拟合场景下的反向修正** 在剪枝不足的深度树中,某些特征可能在噪音数据上分裂,导致验证集性能下降。此时模型可能赋予该特征重要性以抵消过拟合效应[^1]。 3. **多树集成模型的累积效应** 在随机森林等集成方法中,若某特征在多数子树的分类效果较差(如基尼不纯度减少量为负),其重要性累加结果可能为负[^2]。 4. **特征交互的副作用** 当特征存在强相关性时(如共线性),模型可能将主要特征的正贡献与次要特征的负贡献叠加,导致次要特征重要性为负[^4]。 --- #### 二、解决方法与优化策略 1. **切换特征重要性计算方式** - 优先使用**分裂频率(weight)** 代替增益(gain):统计特征在所有树中被用作分裂点的次数(恒非负)[^5]。 - 在Scikit-Learn中使用`model.feature_importances_`默认基于基尼不纯度减少量(非负)[^3]。 2. **增强数据预处理** ```python # 示例:检查高相关特征并删除冗余 corr_matrix = X.corr().abs() upper_tri = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(bool)) to_drop = [column for column in upper_tri.columns if any(upper_tri[column] > 0.8)] X_processed = X.drop(to_drop, axis=1) ``` 3. **调整模型正则化参数** - 增加剪枝强度:提升`min_samples_split`或`min_impurity_decrease` - 在XGBoost中提高`gamma`(分裂最小增益阈值)`lambda`(L2正则化)[^1]。 4. **验证特征稳定性** 通过交叉验证检查特征重要性一致性: ```python from sklearn.model_selection import cross_val_score scores = [] for feature_idx in neg_feature_indices: X_temp = X.drop(X.columns[feature_idx], axis=1) scores.append(cross_val_score(model, X_temp, y, cv=5).mean()) # 若删除负重要性特征后模型性能提升,则保留该操作 ``` 5. **业务角度验证** 对负重要性特征进行可解释性分析(如SHAP值),确认是否由数据异常或业务逻辑冲突导致。 --- #### 三、核心注意事项 - **优先检查数据质量**:异常值或标签泄漏可能导致反直觉特征贡献[^3]。 - **区分模型类型**:单棵决策树(如CART)的特征重要性不会为负;集成树模型(如GBDT)才可能出现负值[^2][^5]。 - **结果解读**:负重要性表明该特征在当前建模过程中**干扰预测精度**,但需结合业务判断是否保留。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值