机器学习:08. sklearn中的特征选择feature_selection

特征选择

概念:就是从所有的特征中,选择出有意义,对模型有帮助的特征,以避免必须将所有特征都导入模型去训练的情况。
特征选择常用的方法有:过滤法,嵌入法,包装法,和降维算法。

导入数据

import pandas as pd
data = pd.read_csv(r"C:\Users\18700\Desktop\03数据预处理和特征工程\digit recognizor.csv")
data.head()

X = data.iloc[:,1:] #特征矩阵
y = data.iloc[:,0] #标签
X.shape #维度指的是特征的数量,这个数据维度太高
#(42000, 784)
"""
这个数据量相对夸张,如果使用支持向量机和神经网络,很可能会直接跑不出来。使用KNN跑一次大概需要半个小时。
用这个数据举例,能更够体现特征工程的重要性。
"""

1. Filter过滤法

过滤方法通常用作预处理步骤,特征选择完全独立于任何机器学习算法。

全部特征——最佳特征子集——算法——评估

1.1 方差过滤 (一般来说,只用来过滤到0/1的方差小的特征)

1.1.1 VarianceThreshold

通过特征本身的方差来筛选特征的类。优先消除方差为0的特征。VarianceThreshold有重要参数threshold,表示方差的阈值,表示舍弃所有方差小于threshold的特征,不填默认为0,即删除所有的记录都相同的特征。

from sklearn.feature_selection import VarianceThreshold
selector = VarianceThreshold() #实例化,不填参数默认方差为0
X_var0 = selector.fit_transform(X) #获取删除不合格特征之后的新特征矩阵
#也可以直接写成 X = VairanceThreshold().fit_transform(X)
X_var0.shape #是一个ndarray
#(42000, 708) #可将特征变少了,删除了方差为0的特征

#X_var0.head() #会报错
pd.DataFrame(X_var0).head() #所以需要转换为dataframe

根据方差的中位数进行过滤

#通过var的中位数进行过滤
import numpy as np
X_fsvar = VarianceThreshold(np.median(X.var().values)).fit_transform(X) #就是舍弃方差小于中位数方差的特征
#X.var()是一个series,.values提取对应的值
X.var().values
np.median(X.var().values)
X_fsvar.shape
#(42000, 392)

特征是伯努利随机变量(二分类)

#若特征是伯努利随机变量,假设p=0.8,即二分类特征中某种分类占到80%以上的时候删除特征
X_bvar = VarianceThreshold(.8 * (1 - .8)).fit_transform(X)
X_bvar.shape
#(42000, 685)

1.1.2 方差过滤对模型的影响

  1. 最近邻算法KNN,单棵决策树,支持向量机SVM,神经网络,回归算法,都需要遍历特征或升维来进行运算,所以他们本身的运算量就很大,需要的时间就很长,因此方差过滤这样的特征选择对他们来说就尤为重要。
  2. 但对于不需要遍历特征的算法,比如随机森林,它随机选取特征进行分枝,本身运算就非常快速,因此特征选择对它来说效果平平。

过滤法的主要对象是:需要遍历特征或升维的算法们;
过滤法的主要目的是:在维持算法表现的前提下,帮助算法们降低计算成本。

方差过滤的影响

如果过滤之后模型的效果反而变差了,那么被过滤掉的特征中有很多是有效特征,就需要放弃过滤。

1.2 相关性过滤

1.2.1 卡方过滤

卡方过滤是专门针对离散型标签(即分类问题)的相关性过滤。卡方检验类feature_selection.chi2计算每个非负特征和标签之间的卡方统计量,并依照卡方统计量由高到低为特征排名。再结合feature_selection.SelectKBest这个可以输入”评分标准“来选出前K个分数最高的特征的类,我们可以借此除去最可能独立于标签,与我们分类目的无关的特征。

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) #选择前300个卡方值的特征
X_fschi.shape
#(42000, 300)

验证模型的效果如何

cross_val_score(RFC(n_estimators=10,random_state=0),X_fschi,y,cv=5).mean()

输出结果

0.9344761904761905
'''
如果模型的效果降低了,这说明我们在设定k=300的时候删除了与模型相关且有效的特征,
我们的K值设置得太小,要么我们需要调整K值,要么我们必须放弃相关性过滤。
'''

1.2.2 选择超参数K

卡方检验的本质是推测两组数据之间的差异,其检验的原假设是”两组数据是相互独立的”。卡方检验返回卡方值和P值两个统计量,我们可以根据P值进行筛选,p<=0.05或0.01,说明两组数据是相关的。
从特征工程的角度,我们希望选取卡方值很大,p值小于0.05的特征,即和标签是相关联的特征。

#选择超参数K
chivalue, pvalues_chi = chi2(X_fsvar,y) #返回卡方值和P值
chivalue
pvalues_chi

#k取多少?我们想要消除所有p值大于设定值,比如0.05或0.01的特征:
#chivalue.shape[0]就是取出特征总个数,pvalues_chi > 0.05返回TRUE Flase,False=0,TRUE=1,所以就是总特征数减去所以p>0.05的特征,就是想保留的特征的数量
k = chivalue.shape[0] - (pvalues_chi > 0.05).sum() 
#X_fschi = SelectKBest(chi2, k=填写具体的k).fit_transform(X_fsvar, y) #就可以把上述的K值带进去
#cross_val_score(RFC(n_estimators=10,random_state=0),X_fschi,y,cv=5).mean()

1.2.3 F检验

F检验,又称ANOVA,方差齐性检验,是用来捕捉每个特征与标签之间的线性关系的过滤方法。它即可以做回归也可以做分类,因此包含feature_selection.f_classif(F检验分类)和feature_selection.f_regression(F检验回归)两个类。它返回F值和p值两个统计量。和卡方过滤一样,选取p值小于0.05或0.01的特征,这些特征与标签时显著线性相关的。

#F检验
from sklearn.feature_selection import f_classif
F, pvalues_f = f_classif(X_fsvar,y)
F
pvalues_f
k = F.shape[0] - (pvalues_f > 0.05).sum()

#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()

1.2.4 互信息法

互信息法是用来捕捉每个特征与标签之间的任意关系(包括线性和非线性关系)的过滤方法。和F检验相似,它既可以做回归也可以做分类,并且包含两个类feature_selection.mutual_info_classif(互信息分类)和feature_selection.mutual_info_regression(互信息回归)。
它返回“每个特征与目标之间的互信息量的估计”,这个估计量在[0,1]之间取值,为0则表示两个变量独立,为1则表示两个变量完全相关。

from sklearn.feature_selection import mutual_info_classif as MIC
result = MIC(X_fsvar,y)
k = result.shape[0] - sum(result <= 0)
#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()

1.3 过滤法总结

先使用方差过滤,然后使用互信息法来捕捉相关性

image.png

2. Embedded嵌入法

嵌入法是一种让算法自己决定使用哪些特征的方法,即特征选择和算法训练同时进行。

image.png

feature_selection.SelectFromModel

class sklearn.feature_selection.SelectFromModel (estimator, threshold=None, prefit=False, norm_order=1,max_features=None)

参数:
estimator:使用的模型评估器,只要是带feature_importances_或者coef_属性,或带有l1和l2惩罚项的模型都可以使用。
threshold:特征重要性的阈值,重要性低于这个阈值的特征都将被删除。

from sklearn.feature_selection import SelectFromModel 
from sklearn.ensemble import RandomForestClassifier as RFC

RFC_ = RFC(n_estimators =10,random_state=0) #需要先实例化随机森林,再带入SelectFromModel。
X_embedded = SelectFromModel(RFC_,threshold=0.005).fit_transform(X,y) #这个是嵌入法的实例化
#在这里我只想取出来有限的特征。0.005这个阈值对于有780个特征的数据来说,是非常高的阈值,因为平均每个特征只能够分到大约0.001的feature_importances_
X_embedded.shape
#(42000, 47)  模型的维度明显被降低了

绘制threshold的学习曲线

import numpy as np
import matplotlib.pyplot as plt

RFC_.fit(X,y).feature_importances_ #看特征重要性
#linspace(小值,大值,个数) 在最大值和最小值之间取20个数
threshold = np.linspace(0,(RFC_.fit(X,y).feature_importances_).max(),20) #让阈值的取值在0到最大值之间取出20个数

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()

image.png

可见,随着阈值越来越高,模型的效果逐渐变差,被删除的特征越来越多,信息损失也逐渐变大。但是在0.003之前,模型的效果都可以维持在0.93以上,因此我们可以从中挑选一个数值来验证一下模型的效果。
验证模型

X_embedded = SelectFromModel(RFC_,threshold=0.00067).fit_transform(X,y)
X_embedded.shape
#(42000, 324)

cross_val_score(RFC_,X_embedded,y,cv=5).mean()
#0.9391190476190475

接着,可以使用细化的学习曲线来找到最佳值

score2 = []
for i in np.linspace(0,0.002,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.002,20),score2)
plt.xticks(np.linspace(0,0.002,20))
plt.show()

可以看到,在0.000632时,模型效果提升到了94%以上,然后使用0.000632来跑一跑我们的SelectFromModel

模型验证

X_embedded = SelectFromModel(RFC_,threshold=0.000632).fit_transform(X,y)
X_embedded.shape 
#(42000, 332) 可以看到特征其实增加了,因为阈值设置的更小了

#验证模型效果如何
cross_val_score(RFC_,X_embedded,y,cv=5).mean()
#0.9407857142857143

我们可能已经找到了现有模型下的最佳结果,如果我们调整一下随机森林的参数呢?

cross_val_score(RFC(n_estimators=100,random_state=0),X_embedded,y,cv=5).mean()
# 0.9630714285714287

可见,在嵌入法下,我们很容易就能够实现特征选择的目标:减少计算量,提升模型表现。

3 Wrapper包装法

包装法也是一个特征选择和算法训练同时进行的方法,但我们往往使用一个目标函数作为黑盒来帮助我们选取特征,而不是自己输入某个评估指标或统计量的阈值。。区别于过滤法和嵌入法的一次训练解决所有问题,包装法要使用特征子集进行多次训练,计算成本位于嵌入法和过滤法之间。

image.png

最典型的目标函数是递归特征消除法(Recursive feature elimination, 简写为RFE)。它是一种贪婪的优化算法,旨在找到性能最佳的特征子集。 它反复创建模型,并在每次迭代时保留最佳特征或剔除最差特征,下一次迭代时,它会使用上一次建模中没有被选中的特征来构建下一个模型,直到所有特征都耗尽为止。

feature_selection.RFE

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

参数estimator是需要填写的实例化后的评估器,n_features_to_select是想要选择的特征个数,step表示每次迭代中希望移除的特征个数。
除此之外,RFE类有两个很重要的属性,.support_:返回所有的特征的是否最后被选中的布尔矩阵,以及.ranking_返回特征的按数次迭代中综合重要性的排名。

from sklearn.feature_selection import RFE

RFC_ = RFC(n_estimators =10,random_state=0) #实例化RFC
#n_features_to_select是模型选择的个数,step每迭代一次删除50个特征
selector = RFE(RFC_, n_features_to_select=332, step=50).fit(X, y) #RFE的实例化和训练 332是上述模型效果最好时的特征数

selector.support_.sum() #support_返回特征是否被选中的布尔矩阵,求和其实就是n_features_to_select
selector.ranking_ #返回特征的重要性排名
X_wrapper = selector.transform(X) 
#交叉验证验证模型
cross_val_score(RFC_,X_wrapper,y,cv=5).mean()
#0.9398809523809524

对包装法画学习曲线

score = []
for i in range(1,751,50): #每50个取一个值,和linspace不同。
    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%以上,比嵌入法和过滤法都高效很多。

4 特征选择总结

  1. 当数据量很大的时候,优先使用方差过滤和互信息法调整,再上其他特征选择方法。
  2. 使用逻辑回归时,优先使用嵌入法。
  3. 使用支持向量机时,优先使用包装法。
  4. 迷茫的时候,从过滤法走起,看具体数据具体分析。

你可能感兴趣的:(机器学习:08. sklearn中的特征选择feature_selection)