引言

特征选择,也称特征子集选择,是指从MM 个特征中选择NN 个特征参与机器学习模型训练,以使模型的性能有所提升,同时,特征选择也降低特征维度,提升了模型的计算效率。
可以说,特征选择提取出了更易于理解的特征,挖掘了底层数据中隐藏的有用信息。毕竟特征越多,并不直接意味着其性能会变好,反之会使模型更复杂,训练时间更长,带来“维度灾难”。

根据特征选择的形式可以将特征选择方法分为3种:

  • Filter:过滤法。按照发散性或者相关性对各个特征进行评分,设定阈值或者待选择特征的个数,选择特征。
  • Wrapper:包裹法。根据目标函数(通常是预测效果评分),每次选择若干特征,或者排除若干特征。
  • Embedded:嵌入法。先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据系数从大到小选择特征。类似于Filter方法,区别是嵌入法是通过训练来确定特征优劣的。

过滤法|Filter

Filter,即过滤法,也叫筛选法。故名思议就是对大量已有的特征在训练之前做筛选的方法,显然此过程与后序学习器无关。过滤法的评估手段一般是判断单维特征与目标变量之间的关系。

方差过滤

方差的统计学意义表征的是数据分布的“发散”程度,即方差越大数据分布越不集中。
在特征选择中,我们往往认为如果特征分布相对集中,即特征取值差异不大,则其对分类器的贡献值相对较小。因此我们可以设定方差的一个阈值,将方差较小的特征剔除掉。

scikit-learn 中的 VarianceThreshold 选择器可用来处理此任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from sklearn.feature_selection import VarianceThreshold

vt = VarianceThreshold(3) #可传入阈值参数
X1 = vt.fit(X) #进行过滤

var_thd = pd.DataFrame(vt.variances_,
columns = ["Variance"],
index = features)
var_thd = var_thd.reset_index()
var_thd = var_thd.sort_values('Variance', ascending = 0) #可打印查看

X_new = vt.transform(X) #留下的数据
featrues_new = vt.get_feature_names_out() #被留下特征的字符名称
X_new = pd.DataFrame(X_new, columns = features_new) #可打印查看

阈值Threshold 可以有很多选择,如:特征中位数 np.median(x.var.valuse);特征除以特征均值:x/x.mean ;方差均值 x.var().mean()等.

卡方过滤

卡方过滤是专门针对离散型标签(即分类问题)的相关性过滤。

卡方统计量反映的是实际频数和理论频数的吻合度,在特征工程中也就是说特征与标签的相关性情况,我们希望被选择的特征与标签之间的卡方统计量更大。

卡方检验类 feature_selection.chi2 计算每个非负特征和标签之间的卡方统计量,并依照卡方统计量由高到低为特征排名。
结合 feature_selection.SelectKBest, 可以根据输入的”评分标准“来选出前K个分数最高的特征。
于是我们可以借此剔除最可能独立于标签,与我们分类目的无关的特征:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2

# 使用SelectKBest转换器类,用卡方函数打分
chi2er = SelectKBest(score_func = chi2, k = 10)
chi2er.fit(X, y)

# 得分情况
scores = pd.DataFrame(chi2er.scores_, columns = ["Scores"], index = features)
scores = scores.reset_index()
scores = scores.sort_values('Scores', ascending = 0) #可打印查看

X_new = chi2er.transform(X) #留下的数据
features_new = chi2er.get_feature_names_out() #被留下特征的字符名称
X_new = pd.DataFrame(X_new, columns = features_new) #可打印查看

ANOVA过滤

与卡方检验类似的还有 F 检验,又称ANOVA,方差齐性检验。它可以用来捕捉每个特征与标签之间的线性关系,可以做回归也可以做分类。
scikit-learn 中包含两个类:

  1. feature_selection.f_classif(分类)
  2. feature_selection.f_regression(回归)

其过滤方法与卡方分布一样。

互信息过滤

互信息法是用来捕捉每个特征与标签之间的任意关系(包括线性和非线性关系)的过滤方法。
和F检验相似,它既可以做回归也可以做分类,在scikit-learn 中包含两个类:

  1. feature_selection.mutual_info_classif(分类)
  2. feature_selection.mutual_info_regression(回归)

这两个类的用法和参数都和F检验一模一样,不过互信息法比F检验更加强大,F检验只能够找出线性关系,而互信息法可以找出任意关系。

Pearson相关系数

可以直接利用 pandas.DataFrame.corr() 计算得到各变量之间的相关系数。
当然为了与前面几种方法对应,我们也可以利用 scipy.stats.pearsonr 结合 feature_selection.SelectKBest进行选择。为此我们需要封装一个函数用于实现对多列的X=[X1,X2,...,Xn]X=[X_1,X_2,...,X_n] 分别计算其与yy 的相关系数的函数:

1
2
3
4
5
6
7
8
9
def multi_pearsonr(X, y): 
# 创建scores和pvalues数组,遍历数据集的每一列。
scores, pvalues = [], []
for column in range(X.shape[1]):
# 只计算该列的皮尔逊相关系数和p值,并将其存储到相应数组中。
cur_score, cur_p = pearsonr(X[:,column], y)
scores.append(abs(cur_score))
pvalues.append(cur_p)
return (np.array(scores), np.array(pvalues))

然后就能用相同的方法实现 top K 的特征选择了:

1
2
m_pearsonr = SelectKBest(score_func=multi_pearsonr, k=10) 
X_pearson = m_pearsonr.fit_transform(X, y)

方差膨胀系数

方差膨胀系数(variance inflation factor,VIF) 是衡量多元线性回归模型中多重共线性(multicollinearity) 严重程度的一种度量。它表示回归系数估计量的方差与假设自变量间不线性相关时方差相比的比值。

多重共线性是指自变量之间存在线性相关关系,即一个自变量可以是其他一个或几个自变量的线性组合。

通常以 10作为判断边界:

  • VIF<10VIF<10 时,不存在多重共线性;
  • 10VIF<10010\leq VIF<100 时,存在较强的多重共线性;
  • VIF100VIF\geq100 时,存在严重多重共线性。
1
2
3
4
5
from statsmodels.stats.outliers_influence import variance_inflation_factor 

vif = pd.DataFrame()
vif['Features'] = feature_cols
vif['vif'] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])]

一般来说,我们要提出过大 VIF 的特征。

Fisher得分

Fisher得分是一种基于距离的特征选择方法,Fisher得分越大,该特征的分类能力越强,说明该特征可以使得分类时类内部距离尽量小,类间距离尽可能大。

实现方法详见下面给出的这个项目。

🚀【推荐】特征选择开源项目:https://github.com/jundongl/scikit-feature

包裹法|Wrapper

包裹式特征选择法的特征选择过程与学习器相关,使用学习器的性能作为特征选择的评价准则,选择出最有利于学习器性能的特征子集。

特征子集的产生方式分为前向搜索后向搜索两种方法,在实现上,常见的方法有:

  1. 稳定性选择(Stability Selection)
  2. 递归特征消除(Recursive Feature Elimination)

这两种方法也被称为 顶层特征选择算法。因为它们都需要有特定的机器学习模型作为支撑。

递归特征消除RFE

递归特征消除(Recursive Feature Elimination, RFE) 的主要思想是,反复构建模型,然后选出其中贡献最差的特征,把选出的特征剔除,然后在剩余特征上继续重复这个过程,直到所有特征都已遍历。这是一种采用了贪心策略的后向搜索方法。

scikit-learn 中封装了 RFE:

1
class sklearn.feature_selection.RFE(estimator, n_features_to_select=None, step=1, verbose=0)

其中的参数:

  • estimator – 基学习器
  • n_features_to_select – 经过特征选择后,特征集中剩余的特征个数
  • step – 默认1,即每次迭代移除一个特征
  • verbose – 默认0,不显示中间过程

RFE 的稳定性很大程度上取决于迭代时,底层使用的预测模型。如果 RFE 采用的是普通的逻辑回归,没有经过正则化的回归是不稳定的,因此 RFE 也不稳定。若采用的是脊回归 Ridge 或 Lasso,则 RFE 稳定。

结合交叉验证的递归特征消除RFECV

RFE 提供了设定 n_features_to_select 的能力,但是保留特征的数量其实是存在一定的盲目性的,这可能使得模型性能变差。
比如,n_features_to_select过小时,相关特征可能被移除特征集,信息丢失;n_features_to_select过大时,无关特征没有被移除特征集,信息冗余。

在工程实践中,RFE通过CV(交叉验证)寻找最优的n_features_to_select,即RFECV。

scikit-learn中RFECV的主要参数为:

  • estimator – 基学习器
  • step – 默认1,即每次迭代移除一个特征
  • cv – 默认2,即二折交叉验证的方式进行特征选择
  • scoring – 根据学习目标设定的模型评分标准
  • verbose – 默认0,即不显示中间过程
  • n_jobs – 默认1,即不使用并行计算,单核计算
1
2
3
4
5
6
from sklearn.feature_selection import RFECV
from sklearn.tree import DecisionTreeClassifier
rfecv = RFECV(estimator=DecisionTreeClassifier()) #以决策树为例
rfecv.fit(X, y)
print(rfecv.support_) #返回bool列表,表明哪些特征被保留
X_new = X.loc[:, results.support_]

稳定性选择

稳定性选择(Stability Selection) 是基于重新抽样技术的特征选择方法。其基本思想是:对原始数据进行多次抽样,每次生成的子样本上进行特征选择,然后统计各个特征被选为"重要"的频率,最终确定那些在大多数子样本中都被认为是"重要"的特征。

稳定性选择不仅可以用来评估特征的重要性,而且由于其基于重新抽样的特性,它还能评估特征选择结果的稳定性,也就是说,对于一个特定的选择方法,其选择结果在不同数据样本上的变动程度。

scikit-learn 在随机LASSO和随机逻辑回归中封装了对稳定性选择的实现。

嵌入法|Embedded

有些机器学习方法本身就具有对特征进行打分的机制,特征排序模型和机器学习模型是耦合在一起的。
在模型训练的过程中,更重要的特征自动获得了更大的权重,通过获取这些权重,就可以得到不同特征的重要性分数。

所以,通过模型机制获得重要性并以此进行特征选择的方法就是嵌入法,这种方法速度快,也易出效果,但需较深厚的先验知识调节模型。

回归模型系数

我们知道线性回归模型可以看作一个多项式,其中每一项的系数可以表征这一维特征的重要程度,越是重要的特征在模型中对应的系数(绝对值)就会越大,而跟输出变量越是无关的特征对应的系数越接近于零。

因此,可以将回归模型系数视为对特征的打分,从而进行特征选择。

正则化模型

我们知道正则化是指对机器学习模型的不同参数增加惩罚或惩罚,以减少模型的自由度,从而避免过度拟合。

对线性模型实施L1L1 正则化的模型我们也叫 LASSO 回归模型。因为它在损失函数中添加了一个与系数绝对值有关的项,可以将一些特征的系数压缩为0,于是就可以通过检查哪些特征的系数被压缩到0来进行特征选择。

1
2
3
4
5
6
7
8
9
10
11
12
13
from sklearn.linear_model import Lasso
from sklearn.feature_selection import SelectFromModel
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
scaler.fit(X)
stdX = scaler.transform(X) #对特征进行尺度归一化

sel_ = SelectFromModel(Lasso(alpha=0.03))
sel_.fit(stdX, y)

features_new = X.columns[(sel_.get_support())]
X_new = X[features]

NOTE:线性模型无法解决相互之间不独立的特征,即是“多重共线性”,对离群点或噪声较为敏感。不过施加L2L2 正则化的 Ridge 回归(岭回归)可以更好地处理这种情况。

参考

  1. 特征选择与提取最全总结之过滤法 - 知乎
  2. 熟练掌握这3种特征选择方法,模型性能至少提升20%
  3. 特征选择系列-01-过滤式详解-从原理到应用_过滤式特征选择-CSDN
  4. 【特征选择】包裹式特征选择法 - wanglei5205 - 博客园
  5. 结合Scikit-learn介绍几种常用的特征选择方法-CSDN
  6. 机器学习——特征选择方法大总结-CSDN博客
  7. 8_feature_selection_华校专