- Filter过滤法
- 留下一半的特征,即选定 阈值 = 特征方差的中位数
X_fsvar = VarianceThreshold(np.median(X.var().values)).fit_transform(X)
# print(X.var().values)
print(X_fsvar.shape)
# (42000, 392) - Embedded嵌入法
- feature_selection.SelectFromModel
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.model_selection import cross_val_score - 画学习曲线
# print(RFC.fit(X, y).feature_importances)
threshold = np.linspace(0, (RFC.fit(X,y).feature_importances).max(), 20) # 等差数列 - Wrapper包装法
- 特征选择总结
1 特征提取
2 特征创造 - 把现有特征进行组合,或相互计算,得到新的特征
3 特征选择 - 选出有意义,对模型有帮助的特征
首先 理解业务 *
极端情况 - 无法依赖对业务的理解来选择特征**,有以下4种方法来选择特征:
Filter过滤法
过滤方法通常用作 预处理步骤
特征选择独立于ML,是根据各种统计检验中的分数以及相关性的各项指标来选择特征
全部特征 —> 最佳特征子集 —> 算法 —> 模型评估
⚪ 方差过滤
一个特征方差很小 = 这个特征基本没什么差异 = 对于样本区分没什么作用
so 无论接下来的特征工程要做什么,优先消除方差为0的特征
# VarianceThreshold
import pandas as pd
import numpy as np
from sklearn.preprocessing import KBinsDiscretizer
data = pd.read_csv(‘digit recognizor.csv’)
X = data.iloc[:, 1:]
y = data.iloc[:, 0]
print(X.shape)
# (42000, 784)
from sklearn.feature_selection import VarianceThreshold
selector = VarianceThreshold(threshold=0)
X_var0 = selector.fit_transform(X)
print(X_var0.shape)
# (42000, 708)
留下一半的特征,即选定 阈值 = 特征方差的中位数
X_fsvar = VarianceThreshold(np.median(X.var().values)).fit_transform(X)
# print(X.var().values)
print(X_fsvar.shape)
# (42000, 392)
当特征是二分类,特征的取值就是伯努利随机变量,这些变量的方差:Var[X] = p(1-p) // p为概率
#若特征是伯努利随机变量,假设p=0.8,即二分类特征中某种分类占到80%以上的时候删除特征
X_bvar = VarianceThreshold(.8 * (1 - .8)).fit_transform(X)
print(X_bvar.shape)
选取超参数 threshold
我们怎样知道,方差过滤掉的到底时噪音还是有效特征呢?过滤后模型到底会变好还是会变坏呢?答案是:每个数据集不一样,只能自己去尝试。这里的方差阈值,其实相当于是一个超参数,要选定最优的超参数,我们可以画学习曲线,找模型效果最好的点。但现实中,我们往往不会这样去做,因为这样会耗费大量的时间。我们只会使用阈**值为0或者阈值很小的方差过滤,来为我们优先消除一些明显用不到的特征,然后我们会选择更优的特征选择方法**
⚪ 相关性过滤
选出与标签相关且有意义的特征 - 评价特征与标签的相关性:1 卡方,2 F检验,3 互信息
卡方检验 过滤 - 针对离散型标签
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
# feature_selection.chi2 计算每个非负特征和标签之间的卡方统计量
# feature_selection.SelectKBest 输出k个分数最高的特征的类
X_fschi = SelectKBest(chi2, k=300).fit_transform(X_fsvar,y)
print(X_fschi.shape)
# (42000, 300)
过滤之后 看模型的表现情况!!!
如果模型表现提升,说明我们相关性过滤是有效的,是过滤掉了模型的噪音的
如果模型效果降低,说明k=300时删除了与模型相关且有效的特征,需调整k值或者放弃相关性过滤
选取超参数K
那如何设置一个最佳的K值呢?在现实数据中,数据量很大,模型很复杂的时候,我们也许不能先去跑一遍模型看看效果,而是希望最开始就能够选择一个最优的超参数k。那第一个方法,就是我们之前提过的学习曲线
import matplotlib.pyplot as plt
score = []
for i in range(390,200,-10):
X_fschi = SelectKBest(chi2, k=i).fit_transform(X_fsvar, y)
once = cross_val_score(RFC(n_estimators=10,random_state=0),X_fschi,y,cv=5).mean()
score.append(once)
plt.plot(range(350,200,-10),score)
plt.show()
通过这条曲线,我们可以观察到,随着K值的不断增加,模型的表现不断上升,这说明,K越大越好,数据中所有的特征都是与标签相关的。
但是运行这条曲线的时间同样也是非常地长,接下来我们就来介绍一种更好的选择k的方法:看**p值选择k**
卡方检验的本质是推测两组数据之间的差异,其检验的原假设是”两组数据是相互独立的”。
卡方检验返回卡方值和P值两个统计量,其中卡方值很难界定有效的范围,而p值,我们一般使用0.01或0.05作为显著性水平,即p值判断
从特征工程的角度,我们希望选取卡方值很大,p值小于0.05的特征,即和标签是相关联的特征。
而调用SelectKBest之前,我们可以直接从chi2实例化后的模型中获得各个特征所对应的卡方值和P值。
chivalue, pvalues_chi = chi2(X_fsvar,y)
# print(chivalue) # 卡方值
# print(pvalues_chi) # p值
k = chivalue.shape[0] - (pvalues_chi > 0.05).sum()
X_fschi = SelectKBest(chi2, k=k).fit_transform(X_fsvar,y)
print(X_fschi.shape)
F检验 ANOVA - 方差齐性检验,用来捕捉每个特征与标签之间的线性关系的过滤方法
分类 - feature_selection.f_classif - 离散型变量
回归 - feature_selection.f_regression - 连续型变量
F检验在数据服从正态分布时效果会非常稳定,使用F检验过滤前,先将数据转换成服从正态分布的方式
F检验的本质是寻找两组数据之间的线性关系,其原假设是”数据不存在显著的线性关系“。
它返回F值和p值两个统计量。
和卡方过滤一样,我们希望选取p值小于0.05或0.01的特征,这些特征与标签时显著线性相关的,而p值大于0.05或0.01的特征则被我们认为是和标签没有显著线性关系的特征,应该被删除。
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_classif
F, pvalues_f = f_classif(X_fsvar, y)
# print(F)
# print(pvalues_f)
k = F.shape[0] - (pvalues_f > 0.05).sum()
X_fsF = SelectKBest(f_classif, k=k).fit_transform(X_fsvar, y)
互信息法 - 捕捉每个特征与标签之间的任意关系
分类 - feature_selection.mutual_info_classif - 离散型变量
回归 - feature_selection.mutual_info_regression - 连续型变量
互信息法 返回”每个特征与目标之间的互信息量的估计”,估计量∈[0, 1]
0 - 两个变量独立
1 - 两个变量完全相关
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import mutual_info_classif as MIC
result = MIC(X_fsvar, y)
k = result.shape[0] - sum(result<=0)
X_fsF = SelectKBest(MIC, k=k).fit_transform(X_fsvar, y)
通常来说,先 方差过滤,再 互信息法 捕捉相关性
Embedded嵌入法
让算法自己决定使用哪种特征的方法,即 特征选择 和 算法 同时进行
1 使用某些ML算法和模型进行训练,得到各个特征的权值系数
权值系数往往代表了特征对于模型的某种贡献或某种重要性
2 基于对这种贡献的评估,找出对模型建立最有用的特征
相比于过滤法,嵌入法的结果会更加精确到模型的效用本身,对于提高模型效力有更好的效果。
并且,由于考虑特征对模型的贡献,因此无关的特征(需要相关性过滤的特征)和无区分度的特征(需要方差过滤的特征)都会因为缺乏对模型的贡献而被删除掉。
即过滤法的进化版
过滤法中使用的统计量可以使用统计知识和常识来查找范围(如p值应当低于显著性水平0.05),而嵌入法中使用的权值系数却没有这样的范围可找——我们可以说,权值系数为0的特征对模型丝毫没有作用,但当大量特征都对模型有贡献且贡献不一时,我们就很难去界定一个有效的临界值。
这种情况下,模型权值系数就是我们的超参数,我们或许需要学习曲线,或者根据模型本身的某些性质去判断这个超参数的最佳值究竟应该是多少。
feature_selection.SelectFromModel
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.model_selection import cross_val_score
data = pd.read_csv(‘digit recognizor.csv’)
X = data.iloc[:, 1:]
y = data.iloc[:, 0]
print(X.shape)
RFC = RFC(n_estimators=10, random_state=0)
X_embedded = SelectFromModel(RFC, threshold=0.005).fittransform(X, y)
# 在这里我只想取出来有限的特征。0.005这个阈值对于有780个特征的数据来说,是非常高的阈值,
# 因为平均每个特征只能够分到大约0.001的feature_importances
print(X_embedded.shape)
画学习曲线
# print(RFC.fit(X, y).feature_importances)
threshold = np.linspace(0, (RFC.fit(X,y).feature_importances).max(), 20) # 等差数列
score = []
for i in threshold:
Xembedded = SelectFromModel(RFC, threshold=i).fittransform(X,y)
once = cross_val_score(RFC, Xembedded, y ,cv=5).mean()
score.append(once)
plt.plot(threshold, score)
plt.show()
在0.00134之前,模型效果都可以维持在0.93以上
即选定了一个 [ 0 , 0.00134 ] 范围,选用更细化的学习曲线来找最佳值
score2 = []
for i in np.linspace(0,0.00134,20):
X_embedded = SelectFromModel(RFC,threshold=i).fittransform(X,y)
once = cross_val_score(RFC,X_embedded,y,cv=5).mean()
score2.append(once)
plt.figure(figsize=[20,5])
plt.plot(np.linspace(0,0.00134,20),score2)
plt.xticks(np.linspace(0,0.00134,20))
plt.show()
得出的特征数目依然小于方差筛选,并且模型的表现也比没有筛选之前更高,已经完全可以和计算一次半小时的
KNN相匹敌(KNN的准确率是96.58%),接下来再对随机森林进行调参,准确率应该还可以再升高不少。可见,
在嵌入法下,我们很容易就能够实现特征选择的目标:减少计算量,提升模型表现。因此,比起要思考很多统计量
的过滤法来说,嵌入法可能是更有效的一种方法。然而,在算法本身很复杂的时候,过滤法的计算远远比嵌入法要
快,所以大型数据中,我们还是会优先考虑过滤法。
Wrapper包装法
与嵌入法相似
使用一个目标函数作为黑盒来选取特征,而不是自己输入的某个评估指标或统计量的阈值
包装法在初始特征集上训练评估器,并且通过coef属性或通过feature_importances属性获得每个特征的重要性。
然后,从当前的一组特征中修剪最不重要的特征。在修剪的集合上递归地重复该过程,直到最终到达所需数量的要选择的特征。
区别于过滤法和嵌入法的一次训练解决所有问题,包装法要使用特征子集进行多次训练,因此它所需要的计算成本是最高的。
最典型的目标函数是递归特征消除法(Recursive feature elimination, 简写为RFE)。它是一种贪婪的优化算法,旨在找到性能最佳的特征子集。
它反复创建模型,并在每次迭代时保留最佳特征或剔除最差特征,下一次迭代时,它会使用上一次建模中没有被选中的特征来构建下一个模型,直到所有特征都耗尽为止。
然后,它根据自己保留或剔除特征的顺序来对特征进行排名,最终选出一个最佳子集。
包装法的效果是所有特征选择方法中最利于提升模型表现的,它可以使用很少的特征达到很优秀的效果。
除此之外,在特征数目相同时,包装法和嵌入法的效果能够匹敌,不过它比嵌入法算得更见缓慢,所以也不适用于太大型的数据。
相比之下,包装法是最能保证模型效果的特征**选择方法。**
RFC = RFC(n_estimators =10,random_state=0)
selector = RFE(RFC, n_features_to_select=340, step=50).fit(X, y)
n_features_to_select 想要选择的特征个数
step 每次迭代中希望移除的特征个数
print(selector.support.sum()) # .support:返回所有特征最后是否被选中的布尔矩阵
print(selector.ranking) # .ranking: 返回特征的按次数迭代中综合重要性的排名
Xwrapper = selector.transform(X)
print(cross_val_score(RFC, X_wrapper, y, cv=5).mean())
特征选择总结
过滤法快,但粗糙;
包装法和嵌入法更精确,但计算量大
当数据量很大时,优先使用方差过滤和互信息法调整,再选用其他特征选择方法
逻辑回归 优先嵌入法
SVM 优先包装法
迷茫的时候 从过滤法开始
降维算法 ????????
