返回介绍

11.2 选择特征

发布于 2024-01-30 22:34:09 字数 8643 浏览 0 评论 0 收藏 0

如果我们想要对机器学习算法好点,那提供给它的特征相互之间应该没有依赖关系,同时又跟预测值高度相关。这意味着,每个特征都可以加入一些重要信息。把它们之中的任何一个删掉,都会导致性能的下降。

如果只有几个特征,我们可以画出一个散点矩阵——每对特征组合都有一个散点。很容易就可以发现特征间的关系。对于显示出明显依赖关系的每对特征,我们可以思考是否应该把其中一个删掉,或者在这两者之外设计一个新的更清楚的特征。

然而,在大多数时间里,我们会在更多的特征里进行选择。仅仅考虑我们用词袋模型对答案质量进行分类这个任务,它需要1000×1000个散点。在这种情况下,我们需要一个更加自动的方法来检测特征之间的重叠,以及解决这种重叠的方法。我们将会在下面这些小节中给出两种通用的做法:筛选器(filter)和封装器(wrapper)。

11.2.1 用筛选器检测冗余特征

筛选器试图在特征丛林中进行清洗,它独立于后续使用的任何机器学习方法。它基于统计方法找出冗余(在这种情形下,在每组冗余特征中只需要保留其中一个)或无关特征。一般来讲,筛选器的工作流如下图所示:

1. 相关性

通过使用相关性,我们很容易看到特征之间的线性关系。这种关系可以用一条直线来拟合。在下面这些图中,我们可以看到不同程度的相关性,以及一个用红色虚线描绘出的潜在线性依赖关系(一个拟合的一维多项式)。每幅图上方的相关系数Cor(X 1 ,X 2 )是用皮尔逊相关系数(Pearson correlation coefficient)计算出来的(皮尔逊r 值),采用的是scipy.stat 里的pearsonr() 函数。

给定两个大小相等的数据序列,它会返回相关系数值和p 值所组成的元组。p 值是该序列产生于一个不相关系统的概率。换句话说,p 值越高,我们越不能信任这个相关系数:

>> from import scipy.stats import pearsonr >> pearsonr([1,2,3], [1,2,3.1]) >> (0.99962228516121843, 0.017498096813278487) >> pearsonr([1,2,3], [1,20,6]) >> (0.25383654128340477, 0.83661493668227405)

在第一种情况下,我们很清楚地知道这两个序列是相关的。而在第二种情况下,我们仍然有一个非零的r 值。

然而,p 值基本上告诉了我们这个相关系数是什么样的,我们不应该对它过多关注。下图中的输出说明了这一点:

在前三个具有高相关系数的情形中,我们可能要把X 1X 2 扔掉,因为它们似乎传递了相似的信息。

然而在最后一种情况中,我们应该把两个特征都保留。在我们的应用中,这种决策当然是由p 值驱动的。

尽管这种方法在前面这个例子中工作得不错,但在实际应用中却并不如意。基于相关性的特征选择方法的一个最大缺点就是,它只能检测出线性关系(可以用一条直线拟合的关系)。如果我们在非线性数据中使用相关性,那就有问题了。在下面这个例子中,我们会有一个二次关系:

除右下图以外的所有图中,尽管人类的眼睛可以立即看到X 1X 2 之间的关系,却没法发现相关系数。很明显,相关性在检测线性关系中是很有用的,但对于其他关系就不行了。

对于非线性关系,互信息出马了。

2. 互信息

在进行特征选择的时候,我们不应该像前一节中那样(线性关系),把焦点放在数据关系的类型上。相反,我们应该考虑一下,在已经给定另一个特征的情况下一个特征可以提供多少信息量。

要理解这个,假设我们想要用特征集合中的house_size 、num_of_levels 和avg_rent_price 特征训练一个分类器,来预测房子是否有电梯。在这个例子里,我们在直觉上可以看到,知道house_size 就意味着我们并不再需要number_of_levels ,因为它包含了冗余信息。但avg_rent_price 就不一样了,我们没法简单从房屋的大小或楼层来推断租赁的价格。因此,我们应该保留这两个特征中的一个,再加上平均租赁价格。

互信息会通过计算两个特征所共有的信息,把上述推理过程形式化表达出来。与相关性不同,它依赖的并不是数据序列,而是数据的分布。要理解它是怎样工作的,我们需要深入了解一点信息熵的知识。

假设我们有一个公平的硬币。在旋转它之前,它是正面还是反面的不确定性是最大的,因为两种情况都有50%的概率。这种不确定性可以通过克劳德•香农(Claude Shannon)的信息熵来衡量:

在公平硬币情景下,我们有两种情况:令x 0 代表硬币正面,x 1 代表硬币反面,p (X 0 )=p (X 1 )=0.5。

因此,我们得到下面的式子:

注意  为方便起见,我们还可以用scipy.stats.entropy([0.5, 0.5], base=2 )。我们把base 这个参数设为2,就可以得到跟前面一样的结果了。否则,这个函数将会通过np.log() 使用自然对数。一般来说,数基对于结果并没有什么影响,只要你的用法是一致的。

现在,想象我们事先知道这个硬币实际上并不是公平的,旋转之后有60%的可能性会出现硬币的正面:

我们可以看到这种情形有较少的不确定性。不管正面出现的概率为0%还是100%,不确定性都将会远离我们在0.5时所得到熵,到达极端的0值,如下图所示:

我们现在修改熵H (X )的计算方式,使之能够应用到2个特征上而不是1个。它衡量了在知道Y 的情况下,X 中所减少的不确定性。这样我们就可以得到,一个特征使另一个特征的不确定性减少的程度。

例如,在对天气情况没有任何了解的情况下,我们完全不能确定外面是否下雨了。但如果我们现在知道外面的草地是湿的,那么这种不确定性就会减少。(我们仍然需要查看洒水机是否打开了。)

更正式地讲,互信息量是这样定义的:

这看起来有一点令人敬畏,但它实际上只不过是一些求和和求积。例如,P ()可以通过把特征值分成一些桶,然后计算进入每个桶里的数字的比例来得到。在下面这个图中,我们把桶的个数设为10。

为了把互信息量限制在[0,1]区间,需要把它除以每个独立变量的信息熵之和,然后就可以得到归一化后的互信息量:

互信息量的一个较好的性质在于,跟相关性不同,它并不只关注线性关系,如下图所示:

所以,我们需要计算每对特征之间的归一互信息量。对于具有较高互信息量的特征对,我们会把其中一个特征扔掉。在进行回归的时候,我们可以把互信息量非常低的特征扔掉。

对于较小的特征集合这种方式的效果或许还可以。但是,在某种程度上说,这个过程会非常缓慢,计算量会以平方级别增长,因为我们要计算的是每对特征之间的互信息量。

筛选器的还有一个巨大缺点,它们扔掉在独立使用时没有用处的特征。但实际情况往往是,一些特征看起来跟目标变量完全独立,但当它们组合在一起时就有效用了。要保留这些特征,我们就需要封装器。

11.2.2 用封装器让模型选择特征

筛选器对删除无用特征有很大的作用,但它们也只能做到这里了。在所有筛选都做完之后,仍然可能有一些特征,它们之间彼此独立,并且和目标变量有一定程度的依赖关系,但是从模型的角度来看,它们却毫无用处。只需要考虑下面这个数据,它描绘的是XOR函数。独立来看,不管A 还是B ,都跟Y 没有任何依赖关系,但把它们放在一起,就有明显的关系:

A

B

Y

0

0

0

0

1

1

1

0

1

1

1

0

所以,为什么不让模型自己给每个特征投票呢?这就是封装器所要做的,如下面这个流程图所示:

在这里,我们把特征的重要性计算放在模型的训练流程里。遗憾的是(但是可以理解),特征重要性并不是一个二元值,而是一个排序值。所以仍然需要给出切分的位置——哪部分特征我们希望保留,以及哪部分想要扔掉?

回到scikit-learn,我们发现在sklearn.feature_selection 包里有各种优秀的封装器类。这个领域中的一个真正主力军叫做RFE ,这个缩写代表的是特征递归消除 (recursive feature elimination)。它会把一个估算器和预期数量的特征当做参数,然后只要发现一个足够小的特征子集,就在这个特征集合里训练估算器。RFE 实例在封装估算器同时,它本身看起来也像是一个估算器。

在下面这个例子中,我们通过datasets 的make_classification() 函数,创建了一个人工构造的分类问题,它包含100个样本。我们创建了10个特征,其中只有3个对解决这个分类问题是有价值的:

>>> from sklearn.feature_selection import RFE >>> from sklearn.linear_model import LogisticRegression >>> from sklearn.datasets import make_classification >>> X,y = make_classification(n_samples=100, n_features=10, n_ informative=3, random_state=0) >>> clf = LogisticRegression() >>> clf.fit(X, y) >>> selector = RFE(clf, n_features_to_select=3) >>> selector = selector.fit(X, y) >>> print(selector.support_) [False True False True False False False False True False] >>> print(selector.ranking_) [4 1 3 1 8 5 7 6 1 2]

当然,真实情景中的问题是,我们该如何知道n_features_to_select 的正确值呢?事实上,我们也无法知道。但在多数时间里,我们都可以采用不同的设置,对数据里的一些样本进行试验,快速得到一个大致正确的估计。

一个好消息是,我们在使用封装器的时候并不需要那么精确。让我们尝试几个不同的n_features_to_select 值,来看看support_ 和ranking_ 会如何改变:

n_features_to_select

support_

ranking_

1

[False False False True False False False False False False]

[ 6 3 5 1 10 7 9 8 2 4]

2

[False False False True False False False False True False]

[5 2 4 1 9 6 8 7 1 3]

3

[False True False True False False False False True False]

[4 1 3 1 8 5 7 6 1 2]

4

[False True False True False False False False True True]

[3 1 2 1 7 4 6 5 1 1]

5

[False True True True False False False False True True]

[2 1 1 1 6 3 5 4 1 1]

6

[ True True True True False False False False True True]

[1 1 1 1 5 2 4 3 1 1]

7

[ True True True True False True False False True True]

[1 1 1 1 4 1 3 2 1 1]

8

[ True True True True False True False True True True]

[1 1 1 1 3 1 2 1 1 1]

9

[ True True True True False True True True True True]

[1 1 1 1 2 1 1 1 1 1]

10

[ True True True True True True True True True True]

[1 1 1 1 1 1 1 1 1 1]

我们可以看到,这个结果十分稳定。在较小特征集合里选择的特征,在更多特征加入进来的时候仍然会被选择。最后,如果走错了方向,我们会用训练/测试集合来报警。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文