博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
树模型特征重要性评估方法
阅读量:4221 次
发布时间:2019-05-26

本文共 8667 字,大约阅读时间需要 28 分钟。

前言

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

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

Random Forests

  • 袋外数据错误率评估

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

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

GBDT

在sklearn中,GBDT和RF的特征重要性计算方法是相同的,都是基于单棵树计算每个特征的重要性,探究每个特征在每棵树上做了多少的贡献,再取个平均值。

在写的比较清楚了,但是还是有一点小的问题,比较中对源代码的解析可以看出,前者计算中丢失了weighted_n_node_samples。

  • 利用Gini计算特征的重要性
    单棵树上特征的重要性定义为:特征在所有非叶节在分裂时加权不纯度的减少,减少的越多说明特征越重要。
    沿用参考博客里的符号,我们将变量重要性评分(variable importance measures)用 VIM V I M 来表示,将Gini指数用 GI G I 来表示
    节点m的Gini指数的计算公式为:
    GIm=1k=1|K|p2mk G I m = 1 − ∑ k = 1 | K | p m k 2
    其中,K表示有K个类别, pmk p m k 表示节点m中类别k所占的比例。直观地说,就是随便从节点m中随机抽取两个样本,其类别标记不一致的概率。
    特征 Xj X j 在节点 m m 的重要性可以表示为加权不纯度的减少
    V
    I
    M
    j
    m
    G
    i
    n
    i
    =
    N
    m
    ×
    G
    I
    m
    N
    l
    ×
    G
    I
    l
    N
    r
    ×
    G
    I
    r
    其中, GIl G I l GIr G I r 分别表示分枝后两个新节点的Gini指数。 Nm N m Nl N l Nr N r 表示节点m、左孩子节点l和右孩子节点r的样本数。
    如果,特征 Xj X j 在决策树i中出现的节点在集合M中,那么 Xj X j 在第i颗树的重要性为
    VIMij=mMVIMjm V I M i j = ∑ m ∈ M V I M j m

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

import numpy as npfrom sklearn.tree import DecisionTreeClassifierfrom sklearn.externals.six import StringIOfrom sklearn import treeimport pydotplusclf = 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×150.4444×90=3.2004 0.48 × 15 − 0.4444 × 9 − 0 = 3.2004
特征A2的重要性为 0.4444×900=3.9996 0.4444 × 9 − 0 − 0 = 3.9996
特征A1和A4的重要性都为0
所以该棵树上所有节点总的加权不纯度减少量为 3.2004+3.9996=7.3 3.2004 + 3.9996 = 7.3
对其进行归一化操作可以得到A1、A2、A3、A4的特征重要性为

[ 0. 0.55555556 0.44444444 0. ]

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

VIMj=i=1nVIMij V I M j = ∑ i = 1 n V I M i j
最后,把所有求得的重要性评分做一个归一化处理即可。
VIMj=VIMji=1cVIMi V I M j = V I M j ∑ i = 1 c V I M i
其中
c c <script type="math/tex" id="MathJax-Element-40">c</script>为特征的总个数

XGBoost

关于XGBoost中特征重要性计算相关代码出现在

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]有比较详尽的解释:():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 xgbimport numpy as npx = [[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 importanceprint bst.get_dump(with_stats=False)fmap = 'gain'importance = bst.get_score(fmap = '',importance_type=fmap)print importanceprint bst.get_dump(with_stats=True)fmap = 'cover'importance = bst.get_score(fmap = '',importance_type=fmap)print importanceprint 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.50:[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.452430:[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.198510:[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.326530:[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.097180:[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.031540:[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.007440:[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.092290:[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.028870: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

你可能感兴趣的文章
编写苹果游戏中心应用程序(翻译 1.4 添加游戏工具包框架)
查看>>
编写苹果游戏中心应用程序(翻译 1.5 在游戏中心验证本地玩家)
查看>>
编写苹果游戏中心应用程序(翻译 1.6 获取本地玩家的信息)
查看>>
编写苹果游戏中心应用程序(翻译 1.7 在游戏中心添加朋友)
查看>>
编写苹果游戏中心应用程序(翻译 1.8 获取本地玩家的好友信息)
查看>>
WebGL自学教程《OpenGL ES 2.0编程指南》翻译——勘误表
查看>>
WebGL自学教程——WebGL示例:12. 要有光
查看>>
WebGL自学教程——WebGL示例:13.0 代码整理
查看>>
WebGL自学教程——WebGL示例:14.0 代码整理
查看>>
恶心的社会
查看>>
中国式危机公关9加1策略(第五章 慎用信息控制策略)
查看>>
展现自己的人生智慧
查看>>
深入理解java多态性
查看>>
Java新手进阶:细说引用类型
查看>>
osg中使用MatrixTransform来实现模型的平移/旋转/缩放
查看>>
(一) Qt Model/View 的简单说明
查看>>
(二)使用预定义模型 QStringListModel例子
查看>>
UVM:7.4.5 加入存储器
查看>>
UVM:7.5.1 期望值与镜像值
查看>>
UVM:7.5.2 常用操作及其对期望值和镜像值的影响
查看>>