机器学习中的特征工程(五)---- 特征选择方法

简介

大概是今年6月份参加微信大数据挑战赛的时候,我才开始认识到特征选择也是机器学习中非常重要的一环。在诸如CTR等比赛中,原始特征往往是不足以挖掘出数据中隐藏的信息的,因此很多时候需要进行特征组合,衍生出更多的组合特征,但是这随之也带来了一个问题,那就是在成千上百的特征中,其实有很多特征对模型性能的提升是毫无帮助的,我们需要筛选出合适的,相对少量的特征来作为模型的输入,这样有助于模型的训练和提高精确度。但是我们如何在较多的特征池中挑选出合适的特征呢?这就是本文所要讨论的特征选择问题。

特征选择

特征选择(Feature Selection)又可称特征子集的的选择或者属性选择,是从大量的特征中选择合适的,相对少量的特征子集用来作为最终模型的输入。通俗地来说,特征选择就是在较多的特征中选择最能体现数据本质的的特征,犹如人类的学习过程,将知识化繁为简,从纷繁复杂的信息中提炼挑选出最具代表性的特征,进而抓住最重要和最本质的部分。
特征选择是特征工程中最重要的内容之一,我们期望丢弃不重要的特征,保留有效的特征。特征选择之后,虽然特征数量减少了,但是模型效果并没有显著下降甚至会表现得更好。特征选择在某种程度上也是一种权衡的过程。
特征选择有点类似于“降维”,它有以下优势:

  • 工程上,避免‘维数灾难’,更少的特征需要的资源更小,有利于模型训练和推理,建模效率高,维护成本低
  • 理论上,更少的特征降低了模型假设类的复杂度,符合奥卡姆剃刀原则,有利于降低估计误差并防止过拟合,在特征质量较差的情况下,甚至能避免建模的错误。
  • 业务上,更少的特征有利于模型的解释,一定数量的特征是在人类的理解承受范围之内,并且可以对数据进行可视化分析。但是超过上百个特征对于人类来说是难以理解的,也降低了业务上的推广效率。

特征选择算法

特征选择技术的发展过程中,一种广为流传的特征选择算法分类如下:

  • 过滤法(Filter Method)
    原理是对特征进行某种得分排序,去排名靠前的特征。
  • 包裹法(Wrapper Method)
    借助模型,评价不同特征子集的效果,去效果最好的子集
  • 嵌入法(Embedding Method)
    借助模型自带的特征选择功能实现特征选择,未被选择的特征的系数或权重为0。

本文将针对这三种算法,分别挑选具有代表性的算法来演示一下特征选择的过程。

过滤法

过滤法首先需要选择评分方法,然后计算所有特征的得分,对特征排序,最后根据阈值或要求的特征数量过滤得到选中的特征。该特征选择方法不涉及后续的模型构建,通常被认为是一种无偏的特征选择方法。另外根据在进行特征选择的时候,是否使用到了目标变量的相关信息,可以将特征方法分为有监督的特征选择和无监督的特征选择,下面会分别举例。
首先我们需要确定评分方法,即如何来评价一个特征的好坏程度?这里先从简单的开始----方差指标。方差的大小表明了数据的离散程度,如果一个特征所有的取值都是一样的或者接近,那说明它数据分布比较集中不够分散,换言之这个特征对目标变量没有区分度。通过方差指标来筛选特征算法很简单,便于解释和理解,由于并没有使用到目标变量的信息,所以这属于无监督的特征选择方法。sklearn中给出了相应的实现,下面是官方的例子。如下:

from sklearn.feature_selection import VarianceThreshold

#     f1 f2 f3 f4
X = [[0, 2, 0, 3], 
     [0, 1, 4, 3], 
     [0, 1, 1, 3]]

selector = VarianceThreshold(threshold=0.0)
selector.fit_transform(X)
筛选结果

方差筛选的阈值设置为0.0,原始一共有4个特征f1,f2,f3,f4,经过方差筛选后只剩下f2,f3。因为特征f1,f4的方差为0,故被筛选掉了。

上面的特征选择使用的是无监督的方差指标评分方法,其优点是简单快捷,但是不足以选择出更具有代表性的特征,并且没有利用到目标变量的信息,下面介绍相关性指标评分方法。所谓相关性代表了特征与特征之间,以及特征与目标变量之间的变化关系。如果特征值的变化会导致预测值的变化,那么特征就是相关的。相关性的强弱代表了这种关系的程度。如果在预测模型中使用特征A能明显消除分类的模糊性,那么它就是强相关的,否则 为弱相关。如果从特征集中删除掉某些特征后,特征A才变得相关,那么它是弱相关的。如果一个特征既不是强相关的,也不是弱相关的,那么认为它是不相关的。特征选择的目标是选择相关性强的特征。评价相关性的指标有很多,不过文本主要介绍皮尔逊相关系数。两个变量之间的皮尔逊相关系数定义为两个变量的协方差除以它们标准差的乘积,如下:

皮尔逊相关公式
其物理含义为:一个变量的增加与另一个变量的增加越相关,则 越接近1;反之,一个变量的增加与另外一个变量的减少越相关,则 越接近-1。如果 和 独立,则 越接近0;反之则不然,如果两个变量存在这很强的关系,但是皮尔逊相关系数仍可能很小。

关于协方差与相关系数等概念可以参考知乎如何通俗易懂地解释「协方差」与「相关系数」的概念?

知道了皮尔逊相关系数的作用之后,我们就可以用它来做特征选择了,具体来说就是分别计算每个特征与目标变量之间的相关系数以及相关系数的P值。可以使用scipy中的pearsonr函数来计算皮尔逊相关系数从而来进行特征选择,代码如下:

from sklearn.feature_selection import SelectKBest
from scipy.stats import pearsonr
from sklearn.datasets import load_iris

iris = load_iris()

def scoring(x, y):
    t = [pearsonr(x[:,i], y) for i in range(x.shape[1])]
    l = [i[0] for i in t]
    tu = (i[1] for i in t)
    return l, tu 

X_new = SelectKBest(lambda X, Y: scoring(X, Y), k=2).fit_transform(iris.data, iris.target)
X_new

选择的特征为:

对比之后可知使用皮尔逊相关系数最终选出来iris数据集的后面2个特征,即第三个和第四个特征。
当然也可以使用卡方检验来衡量自变量uid定性因变量饿的相关性。假设自变量有N种取值,因变量有M种取值,考虑自变量等于i且因变量等于j的样本频数的观察值与期望的差距,构建统计量:
卡方检验
不难发现,这个统计量的含义简而言之就是自变量对因变量的相关性。用feature_selection库的SelectKBest类结合卡方检验来选择特征的代码如下:

from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2

#选择K个最好的特征,返回选择特征后的数据
x_new = SelectKBest(chi2, k=2).fit_transform(iris.data, iris.target)
x_new

结果跟使用皮尔逊相关系数选择的特征一样。

互信息也是测量变量之间关联程度的一种评价方法,当且仅当两个随机变量独立时,MI值为0,当两个随机变量关联性越强,MI值越大。互信息在决策树中成为信息增益,它是信息论的基本概念之一。经典的互信息也是评价定性自变量对定性因变量的相关性的,互信息计算公式如下:

为了处理定量数据,最大信息系数法被提出,使用feature_selection库的SelectKBest类结合最大信息系数法来选择特征的代码如下:

from sklearn.feature_selection import SelectKBest
from minepy import MINE
 
def scoring(x, y):
    m = MINE()
    res = []
    for i in range(x.shape[1]):
        m.compute_score(x[:,i], y)
        res.append(m.mic())
    
    tu = (0.5 for _ in res)
    return res, tu

X_new = SelectKBest(lambda X, Y: scoring(X, Y), k=2).fit_transform(iris.data, iris.target)
X_new

这里得到的结果也是一样的。

包裹法

如果特征加入后,模型的性能提升只有万分之几,一般这样的特征就不应该被选中。包裹法设计模型构建和子集搜索,相对过滤法是一种计算成本很高的特征选择方法,但是往往精度不高。有这样一种观点,特征选择方法的最终目的是寻找对性能有提升的特征(组合),而不一定要找到相关的特征。
递归消除特征法使用一个基模型来进行多轮训练,每轮训练后,消除若干权值系数的特征,再基于新的特征集进行下一轮训练。使用feature_selection库的RFE类来选择特征的代码如下:

from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression

#递归特征消除法,返回特征选择后的数据
#参数estimator为基模型
#参数n_features_to_select为选择的特征个数
x_new = RFE(estimator=LogisticRegression(), n_features_to_select=2).fit_transform(iris.data, iris.target)
x_new

输出依然是一样的。

嵌入法

嵌入法特征选择比较特殊,它要求学习算法本身具有特征选择的功能。例如带L1正则项的回归算法,算法输出回归系数为0的特征即是被删除掉的特征,说明其重要程度不高,从而表现为学习算法自带特征选择功能。正则化的决策树也具有同样的功能。树模型中权重为0的特征是没有被模型选中的特征,所以树模型的特征选择也应该归属于嵌入法。
由于嵌入法设置未选中的特征稀疏或权重为0,所以在实践中,嵌入法会是一个循环的过程,例如第二次建模只输入第一次选中的特征,此时由于特征空间的变化,第二次建模的时候也可能产生系数或权重为0的特征,此时再重复运行,直到所有的特征系数或权重都不为0。
1、基于随机森林的特征选择
决策树构造的过程也是特征选择的过程。决策树在构造的过程中会选择具有最大信息增益(ID3算法)、最大信息增益比(C4.5)、最大GINI指数(CART算法)的特征,关于决策树的构造算法,可以参考我这篇博客机器学习中的特征工程(四)---- 特征离散化处理方法。随机森林基于决策树在行列上采样构造树模型,最终计算每个特征的不纯度或基尼的减少的平均数,排序后的得到特征的重要性,从而得到平均不纯度减少、基尼减少、方差的特征选择方法。该方法具有稳定性特征选择的特点。同样,也可以使用梯度提升树(GBDT)来作为特征选择的基模型,它们本质上原理都差不多。基于随机森林和GBDT的特征选择算法代码示例如下:

from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import GradientBoostingClassifier, RandomForestClassifier

#GBDT作为基模型的特征选择
# clf = GradientBoostingClassifier()
# x_new = SelectFromModel(GradientBoostingClassifier()).fit_transform(iris.data, iris.target)

# 随机森林作为基模型的特征选择
clf = RandomForestClassifier()
x_new = SelectFromModel(clf).fit_transform(iris.data, iris.target)

clf.fit(iris.data, iris.target)
print("selected feature's shape: ", x_new.shape)
print("the importances of all original features: ", clf.feature_importances_.round(6))
结果

这里打印出来了选择的特征的形状,跟上面一样,是选择了后面2个特征。并且打印出了每个特征的重要程度,也可以看出是后面两个特征更加重要一点。

2、基于正则的特征选择
使用L1正则的模型中会得到稀疏解,即大部分特征对应的系数为0,很明显系数为0或者接近0的特征是没有被选择的特征,即L1正则化的效果具有特征选择的作用。使用L2的模型中则会输出趋于一致的系数,得到较为稳定的模型。下面是Sklearn中给出的使用带L1正则的逻辑回归来进行特征选择的例子。如下:

from sklearn.feature_selection import SelectFromModel
from sklearn.linear_model import LogisticRegression

X = [[ 0.87, -1.34,  0.31 ],
     [-2.79, -0.02, -0.85 ],
     [-1.34, -0.48, -2.55 ],
     [ 1.92,  1.48,  0.65 ]]
y = [0, 1, 0, 1]

lr = LogisticRegression(penalty='l2', C=0.01)
selector = SelectFromModel(estimator=lr).fit(X, y)
print('特征的系数: ', selector.estimator_.coef_)

print('选择器的阈值: ', selector.threshold_)

print('特征选择: ', selector.get_support())
print(selector.transform(X))
特征选择

注意,最新的sklearn版本中的SelecFromModel貌似已经不支持L1正则化,会报错,因此这里使用的是L1正则化。

参考

  • https://scikit-learn.org/stable/modules/generated/sklearn.feature_selection.VarianceThreshold.html
  • https://zh.wikipedia.org/wiki/%E7%9A%AE%E5%B0%94%E9%80%8A%E7%A7%AF%E7%9F%A9%E7%9B%B8%E5%85%B3%E7%B3%BB%E6%95%B0
  • https://www.kaggle.com/sz8416/6-ways-for-feature-selection
  • https://www.zhihu.com/question/28641663

你可能感兴趣的:(机器学习中的特征工程(五)---- 特征选择方法)