特征选择:概述与方法

不知各位读者是否遇到这种情形:在一项机器学习工作中,千方百计地变换模型并调整参数,但收效甚微;后经高人指点,对进入模型的特征做了一些修改后,即使用了最基础的模型,其效果也有如脱胎换骨。这似乎在暗示我们,在机器学习任务中,“使用什么样的特征”往往要比“使用什么样的模型”更重要

以大数据的普遍流行为分割线,无论是在之前的数据因稀少而珍贵的时代,还是现在的数据「泛滥」时代,选择特征一直都是机器学习研究者所关注的重要内容。数据少的时候,人们希望通过增加特征来找到目标变量的变化趋势;数据多的时候,人们希望剔除冗余和无关因素的影响来加速模型收敛,提升模型精度。

下面,我将以scikit-learn中的相关方法为例,梳理特征选择的基本理论。

特征选择:概述与方法

特征选择,顾名思义,就是从所有给定的特征中选出一个或多个用于构建模型。为了评估选出的特征是否让模型具有了更好的表现效果,还需要定义评价指标来对特征子集进行打分。

进行特征选择的一个最直观的想法是,穷举特征的所有组合,分别进行评价。虽然这种方法在理论上能够获得最优的组合,但对于高维数据而言,会面临组合爆炸的问题,不太可行。为了提高可行性,我们需要采用其他的方式来选择特征。

基于方差的特征选择

方差是衡量数据离散程度的指标,值越小说明数据变化的程度越小。举一个极端的例子,如果数据集中的某个特征的方差为0,说明该特征的取值在所有样本中都是一致的,那么它对于我们区分目标变量毫无帮助,是可以剔除的。

一般的,我们可以设置一个方差阈值,然后将每个特征的方差与阈值进行比较。如果某特征的方差低于该阈值,则认为该特征的数据变化不大,并推断其所包含的信息不足以对目标变量进行区分,因而予以剔除

下面是一个sklearn中的例子:

>>> from sklearn.feature_selection import VarianceThreshold
>>> X = [[0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 1, 1], [0, 1, 0], [0, 1, 1]]
>>> sel = VarianceThreshold(threshold=(.8 * (1 - .8)))
>>> sel.fit_transform(X)
array([[0, 1],
       [1, 0],
       [0, 0],
       [1, 1],
       [1, 0],
       [1, 1]])

易知,X的第一个维度的方差不满足阈值要求,该特征被剔除。

读者应该已经发现,这种做法是有「问题」的:在上面的例子中,如果目标变量的取值刚好与第一个特征的取值变化相同,换句话说,它们具有很高的相关性,这时剔除该特征是不合适的。

这种问题的本质,在于判定哪些变量需要剔除时没有考虑目标变量的取值。因此,它是一种相对粗糙的方法,多用于特征初筛,剔除那些在我们看来几乎绝对不可能提升模型效果的特征。

相关系数法

回归问题为例对本方法及下一个方法进行说明。

要衡量某个特征对于目标的预测是否重要,一个直观的想法是计算特征与目标的相关性。例如,可以计算特征列与目标列的皮尔逊相关系数:

from sklearn.datasets import load_boston  # 波士顿房价数据集,在1.2版本后将不再可用
X, y = load_boston(return_X_y=True)
print(X.shape)
# 输出:(506, 13)
# 样本数为506,特征数为13
from sklearn.feature_selection import r_regression
print(r_regression(X, y))
# 输出:
# [-0.38830461  0.36044534 -0.48372516  0.17526018 -0.42732077  0.69535995
#  -0.37695457  0.24992873 -0.38162623 -0.46853593 -0.50778669  0.33346082
#  -0.73766273]

r_regression实际上计算的是各个特征与y的相关系数,可以验证如下:

import numpy as np; print(np.corrcoef(X[:,0], y)[0][1])
# 得到第一个特征与y的相关系数,可以类似地得到其他特征的相关系数

接下来,可以将相关系数从大到小进行排序,从而选择前k个最相关的特征用于建模。sklearn已经帮我们实现了这个过程:

from sklearn.feature_selection import SelectKBest

k = 3  # 这里选择前3个最相关的特征
selector = SelectKBest(r_regression, k=3)  # r_regression就是评分函数
X_new = selector.fit_transform(X, y)
print(selector.get_feature_names_out())
# 输出:['x1', 'x5', 'x11']

可以看出,经过r_regression评分,选择出的三个最可能影响目标的特别分别是x1x5x11,即原始X的第2、6和12列。

由于X是numpy数组,没有列名,因此sklearn自动生成了列名。如果传入的X是pandas.DataFrame,那么get_feature_names_out将会输出X的列名。

R 2 R^2 R2 F F F统计量

在使用相关系数进行特征选择时,某些特征与目标的负相关程度很高,也可以用于建模预测,但有可能会被遗漏;此外,多重共线性问题也是相关系数法所无法避免的。为此,需要一些改进的方法。

我们知道,判定系数 R 2 R^2 R2可以用来衡量所建立的回归模型的“好坏”,其计算公式如下:
R 2 = S S E / S S T = 1 − S S R S S T R^2=SSE/SST=1-\frac{SSR}{SST} R2=SSE/SST=1SSTSSR
其中,称SST为总平方和(total sum of squares)、SSE为解释平方和(explained sum of squares)、SSR为残差平方和(residual sum of squares)。它们各自的定义如下:

S S T = ∑ i = 1 n ( y i − y ˉ ) 2 SST=\sum_{i=1}^{n}(y_i-\bar y)^2 SST=i=1n(yiyˉ)2

S S E = ∑ i = 1 n ( y ^ i − y ˉ ) 2 SSE=\sum_{i=1}^{n}(\hat y_i-\bar y)^2 SSE=i=1n(y^iyˉ)2

S S R = ∑ i = 1 n ( y i − y ^ i ) 2 SSR=\sum_{i=1}^{n}(y_i-\hat y_i)^2 SSR=i=1n(yiy^i)2

下图有助于理解上述三个概念:

图片来自:李子奈等.《计量经济学(第三版)》

SST度量了 y i y_i yi的总样本波动,反映了样本观测值总体离差的大小;SSE度量了 y i ^ \hat {y_i} yi^的样本波动,反映由模型中解释变量所解释的那部分离差的大小;SSR度量了残差的样本波动,反映观测值与估计值偏离的程度,也是解释变量未解释的那部分离差的大小。

它们之间有如下关系: S S T = S S R + S S E SST=SSR+SSE SST=SSR+SSE(证明略)。因此可以知道,SSE不可能大于SST,所以 R 2 R^2 R2的取值范围为 [ 0 , 1 ] [0,1] [0,1]。越接近于1,说明模型的拟合得越好。

还有一个困扰人的问题:判定系数表示为 R 2 R^2 R2,那么是否说明它就等于相关系数 r r r的平方呢?

对于简单线性回归而言,上述结论是正确的;否则,就是不正确的。

我们知道,两个卡方分布分别除以其自由度后构造的新的统计量就服从F分布。这里先说结论:在线性回归分析中,有 S S R ∼ χ ( 1 ) SSR \sim \chi(1) SSRχ(1) S S E ∼ χ ( n − 2 ) SSE \sim \chi(n-2) SSEχ(n2)。因此:
F ( 1 , n − 2 ) = S S R S S E / ( n − 2 ) = S S R / S S T S S E / S S T ∗ ( n − 2 ) = R 2 1 − R 2 ∗ ( n − 2 ) F(1,n-2)=\frac{SSR}{SSE/(n-2)}=\frac{SSR/SST}{SSE/SST}*(n-2)=\frac{R^2}{1-R^2}*(n-2) F(1,n2)=SSE/(n2)SSR=SSE/SSTSSR/SST(n2)=1R2R2(n2)
在简单线性回归中,又有 R 2 = r 2 R^2=r^2 R2=r2,所以:
F ( 1 , n − 2 ) = r 2 1 − r 2 ∗ ( n − 2 ) F(1,n-2)=\frac{r^2}{1-r^2}*(n-2) F(1,n2)=1r2r2(n2)
这也就意味着,如果知道了自变量和因变量的相关系数,就可以求出其F统计量的值,从而判定该自变量在回归模型中是否具有解释力。

在sklearn中,可以用f_regression来作为评价函数进行特征选择:

from sklearn.feature_selection import f_regression

selector = SelectKBest(f_regression, k=3)
X_new = selector.fit_transform(X, y)
print(selector.get_feature_names_out())
# 输出:['x5', 'x10', 'x12']

可以看出,经过f_regression选择后,得出的三个最能解释因变量的为第5、10和12个特征。

执行以下代码,以获得各个特征对因变量的解释力的F统计量值:

print(f_regression(X, y))
# array([ 89.48611476,  75.2576423 , 153.95488314,  15.97151242,
#       112.59148028, 471.84673988,  83.47745922,  33.57957033,
#        85.91427767, 141.76135658, 175.10554288,  63.05422911,
#       601.61787111]), 
# array([1.17398708e-19, 5.71358415e-17, 4.90025998e-31, 7.39062317e-05,
#       7.06504159e-24, 2.48722887e-74, 1.56998221e-18, 1.20661173e-08,
#       5.46593257e-19, 5.63773363e-29, 1.60950948e-34, 1.31811273e-14,
#       5.08110339e-88])

第一个array为各个因变量的F统计量值,可以看出,SelectKBest实际上是选出了具有最大F值的前3个特征;第二个array为对应的p值。读者可以利用相关系数自行验证F统计量的值是否满足 F = r 2 / ( 1 − r 2 ) ∗ ( n − 2 ) F=r^2/(1-r^2)*(n-2) F=r2/(1r2)(n2)


参考内容:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芳樽里的歌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值