背景
一个典型的机器学习任务,是通过样本的特征来预测样本所对应的值。如果样本的特征少,我们会考虑增加特征。而现实中的情况往往是特征太多了,需要减少一些特征。
什么是特征选择?
特征选择是特征工程里的一个重要问题,其目标是寻找最优特征子集。特征选择能剔除不相关(irrelevant)或冗余(redundant )的特征,从而达到减少特征个数,提高模型精确度,减少运行时间的目的。另一方面,选取出真正相关的特征简化模型,协助理解数据产生的过程。 我们常能听到“数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已”,由此可见其重要性。
为什么要进行特征选择?
之所以要考虑特征选择,是因为机器学习经常面临过拟合的问题。 过拟合的表现是模型参数太贴合训练集数据,模型在训练集上效果很好而在测试集上表现不好,也就是在高方差。简言之模型的泛化能力差。过拟合的原因是模型对于训练集数据来说太复杂,要解决过拟合问题,一般考虑如下方法:
- 收集更多数据
- 通过正则化引入对复杂度的惩罚
- 选择更少参数的简单模型
- 对数据降维(降维有两种方式:特征选择和特征抽取)
其中第1条一般是很难做到的,第3可能模型效果不好,一般我们采用第2和第4点。本文主要给大家分享特征选择。
特征选择的一般过程
- 生成子集:搜索特征子集,为评价函数提供特征子集
- 评价函数:评价特征子集的好坏
- 停止准则:与评价函数相关,一般是阈值,评价函数达到一定标准后就可停止搜索
- 验证过程:在验证数据集上验证选出来的特征子集的有效性
但是, 当特征数量很大的时候, 这个搜索空间会很大,如何找最优特征还是需要一些相关经验。
特征选择的三类方法
根据特征选择的形式,可分为三大类方法:
- Filter(过滤法):按照
发散性
或相关性
对各个特征进行评分,设定阈值或者待选择特征的个数进行筛选 - Wrapper(包装法):根据目标函数(往往是预测效果评分),每次选择若干特征,或者排除若干特征
- Embedded(嵌入法):先使用某些机器学习的模型进行训练,得到各个特征的权值系数,根据系数从大到小选择特征(类似于Filter,只不过系数是通过训练得来的)
sklearn中的特征选择方法
移除低方差特征-VarianceThreshold
VarianceThreshold是特征选择的一个简单方法,去掉那些方差没有达到阈值的特征。这是一种过滤法,默认情况下,删除零方差的特征,例如那些只有一个值的样本。
假设我们有一个有布尔特征的数据集,然后我们想去掉那些超过80%的样本都是0(或者1)的特征。布尔特征是伯努利随机变量,方差为 p(1-p),代码如下所示。
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))) result = sel.fit_transform(X) print(X, "\n" ,result) 复制代码
VarianceThreshold去掉了第一列,第一列里面0的比例为5/6,超过了4/5。
单变量特征选择-SelectKBest/SelectPercentile/GenericUnivariateSelect
单变量特征选择通过单变量统计检验选择特征,可以看作是一个估计器的预处理步骤,这也是一种过滤法。
sklearn将特征选择视为日常的转换操作:
- SelectKBest: 只保留 k 个最高分的特征;
- SelectPercentile: 只保留用户指定百分比的最高得分的特征;对每个特征使用常见的单变量统计检验:假正率
SelectFpr
、错误发现率SelectFdr
或者总体错误率SelectFwe
; - GenericUnivariateSelect: 通过结构化策略进行特征选择或者通过超参数搜索估计器进行特征选择。
例如,使用卡方检验选择两个最优特征。
from sklearn.datasets import load_iris from sklearn.feature_selection import SelectKBest from sklearn.feature_selection import chi2 iris = load_iris() X, y = iris.data, iris.target X_new = SelectKBest(chi2, k=2).fit_transform(X, y) print(X.shape, X_new.shape) # (150, 4) (150, 2) 复制代码
根据FPR,根据卡方检验选择低于alpha的p值。
from sklearn.datasets import load_breast_cancer from sklearn.feature_selection import SelectFdr, chi2 X, y = load_breast_cancer(return_X_y=True) X_new = SelectFdr(chi2, alpha=0.01).fit_transform(X, y) X.shape, X_new.shape # ((569, 30), (569, 16)) 复制代码
对于单变量特征选择来说,如果是分类问题,可采用chi2
(卡方检验)、f_classif
(方差分析的F值)、mutual_info_classif
(互信息)。
- chi2:卡方检验,经典的卡方检验是检验定性自变量对定性因变量的相关性;
- f_classif:方差分析,计算方差分析(ANOVA)的F值 (组间均方 / 组内均方);
- mutual_info_classif:互信息,互信息方法可以捕捉任何一种统计依赖,但是作为非参数方法,需要更多的样本进行准确的估计。
如果是回归问题,可采用f_regression
(相关系数的F值)、mutual_info_regression
(互信息)
- f_regression:相关系数,计算每个变量与目标变量的相关系数,然后计算出F值和P值
- mutual_info_regression:互信息,互信息度量 X 和 Y 共享的信息:它度量知道这两个变量其中一个,对另一个不确定度减少的程度。
警告
注意不要对分类问题使用回归评分函数,否则会得到无用的结果。
递归特征清除-RFE(Recursive feature elimination)
递归特征清除就是给定一个外部估计量来给特征分配权重(例如,一个线性模型的系数),这是一种包装法。
递归特征消除(RFE)的目标是通过递归考虑越来越小的特征集来选择特征。
首先,估计量通过对初始特征集进行训练得到,每个特性的重要性可以通过任何特定的属性(如coef_
, feature_importances_
)获得。
然后,从当前的特征集合中剪枝出最不重要的特征。这个过程在修剪过的集合上递归地重复,直到最终达到想要选择的特征的数量。
RFECV
使用交叉验证方法执行RFE,以找到最优特征数。
下面的例子,显示了一个数字分类任务中像素的相关性。
from sklearn.svm import SVC from sklearn.datasets import load_digits from sklearn.feature_selection import RFE import matplotlib.pyplot as plt # Load the digits dataset digits = load_digits() X = digits.images.reshape((len(digits.images), -1)) y = digits.target # (1797, 8, 8) 1797 (1797, 64) (1797,) print(digits.images.shape, len(digits.images), X.shape, y.shape) # 创建RFE对象并对每个像素进行排序 svc = SVC(kernel="linear", C=1) rfe = RFE(estimator=svc, n_features_to_select=1, step=1) rfe.fit(X, y) print(digits.images[0].shape) # (8, 8) ranking = rfe.ranking_.reshape(digits.images[0].shape) # Plot pixel ranking plt.matshow(ranking, cmap=plt.cm.Blues) plt.colorbar() plt.title("Ranking of pixels with RFE") plt.show() 复制代码
ranking
表示特征的排序,ranking_[i]
表示的就是第i个特征的排名位置。估计最佳的属性被排为1。
下面的例子,通过交叉验证自动调整所选特征的数量。
import matplotlib.pyplot as plt from sklearn.svm import SVC from sklearn.model_selection import StratifiedKFold from sklearn.feature_selection import RFECV from sklearn.datasets import make_classification # 使用3个信息丰富的特征构建分类任务 X, y = make_classification(n_samples=1000, n_features=25, n_informative=3, n_redundant=2, n_repeated=0, n_classes=8, n_clusters_per_class=1, random_state=0) print(X.shape, y.shape) # (1000, 25) (1000,) # Create the RFE object and compute a cross-validated score. svc = SVC(kernel="linear") # “准确度”评分与正确分类数成正比 min_features_to_select = 1 # Minimum number of features to consider rfecv = RFECV(estimator=svc, step=1, cv=StratifiedKFold(2), scoring='accuracy', min_features_to_select=min_features_to_select) rfecv.fit(X, y) # Optimal number of features : 3 print("Optimal number of features : %d" % rfecv.n_features_) # 画出特征数与交叉验证分数的对比 plt.figure() plt.xlabel("Number of features selected") plt.ylabel("Cross validation score (nb of correct classifications)") plt.plot(range(min_features_to_select, len(rfecv.grid_scores_) + min_features_to_select), rfecv.grid_scores_) plt.show() 复制代码
grid_scores_
表示交叉验证分数,比如,grid_scores_[i]
对应于第 i 个特征子集的 CV 分数。这意味着第一个分数是所有要素的分数,第二个分数是一组要素被删除等时的分数。每个中删除的特征的数量等于step参数的值。默认情况下,该值为1。
训练基模型,选择权值系数较高的特征-SelectFromModel
SelectFromModel是一种元转换器,这是一种嵌入法,可以与那些有coef_
或者feature_importances_
属性的模型一起使用。 如果coef_
或者feature_importances_
小于阈值,我们就认为特征是不重要的。 除了指定阈值以外,也可以使用启发式的方式。有效的启发式方法包括均值、中位数或者乘以系数,比如: 0.1*均值。
SelectFromModel例子如下所示,首先,加载数据集。
from sklearn.datasets import load_diabetes diabetes = load_diabetes() X, y = diabetes.data, diabetes.target 复制代码
我们使用的糖尿病数据集由442名糖尿病患者的10个特征组成。
然后,通过coefficients
查看特征重要性。
import matplotlib.pyplot as plt import numpy as np from sklearn.linear_model import LassoCV lasso = LassoCV().fit(X, y) importance = np.abs(lasso.coef_) feature_names = np.array(diabetes.feature_names) plt.bar(height=importance, x=feature_names) plt.title("Feature importances via coefficients") plt.show() 复制代码
使用SelectFromModel方法选择特征,因为我们只想选择2个特征,所以我们将这个阈值设置为略高于第三个最重要特征的系数。
from sklearn.feature_selection import SelectFromModel from time import time threshold = np.sort(importance)[-3] + 0.01 tic = time() sfm = SelectFromModel(lasso, threshold=threshold).fit(X, y) toc = time() print("Features selected by SelectFromModel: " f"{feature_names[sfm.get_support()]}") print(f"Done in {toc - tic:.3f}s") # Features selected by SelectFromModel: ['s1' 's5'] # Done in 0.067s 复制代码
顺序特征选择-SequentialFeatureSelector
顺序特征选择(SFS)可以前向特征选择或后向特征选择。这也是一种包装法。
前向SFS是一个贪婪的过程,它迭代地寻找最好的新特性添加到所选的特性集合中。
具体地说,我们一开始从零特征开始,当一个评估者被训练在这一特征上时,我们会找到一个能够最大化交叉验证分数的特征。一旦第一个特性被选择,我们通过向所选特性集合添加一个新特性来重复这个过程。当达到所需的选定特性数量(由n_features_to_select参数确定)时,过程停止。
后向SFS遵循同样的思想,但却朝着相反的方向工作:我们不是从没有特性开始,然后贪婪地添加特性,而是从所有特性开始,然后贪婪地从集合中删除特性。
通常,向前选择和向后选择不会产生相同的结果。
另外,一个可能比另一个快得多,这取决于所要求的选择特性的数量:如果我们有10个特性,要求7个选择特性,向前选择将需要执行7次迭代,而向后选择将只需要执行3次。
SFS与(RFE和SelectFromModel)的不同之处在于, 它不需要底层模型公开coef_或feature_importes_属性。
然而,与其他方法相比,考虑到需要评估更多的模型,它可能会慢一些。
例如在逆向选择中,使用k-fold交叉验证从m个特性到m - 1个特性的迭代需要拟合m * k个模型,而RFE只需要一次拟合,而SelectFromModel总是只做一次拟合,不需要迭代。
简而言之,使用SequentialFeatureSelection
方法进行特征选择的话,SFS是一个贪婪的过程,在每一次迭代中,我们根据交叉验证得分选择最好的新特征添加到我们选择的特征中。也就是说,我们从0个特征开始,选择得分最高的最好的一个特征。重复这个过程,直到我们达到所选择特征的期望数目。我们也可以沿着相反的方向(反向SFS),即从所有的特性开始,贪婪地逐个选择要删除的特性。
我们将在这里说明这两种方法。
from sklearn.feature_selection import SequentialFeatureSelector tic_fwd = time() sfs_forward = SequentialFeatureSelector(lasso, n_features_to_select=2, direction='forward').fit(X, y) toc_fwd = time() tic_bwd = time() sfs_backward = SequentialFeatureSelector(lasso, n_features_to_select=2, direction='backward').fit(X, y) toc_bwd = time() print("Features selected by forward sequential selection: " f"{feature_names[sfs_forward.get_support()]}") print(f"Done in {toc_fwd - tic_fwd:.3f}s") print("Features selected by backward sequential selection: " f"{feature_names[sfs_backward.get_support()]}") print(f"Done in {toc_bwd - tic_bwd:.3f}s") # Features selected by forward sequential selection: ['bmi' 's5'] # Done in 2.177s # Features selected by backward sequential selection: ['bmi' 's5'] # Done in 6.433s 复制代码
总结
本文主要讲述了特征选择的三类方法(过滤法、包装法、嵌入法)以及sklearn中常用的特征选择方法,具体方法如下表格:
类 | 方法 | 功能 |
VarianceThreshold | 过滤法 | 方差选择法,去掉那些方差没有达到阈值的特征。 |
SelectKBest/SelectPercentile/GenericUnivariateSelect | 过滤法 | 相关系数法,通过单变量统计检验选择特征,可选择卡方检验、互信息法等。 |
RFE/RFECV | 包装法 | 递归的训练基模型,从当前的特征集合中剪枝出最不重要的特征。 |
SelectFromModel | 嵌入法 | 训练基模型,选择权值系数较高的特征,有基于惩罚项的特征选择法和基于树模型的特征选择法。 |
SequentialFeatureSelector | 包装法 | 通过前向特征选择或后向特征选择,在每一次迭代中,我们根据交叉验证得分选择最好的新特征添加到我们选择的特征中。 |