一直想写一篇关于特征选择(Feature Selection)的博客。
有两个原因:第一、特征选择对于传统机器学习任务是十分重要的;第二、自己在硕士期间的研究方向就是特征选择,对于学界前沿的特征选择方法是有那么一丢丢了解的。在有监督,无监督,半监督以及单标签,多标签各种场景下,也做过一些工作:
《Local-nearest-neighbors-based feature weighting for gene selection》 TCBB journal
《Unsupervised Feature Selection with Joint Clustering Analysis》 CIKM 2017
《Semi-Supervised Multi-Label Feature Selection by Preserving Feature-Label Space Consistency 》 CIKM 2018
《Probabilistic Margin-Aware Multi-Label Feature Selection by Preserving Spatial Consistency》IJCNN 2019
然而,本篇博客不介绍学界中前沿的特征选择方法。我们从实用性出发,重点介绍几个适合工业界的实操性比较强的有监督特征选择方法。
在学界,根据特征选择的形式可以将特征选择方法分为3种:
Filter:过滤式方法,它不是直接优化某一个算法,而是基于某一个准则进行特征选择,如按照发散性或者相关性对各个特征进行评分,设定阈值或者待选择阈值的个数,选择特征。过滤式方法通常是独立地选择特征,这可能会忽略特征组合之间的相关性。但,过滤式方法时间复杂度低。
Wrapper:封装式方法。与过滤式方法不同的是,封装式方法是直接优化一个具体算法来选择特征的。具体来说,每次迭代时,它是根据目标函数优化一个具体算法,然后依据优化的效果(通常是预测效果评分),选择若干特征,或者排除若干特征。
Embedded:嵌入式方法,先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据系数从大到小选择特征。类似于Filter方法,但是在训练过程中,就确定特征的优劣,从而选择特征。
综上所述,我们可以得到如下结论:
在实际中,尤其是在工业界中,数据量常常数亿级,封装式方法是不具有实操性的,而过滤式方法和嵌入式方法由于其有低时间复杂度的优良特性,因此,这两类方法在工业界常常用到。本文接下来就重点介绍这两类方法。
本章我们介绍一些Filter过滤式的方法。Filter 方法针对于每个特征进行单独考量。它会对每个特征计算得到一个评分,通过设定阈值来过滤掉那些评分低于阈值的属性,这就是单变量特征选择方法。常规的方法有方差选择法、相关系数法、假设检验法、互信息法等等。
注:iris数据集是一个分类问题,它有150个样本,4个特征,3个类别。Boston House Price数据集是一个回归问题,共有 506 个样本,13 个特征和1个输出变量,即价格。
由于上述数据简单且有代表性,接下来很多方法的代码我们都以此例。
使用方差选择法,先要计算各个特征的方差,然后根据阈值,选择方差大于阈值的特征。因为特征的方差越小就越稳定,那么对于预测结果的辨识度也会变小。
VarianceThreshold是一个简单的用于特征选择的baseline方法。它将移除所有那些不满足阈值的特征。缺省情况下,它会移除所有0-variance的特征(表示该feature下具有相同值)。使用feature_selection库的VarianceThreshold类来选择特征的代码如下:
from sklearn.datasets import load_iris
from sklearn.feature_selection import VarianceThreshold
# 方差选择法,返回值为特征选择后的数据
# 参数threshold为方差的阈值
iris = load_iris()
print(iris.data[0])
# #选择方差大于3的特征
# 输出满足高方差特征的第一个样本值
print(VarianceThreshold(threshold=3).fit_transform(iris.data)[0])
print(VarianceThreshold(threshold=3).fit_transform(iris.data).shape)
输出为:
[ 5.1 3.5 1.4 0.2]
[ 1.4]
(150, 1)
只有1个特征满足方差为3,且这个特征是第3个特征。
相关系数法主要用于回归问题中。我们分别计算训练集中各个特征与输出值之间的相关系数,设定一个阈值,选择相关系数较大的部分特征。这个相关系数一般是皮尔森相关系数。
皮尔森相关系数是一种最简单的,能帮助理解特征和响应变量之间关系的方法,该方法衡量的是变量之间的线性相关性,结果的取值区间为[-1,1],-1表示完全的负相关(这个变量下降,那个就会上升),+1表示完全的正相关,0表示没有线性相关。皮尔森相关系数的介绍。
代码如下:
from sklearn.datasets import load_boston
from scipy.stats import pearsonr
import numpy as np
boston = load_boston()
def select_by_pearson(X, Y, topk):
"""
使用 Pearson Correlation Coefficient 方法来选取最优特征集
"""
feature_index_list = []
for i in range(len(X[0])): # for each feature
feature_i = [x[i] for x in X]
corr = pearsonr(feature_i, Y)[0] # correlation with Y. pearsonr输出二元组(评分,P值)的数组
feature_index_list.append(corr)
# 按绝对值逆序输出
feature_index_topk_list = np.argsort(np.abs(feature_index_list[::-1]))[0:topk]
return feature_index_topk_list
# 输出排名最靠前的topk个特征的索引
feature_index_topk_list = select_by_pearson(boston.data, boston.target, 5)
print (feature_index_topk_list)
输出为:
[ 9 5 1 11 6]
需要注意的是:Pearson相关系数的一个明显缺陷是,作为特征排序机制,只对线性关系敏感。如果关系是非线性的,即便两个变量具有一一对应的关系,Pearson相关性也可能会接近0。例如
x = np.random.uniform(-1, 1, 100000)
print pearsonr(x, x**2)[0]
-0.00230804707612
特征选择中还可以使用的filter方法是假设检验,比如卡方检验。卡方检验用于分类问题中。
卡方检验可以检验某个特征分布和输出值分布之间的相关性。卡方检验原理及应用可以参看这篇博客。在sklearn中,可以使用chi2这个类来做卡方检验得到所有特征的卡方值与显著性水平P临界值,我们可以给定卡方值阈值, 选择卡方值较大的部分特征。用feature_selection库的SelectKBest类结合卡方检验来选择特征的代码如下:
from sklearn.datasets import load_boston, load_iris
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
from scipy.stats import pearsonr
import numpy as np
boston = load_boston()
iris = load_iris()
def select_by_chi2(X, Y, topk):
"""
使用 ChiSquare 方法来选取最优特征集
"""
sel = SelectKBest(chi2, topk)
# sel = SelectPercentile(chi, 0.8)
new_X = sel.fit_transform(X, Y)
return new_X
new_X = select_by_chi2(iris.data, iris.target, 2)
除了卡方检验,我们还可以使用F检验和t检验,它们都是使用假设检验的方法,只是使用的统计分布不是卡方分布,而是F分布和t分布而已。在sklearn中,有F检验的函数f_classif和f_regression,分别在分类和回归特征选择时使用
互信息法,即从信息熵的角度分析各个特征和输出值之间的关系评分。互信息值越大,说明该特征和输出值之间的相关性越大,越需要保留。相关知识可以参考该博客。在sklearn中,可以使用mutual_info_classif(分类)和mutual_info_regression(回归)来计算各个输入特征和输出值之间的互信息。
from sklearn.datasets import load_boston, load_iris
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import mutual_info_classif
import numpy as np
boston = load_boston()
iris = load_iris()
def select_by_MI(X, Y, topk):
"""
使用互信息方法来选取最优特征集
"""
sel = SelectKBest(mutual_info_classif, topk)
# sel = SelectPercentile(chi, 0.8)
new_X = sel.fit_transform(X, Y)
return new_X
new_X = select_by_MI(iris.data, iris.target, 2)
Embedded嵌入法,先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据系数从大到小选择特征。Embedded 方法相当于将特征选择嵌入到了模型训练的过程中。
利用Lasso进行特征选择是嵌入式方法中最开始使用的方法。在经典线性回归中,加入L1正则化,即为优化下式:
Lasso方法下解出的参数常常具有稀疏的特征,即很多特征对应的参数会为零,这就使得特征选择成为可能:我们可以训练一个Lasso模型,然后将系数为零的特征去除。结合sklearn.linear_model.Lasso,代码如下:
from sklearn.datasets import load_boston, load_iris
from sklearn import linear_model
boston = load_boston()
iris = load_iris()
clf = linear_model.Lasso(alpha=0.02)
clf.fit(iris.data, iris.target)
print(clf.coef_)
'''
[-0. -0. 0.28777827 0.35160573]
'''
sklearn中的alpha即为上式中的lambda。
当然,在实际的工作中,Lasso的参数lambda越大,参数的解越稀疏,选出的特征越少。那么如何确定使用多大的lambda?一个比较稳妥的方案是对于一系列lambda,比如说回归问题,用交叉验证计算模型的RMSE值,然后选择RMSE的极小值点时的lambda作为最终的lambda。
基于树模型的方法一般是利用树模型分裂节点时计算每个特征带来的增益作为评价特征重要性的方法。也有的是以特征分裂的次数作为评价方法。
GBDT的典型实现代表:Xgboost和LightGBM中集成了获取特征重要性排序的接口。
Xgboost如何用于特征选择?请参考这篇博客
LGBM如何用于特征选择的原理和Xgboost是差不多的。LGBM的文档中是这样介绍特征选择的方法的:
可以看到,默认的是以分裂次数多少作为特征重要的排序依据;也可以选择”gain“即计算每个特征带来的增益作为评价特征重要性的方法。
我们训练好LGBM的模型bst后,进行特征选择的代码如下:
feature_importance = pd.DataFrame({
'feature_name': bst.feature_name() ,
'feature_importance': bst.feature_importance(),
}).sort_values(by='feature_importance', ascending=False)
上面几章介绍了几种最常见的过滤式和嵌入式特征选择算法。
那么,在实际中,我们怎么选择这几种算法呢?
在样本数和特征数都十分巨大的情况下,个人推荐一般情况下使用过滤法即可。在优化模型,精益求精的时候可以考虑嵌入法。当然,这主要是工程实践经验,理论性考虑会少一些。
那么,我们一般选择多少个特征呢?
一般是看数据量,如果数据量少,但是特征数多,那么就需要选择特征,具体怎么选择看问题,初期过滤法,后期优化过滤法。“样本数目在特征数目的10倍以上就可以保留全部的特征”,算是一个工程经验,不用太在意理论依据。
还有一个常见的情况是,对于分类问题,预测类别是不平衡的,在处理中,是先进行特征选择,再进行不平衡数据处理;还是先对不平衡数据进行处理,再进行特征选择?
一般都是先进行特征选择再考虑类别是否不平衡。当然也不绝对。之所以先考虑特征选择,是因为我们的特征数太多了,比如百万级,必须要先把特征数减到一定程度才可以开始建模。如果特征数不多,那么甚至可以不需要特征选择,可以直接考虑类别不平衡的问题。
【1】 1.13. Feature selection
【2】Scikit-learn介绍几种常用的特征选择方法
【3】机器学习中,有哪些特征选择的工程方法?
【4】特征选择的探索
【5】特征工程之特征选择