机器学习(10)---特征选择


一、概述

 1. 从所有的特征中选择出有意义对模型有帮助的特征以避免必须将所有特征都导入模型去训练的情况。
 2. 我们来看一组数据

import pandas as pd
data = pd.read_csv(r"D:\Download\digit recognizor.csv")
X = data.iloc[:,1:]
y = data.iloc[:,0]
print(X.shape)

在这里插入图片描述
这个数据量相对夸张如果使用支持向量机和神经网络很可能会直接跑不出来。使用KNN跑一次大概需要半个小时。用这个数据举例能更够体现特征选择的重要性。

二、Filter过滤法

2.1 过滤法说明

 1. 过滤方法通常用作预处理步骤特征选择完全独立于任何机器学习算法。它是根据各种统计检验中的分数以及相关性的各项指标来选择特征。
 2. 处理步骤图
在这里插入图片描述

2.2 方差过滤

 1. 这是通过特征本身的方差来筛选特征的类。比如一个特征本身的方差很小就表示样本在这个特征上基本没有差异可能特征中的大多数值都一样甚至整个特征的取值都相同那这个特征对于样本区分没有什么作用。所以无论接下来的特征工程要做什么都要优先消除方差为0的特征。VarianceThreshold有重要参数threshold表示方差的阈值表示舍弃所有方差小于threshold的特征不填默认为0即删除所有的记录都相同的特征。
 2. 处理上面数据

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

在这里插入图片描述
注意我们可以看到特征数量已经由784变为了708。但是依然剩下了708多个特征明显还需要进一步的特征选择。然而如果我们知道我们需要多少个特征方差也可以帮助我们将特征选择一步到位。比如说我们希望留下一半的特征那可以设定一个让特征总数减半的方差阈值只要找到特征方差的中位数再将这个中位数作为参数threshold的值输入就好了如下面代码块

import numpy as np
X_fsvar = VarianceThreshold(np.median(X.var().values)).fit_transform(X)
X.var().values
np.median(X.var().values)
print(X_fsvar.shape)

2.3 方差过滤对模型影响

 1. KNN方差过滤前后

import pandas as pd
from sklearn.feature_selection import VarianceThreshold
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)

#======【TIME WARNING35mins +】======#
print(cross_val_score(KNN(),X,y,cv=5).mean())

#======【TIME WARNING4 hours】======#
print(cross_val_score(KNN(),X,y,cv=5).mean())

在这里插入图片描述
在这里插入图片描述
 2. 随机森林方差过滤前后

import pandas as pd
from sklearn.feature_selection import VarianceThreshold
import numpy as np
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestClassifier as RFC
X_fsvar = VarianceThreshold(np.median(X.var().values)).fit_transform(X)
print(cross_val_score(RFC(n_estimators=10,random_state=0),X,y,cv=5).mean())
print(cross_val_score(RFC(n_estimators=10,random_state=0),X_fsvar,y,cv=5).mean())

在这里插入图片描述

注解1首先可以观察到的是随机森林的准确率略逊于KNN但运行时间却连KNN的1%都不到只需要十几秒钟。其次方差过滤后随机森林的准确率也微弱上升但运行时间却几乎是没什么变化依然是11秒钟。
2为什么随机森林运行如此之快为什么方差过滤对随机森林没很大的有影响这是由于两种算法的原理中涉及到的计算量不同。
3最近邻算法KNN单棵决策树支持向量机SVM神经网络回归算法都需要遍历特征或升维来进行运算所以他们本身的运算量就很大需要的时间就很长因此方差过滤这样的特征选择对他们来说就尤为重要。但对于不需要遍历特征的算法比如随机森林它随机选取特征进行分枝本身运算就非常快速因此特征选择对它来说效果平平。这其实很容易理解无论过滤法如何降低特征的数量随机森林也只会选取固定数量的特征来建模而最近邻算法就不同了特征越少距离计算的维度就越少模型明显会随着特征的减少变得轻量。因此过滤法的主要对象是需要遍历特征或升维的算法们而过滤法的主要目的是在维持算法表现的前提下帮助算法们降低计算成本。

 3. 对受影响的算法如KNN来说我们可以将方差过滤的影响总结如下

阈值很小被过滤掉得特征比较少阈值比较大被过滤掉的特征有很多
模型表现不会有太大影响可能变更好代表被滤掉的特征大部分是噪音也可能变糟糕代表被滤掉的特征中很多都是有效特征
运行时间可能降低模型的运行时间基于方差很小的特征有多少当方差很小的特征不多时对模型没有太大影响一定能够降低模型的运行时间算法在遍历特征时的计算越复杂运行时间下降得越多

三、相关性过滤

 方差挑选完毕之后我们就要考虑下一个问题相关性了。我们希望选出与标签相关且有意义的特征因为这样的特征能够为我们提供大量信息。如果特征与标签无关那只会白白浪费我们的计算内存可能还会给模型带来噪音。在sklearn当中我们有三种常用的方法来评判特征与标签之间的相关性卡方F检验互信息

3.1 卡方过滤

 1. 卡方过滤是专门针对离散型标签即分类问题的相关性过滤。卡方检验类feature_selection.chi2计算每个非负特征和标签之间的卡方统计量并依照卡方统计量由高到低为特征排名。再结合feature_selection.SelectKBest这个可以输入”评分标准“来选出前K个分数最高的特征的类我们可以借此除去最可能独立于标签与我们分类目的无关的特征。
 2. 另外如果卡方检验检测到某个特征中所有的值都相同会提示我们使用方差先进行方差过滤。并且刚才我们已经验证过当我们使用方差过滤筛选掉一半的特征后模型的表现时提升的。因此在这里我们使用threshold=中位数时完成的方差过滤的数据来做卡方检验如果方差过滤后模型的表现反而降低了那我们就不会使用方差过滤后的数据而是使用原数据。
 3. 代码块举例

from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.model_selection import cross_val_score
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
#假设在这里我一直我需要300个特征
X_fschi = SelectKBest(chi2, k=300).fit_transform(X_fsvar, y)
print(X_fschi.shape) #输出42000300
cross_val_score(RFC(n_estimators=10,random_state=0),X_fschi,y,cv=5).mean()

在这里插入图片描述

注解可以看出模型的效果降低了这说明我们在设定k=300的时候删除了与模型相关且有效的特征我们的K值设置得太小要么我们需要调整K值要么我们必须放弃相关性过滤。当然如果模型的表现提升则说明我们的相关性过滤是有效的是过滤掉了模型的噪音的这时候我们就保留相关性过滤的结果。

3.2 F检验

 1. F检验又称ANOVA方差齐性检验是用来捕捉每个特征与标签之间的线性关系的过滤方法。它即可以做回归也可以做分类因此包含feature_selection.f_classifF检验分类和feature_selection.f_regressionF检验回归两个类。其中F检验分类用于标签是离散型变量的数据而F检验回归用于标签是连续型变量的数据。
 2. 和卡方检验一样这两个类需要和类SelectKBest连用并且我们也可以直接通过输出的统计量来判断我们到底要设置一个什么样的K。需要注意的是F检验在数据服从正态分布时效果会非常稳定因此如果使用F检验过滤我们会先将数据转换成服从正态分布的方式。
 3. 代码块举例

from sklearn.feature_selection import f_classif
F, pvalues_f = f_classif(X_fsvar,y)
k = F.shape[0] - (pvalues_f > 0.05).sum() #392
X_fsF = SelectKBest(f_classif, k=392).fit_transform(X_fsvar, y)
print(cross_val_score(RFC(n_estimators=10,random_state=0),X_fsF,y,cv=5).mean())

在这里插入图片描述

注解所有的特征都是和标签相关的因此我们不需要相关性过滤。

3.3 互信息法

 1. 互信息法是用来捕捉每个特征与标签之间的任意关系包括线性和非线性关系的过滤方法。和F检验相似它既可以做回归也可以做分类并且包含两个类feature_selection.mutual_info_classif互信息分类和feature_selection.mutual_info_regression互信息回归。这两个类的用法和参数都和F检验一模一样不过互信息法比F检验更加强大F检验只能够找出线性关系而互信息法可以找出任意关系。
 2. 互信息法不返回p值或F值类似的统计量它返回“每个特征与目标之间的互信息量的估计”这个估计量在[0,1]之间取值为0则表示两个变量独立为1则表示两个变量完全相关。
 3. 代码块举例

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)
print(cross_val_score(RFC(n_estimators=10,random_state=0),X_fsmic,y,cv=5).mean())

注解所有特征的互信息量估计都大于0因此所有特征都与标签相关。

3.4 过滤法总结

 到这里我们学习了常用的基于过滤法的特征选择包括方差过滤基于卡方F检验和互信息的相关性过滤。通常来说我会建议先使用方差过滤然后使用互信息法来捕捉相关性不过了解各种各样的过滤方式也是必要的。所有信息被总结在下表

说明超参数的选择
VarianceThreshold方差过滤可输入方差阈值返回方差大于阈值的新特征矩阵看具体数据究竟是含有更多噪声还是更多有效特征一般就使用0或1来筛选也可以画学习曲线或取中位数
SelectKBest用来选取K个统计量结果最佳的特征生成符合统计量要求的新特征矩阵看配合使用的统计量
chi2卡方检验专用于分类算法捕捉相关性追求p小于显著性水平的特征
f_classifF检验分类只能捕捉线性相关性要求数据服从正态分布追求p小于显著性水平的特征
f_regressionF检验回归只能捕捉线性相关性要求数据服从正态分布追求p小于显著性水平的特征
mutual_info_classif互信息分类可以捕捉任何相关性不能用于稀疏矩阵追求互信息估计大于0的特征
mutual_info_regression互信息回归可以捕捉任何相关性不能用于稀疏矩阵追求互信息估计大于0的特征

四、Embedded嵌入法

4.1 嵌入法说明

 1. 1嵌入法是一种让算法自己决定使用哪些特征的方法即特征选择和算法训练同时进行。2在使用嵌入法时我们先使用某些机器学习的算法和模型进行训练得到各个特征的权值系数根据权值系数从大到小选择特征。这些权值系数往往代表了特征对于模型的某种贡献或某种重要性比如决策树和树的集成模型中的feature_importances_属性可以列出各个特征对树的建立的贡献我们就可以基于这种贡献的评估找出对模型建立最有用的特征。3因此相比于过滤法嵌入法的结果会更加精确到模型的效用本身对于提高模型效力有更好的效果。并且由于考虑特征对模型的贡献因此无关的特征需要相关性过滤的特征和无区分度的特征需要方差过滤的特征都会因为缺乏对模型的贡献而被删除掉可谓是过滤法的进化版。

在这里插入图片描述

 2. 然而嵌入法也不是没有缺点过滤法中使用的统计量可以使用统计知识和常识来查找范围如p值应当低于显著性水平0.05而嵌入法中使用的权值系数却没有这样的范围可找——我们可以说权值系数为0的特征对模型丝毫没有作用但当大量特征都对模型有贡献且贡献不一时我们就很难去界定一个有效的临界值。这种情况下模型权值系数就是我们的超参数我们或许需要学习曲线或者根据模型本身的某些性质去判断这个超参数的最佳值究竟应该是多少。

注解在机器学习中超参数hyperparameters是指在模型训练过程中需要手动设置的参数而不是由模型自动学习得到的参数。这些超参数通常用于控制模型的学习过程和性能。与超参数相对的是模型参数model parameters它们是模型自动学习得到的参数例如权重和偏置。

 3. 模块feature_selection.SelectFromModel(estimator, threshold=None, prefit=False, norm_order=1,max_features=None)
 SelectFromModel是一个元变换器可以与任何在拟合后具有coef_feature_importances_属性或参数中可选惩罚项的评估器一起使用比如随机森林和树模型就具有属性、feature_importances_逻辑回归就带有l1和l2惩罚项线性支持向量机也支持l2惩罚项。
 对于有feature_importances_的模型来说若重要性低于提供的阈值参数则认为这些特征不重要并被移除。feature_importances_的取值范围是[0,1]如果设置阈值很小比如0.001就可以删除那些对标签预测完全没贡献的特征。如果设置得很接近1可能只有一两个特征能够被留下。

选读使用惩罚项的模型的嵌入法。
对于使用惩罚项的模型来说正则化惩罚项越大特征在模型中对应的系数就会越小。当正则化惩罚项大到一定的程度的时候部分特征系数会变成0当正则化惩罚项继续增大到一定程度时所有的特征系数都会趋于0。但是我们会发现一部分特征系数会更容易先变成0这部分系数就是可以筛掉的。也就是说我们选择特征系数较大的特征。另外支持向量机和逻辑回归使用参数C来控制返回的特征矩阵的稀疏性参数C越小返回的特征越少。Lasso回归用alpha参数来控制返回的特征矩阵alpha的值越大返回的特征越少。

参数说明
estimator使用的模型评估器只要是带feature_importances_或者coef_属性或带有l1和l2惩罚项的模型都可以使用
threshold特征重要性的阈值重要性低于这个阈值的特征都将被删除
prefit默认False判断是否将实例化后的模型直接传递给构造函数。如果为True则必须直接调用fit和transform不能使用fit_transform并且SelectFromModel不能与cross_val_scoreGridSearchCV和克隆估计器的类似实用程序一起使用。
norm_orderk可输入非零整数正无穷负无穷默认值为1在评估器的coef_属性高于一维的情况下用于过滤低于阈值的系数的向量的范数的阶数。
max_features在阈值设定下要选择的最大特征数。要禁用阈值并仅根据max_features选择请设置threshold = -np.inf

注意我们重点要考虑的是前两个参数。

4.2 以随机森林为例的嵌入法

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)
#在这里我只想取出来有限的特征。0.005这个阈值对于有780个特征的数据来说是非常高的阈值因为平均每个特征
只能够分到大约0.001的feature_importances_
X_embedded.shape
#模型的维度明显被降低了
#同样的我们也可以画学习曲线来找最佳阈值
#======【TIME WARNING10 mins】======#
import numpy as np
import matplotlib.pyplot as plt
RFC_.fit(X,y).feature_importances_
threshold = np.linspace(0,(RFC_.fit(X,y).feature_importances_).max(),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()

在这里插入图片描述

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

X_embedded = SelectFromModel(RFC_,threshold=0.00067).fit_transform(X,y)X_embedded.shape
cross_val_score(RFC_,X_embedded,y,cv=5).mean()

注解可以看出特征个数瞬间缩小到324多这比我们在方差过滤的时候选择中位数过滤出来的结果392列要小并且交叉验证分数0.9399高于方差过滤后的结果0.9388这是由于嵌入法比方差过滤更具体到模型的表现的缘故换一个算法使用同样的阈值效果可能就没有这么好了。和其他调参一样我们可以在第一条学习曲线后选定一个范围使用细化的学习曲线来找到最佳值。

#======【TIME WARNING10 mins】======#
score2 = []
foriinnp.linspace(0,0.00134,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.00134,20),score2)
plt.xticks(np.linspace(0,0.00134,20))
plt.show()

在这里插入图片描述

注解查看结果果然0.00067并不是最高点真正的最高点0.000564已经将模型效果提升到了94%以上。

#=====【TIME WARNING2 min】=====#
#我们可能已经找到了现有模型下的最佳结果如果我们调整一下随机森林的参数呢
cross_val_score(RFC(n_estimators=100,random_state=0),X_embedded,y,cv=5).mean()

注解得出的特征数目依然小于方差筛选并且模型的表现也比没有筛选之前更高已经完全可以和计算一次半小时的KNN相匹敌KNN的准确率是96.58%接下来再对随机森林进行调参准确率应该还可以再升高不少。可见在嵌入法下我们很容易就能够实现特征选择的目标减少计算量提升模型表现。因此比起要思考很多统计量的过滤法来说嵌入法可能是更有效的一种方法。然而在算法本身很复杂的时候过滤法的计算远远比嵌入法要快所以大型数据中我们还是会优先考虑过滤法。

五、Wrapper包装法

5.1 包装法说明

 1. 装法也是一个特征选择和算法训练同时进行的方法与嵌入法十分相似它也是依赖于算法自身的选择比如coef_属性或feature_importances_属性来完成特征选择。但不同的是我们往往使用一个目标函数作为黑盒来帮助我们选取特征而不是自己输入某个评估指标或统计量的阈值。包装法在初始特征集上训练评估器并且通过coef_属性或通过feature_importances_属性获得每个特征的重要性。然后从当前的一组特征中修剪最不重要的特征。在修剪的集合上递归地重复该过程直到最终到达所需数量的要选择的特征。区别于过滤法和嵌入法的一次训练解决所有问题包装法要使用特征子集进行多次训练因此它所需要的计算成本是最高的。
在这里插入图片描述
 2. 注意在这个图中的“算法”指的不是我们最终用来导入数据的分类或回归算法即不是随机森林而是专业的数据挖掘算法即我们的目标函数。这些数据挖掘算法的核心功能就是选取最佳特征子集。
 3. 最典型的目标函数是递归特征消除法Recursive feature elimination, 简写为RFE。它是一种贪婪的优化算法旨在找到性能最佳的特征子集。它反复创建模型并在每次迭代时保留最佳特征或剔除最差特征下一次迭代时它会使用上一次建模中没有被选中的特征来构建下一个模型直到所有特征都耗尽为止。然后它根据自己保留或剔除特征的顺序来对特征进行排名最终选出一个最佳子集。包装法的效果是所有特征选择方法中最利于提升模型表现的它可以使用很少的特征达到很优秀的效果。除此之外在特征数目相同时包装法和嵌入法的效果能够匹敌不过它比嵌入法算得更见缓慢所以也不适用于太大型的数据。相比之下包装法是最能保证模型效果的特征选择方法。
 4. 模块feature_selection.RFE(estimator, n_features_to_select=None, step=1, verbose=0)
 参数estimator是需要填写的实例化后的评估器n_features_to_select是想要选择的特征个数step表示每次迭代中希望移除的特征个数。除此之外RFE类有两个很重要的属性.support_返回所有的特征的是否最后被选中的布尔矩阵以及.ranking_返回特征的按数次迭代中综合重要性的排名。

5.2 以随机森林为例的包装法

fromsklearn.feature_selectionimportRFE
RFC_ = RFC(n_estimators =10,random_state=0)
selector = RFE(RFC_, n_features_to_select=340, step=50).fit(X, y)
cross_val_score(RFC_,X_wrapper,y,cv=5).mean()
#======【TIME WARNING: 15 mins】======#
core = []
foriinrange(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%以上比嵌入法和过滤法都高效很多。我们可以放大图像寻找模型变得非常稳定的点来画进一步的学习曲线就像我们在嵌入法中做的那样。如果我们此时追求的是最大化降低模型的运行时间我们甚至可以直接选择50作为特征的数目这是一个在缩减了94%的特征的基础上还能保证模型表现在90%以上的特征组合不可谓不高效。
同时我们提到过在特征数目相同时包装法能够在效果上匹敌嵌入法。试试看如果我们也使用340作为特征数目运行一下可以感受一下包装法和嵌入法哪一个的速度更加快。由于包装法效果和嵌入法相差不多在更小的范围内使用学习曲线我们也可以将包装法的效果调得很好。

六、总结

 经验来说过滤法更快速但更粗糙。包装法和嵌入法更精确比较适合具体到算法去调整但计算量比较大运行时间长。当数据量很大的时候优先使用方差过滤和互信息法调整再上其他特征选择方法。使用逻辑回归时优先使用嵌入法。使用支持向量机时优先使用包装法。迷茫的时候从过滤法走起看具体数据具体分析。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: 机器学习