特征选择(过滤法、包装法、嵌入法)


有这么一句话在业界广泛流传:数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。特征工程目的是最大限度地从原始数据中提取特征以供算法和模型使用。这里主要说的是特征选择。

当数据预处理完成后,我们需要进行特征选择,即选择有意义的特征输入机器学习的算法和模型进行训练。特征选择主要有两个功能:

  • 减少特征数量、降维,使模型泛化能力更强,减少过拟合。
  • 去掉似是而非不易判别的特征,提高精度。

通常来说,从两个方面考虑来选择特征:

  • 特征是否发散:如果一个特征不发散,例如方差接近于0,那么就是说样本在这个特征上基本上没有差异,这个特征对于样本的区分并没有什么用。
  • 特征与目标的相关性:这点比较显而易见,与目标相关性高的特征,应当优选选择。
    根据特征选择的形式又可以将特征选择方法分为3种:

Filter:过滤法,按照发散性或者相关性对各个特征进行评分,设定阈值或者待选择阈值的个数,选择特征。
Wrapper:包装法,根据目标函数(通常是预测效果评分),每次选择若干特征,或者排除若干特征。
Embedded:嵌入法,先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据系数从大到小选择特征。类似于Filter方法,但是是通过训练来确定特征的优劣。

Filter过滤式方法

过滤式方法(Filter)通常是独立地选择特征,这可能会忽略特征组合之间的相关性。

方差选择法

使用方差选择法,先要计算各个特征的方差,然后根据阈值,选择方差大于阈值的特征。使用feature_selection库的VarianceThreshold类来选择特征的代码如下:

from sklearn.feature_selection import VarianceThreshold
# 方差选择法,返回值为特征选择后的数据
# 参数threshold为方差的阈值
VarianceThreshold(threshold=3).fit_transform(iris.data)
# iris.data为鸢尾花数据

相关系数法

使用相关系数法,先要计算各个特征对目标值的相关系数以及相关系数的P值。用feature_selection库的SelectKBest类结合相关系数来选择特征的代码如下:

from sklearn.feature_selection import SelectKBest
from scipy.stats import pearsonr
#选择K个最好的特征,返回选择特征后的数据
#第一个参数为计算评估特征是否好的函数,该函数输入特征矩阵和目标向量,输出二元组(评分,P值)的数组,数组第i项为第i个特征的评分和P值。在此定义为计算相关系数
#参数k为选择的特征个数
SelectKBest(lambda X, Y: array(map(lambda x:pearsonr(x, Y), X.T)).T, k=2).fit_transform(iris.data, iris.target)

卡方检验

经典的卡方检验是检验定性自变量对定性因变量的相关性。假设自变量有N种取值,因变量有M种取值,考虑自变量等于i且因变量等于j的样本频数的观察值与期望的差距,构建统计量:
χ 2 = ∑ ( A − E ) 2 E \chi^{2}=\frac{\sum (A-E)^{2}}{E} χ2=E(AE)2
用feature_selection库的SelectKBest类结合卡方检验来选择特征的代码如下:

from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
#选择K个最好的特征,返回选择特征后的数据
SelectKBest(chi2, k=2).fit_transform(iris.data, iris.target)

互信息法和最大信息系数Mutual information and maximal information coefficient (MIC)

经典的互信息也是评价定性自变量对定性因变量的相关性的,互信息计算公式如下:
I ( X ; Y ) = ∑ x ϵ X ∑ y ϵ Y p ( x , y ) l o g p ( x , y ) p ( x ) p ( y ) ) I(X;Y)= \sum_{x\epsilon X} \sum_{y\epsilon Y}p(x,y)log \frac{p(x,y)}{p(x)p(y))} I(X;Y)=xϵXyϵYp(x,y)logp(x)p(y))p(x,y)
若想把互信息直接用于特征选择其实不是太方便:1、它不属于度量方式,也没有办法归一化,在不同数据及上的结果无法做比较;2、对于连续变量的计算不是很方便(X和Y都是集合,x,y都是离散的取值),通常变量需要先离散化,而互信息的结果对离散化的方式很敏感。最大信息系数克服了这两个问题。它首先寻找一种最优的离散化方式,然后把互信息取值转换成一种度量方式,取值区间在[0,1]。
使用feature_selection库的SelectKBest类结合最大信息系数法来选择特征的代码如下:

from sklearn.feature_selection import SelectKBest
from minepy import MINE 
#由于MINE的设计不是函数式的,定义mic方法将其为函数式的,返回一个二元组,二元组的第2项设置成固定的P值0.5
def mic(x, y):
    m = MINE()
    m.compute_score(x, y)
    return (m.mic(), 0.5)
#选择K个最好的特征,返回特征选择后的数据
SelectKBest(lambda X, Y: array(map(lambda x:mic(x, Y), X.T)).T, k=2).fit_transform(iris.data, iris.target)

Wrapper封装式方法

递归特征消除法Recursive feature elimination (RFE)

递归特征消除的主要思想是反复的构建模型(如SVM或者回归模型)然后选出最差的(或者最好的)的特征(可以根据系数来选),把选出来的特征放到一边,然后在剩余的特征上重复这个过程,直到所有特征都遍历了。这个过程中特征被消除的次序就是特征的排序。因此,这是一种寻找最优特征子集的贪心算法。

RFE的稳定性很大程度上取决于在迭代的时候底层用哪种模型。例如,假如RFE采用的普通的回归,没有经过正则化的回归是不稳定的,那么RFE就是不稳定的;假如采用的是Ridge,而用Ridge正则化的回归是稳定的,那么RFE就是稳定的。
使用feature_selection库的RFE类来选择特征的代码如下:

from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression
#递归特征消除法,返回特征选择后的数据
#参数estimator为基模型
#参数n_features_to_select为选择的特征个数
RFE(estimator=LogisticRegression(), n_features_to_select=2).fit_transform(iris.data, iris.target)

Embedded过滤式方法

基于惩罚项的特征选择法

基于惩罚项的特征选择法其实是基于正则的特征选择法。正则化就是把额外的约束或者惩罚项加到已有模型(损失函数)上,以防止过拟合并提高泛化能力。损失函数由原来的 E ( X , Y ) E(X,Y) E(X,Y)变为 E ( X , Y ) + α ∣ ∣ w ∣ ∣ E(X,Y)+\alpha||w|| E(X,Y)+αw,w是模型系数组成的向量(有些地方也叫参数parameter,coefficients),||·||一般是 L 1 L_{1} L1或者 L 2 L_{2} L2范数, α \alpha α是一个可调的参数,控制着正则化的强度。当用在线性模型上时, L 1 L_{1} L1正则化和 L 2 L_{2} L2正则化也称为Lasso和Ridge。

L 1 L_{1} L1正则化Lasso

L 1 L_{1} L1正则化将系数w的l1范数作为惩罚项加到损失函数上,由于正则项非零,这就迫使那些弱的特征所对应的系数变成0。因此 L 1 L_{1} L1正则化往往会使学到的模型很稀疏(系数w经常为0),这个特性使得 L 1 L_{1} L1正则化成为一种很好的特征选择方法。

L2正则化Ridge regression

L 2 L_{2} L2正则化将系数向量的 L 2 L_{2} L2范数添加到了损失函数中。由于 L 2 L_{2} L2惩罚项中系数是二次方的,这使得 L 2 L_{2} L2 L 1 L_{1} L1有着诸多差异,最明显的一点就是, L 2 L_{2} L2正则化会让系数的取值变得平均。对于关联特征,这意味着他们能够获得更相近的对应系数。以Y=X1+X2为例,假设X1和X2具有很强的关联,如果用L1正则化,不论学到的模型是Y=X1+X2还是Y=2X1,惩罚都是一样的,都是2 alpha。但是对于L2来说,第一个模型的惩罚项是2 α \alpha α,但第二个模型的是4* α \alpha α。可以看出,系数之和为常数时,各系数相等时惩罚是最小的,所以才有了L2会让各个系数趋于相同的特点。

基于树模型的特征选择法

树模型有准确率高、鲁棒性好、易于使用等优点,这使得它成为了目前最流行的机器学习算法之一。

平均不纯度减少 mean decrease impurity

随机森林由多个决策树构成。决策树中的每一个节点都是关于某个特征的条件,为的是将数据集按照不同的响应变量进行分裂。利用不纯度可以确定节点基于哪个特征进行分裂,对于分类问题,通常采用基尼系数或者信息增益 ,对于回归问题,通常采用的是方差或者最小二乘拟合。当训练决策树的时候,可以计算出每个特征减少了多少树的不纯度。对于一个决策树森林来说,可以算出每个特征平均减少了多少不纯度,并把它平均减少的不纯度作为特征选择的值。

平均精确率减少 Mean decrease accuracy

另一种常用的特征选择方法就是直接度量每个特征对模型精确率的影响。主要思路是打乱每个特征的特征值顺序,并且度量顺序变动对模型的精确率的影响。很明显,对于不重要的变量来说,打乱顺序对模型的精确率影响不会太大,但是对于重要的变量来说,打乱顺序就会降低模型的精确率。

决定节点分裂的次数

查看每个特征作为节点分裂依据的次数,次数越多则认为这个特征越重要。根据次数对特征进行排序。

随机森林进行特征选择

特征重要性​度量
1)对每一颗决策树,选择相应的袋外数据(out of bag,OOB)​计算袋外数据误差,记为errOOB1。
所谓袋外数据是指,每次建立决策树时,通过重复抽样得到一个数据用于训练​决策树,这时还有大约1/3的数据没有被利用,没有参与决策树的建立。这部分数据可以用于对决策树的性能进行评估,计算模型的预测错误率,称为袋外数据误差。
​这已经经过证明是无偏估计的,所以在随机森林算法中不需要再进行交叉验证或者单独的测试集来获取测试集误差的无偏估计。
​2)随机对袋外数据OOB所有样本的特征X加入噪声干扰(可以随机改变样本在特征X处的值),再次计算袋外数据误差,记为errOOB2。
3)​假设森林中有N棵树,则特征X的重要性=∑(errOOB2-errOOB1)/N。这个数值之所以能够说明特征的重要性是因为,如果加入随机噪声后,袋外数据准确率大幅度下降(即errOOB2上升),说明这个特征对于样本的预测结果有很大影响,进而说明重要程度比较高。
特征选择
在特征重要性的基础上,特征选择的步骤如下:
1)计算每个特征的重要性,并按降序排序
2)确定要剔除的比例,依据特征重要性剔除相应比例的特征,得到一个新的特征集
3)用新的特征集重复上述过程,直到剩下m个特征(m为提前设定的值)。
4)根据上述过程中得到的各个特征集和特征集对应的袋外误差率,选择袋外误差率最低的特征集。​

决策树进行特征选择

加权不纯度的减少

import numpy as np
from sklearn.tree import DecisionTreeClassifier
from sklearn.externals.six import StringIO
from sklearn import tree
import pydotplus

clf = DecisionTreeClassifier()
x = [[1,1,1,1,1,2,2,2,2,2,3,3,3,3,3],
     [1,1,2,2,1,1,1,2,1,1,1,1,2,2,1],
     [1,1,1,2,1,1,1,2,2,2,2,2,1,1,1],
     [1,2,2,1,1,1,2,2,3,3,3,2,2,3,1]
     ]
y =  [1,1,2,2,1,1,1,2,2,2,2,2,2,2,1]
x = np.array(x)
x = np.transpose(x)
clf.fit(x,y)
print(clf.feature_importances_)
feature_name = ['A1','A2','A3','A4']
target_name = ['1','2']
dot_data = StringIO()
tree.export_graphviz(clf,out_file = dot_data,feature_names=feature_name,
                     class_names=target_name,filled=True,rounded=True,
                     special_characters=True)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_pdf("WineTree.pdf")
print('Visible tree plot saved as pdf.')

XGBoost进行特征选择

在XGBoost中提供了三种特征重要性的计算方法:

‘weight’ - the number of times a feature is used to split the data across all trees.
‘gain’ - the average gain of the feature when it is used in trees
‘cover’ - the average coverage of the feature when it is used in trees

weight就是在所有树中特征用来分割的节点个数总和;
gain就是特征用于分割的平均增益
cover

import xgboost as xgb
import numpy as np
x = [[1,1,1,1,1,2,2,2,2,2,3,3,3,3,3],
     [1,1,2,2,1,1,1,2,1,1,1,1,2,2,1],
     [1,1,1,2,1,1,1,2,2,2,2,2,1,1,1],
     [1,2,2,1,1,1,2,2,3,3,3,2,2,3,1]
     ]
y =  [0,0,1,1,0,0,0,1,1,1,1,1,1,1,0]
x = np.array(x)
x = np.transpose(x)

params = {
    'max_depth': 10,
    'subsample': 1,
    'verbose_eval': True,
    'seed': 12,
    'objective':'binary:logistic'
}
xgtrain = xgb.DMatrix(x, label=y)
bst = xgb.train(params, xgtrain, num_boost_round=10)

fmap = 'weight'
importance = bst.get_score(fmap = '',importance_type=fmap)
print(importance)
print(bst.get_dump(with_stats=False))

fmap = 'gain'
importance = bst.get_score(fmap = '',importance_type=fmap)
print(importance)
print(bst.get_dump(with_stats=True))

fmap = 'cover'
importance = bst.get_score(fmap = '',importance_type=fmap)
print(importance)
print(bst.get_dump(with_stats=True))

结果如下:

#使用weight的结果为
{‘f0’: 1, ‘f1’: 3, ‘f2’: 5, ‘f3’: 2}

#使用gain的结果为
{‘f0’: 0.440551, ‘f1’: 1.8495799999999998, ‘f2’: 1.9555256, ‘f3’: 1.492955}

#使用cover的结果为
{‘f0’: 2.89962, ‘f1’: 3.34311, ‘f2’: 3.2390999999999996, ‘f3’: 2.757295}

可以看出,不同的特征重要性度量方法得出的结果也是不尽相同的。

PCA

LDA

参考:
https://www.cnblogs.com/bonelee/p/8632866.html
https://blog.csdn.net/akenseren/article/details/80816210
卡方分布
https://blog.csdn.net/bitcarmanlee/article/details/52279907
树特征选择
https://blog.csdn.net/tinkle181129/article/details/80231871

  • 14
    点赞
  • 97
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值