课程地址:《菜菜的机器学习sklearn课堂》_哔哩哔哩_bilibili
目录
概述
过滤法 Filter
(一)方差过滤
1、VarianceThreshold类
2、对模型的影响
3、选取超参数threshold
(二)相关性过滤
1、卡方(feature_selection.chi2,仅分类)
选取超参数K(画学习曲线;看p值)
2、F检验(feature_selection.f_classif 分类;feature_selection.f_regression 回归)
3、互信息(feature_selection.mutual_info_classif 分类;feature_selection.mutual_info_regression 回归)
(三)过滤法总结
嵌入法 Embedding(过滤法的进化版)
feature_selection.SelectFromModel
包装法 Wrapper(结合了过滤法和嵌入法)
feature_selection.RFE
feature_selection.RFECV
特征选择总结
当数据预处理完成后,就是特征工程了。特征工程有三种:
本文主要讲特征选择 feature_selection,它完全独立于任何机器学习算法
特征选择的第一步是根据目标,用业务常识来选择特征,即理解业务。例如:
以判断“是否存活”为目的
但如果无法依赖对业务的理解来选择特征,有四种方法可以用来选择特征:
数据集digit recognizor:
#导入数据,让我们使用digit recognizor数据来一展身手
import pandas as pd
data = pd.read_csv(r".\digit recognizor.csv")
X = data.iloc[:,1:] # 特征,也即pixel
y = data.iloc[:,0] # label
X.shape # (42000, 784)
根据各种统计检验中的分数以及相关性的各项指标来选择特征
通过特征本身的方差来筛选特征,优先消除方差为0的特征
from sklearn.feature_selection import VarianceThreshold
selector = VarianceThreshold() #实例化,不填参数默认方差为0
X_var0 = selector.fit_transform(X) #获取删除不合格特征之后的新特征矩阵
#也可以直接写成 X = VairanceThreshold().fit_transform(X)
X_var0.shape #(42000, 708)
pd.DataFrame(X_var0).head()
删了方差为0的特征后还剩708个特征,还需要进一步特征选择
import numpy as np
# X.var() 每一列的方差,是Series
# np.median(X.var().values) 1352.286703180131
X_fsvar = VarianceThreshold(np.median(X.var().values)).fit_transform(X)
X_fsvar.shape #(42000, 392)
#若特征是伯努利随机变量,假设p=0.8,即二分类特征中某种分类占到80%以上的时候删除特征
X_bvar = VarianceThreshold(.8 * (1 - .8)).fit_transform(X)
X_bvar.shape # (42000, 685)
KNN和随机森林分别在方差过滤前、后运行效果和运行时间的对比
(1)导入模块并准备数据
# KNN vs 随机森林在不同方差过滤效果下的对比
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.neighbors import KNeighborsClassifier as KNN
from sklearn.model_selection import cross_val_score
import numpy as np
# 未过滤的数据
X = data.iloc[:,1:]
y = data.iloc[:,0]
# 使用中位数过滤后的数据
X_fsvar = VarianceThreshold(np.median(X.var().values)).fit_transform(X)
(2)KNN方差过滤前
python中的魔法命令,可以直接使用 %%timeit 来计算运行这个cell中的代码所需的时间
为了计算所需的时间,需要将这个cell中的代码运行很多次(通常是7次)后求平均值,因此运行%%timeit的时间会远远超过cell中的代码单独运行的时间usageerror: line magic function "%%time" not found_南风有翼的博客-CSDN博客_line magic function `%%time` not found.
cross_val_score(KNN(),X,y,cv=5).mean()
%%timeit # 将%%time放在代码块的顶行顶格,否则会报错
cross_val_score(KNN(),X,y,cv=5).mean()
(3)KNN方差过滤后
cross_val_score(KNN(),X_fsvar,y,cv=5).mean()
%%timeit
cross_val_score(KNN(),X_fsvar,y,cv=5).mean()
对于KNN,过滤后的效果十分明显:
(4)随机森林方差过滤前
cross_val_score(RFC(n_estimators=10,random_state=0),X,y,cv=5).mean()
%%timeit
# 查看一下模型运行的时间
cross_val_score(RFC(n_estimators=10,random_state=0),X,y,cv=5).mean()
(5)随机森林方差过滤后
cross_val_score(RFC(n_estimators=10,random_state=0),X_fsvar,y,cv=5).mean()
%%timeit
# 查看一下模型运行的时间
cross_val_score(RFC(n_estimators=10,random_state=0),X_fsvar,y,cv=5).mean()
为什么随机森林运行如此之快,且方差过滤对随机森林没有很大影响?
—— 因为两种算法的原理中涉及到的计算量不同
- 最近邻KNN、单棵决策树、SVM、NN、回归算法,都需要遍历特征或升维来进行运算,故运算量大,需要时间长,故方差过滤这样的特征选择对它们来说尤为重要 —— 对于最近邻算法来说,特征越少,距离计算的维度就越少,模型明显会随着特征的减少变得轻量
- 对于不需要遍历特征的算法,如随机森林(随机选取特征进行分枝),本身运算就非常快速,故特征选择对它来说效果平平 —— 无论过滤法如何降低特征的数量,随机森林也只会选取固定数量的特征来建模
对受影响的算法来说,将方差过滤的影响总结如下:
在我们的对比中使用的方差阈值是特征方差的中位数,因此属于阈值比较大,过滤掉的特征比较多的情况。但无论是KNN还是随机森林,在过滤掉一半特征之后,模型的精确度都上升了,这说明被过滤掉的特征在当前随机模式 random_state=0 下大部分是噪音
这里的方差阈值是一个超参数,要选定最优的超参数,可以画学习曲线,找模型效果最好的点(但这样做会耗费大量时间,现实中不会采用)
现实中一般只会使用阈值为0或者阈值很小的方差过滤,优先消除一些明显用不到的特征,再选择更优的特征选择方法继续削减特征数量
选出与标签相关且有意义的特征
在sklearn中有三种方法来评判特征与标签之间的相关性:卡方、F检验、互信息
计算每个非负特征(此处需要做数据预处理)和标签之间的卡方统计量,并按照卡方统计量由高到低为特征排名
feature_selection.SelectKBest 可以输入“评分标准”,选出前K个分数最高的特征
如果卡方检验检测到某个特征中所有的值都相同,会提示我们使用方差先进行方差过滤
刚才已经验证过,当我们使用方差过滤筛选掉一半的特征后,模型的表现是提升的,因此在这里使用 threshold=中位数时完成的方差过滤的数据 来做卡方检验
(如果方差过滤后模型的表现反而降低了,就不使用方差过滤后的数据,而是使用原数据)
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.model_selection import cross_val_score
from sklearn.feature_selection import SelectKBest # 输入“评分标准”,选出前K个分数最高的特征
from sklearn.feature_selection import chi2
#假设在这里我知道我需要300个特征
X_fschi = SelectKBest(chi2, k=300).fit_transform(X_fsvar, y) # SelectKBest(模型依赖的统计量,k=表示选出前k个统计量最大的特征,也即特征数)
X_fschi.shape # (42000, 300)
# 验证一下模型的效果如何
cross_val_score(RFC(n_estimators=10,random_state=0),X_fschi,y,cv=5).mean()
模型的效果降低了,说明在设定 k=300 时删除了与模型相关且有效的特征 —> k值设置的太小,要么需要调整k值,要么放弃相关性过滤 (若模型表现提升,则说明相关性过滤有效,过滤掉了模型的噪音,这时保留相关性过滤的结果)
方法1:学习曲线(运行时间长)
%matplotlib inline
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(390,200,-10),score)
plt.show()
随着K值的不断增加,模型的表现不断上升,这说明K越大越好,数据中所有的特征都是与标签相关的
方法2:看p值
卡方检验的本质是推测两组数据之间的差异,其检验的原假设是“两组数据是相互独立的”,返回卡方值和P值两个统计量
从特征工程角度,希望选取卡方值很大、P值小于0.05的特征(即和标签相关的特征)
调用SelectKBest之前,可以直接从chi2实例化后的模型中获得各个特征所对应的卡方值和P值
chivalue, pvalues_chi = chi2(X_fsvar,y)
chivalue
pvalues_chi
所有特征的p值都是0,说明对于digit recognizor这个数据集来说,方差过滤已经把所有和标签无关的特征都剔除了,或这个数据集本身就不含与标签无关的特征。此时舍弃任何一个特征,都会舍弃对模型有用的信息,使模型表现下降
因此,在我们对计算速度满意时,不需要使用相关性过滤。若认为运算速度太缓慢,可以酌情删除一些特征,但会牺牲模型的表现
# k取多少?我们想要消除所有p值大于设定值,比如0.05或0.01的特征:
# 想保留的特征数量k = 特征数量-想要删除的特征数
k = chivalue.shape[0] - (pvalues_chi > 0.05).sum() # chivalue.shape[0]为392(特征总数),pvalues_chi > 0.05返回的是布尔值,sum后就是True的数量(想要删除的特征数,即不相关的特征)
# X_fschi = SelectKBest(chi2, k=填写具体的k).fit_transform(X_fsvar, y)
# cross_val_score(RFC(n_estimators=10,random_state=0),X_fschi,y,cv=5).mean()
F检验(ANOVA / 方差齐性检验):捕捉每个特征与标签之间的线性关系。F检验在数据服从正态分布时效果会非常稳定,故使用F检验过滤前会先将数据转换成服从正态分布的方式
from sklearn.feature_selection import f_classif
F, pvalues_f = f_classif(X_fsvar,y)
F
pvalues_f
F.shape # (392,)
k = F.shape[0] - (pvalues_f > 0.05).sum()
k # 392
# X_fsF = SelectKBest(f_classif, k=填写具体的k).fit_transform(X_fsvar, y)
# cross_val_score(RFC(n_estimators=10,random_state=0),X_fsF,y,cv=5).mean()
没有任何特征的p值>0.01,所有特征都是和标签相关的,故不需要相关性过滤
互信息法:捕捉每个特征与标签之间的任意关系(包括线性和非线性关系)
from sklearn.feature_selection import mutual_info_classif as MIC
result = MIC(X_fsvar,y)
result
k = result.shape[0] - sum(result <= 0) # result.shape[0]为392
k # 392
# X_fsmic = SelectKBest(MIC, k=填写具体的k).fit_transform(X_fsvar, y)
# cross_val_score(RFC(n_estimators=10,random_state=0),X_fsmic,y,cv=5).mean()
所有特征的互信息量估计都>0,故所有特征都与标签相关
基于过滤法的特征选择,包括方差过滤、基于卡方/F检验/互信息的相关性过滤
建议先使用方差过滤,然后使用互信息法来捕捉相关性
让算法自己决定使用哪些特征,即特征选择和算法训练同时进行
在使用嵌入法时,先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据权值系数从大到小选择特征
相比于过滤法,嵌入法的结果会更加精确到模型的效用本身,对于提高模型效力有更好的效果;同时,由于考虑特征对模型的贡献,因此无关的特征(需要相关性过滤的特征)和无区分度的特征(需要方差过滤的特征)都会因为缺乏对模型的贡献而被删除
嵌入法的缺点:
SelectFromModel 是一个元变换器,可以与任何在拟合后具有 coef_ 、 feature_importances_ 属性,或参数中可选惩罚项的评估器一起使用。例如:
(1)对于有 feature_importances_ (取值范围是[0,1])的模型来说,若重要性低于提供的阈值参数,则认为这些特征不重要并被移除
(2)对于使用惩罚项的模型来说,正则化惩罚项越大,特征在模型中对应的系数就会越小;当正则化惩罚项大到一定程度时,部分特征系数会变成0,这部分系数是可以筛掉的。即选择特征系数较大的特征
例:使用随机森林为例,使用学习曲线寻找最佳特征值
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier as RFC
RFC_ = RFC(n_estimators =10,random_state=0) # 随机森林的实例化
X_embedded = SelectFromModel(RFC_,threshold=0.005).fit_transform(X,y) # SelectFromModel(RFC_,threshold=0.005)为嵌入法的实例化
#在这里我只想取出来有限的特征。0.005这个阈值对于有784个特征的数据来说,是非常高的阈值,因为平均每个特征只能够分到大约0.001的feature_importances_
X_embedded.shape # (42000, 47)
#模型的维度明显被降低了
#同样的,我们也可以画学习曲线(横轴:某个范围内超参数的取值;纵轴:模型的表现)来找最佳阈值
import numpy as np
import matplotlib.pyplot as plt
RFC_.fit(X,y).feature_importances_
# range(开头,结尾,步长)
# np.linspace(开头,结尾,数量)
threshold = np.linspace(0,(RFC_.fit(X,y).feature_importances_).max(),20) # 选取最小值和最大值中间的有限个数,是学习曲线的x轴
threshold # 阈值越大,被砍掉的特征就越多
score = []
for i in threshold:
X_embedded = SelectFromModel(RFC_,threshold=i).fit_transform(X,y)
once = cross_val_score(RFC_,X_embedded,y,cv=5).mean()
score.append(once)
plt.plot(threshold,score)
plt.show()
从图像上来看,随着阈值越来越高,模型的效果逐渐变差,被删除的特征越来越多,信息损失也逐渐变大
# 从中挑选一个数值来验证一下模型的效果
X_embedded = SelectFromModel(RFC_,threshold=0.001).fit_transform(X,y)
X_embedded.shape # (42000, 279)
cross_val_score(RFC_,X_embedded,y,cv=5).mean() # 0.9386904761904763
和其他调参一样,可以在第一条学习曲线后选定一个范围,使用细化的学习曲线来找到最佳值
score2 = []
for i in np.linspace(0,0.001,20):
X_embedded = SelectFromModel(RFC_,threshold=i).fit_transform(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.001,20),score2)
plt.xticks(np.linspace(0,0.001,20))
plt.show()
最高点0.000158已经将模型效果提升到了94.1%以上
X_embedded = SelectFromModel(RFC_,threshold=0.000158).fit_transform(X,y)
X_embedded.shape # (42000, 421)
cross_val_score(RFC_,X_embedded,y,cv=5).mean() # 0.9411428571428571
#我们可能已经找到了现有模型下的最佳结果,如果我们调整一下随机森林的参数呢?
cross_val_score(RFC(n_estimators=100,random_state=0),X_embedded,y,cv=5).mean()
总结:比起要思考很多统计量的过滤法来说,嵌入法可能是更有效的一种方法,然而在算法本身很复杂的时候,过滤法的计算远远比嵌入法快,故大型数据中还是会优先考虑过滤法
特征选择和算法训练同时进行,与嵌入法十分相似,也是依赖于算法自身的选择,比如 coef_ 属性或 feature_importances_ 属性来完成特征选择
上图中的算法,不是我们最终用来导入数据的分类或回归算法,而是专业的数据挖掘算法(即目标函数),这些数据挖掘算法的核心功能就是选取最佳特征子集。最典型的目标函数是递归特征消除法(Recursive feature elimination, 简写为RFE)。它是一种贪婪的优化算法,旨在找到性能最佳的特征子集。它反复创建模型,并在每次迭代时保留最佳特征或剔除最差特征,下一次迭代时,它会使用上一次建模中没有被选中的特征来构建下一个模型,直到所有特征都耗尽为止。 然后,它根据自己保留或剔除特征的顺序来对特征进行排名,最终选出一个最佳子集
不同点:
参数:
属性:
from sklearn.feature_selection import RFE
RFC_ = RFC(n_estimators =10,random_state=0)
selector = RFE(RFC_, n_features_to_select=421, step=50).fit(X, y) # 421是之前嵌入法选择出的最佳特征数
selector.support_.sum() # 就是n_features_to_select的值
selector.ranking_ # 可以有多个特征重要性相同,故排名一样
X_wrapper = selector.transform(X) # 使用包装法得到的特征矩阵
cross_val_score(RFC_,X_wrapper,y,cv=5).mean() # 0.9395
对包装法画学习曲线:
score = []
for i in range(1,751,50):
X_wrapper = RFE(RFC_,n_features_to_select=i, step=50).fit_transform(X,y)
once = cross_val_score(RFC_,X_wrapper,y,cv=5).mean()
score.append(once)
plt.figure(figsize=[20,5])
plt.plot(range(1,751,50),score)
plt.xticks(range(1,751,50))
plt.show()
在包装法下面应用50个特征时,模型的表现就已经达到了90%以上,比嵌入法和过滤法都高效很多(包装法是最容易在最小的特征数量状况下找到最佳的模型表现的方法)
在交叉验证循环中执行RFE以找到最佳数量的特征,增加参数cv(交叉验证次数),其他用法同RFE
过滤法更快速,但更粗糙。迷茫的时候,从过滤法走起
包装法和嵌入法更精确,比较适合具体到算法去调整,但计算量比较大,运行时间长