likes
comments
collection
share

【机器学习|Python】sklearn中的特征选择方法

作者站长头像
站长
· 阅读数 8

前言

本文对sklearn中特征选择模块中的常用方法进行介绍和使用说明,主要介绍工具中的内容,即该库中的相关方法包含的常用接口和基本使用,了解原理可以关注以下两篇文章:

sklearn中的特征选择

  • sklearn.feature_selection:sklearn中的特征选择模块

本文主要涉及的方法:

  • 过滤法:特征选择完全独立于任何机器学习算法;根据各种统计检验中的分数或相关性的各项指标来选择特征。
    • 方差过滤:sklearn.feature_selection.VarianceThreshold
    • 卡方检验:sklearn.feature_selection.chi2
    • F检验:sklearn.feature_selection.f_classif(f_regression)
    • 互信息法:sklearn.feature_selection.mutual_info_classif(mutual_info_regression)
    • 选择前 k 个最优值:sklearn.feature_selection.SelectKBest
  • 嵌入法:特征选择和模型训练同时进行,通过使用相关机器学习模型对数据的拟合来计算各个特征的重要性,利用重要性对特征进行筛选。
    • 模型自己决定使用哪些特征:sklearn.feature.SelectFromModel
  • 包装法:特征选择和算法训练同时进行,相对过滤法和嵌入法拥有更高的计算成本,使用特征子集进行多次迭代训练。
    • 递归特征消除法:sklearn.feature.RFE

本文对sklearn中的相关方法仅介绍常用接口、属性以及相关参数。

过滤法

在很多场景下,互信息法的表现要优于卡方检验和F检验,我们一般先使用方差过滤对特征进行初步筛选,然后使用互信息法进一步进行过滤。

  • 视不同情况使用不同方法,互信息法不一定就是最好的,要多尝试

方差过滤

方差过滤:顾名思义就是依据特征本身的方差来进行筛选过滤,只能处理连续型数值变量;对于数据集中的所有特征,首先计算所有特征数据对应的方差,然后自定义设置一个阈值,丢弃方差小于等于该阈值的特征,保留方差大于该阈值的特征。

sklearn中的方差过滤sklearn.feature_selection.VarianceThreshold

  • threshold:设置方差过滤的阈值,默认为0

常用属性或接口

import pandas as pd
import numpy as np
from sklearn.feature_selection import VarianceThreshold

data = pd.DataFrame(np.random.randint(10, 100, (5, 2)))  # 准备测试数据

# 常用接口
var_thre = VarianceThreshold(threshold=0)

var_thre.fit(data)  # 拟合数据

result = var_thre.transform(data)   # 变换数据
result = var_thre.fit_transform(data)    # 拟合和变换一步达成
data = var_thre.inverse_transform(result)  # 逆向变换 (将数据转为过滤之前的原始数据)

# 一般这样用
result = VarianceThreshold(threshold=0).fit_transform(data)

我们一般会先将阈值设置为0,也就是过滤掉所有方差为0的特征,用方差过滤对特征做最初部的选择,然后自行判断阈值设置,一般会先计算所有特征的方差并对其从大到小排序,选择其中的某些值作为超参数(阈值)以指定保留的特征数量;若条件允许,也可以绘制学习曲线,观察不同方差值对模型性能的影响以确定最优值。

相关性过滤

卡方过滤:基于卡方检验,专门针对离散型分类变量(特征)的相关性过滤;首先计算每个特征和标签之间的卡方统计量,根据所有特征的卡方统计量来对特征进行过滤筛选,按卡方统计量从大到小选择前 k 个特征。

F检验:可以用于连续回归型变量,也可以用于离散分类型变量;寻找两组数据之间的线性关系,用于判断两个或以上变量之间是否有明显差异,F统计量越大,变量之间差异越大,我们认为其相关性越强,因此按F统计量从大到小选择前 k 个特征。

互信息法:用来捕捉每个特征与标签之间的任意关系(包括线性和非线性关系)的过滤方法,既可以做回归也可以做分类;两变量之间的互信息越大,我们认为其相关性越强,按互信息从大到小选择的前 k 个特征。

相关性过滤方法

  • sklearn中的卡方检验:sklearn.feature_selection.chi2
    • chi2():输入特征矩阵和对应标签,返回一个二维数组,其中每个数组元素中的第一个元素为各个特征与标签的卡方统计量,第二个元素为各个特征对应的p值,卡方统计量越大,表示特征与标签的相关性越强;对应p值越小,表示结果越显著,拒绝原假设的概率更高。(卡方统计了越大越好,p值越小越好)
  • sklearn中的F检验:sklearn.feature_selection.f_classif(f_regression)
    • 'f_classif(f_regression)':与卡方检验基本相同,输入特征矩阵和对应标签,返回一个二维数组,每个数组元素第一个值为F统计量,第二个元素为p值,F统计量越大越好,p值越小越好。
  • sklearn中的互信息法:sklearn.feature_selection.mutual_info_classif(mutual_info_regression)
    • mutual_info_classif(mutual_info_regression):输入特征矩阵和对应标签,返回一个一维数组,数组中的各元素为各特征与标签之间的互信息值。

sklearn中选择前k个最优值的类sklearn.feature_selection.SelectKBest

  • score_func:评分函数,该函数返回一个衡量指标,SelectKBest会按照该函数返回的指标进行选择
  • k:默认值为10,根据设置的评分函数 score_func 的返回值选择前k个分值最高的特征

一般情况下 score_func 的返回值应该是一维的,表示每个特征对应的衡量指标;而chi2 等返回的是一个二维数组,SelectKBest则会根据每个元素的第一个值进行选择,在这里也就是根据卡方统计量进行选择。

常用属性或接口(以卡方过滤为例,其它方法则在SelectKBest中设置对应评分函数即可):

import pandas as pd
import numpy as np
from sklearn.feature_selection import chi2, f_classif, f_regression, SelectKBest
from sklearn.feature_selection import mutual_info_classif as mic
from sklearn.feature_selection import mutual_info_regression as mir

data = pd.DataFrame(np.random.randint(10, 100, (5, 2)))  # 准备测试数据

# 常用接口(以卡方过滤为例)
skb_chi = SelectKBest(score_func=chi2, k=1)
data_X, data_y = data.iloc[:, :-1], data.iloc[:, -1]

# 拟合数据
skb_chi.fit(data_X, data_y)

result = skb_chi.transform(data_X)   # 变换数据
result = skb_chi.fit_transform(data_X, data_y)    # 拟合和变换一步达成
data_X = skb_chi.inverse_transform(result)  # 逆向变换 (将数据转为过滤之前的原始数据)

# 常用属性
skb_chi.get_support()  # 在拟合数据后,返回一个布尔数组,对应值表示过滤结果,True为选择特征,False为筛除特征

# 一般这样用
result = SelectKBest(score_func=chi2, k=1).fit_transform(data_X, data_y)

与方差过滤中超参数的确定一样,这里的k值也要多进行尝试,除了调参经验以外没有特别有效的方法,可以绘制学习曲线等方法来观察不同超参数对模型性能的影响。

嵌入法

根据模型来选择特征sklearn.feature_selection.SelectFromModel

在很多情况下,嵌入法相对过滤法要有着更好的效果,因为特征选择基于的特征重要性是对所使用的模型本身而言的。

SelectFromModel 是一个元变换器,可以与任何在拟合后具有coef_,feature_importances_属性或参数中可选惩 罚项的评估器一起使用(比如随机森林和树模型就具有属性feature_importances_,逻辑回归就带有L1和L2惩罚项,线性支持向量机也支持l2惩罚项)。

SelectFromModel 根据模型自身计算得到的特征重要性或各特征对应的权值系数来进行判断,设置特征重要性阈值,小于该阈值的都会被筛除;对于提前设置带有L1或L2正则化惩罚项的模型,它则会删除掉所有因惩罚项导致权重系数为0的特征。

对于带有feature_importances_属性的模型和不设置正则化惩罚项的带有coef_属性的模型,需要设置的超参数为特征重要性的阈值;而对于带惩罚项的模型,则需要关注将惩罚项系数设置为多少,系数越大,筛选的强度越高,因惩罚项导致权重系数降为0的特征就越多,也就是筛除的特征就越多。

常用参数说明

  • estimator:没有默认值,即必须设置;使用的模型评估器,只要是带feature_importances_或者coef_属性,或带有l1和l2惩罚项的模型都可以使用
  • threshold:默认为None;特征重要性的阈值,重要性低于这个阈值的特征都将被删除
  • prefit:默认False,判断是否将实例化后的模型直接传递给构造函数。也就是指定一个已经拟合过的估计器模型,这样SelectFromModel中就不会再次重复进行数据拟合操作了。
  • norm_order:默认为1;值为0,不对数据进行规范化处理;值为1、2,分别对数据进行L1规范化或L2规范化处理;(L1规范化就是某特征下所有数据加起来和为1;L2规范化就是某特征下的数据的欧几里得范数为1),
    • L1规范化对outlier(离群值)更为鲁棒,使向量更稀疏,有利于特征选择;L2规范化对outlier相对更敏感,但保留向量相对更完整;大多使用默认情况,也就是L1规范即可。
  • max_features:默认为None;在阈值设定下,要选择的最大特征数。要禁用阈值、即仅根据max_features选择,请需要设置threshold为-np.inf

常用属性和接口

import pandas as pd
import numpy as np
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestRegressor

data = pd.DataFrame(np.random.randint(10, 100, (5, 2)))  
data_X, data_y = data.iloc[:, :-1], data.iloc[:, -1]  # 准备测试数据
rfc = RandomForestRegressor(random_state=42)  # 以随机森林为例

# 常用接口
sfm = SelectFromModel(rfc, threshold=0.05)

sfm.fit(data_X, data_y)  # 拟合数据

result = sfm.transform(data_X)  # 特征选择
result = sfm.fit_transform(data_X, data_y)  # 拟合和变换一步达成
data_X = sfm.inverse_transform(result)  # 逆向变换,返回过滤前的特征矩阵

# 若使用prefit则表示使用已经训练过的模型,不用再进行拟合
rfc.fit(data_X, data_y)
result = SelectFromModel(rfc, threshold=0.05, prefit=True, max_features=1, norm_order=1).transform(data_X)

# 常用属性
sfm.get_support()  # 在拟合数据后,返回一个布尔数组,对应值表示过滤结果,True为选择特征,False为筛除特征

包装法

包装法:是依赖于算法自身的选择,比如 coef_属性或feature_importances_属性来完成特征选择。但不同的是,我们往往使用一个目标函数来帮助我们选取特征,而不是自己输入某个评估指标或统计量的阈值。

递归特征消除法(Recursive Feature Elimination,RFE):一种基于贪婪策略的优化算法,是最典型的目标函数。反复创建模型,在每次迭代时保留最佳特征(或剔除最差特征),下一次迭代时,使用上一次建模中没有被选中的特征来构建下一个模型,直到所有特征都耗尽为止;最终根据保留或剔除特征的顺序来对特征进行排名,从而找到一个最佳子集。

sklearn中的RFE中的算法执行流程:

  1. 使用全部特征训练模型。
  2. 根据模型对各特征的重要性评分。
  3. 去除重要性最低的 n 个特征。
  4. 在新的特征子集上重复步骤1~3,递归训练并减少特征数。
  5. 直到达到指定的特征子集大小或其他停止条件。
  6. 返回保留下来的特征子集。

sklearn中的RFE:sklearn.feature_selection.RFE

常用参数说明:

  • estimator:无默认值,即必须传入;使用的模型评估器
  • n_features_to_select:默认值为None;特征保留的个数
  • step:默认值为1;每次迭代中希望移除的特征个数
  • verbose:默认值为0;用于控制打印日志信息(0:不打印信息,1:打印特征消除的进度条,2:打印每次特征消除的详细信息)

常用接口或属性:

import pandas as pd
import numpy as np
from sklearn.feature_selection import RFE
from sklearn.ensemble import RandomForestRegressor

data = pd.DataFrame(np.random.randint(10, 100, (5, 3)))  
data_X, data_y = data.iloc[:, :-1], data.iloc[:, -1]  # 准备测试数据
rfc = RandomForestRegressor(random_state=42)  # 以随机森林为例

# 常用接口
rfe = RFE(estimator=rfc, n_features_to_select=1, step=1, verbose=0)

rfe.fit(data_X, data_y)  # 拟合数据

result = rfe.transform(data_X)  # 特征选择
result = rfe.fit_transform(data_X, data_y)  # 拟合和变换一步达成
data_X = rfe.inverse_transform(result)  # 逆向变换,返回过滤前的特征矩阵

# 常用属性
rfe.support_  # 返回一个布尔数组,表示每个特征是否选择的情况
rfe.ranking_  # 返回一个数组,表示每个特征数次迭代中综合重要性的排名
rfe.n_features_  # 最终选择的特征个数

# 一般这样用
result = RFE(estimator=rfc, verbose=1).fit_transform(data_X, data_y)

sklearn.feature_selection.RFECV 在RFE的基础上增加了一个参数cv,其它的和RFE几乎相同;RFECV与RFE基本相同,区别在于每次迭代中使用交叉验证对某些特征下的模型进行评价,结果相对更准确和稳定。

RFECV相关参数:

  • estimator:无默认值,即必须传入;使用的模型评估器
  • min_features_to_select:默认值为None;特征保留的个数(注意这里是'min',不是'n')
  • step:默认值为1;每次迭代中希望移除的特征个数
  • verbose:默认值为0;用于控制打印日志信息(0:不打印信息,1:打印特征消除的进度条,2:打印每次特征消除的详细信息)
  • cv:默认值为5;表示将数据集分为几折
  • scoring:默认值为None;评分函数,允许自定义评分器,但一般通过字符串来指定,常用的值有:'accuracy'、'f1'、'r2'、'neg_mean_squared_error'、'mutual_info_score'等
  • n_jobs:默认值为None;使用线程数,设置为-1表示当前可用的全部线程。

常用属性和接口与RFE基本一致

from sklearn.feature_selection import RFECV
rfe_cv = RFECV(estimator=rfc, min_features_to_select=1, step=1, verbose=0, cv=5, scoring='r2', n_jobs=-1)

总结

本文介绍了sklern中常见特征选择方法的使用,其中过滤法是速度最快的,但也是最粗糙的,我们一般使用过滤法对特征进行初步选择,然后看情况来选择使用嵌入法还是包装法;包装法由于需要多次迭代,因而算力和时间成本要求更大,因此不适合用于大型数据集,但包装法的效果最好;嵌入法效果和速度都不错,可以用于一般情况。一般而言,当数据量很大的时候,优先使用方差过滤和互信息法调整,再使用其他特征选择方法。使用逻辑回归时,优先使用嵌入法;使用支持向量机时,优先使用包装法;数据量很大时,使用嵌入法,迷茫的时候,从过滤法开始,然后具体数据具体分析,多进行尝试。