数据挖掘:特征工程——特征提取与选择

数据挖掘:特征工程——特征提取与选择

在这里插入图片描述
特征的处理和构建已经在上一篇特征处理和构建文章中有所总结。接来下对特征提取和选择进行说明。
:特征提取的范围很大,一般理解的话,它提取的对象是原始数据,目的就是自动地构建新的特征,将原始数据转换为一组具有明显物理意义(比如几何特征、纹理特征)或者统计意义的特征。
一般常用的方法包括降维(PCA、ICA、LDA等)、图像方面的SIFT、Gabor、HOG等、文本方面的词袋模型、词嵌入模型等,这里简单介绍这几种方法的一些基本概念。
而本文章仅对结构性数据进行说明,因此,特征提取=特征降维。

一、什么是特征降维与特征选择?

一般经过特征处理和生成后,会产生大量的特征,而这些特征中有的特征是很重要的,但不是每一项特征都对模型有用,因此,要将这类没用的特征剔除掉。所以,特征提取(降维)与特征选择的主要目的就是为了剔除无用的特征。
之前一直有个疑惑,既然特征降维与特征选择都是为了剔除特征,那这两者之间有什么区别和联系?

二、特征降维与选择的区别和联系

特征提取与特征选择都是为了从原始特征中找出最有效的特征

降维:本质上是从一个维度空间映射到另一个维度空间,特征的多少别没有减少,当然在映射的过程中特征值也会相应的变化。特征的多少并没有减少,只是换了个维度进行表示强调通过特征转换的方式得到一组具有明显物理或统计意义的特征
举例:现在的特征是1000维,我们想要把它降到500维。降维的过程就是找个一个从1000维映射到500维的映射关系。原始数据中的1000个特征,每一个都对应着降维后的500维空间中的一个值。假设原始特征中有个特征的值是9,那么降维后对应的值可能是3。

特征选择:就是单纯地从提取到的所有特征中选择部分特征作为训练集特征,特征在选择前和选择后不改变值,但是选择后的特征维数肯定比选择前小,毕竟我们只选择了其中的一部分特征。特征的多少发生了改变,选取最有利于模型的特征是从特征集合中挑选一组具有明显物理或统计意义的特征子集
举例:现在的特征是1000维,现在我们要从这1000个特征中选择500个,那个这500个特征的值就跟对应的原始特征中那500个特征值是完全一样的。对于另个500个没有被选择到的特征就直接抛弃了。假设原始特征中有个特征的值是9,那么特征选择选到这个特征后它的值还是9,并没有改变。

总结两者都能帮助减少特征的维度、数据冗余,特征提取有时能发现更有意义的特征属性,特征选择的过程经常能表示出每个特征的重要性对于模型构建的重要性。可以先进行特征的提取,再从中选择更有效的特征。

特征降维

降维,降低数据特征的维度。
机器学习之降维方法总结
在这里插入图片描述

  • 降维意义
    维数灾难:在给定精度下,准确地对某些变量的函数进行估计,所需样本量会随着样本维数的增加而呈指数形式增长。降维可以消除。
    降维的作用
    1. 降低时间复杂度和空间复
    2. 节省了提取不必要特征的开销
    3. 去掉数据集中夹杂的噪声
    4. 较简单的模型在小数据集上有更强的鲁棒性
    5. 当数据能有较少的特征进行解释,我们可以更好 的解释数据,使得我们可以提取知识。
    6. 实现数据可视化

举两个例子来说明降维的作用:
示例一:假如现在我们只有两个variables,为了了解这两者之间的关系,可以使用散点图将它们两者的关系画出:
在这里插入图片描述
那如果我们有100variables时,如果我们要看每个变量之间的关系,那需要100(100-1)/2 = 5000个图,而且把它们分开来看也没什么意义?

示例二:假如我们现在有两个意义相似的variables——Kg (X1) and Pound (X2),如果两个变量都使用会存在共线性,所以在这种情况下只使用一个变量就行。可以将二维数据降维一维。
在这里插入图片描述
降维——机器学习笔记——降维(特征提取)
特征降维与选择的区别和联系

要找到k个新的维度的集合,这些维度是原来k个维度的组合,这个方法可以是监督的,也可以是非监督的,(PCA-非监督的,LDA(线性判别分析)-监督的),这两个都是线性投影来进行降为的方法。另外,因子分析,和多维标定(MDS)也是非监督的线性降为方法。
接下来按照线性和非线性降维方法进行说明。主要在sklearn.decomposition中。

三、线性降维方法

假设数据集采样来自高维空间的一个全局线性的子空间,即构成数据的各变量之间是独立无关的。通过特征的线性组合来降维,本质上是把数据投影到低维线性子空间。线性方法相对比较简单且容易计算,适用于具有全局线性结构的数据集。

3.1 主成分分析(PCA)

PCA方法是一种无监督学习方法,把原始n维数据x变换到一个新的坐标系统中,找到第一个坐标,数据集在该坐标的方差最大(方差最大也就是我们在这个数据维度上能更好的区分不同类型的数据),然后找到第二个坐标,该坐标与原来的坐标正交。该过程会一直重复,直到新坐标的数目与原来的特征个数相同,这时候我们会发现数据打大部分方差都在前面几个坐标上表示,这些新的维度就是我们所说的主成分。
只保留前k个主成分,这前k个主成分的方差是最大的,因此对模型的贡献最大。而抛弃的n-k个维度对于模型的影响很有限,这样我们就降低了运算量。

PCA工作其实是在寻找最大方差所属的方向。其中PCA中特征缩放是必须的,因为必须保证变量在同一个数量级,否则,数值大的贡献很大,导致我们可能忽略了数值小却很至关重要的特征。

基本思想:构造原变量的一系列线性组合形成几个综合指标,以去除数据的相关性,并使低维数据最大程度保持原始高维数据的方差信息。
PCA基本工作步骤:

  1. 将X记为包含了m个自变量的矩阵,对x进行特征缩放
  2. 计算x的方差矩阵,记作A
  3. 计算矩阵A的特征值和特征向量,对特征值进行自大到小排序
  4. 选择需要解释方差的百分比P,借此进行特征选择p个(这里的特征是我们提取到的,区别于m中的任意一个变量)
    在这里插入图片描述
  5. 这里的p个特征就是我们留下的新特征了

主成分个数P的确定

  • 贡献率:第i个主成分的方差在全部方差中所占比重,反映第i个主成分所提取的总信息的份额。
  • 累计贡献率:前k个主成分在全部方差中所占比重
    主成分个数的确定:累计贡献率>0.85
  • 相关系数矩阵or协方差阵?
    当涉及变量的量纲不同或取值范围相差较大的指标时,应考虑从相关系数矩阵出发进行主成分分析;相关系数矩阵消除了量纲的影响。
    对同度量或取值范围相差不大的数据,从协方差阵出发.
    PCA讲解
    在这里插入图片描述
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
x = ss.fit_transform(x)
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test = train_test_split(x,y,test_size = 0.25,random_state = 0)

from sklearn.decomposition import PCA
pca = PCA(n_components=None)    #当为None时我们只是取p = m,并没有进行取舍,展示所有的结果。
x_train = pca.fit_transform(x_train)
x_test = pca.transform(x_test)
print('各个变量的解释方差能力:'+str(pca.explained_variance_))     #这个步骤会自动得出每个变量的解释方差的能力,并自大到小排序。方便我们进行取舍。  
----------------------------------------------------------------------
#假设我们的P设置为85%,则我们需要用到上述代码的结果的p个变量解释方差达到85%。这里就确定了p的取值。
pca = PCA(n_components=p)  	#这里的p时上个代码片得出的p
x_train = pca.fit_transform(x_train)
x_test = pca.transform(x_test)

3.2 线性判别分析(LDA)

LDA 是一种有监督学习算法,相比较 PCA,它考虑到数据的类别信息,而 PCA 没有考虑,只是将数据映射到方差比较大的方向上而已。
因为考虑数据类别信息,所以 LDA 的目的不仅仅是降维,还需要找到一个投影方向,使得投影后的样本尽可能按照原始类别分开,即寻找一个可以最大化类间距离以及最小化类内距离的方向。(通过将数据在低纬度上进行投影,投影后希望每一种类别数据的投影点尽可能的接近,而不同类别的数据的类别中心之间的距离尽可能的大)

LDA的算法步骤:

  1. 计算类内散度矩阵Sw
  2. 计算类间散度矩阵Sb
  3. 计算矩阵Sw^−1*Sb
  4. 计算Sw^−1*Sb的最大的d个特征值和对应的d个特征向量(w1,w2,…wd),得到投影矩阵W
  5. 对样本集中的每一个样本特征xi,转化为新的样本zi=WT*xi
  6. 得到输出样本集
  • LDA 的优点如下:
    • 相比较 PCA,LDA 更加擅长处理带有类别信息的数据;
    • 线性模型对噪声的鲁棒性比较好,LDA 是一种有效的降维方法。
  • 相应的,也有如下缺点:
    • LDA 对数据的分布做出了很强的假设,比如每个类别数据都是高斯分布、各个类的协方差相等。这些假设在实际中不一定完全满足。
    • LDA 模型简单,表达能力有一定局限性。但这可以通过引入核函数拓展 LDA 来处理分布比较复杂的数据。

LDA原理解析

from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
lda = LinearDiscriminantAnalysis(n_components=2)
lda.fit(X,y)
X_new = lda.transform(X)

3.3 PCA和LDA之间的区别

LDA与PCA都是常用的降维方法,二者的区别在于:

  1. 出发思想不同。PCA主要是从特征的协方差角度,去找到比较好的投影方式,即选择样本点投影具有最大方差的方向( 在信号处理中认为信号具有较大的方差,噪声有较小的方差,信噪比就是信号与噪声的方差比,越大越好。);而LDA则更多的是考虑了分类标签信息,寻求投影后不同类别之间数据点距离更大化以及同一类别数据点距离最小化,即选择分类性能最好的方向。
  2. 学习模式不同。PCA属于无监督式学习,因此大多场景下只作为数据处理过程的一部分,需要与其他算法结合使用,例如将PCA与聚类、判别分析、回归分析等组合使用;LDA是一种监督式学习方法,本身除了可以降维外,还可以进行预测应用,因此既可以组合其他模型一起使用,也可以独立使用。
  3. 降维后可用维度数量不同。LDA降维后最多可生成C-1维子空间(分类标签数-1),因此LDA与原始维度N数量无关,只有数据标签分类数量有关;而PCA最多有n维度可用,即最大可以选择全部可用维度。

3.3 奇异值分解(SVD)

奇异值分解(SVD)原理详解及推导
SVD和PCA区别和联系

3.4 因子分析

因子分析是通过研究变量间的相关系数矩阵,把这些变量间错综复杂的关系归结成少数几个综合因子,并据此对变量进行分类的一种统计分析方法。由于归结出的因子个数少于原始变量的个数,但是它们又包含原始变量的信息,所以,这一分析过程也称为降维。
因子分析的主要目的有以下三个:
(1)探索结构:在变量之间存在高度相关性的时候我们希望用较少的因子来概括其信息;
(2)简化数据:把原始变量转化为因子得分后,使用因子得分进行其他分析,比如聚类分析、回归分析等;
(3)综合评价:通过每个因子得分计算出综合得分,对分析对象进行综合评价。
因子分析就是将原始变量转变为新的因子,这些因子之间的相关性较低,而因子内部的变量相关程度较高。
sklearn中的因子分析问题
(1)sklearn.decompose.FactorAnalysis:sklearn可以做因子分析,但是只能做因子分析,不能旋转。不能旋转的因子分析对原始维度缺少一定的解释力,并且因子间可能存在一定的相关性,达不到因子分析的既定效果。
(2)factor_analyzer.FactorAnalyzer:既可做因子分析也能做因子的旋转,格式如下:FactorAnalyzer(rotation=None, n_factors=n, method=‘principal’)
因子分析相关概念
因子分析和PCA的区别

四、非线性降维方法

数据的各个属性间是强相关的

4.1 流形学习

流形是线性子空间的一种非线性推广,流形学习是一种非线性的维数约简方法
假设:高维数据位于或近似位于潜在的低维流行上
思想:保持高维数据与低维数据的某个“不变特征量”而找到低维特征表示
以不变特征量分为:
Isomap:测地距离
LLE:局部重构系数
LE:数据领域关系

4.2 等距特征映射(ISOMAP)

基本思想:通过保持高维数据的测地距离与低维数据的欧式距离的不变性来找到低维特征表示
测地距离:离得较近的点间的测地距离用欧氏距离代替;离得远的点间的测地距离用最短路径逼近

4.3 局部线性嵌入(LLE)

假设:采样数据所在的低维流形在局部是线性的,即每个采样点可以用它的近邻点线性表示
基本思想:通过保持高维数据与低维数据间的局部领域几何结构,局部重构系数来实现降维

各种降维方法的代码展示降维方法 -简直太全!- 附Python代码(Random Forest、Factor Analysis、corr、PCA、ICA、IOSMA

五、特征选择

这两篇文章对特征选择的介绍比较完善,下面的内容也基本上是从这上面摘抄下来,整理的。

特征选择 (feature_selection)
特征工程

从给定的特征集合中选出相关特征子集的过程称为特征选择(feature selection)。其目的是

  1. 减少特征数量、降维,使模型泛化能力更强,减少过拟合;
  2. 增强对特征和特征值之间的理解。

特征选择放入思想
确保不丢失重要的特征,否则就会因为缺少重要的信息而无法得到一个性能很好的模型。非重要特征指的是以下两种:

  • 不相关特征,与当前学习任务无关的特征。
  • 冗余特征,它们所包含的信息可以从其他特征中推演出来。冗余特征通常都不起作用,去除它们可以减轻模型训练的负担;但如果冗余特征恰好对应了完成学习任务所需要的某个中间概念,则它是有益的,可以降低学习任务的难度。

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

  1. 特征是否发散:如果一个特征不发散,例如方差接近于0,也就是说样本在这个特征上基本上没有差异,这个特征对于样本的区分并没有什么用。
  2. 特征与目标的相关性:这点比较显见,与目标相关性高的特征,应当优选选择。除移除低方差法外,本文介绍的其他方法均从相关性考虑。

在没有任何先验知识,即领域知识的前提下,要想从初始特征集合中选择一个包含所有重要信息的特征子集,唯一做法就是遍历所有可能的特征组合。但这种做法并不实际,也不可行,因为会遭遇组合爆炸,特征数量稍多就无法进行。
一个可选的方案是:产生一个候选子集,评价出它的好坏。基于评价结果产生下一个候选子集,再评价其好坏。这个过程持续进行下去,直至无法找到更好的后续子集为止。
这里有两个问题:如何根据评价结果获取下一个候选特征子集?如何评价候选特征子集的好坏?

  • 子集获取
    给定特征集合 A={A1,A2,…,Ad} ,首先将每个特征看作一个候选子集(即每个子集中只有一个元素),然后对这 d 个候选子集进行评价。
    假设 A2 最优,于是将 A2 作为第一轮的选定子集。
    然后在上一轮的选定子集中加入一个特征,构成了包含两个特征的候选子集。
    假定 A2,A5 最优,且优于 A2 ,于是将 A2,A5 作为第二轮的选定子集。

    假定在第 k+1 轮时,本轮的最优的特征子集不如上一轮的最优的特征子集,则停止生成候选子集,并将上一轮选定的特征子集作为特征选择的结果。
    这种逐渐增加相关特征的策略称作前向 forward搜索。类似地,如果从完整的特征集合开始,每次尝试去掉一个无关特征,这种逐渐减小特征的策略称作后向backward搜索
    也可以将前向和后向搜索结合起来,每一轮逐渐增加选定的相关特征(这些特征在后续迭代中确定不会被去除),同时减少无关特征,这样的策略被称作是双向bidirectional搜索
    该策略是贪心的,因为它们仅仅考虑了使本轮选定集最优。但是除非进行穷举搜索,否则这样的问题无法避免。

  • 子集评价
    给定数据集 D,假设所有属性均为离散型。对属性子集 A,假定根据其取值将 D 分成了 V 个子集,计算属性子集 A 的信息增益。信息增益越大,表明特征子集 A 包含的有助于分类的信息越多。所以对于每个候选特征子集,可以基于训练集 D 来计算其信息增益作为评价准则。
    更一般地,特征子集 A 实际上确定了对数据集 D 的一个划分规则。每个划分区域对应着 A 上的一个取值,而样本标记信息 y 则对应着 D 的真实划分。通过估算这两种划分之间的差异,就能对 A 进行评价:与 y 对应的划分的差异越小,则说明 A 越好。
    信息熵仅仅是判断这个差异的一种方法,其他能判断这两个划分差异的机制都能够用于特征子集的评价。

将特征子集搜索机制与子集评价机制结合就能得到特征选择方法。事实上,决策树/随机森林可以用于特征选择,所有树结点的划分属性所组成的集合就是选择出来的特征子集。其他特征选择方法本质上都是显式或者隐式地结合了某些子集搜索机制和子集评价机制。

特征选择的方法

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

拿到数据集,一个特征选择方法,往往很难同时完成这两个目的。通常情况下,选择一种自己最熟悉或者最方便的特征选择方法(往往目的是降维,而忽略了对特征和数据理解的目的)。一般用嵌入法比较多

主要用sklearn.feature_selection来实现。

5.1 过滤法

该方法先对数据集进行特征选择,然后再训练学习器。特征选择过程与后续学习器无关。即先采用特征选择对初始特征进行过滤,然后用过滤后的特征训练模型。

  • 优点是计算时间上比较高效,而且对过拟合问题有较高的鲁棒性;
  • 缺点是倾向于选择冗余特征,即没有考虑到特征之间的相关性。

5.1.1 移除低方差的特征 (Removing features with low variance)

假设某特征的特征值只有0和1,并且在所有输入样本中,95%的实例的该特征取值都是1,那就可以认为这个特征作用不大。方差低,意味着该数据提供的信息比较少。如果100%都是1,那这个特征就没意义了。而且实际当中,一般不太会有95%以上都取某个值的特征存在,所以这种方法虽然简单但是不太好用。可以把它作为特征选择的预处理,先去掉那些取值变化小的特征,然后再从接下来提到的的特征选择方法中选择合适的进行进一步的特征选择。

使用方差选择法,先要计算各个特征的方差,然后根据阈值,选择方差大于阈值的特征。当特征值都是离散型变量的时候这种方法才能用,如果是连续型变量,就需要将连续变量离散化之后才能用

from sklearn.feature_selection import VarianceThreshold
X = [[0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 1, 1], [0, 1, 0], [0, 1, 1]]
sel = VarianceThreshold(threshold=(.8 * (1 - .8)))
sel.fit_transform(X)
array([[0, 1],
       [1, 0],
       [0, 0],
       [1, 1],
       [1, 0],
       [1, 1]])

5.1.2 单变量特征选择 (Univariate feature selection)

单变量特征选择的原理是分别单独的计算每个变量的某个统计指标,根据该指标来判断哪些指标重要,剔除那些不重要的指标。而这种指标的设定可以用卡方检验,相关系数等。

对于分类问题(y离散),可采用:卡方检验,互信息等。
对于回归问题(y连续),可采用:皮尔森相关系数,最大信息系数等。

这种方法比较简单,易于运行,易于理解,通常对于理解数据有较好的效果(但对特征优化、提高泛化能力来说不一定有效)。

5.1.2.1 卡方(Chi2)检验

卡方检验是检验定性自变量对定性因变量的相关性。分类问题

我们可以对样本进行一次chi2 测试来选择最佳的两项特征
from sklearn.datasets import load_iris
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
iris = load_iris()
X, y = iris.data, iris.target
X.shape
(150, 4)
X_new = SelectKBest(chi2, k=2).fit_transform(X, y)
X_new.shape
(150, 2)
5.1.2.2 相关系数

相关系数的介绍在探索性数据分析中有所提到。
Pearson Correlation速度快、易于计算,经常在拿到数据(经过清洗和特征提取之后的)之后第一时间就执行。Scipy的 pearsonr 方法能够同时计算 相关系数 和p-value。

import numpy as np
from scipy.stats import pearsonr
np.random.seed(0)
size = 300
x = np.random.normal(0, 1, size)
pearsonr(x, y)的输入为特征矩阵和目标向量
print("Lower noise", pearsonr(x, x + np.random.normal(0, 1, size)))
print("Higher noise", pearsonr(x, x + np.random.normal(0, 10, size)))

输出为二元组(sorce, p-value)的数组
Lower noise (0.71824836862138386, 7.3240173129992273e-49)
Higher noise (0.057964292079338148, 0.31700993885324746)

Scikit-learn提供的 f_regrssion 方法能够批量计算特征的f_score和p-value,非常方便,参考sklearn的 pipeline。

5.1.2.3 互信息和最大信息系数

互信息为随机变量X与Y之间的互信息I(X;Y)为单个事件之间互信息的数学期望)也是评价定性自变量对定性因变量的相关性的。
互信息直接用于特征选择其实不是太方便:

  1. 它不属于度量方式,也没有办法归一化,在不同数据及上的结果无法做比较;
  2. 对于连续变量的计算不是很方便(X和Y都是集合,x,y都是离散的取值),通常变量需要先离散化,而互信息的结果对离散化的方式很敏感。

最大信息系数克服了这两个问题。它首先寻找一种最优的离散化方式,然后把互信息取值转换成一种度量方式,取值区间在[0,1]。 minepy 提供了MIC功能。

from minepy import MINE
m = MINE()
x = np.random.uniform(-1, 1, 10000)
m.compute_score(x, x**2)
print(m.mic())
1.0

但MIC的统计能力遭到了 一些质疑 ,当零假设不成立时,MIC的统计就会受到影响。在有的数据集上不存在这个问题,但有的数据集上就存在这个问题。

5.1.6 距离相关系数

距离相关系数是为了克服Pearson相关系数的弱点而生的。在x和x2这个例子中,即便Pearson相关系数是0,我们也不能断定这两个变量是独立的(有可能是非线性相关);但如果距离相关系数是0,那么我们就可以说这两个变量是独立的。距离相关系数Python实现
尽管有 MIC 和 距离相关系数存在,但当变量之间的关系接近线性相关的时候,Pearson相关系数仍然是不可替代的。

  1. Pearson相关系数计算速度快,这在处理大规模数据的时候很重要。
  2. Pearson相关系数的取值区间是[-1,1],而MIC和距离相关系数都是[0,1]。这个特点使Pearson相关系数能够表征更丰富的关系,符号表示关系的正负,绝对值能够表示强度。当然,Pearson相关性有效的前提是两个变量的变化关系是单调的。另外的两种相关系数也可以做参考。

以下内容主要出自这篇文章基于模型的特征选择详解 (Embedded & Wrapper)

5.2 包裹法

相比于过滤式特征选择不考虑后续学习器,包裹式特征选择直接把最终将要使用的学习器的性能作为特征子集的评价原则。其目的就是为给定学习器选择最有利于其性能、量身定做的特征子集。

  • 优点是直接针对特定学习器进行优化,考虑到特征之间的关联性,因此通常包裹式特征选择比过滤式特征选择能训练得到一个更好性能的学习器,
  • 缺点是由于特征选择过程需要多次训练学习器,故计算开销要比过滤式特征选择要大得多。

建立在基于模型的特征选择方法基础之上的,例如回归和SVM,在不同的子集上建立模型,然后汇总最终确定特征得分。

5.2.1 稳定性选择 (Stability Selection)

在sklearn的官方文档中,该方法叫做随机稀疏模型 (Randomized sparse models):基于L1的稀疏模型的局限在于,当面对一组互相关的特征时,它们只会选择其中一项特征。为了减轻该问题的影响可以使用随机化技术,通过多次重新估计稀疏模型来扰乱设计矩阵,或通过多次下采样数据来统计一个给定的回归量被选中的次数。

稳定性选择是一种基于 二次抽样选择算法 相结合较新的方法,选择算法可以是回归、SVM或其他类似的方法。它的主要思想是在不同的数据子集和特征子集上运行特征选择算法,不断的重复,最终汇总特征选择结果,比如可以统计某个特征被认为是重要特征的频率(被选为重要特征的次数除以它所在的子集被测试的次数)。理想情况下,重要特征的得分会接近100%。稍微弱一点的特征得分会是非0的数,而最无用的特征得分将会接近于0。

sklearn在 随机lasso(RandomizedLasso) 和 随机逻辑回归(RandomizedLogisticRegression) 中有对稳定性选择的实现。

from sklearn.linear_model import RandomizedLasso
from sklearn.datasets import load_boston
boston = load_boston()

#using the Boston housing data. 
#Data gets scaled automatically by sklearn's implementation
X = boston["data"]
Y = boston["target"]
names = boston["feature_names"]

rlasso = RandomizedLasso(alpha=0.025)
rlasso.fit(X, Y)

print("Features sorted by their score:")
print(sorted(zip(map(lambda x: format(x, '.4f'), rlasso.scores_), names), reverse=True))
Features sorted by their score:
[('1.0000', 'RM'), ('1.0000', 'PTRATIO'), ('1.0000', 'LSTAT'), ('0.6450', 'CHAS'), ('0.6100', 'B'), ('0.3950', 'CRIM'), ('0.3800', 'TAX'), ('0.2250', 'DIS'), ('0.2000', 'NOX'), ('0.1150', 'INDUS'), ('0.0750', 'ZN'), ('0.0200', 'RAD'), ('0.0100', 'AGE')]

在上边这个例子当中,最高的3个特征得分是1.0,这表示他们总会被选作有用的特征(当然,得分会受到正则化参数alpha的影响,但是sklearn的随机lasso能够自动选择最优的alpha)。接下来的几个特征得分就开始下降,但是下降的不是特别急剧,这跟纯lasso的方法和随机森林的结果不一样。能够看出稳定性选择对于克服过拟合和对数据理解来说都是有帮助的:总的来说,好的特征不会因为有相似的特征、关联特征而得分为0,这跟Lasso是不同的。对于特征选择任务,在许多数据集和环境下,稳定性选择往往是性能最好的方法之一

5.2.2 递归特征消除(RFE)

递归消除特征法使用一个基模型来进行多轮训练,每轮训练后,移除若干权值系数的特征,再基于新的特征集进行下一轮训练。

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

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

Sklearn提供了 RFE 包,可以用于特征消除,还提供了 RFECV ,可以通过交叉验证来对的特征进行排序,以此来选择最佳数量的特征:对于一个数量为d的feature的集合,他的所有的子集的个数是2的d次方减1(包含空集)。指定一个外部的学习算法,比如SVM之类的。通过该算法计算所有子集的validation error。选择error最小的那个子集作为所挑选的特征。

sklearn官方解释:对特征含有权重的预测模型(例如,线性模型对应参数coefficients),RFE通过递归减少考察的特征集规模来选择特征。首先,预测模型在原始特征上训练,每个特征指定一个权重。之后,那些拥有最小绝对值权重的特征被踢出特征集。如此往复递归,直至剩余的特征数量达到所需的特征数量。

from sklearn.feature_selection import RFE
from sklearn.linear_model import LinearRegression
from sklearn.datasets import load_boston

boston = load_boston()
X = boston["data"]
Y = boston["target"]
names = boston["feature_names"]

#use linear regression as the model
lr = LinearRegression()
#rank all features, i.e continue the elimination until the last one
rfe = RFE(lr, n_features_to_select=1)
rfe.fit(X,Y)

print("Features sorted by their rank:")
print(sorted(zip(rfe.ranking_, names)))
Features sorted by their rank:
[(1, 'NOX'), (2, 'RM'), (3, 'CHAS'), (4, 'PTRATIO'), (5, 'DIS'), (6, 'LSTAT'), (7, 'RAD'), (8, 'CRIM'), (9, 'INDUS'), (10, 'ZN'), (11, 'TAX'), (12, 'B'), (13, 'AGE')]

5.3 嵌入法

在过滤式和包裹式特征选择方法中,特征选择过程与学习器训练过程有明显的分别。单变量特征选择方法独立的衡量每个特征与响应变量之间的关系。而有些机器学习方法本身就具有对特征进行打分的机制,或者很容易将其运用到特征选择任务中。
嵌入式特征选择是将特征选择与学习器训练过程融为一体,两者在同一个优化过程中完成的。即学习器训练过程中自动进行了特征选择
常用的方法包括:

  1. 利用正则化,如L_1, L_2 范数,主要应用于如线性回归、逻辑回归以及支持向量机(SVM)等算法。
  2. 使用决策树思想,包括决策树、随机森林、Gradient Boosting 等。

5.3.1 线性模型和正则化

5.3.1.1 线性模型

使用L1范数作为惩罚项的线性模型(Linear models)除了降低过拟合风险之外,还有一个好处,它更容易获得稀疏解:大部分特征对应的系数为0。当你希望减少特征的维度以用于其它分类器时,可以通过 feature_selection.SelectFromModel 来选择不为0的系数。特别指出,常用于此目的的稀疏预测模型有 linear_model.Lasso(回归), linear_model.LogisticRegression 和 svm.LinearSVC(分类):

下面将介绍如何用回归模型的系数来选择特征越是重要的特征在模型中对应的系数就会越大,而跟输出变量越是无关的特征对应的系数就会越接近于0。在噪音不多的数据上,或者是数据量远远大于特征数的数据上,如果特征之间相对来说是比较独立的,那么即便是运用最简单的线性回归模型也一样能取得非常好的效果。

from sklearn.linear_model import LinearRegression
import numpy as np

# A helper method for pretty-printing linear models
def pretty_print_linear(coefs, names=None, sort=False):
    if names == None:
        names = ["X%s" % x for x in range(len(coefs))]
    lst = zip(coefs, names)
    if sort:
        lst = sorted(lst, key=lambda x: -np.abs(x[0]))
    return " + ".join("%s * %s" % (round(coef, 3), name) for coef, name in lst)

np.random.seed(0)  # 有了这段代码,下次再生成随机数的时候,与上次一样的结果
size = 5000  # 表示抽取多少个样本
# 随机生成3个特征的样本,每个维度的特征都是服从期望为0,标准差为1的正态分布
X = np.random.normal(0, 1, (size, 3))  # 抽取5000个样本,每个样本都是3维的
# Y = X0 + 2*X1 + noise
Y = X[:, 0] + 2 * X[:, 1] + np.random.normal(0, 2, size)
lr = LinearRegression()
lr.fit(X, Y)

print("Linear model:", pretty_print_linear(lr.coef_))
>>> Linear model: 0.984 * X0 + 1.995 * X1 + -0.041 * X2

在这个例子当中,尽管数据中存在一些噪音,但这种特征选择模型仍然能够很好的体现出数据的底层结构。当然这也是因为例子中的这个问题非常适合用线性模型来解:特征和响应变量之间全都是线性关系,并且特征之间均是独立的。

然而,在很多实际的数据当中,往往存在多个互相关联的特征,这时候模型就会变得不稳定,对噪声很敏感,数据中细微的变化就可能导致模型的巨大变化(模型的变化本质上是系数,或者叫参数),这会让模型的预测变得困难,这种现象也称为多重共线性。例如,假设我们有个数据集,它的真实模型应该是Y=X1+X2,当我们观察的时候,发现Y′=X1+X2+e,e是噪音。如果X1和X2之间存在线性关系,例如X1约等于X2,这个时候由于噪音e的存在,我们学到的模型可能就不是Y=X1+X2了,有可能是Y=2X1,或者Y=−X1+3X2。【在X1约等于X2的线性关系下,学到的这三种模型,理论上来说都是正确的,注意他们有个共同的特点是,系数之和为2】

size = 100
# 另外一个种子为5的随机采样,若要执行相同的结果,以种子号来区分随机采样的结果
np.random.seed(seed=5)  
X_seed = np.random.normal(0, 1, size)

X1 = X_seed + np.random.normal(0, .1, size)
X2 = X_seed + np.random.normal(0, .1, size)
X3 = X_seed + np.random.normal(0, .1, size)

X = np.array([X1, X2, X3]).T
Y = X1 + X2 + X3 + np.random.normal(0, 1, size)

lr = LinearRegression()
lr.fit(X, Y)
print("Linear model:", pretty_print_linear(lr.coef_))
>>> Linear model: -1.291 * X0 + 1.591 * X1 + 2.747 * X2

这个例子和上个例子中,三个特征都来源于标准正态分布的随机采样,这里系数之和接近3,基本上和上上个例子的结果一致,应该说学到的模型对于预测来说还是不错的。但是,如果从系数的字面意思上去解释特征的重要性的话,X2对于输出变量来说具有很强的正面影响,而X0具有负面影响,而实际上所有特征与输出变量之间的影响是均等的。同样的方法和套路可以用到类似的线性模型上,比如逻辑回归。

5.3.1.2 L1正则化(Lasso)

正则化就是把额外的约束或者惩罚项加到已有模型(损失函数)上,以防止过拟合并提高泛化能力。损失函数由原来的L(X,Y)变为 L ( X , Y ) + α w w L(X,Y)+\alpha \left| w \right| ,w 是模型系数组成的向量(有些地方也叫参数parameter,coefficients), ∥⋅∥一般是L1或者L2范数(w开几次方),α是一个可调的参数,控制着正则化的强度。当用在线性模型上时,L1正则化称为Lasso(least absolute shrinkage and selection operator)Ridge,L2正则化称为Ridge。

L1正则化将系数w的l1范数作为惩罚项加到损失函数上,由于正则项非零,这就迫使那些弱的特征所对应的系数变成0。因此L1正则化往往会使学到的模型很稀疏(系数w经常为0),这个特性使得L1正则化成为一种很好的特征选择方法。
Scikit-learn为线性回归模型提供了Lasso,为分类模型提供了L1逻辑回归。下面的例子在波士顿房价数据上运行了Lasso,其中参数alpha是通过grid search进行优化的。

from sklearn.linear_model import Lasso
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_boston
import numpy as np

boston = load_boston()
scaler = StandardScaler()
X = scaler.fit_transform(boston["data"])
Y = boston["target"]
names = boston["feature_names"]

lasso = Lasso(alpha=.3)
lasso.fit(X, Y)
print("Lasso model: ", pretty_print_linear(lasso.coef_, names, sort=True))
Lasso model: -3.707 * LSTAT + 2.992 * RM + -1.757 * PTRATIO + -1.081 * DIS + -0.7 * NOX + 0.631 * B + 0.54 * CHAS + -0.236 * CRIM + 0.081 * ZN + -0.0 * INDUS + -0.0 * AGE + 0.0 * RAD + -0.0 * TAX

可以看到,很多特征的系数都是0。如果继续增加α的值,得到的模型就会越来越稀疏,即越来越多的特征系数会变成0。然而,L1正则化像非正则化线性模型一样也是不稳定的,如果特征集合中具有相关联的特征,当数据发生细微变化时也有可能导致很大的模型差异

5.3.1.3 L2正则化(Ridge Regression)

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

可以看出,L2正则化对于特征选择来说一种稳定的模型,不像L1正则化那样,系数会因为细微的数据变化而波动。所以L2正则化和L1正则化提供的价值是不同的,L2正则化对于特征理解来说更加有用:表示能力强的特征对应的系数是非零。
回过头来看看3个互相关联的特征的例子,分别以10个不同的种子随机初始化运行10次,来观察L1和L2正则化的稳定性。

import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import Ridge

size = 100
# We run the method 10 times with different random seeds
for i in range(5):
    print("Random seed %s" % i)
    np.random.seed(seed=i)
    X_seed = np.random.normal(0, 1, size)
    X1 = X_seed + np.random.normal(0, .1, size)
    X2 = X_seed + np.random.normal(0, .1, size)
    X3 = X_seed + np.random.normal(0, .1, size)
    Y = X1 + X2 + X3 + np.random.normal(0, 1, size)
    X = np.array([X1, X2, X3]).T
    lr = LinearRegression()
    lr.fit(X, Y)
    print("Linear model:", pretty_print_linear(lr.coef_))
    ridge = Ridge(alpha=10)
    ridge.fit(X, Y)
    print("Ridge model:", pretty_print_linear(ridge.coef_))
    print()
>>>
Random seed 0
Linear model: 0.728 * X0 + 2.309 * X1 + -0.082 * X2
Ridge model: 0.938 * X0 + 1.059 * X1 + 0.877 * X2

Random seed 1
Linear model: 1.152 * X0 + 2.366 * X1 + -0.599 * X2
Ridge model: 0.984 * X0 + 1.068 * X1 + 0.759 * X2

Random seed 2
Linear model: 0.697 * X0 + 0.322 * X1 + 2.086 * X2
Ridge model: 0.972 * X0 + 0.943 * X1 + 1.085 * X2

Random seed 3
Linear model: 0.287 * X0 + 1.254 * X1 + 1.491 * X2
Ridge model: 0.919 * X0 + 1.005 * X1 + 1.033 * X2

Random seed 4
Linear model: 0.187 * X0 + 0.772 * X1 + 2.189 * X2
Ridge model: 0.964 * X0 + 0.982 * X1 + 1.098 * X2

可以看出,不同的数据上线性回归得到的模型(系数)相差甚远,但对于L2正则化模型来说,结果中的系数非常的稳定,差别较小,都比较接近于1,能够反映出数据的内在结构。

对于SVM和逻辑回归,参数C控制稀疏性:C越小,则稀疏性越大,被选中的特征越少。对于Lasso,参数alpha控制了稀疏性,如果alpha越小,则稀疏性越小,被选择的特征越多。

5.3.2 基于树模型的特征选择

基于树的预测模型能够用来计算特征的重要程度,因此能用来去除不相关的特征(结合 sklearn.feature_selection.SelectFromModel)。
随机森林具有准确率高、鲁棒性好、易于使用等优点,这使得它成为了目前最流行的机器学习算法之一。随机森林提供了两种特征选择的方法:平均不纯度减少(mean decrease impurity) 和 平均精确率减少(mean decrease accuracy)。

5.3.2.1 平均不纯度减少

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

from sklearn.datasets import load_boston
from sklearn.ensemble import RandomForestRegressor
import numpy as np

#Load boston housing dataset as an example
boston = load_boston()
X = boston["data"]
Y = boston["target"]
names = boston["feature_names"]
rf = RandomForestRegressor()
rf.fit(X, Y)
print("Features sorted by their score:")
print(sorted(zip(map(lambda x: "%.4f"%x, rf.feature_importances_), names), reverse=True))
Features sorted by their score:
[('0.5128', 'LSTAT'), ('0.2896', 'RM'), ('0.0792', 'DIS'), ('0.0311', 'CRIM'), ('0.0188', 'NOX'), ('0.0179', 'AGE'), ('0.0174', 'TAX'), ('0.0123', 'PTRATIO'), ('0.0086', 'B'), ('0.0074', 'RAD'), ('0.0037', 'INDUS'), ('0.0007', 'ZN'), ('0.0007', 'CHAS')]


#根据特征的重要性绘制柱状图
features = data_fea.columns
importances = rf.feature_importances_
indices = np.argsort(importances[0:9])  # 因指标太多,选取前10个指标作为例子
plt.title('Index selection')
plt.barh(range(len(indices)), importances[indices], color='pink', align='center')
plt.yticks(range(len(indices)), [features[i] for i in indices])
plt.xlabel('Relative importance of indicators')
plt.show()

这里特征得分实际上采用的是 Gini Importance 。使用基于不纯度的方法的时候,要记住:

  1. 这种方法存在偏向 ,对具有更多类别的变量会更有利;
  2. 对于存在关联的多个特征,其中任意一个都可以作为指示器(优秀的特征),并且一旦某个特征被选择之后,其他特征的重要度就会急剧下降(因为不纯度已经被选中的那个特征降下来了,其他的特征就很难再降低那么多不纯度了,这样一来,只有先被选中的那个特征重要度很高,其他的关联特征重要度往往较低)。在理解数据时,这就会造成误解,导致错误的认为先被选中的特征是很重要的,而其余的特征是不重要的,但实际上这些特征对响应变量的作用确实非常接近的(这跟Lasso是很像的)。

特征随机选择 方法稍微缓解了这个问题,但总的来说并没有完全解决。下面的例子中,X0、X1、X2是三个互相关联的变量,在没有噪音的情况下,输出变量是三者之和。

from sklearn.ensemble import RandomForestRegressor
import numpy as np

size = 10000
np.random.seed(seed=10)
X_seed = np.random.normal(0, 1, size)
X0 = X_seed + np.random.normal(0, .1, size)
X1 = X_seed + np.random.normal(0, .1, size)
X2 = X_seed + np.random.normal(0, .1, size)
X = np.array([X0, X1, X2]).T
Y = X0 + X1 + X2

rf = RandomForestRegressor(n_estimators=20, max_features=2)
rf.fit(X, Y)
print('Scores for X0, X1, X2:', ['%.3f'%x for x in rf.feature_importances_])
>>>
Scores for X0, X1, X2 ['0.272', '0.548', '0.179']

当计算特征重要性时,可以看到X1的重要度比X2的重要度要高出3倍,但实际上他们真正的重要度是一样的。尽管数据量已经很大且没有噪音,且用了20棵树来做随机选择,但这个问题还是会存在
需要注意的一点是,关联特征的打分存在不稳定的现象,这不仅仅是随机森林特有的,大多数基于模型的特征选择方法都存在这个问题

5.3.2.2 平均精确率减少

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

这个方法sklearn中没有直接提供,但是很容易实现,下面继续在波士顿房价数据集上进行实现。

from sklearn.cross_validation import ShuffleSplit
from sklearn.metrics import r2_score
from sklearn.datasets import load_boston
from collections import defaultdict
from sklearn.ensemble import RandomForestRegressor
import numpy as np

boston = load_boston()
X = boston["data"]
Y = boston["target"]
names = boston["feature_names"]

rf = RandomForestRegressor()
scores = defaultdict(list)
# crossvalidate the scores on a number of different random splits of the data
for train_idx, test_idx in ShuffleSplit(len(X), 100, .3):
    X_train, X_test = X[train_idx], X[test_idx]
    Y_train, Y_test = Y[train_idx], Y[test_idx]
    r = rf.fit(X_train, Y_train)
    acc = r2_score(Y_test, rf.predict(X_test))
    for i in range(X.shape[1]):
        X_t = X_test.copy()
        np.random.shuffle(X_t[:, i])
        shuff_acc = r2_score(Y_test, rf.predict(X_t))
        scores[names[i]].append((acc - shuff_acc) / acc)
print("Features sorted by their score:")
print(sorted( [(float('%.4f'%np.mean(score)), feat) for
              feat, score in scores.items()], reverse=True) )
Features sorted by their score:
[(0.7508, 'LSTAT'), (0.5691, 'RM'), (0.0947, 'DIS'), (0.0396, 'CRIM'), (0.0371, 'NOX'), (0.0223, 'PTRATIO'), (0.0173, 'TAX'), (0.0132, 'AGE'), (0.0071, 'B'), (0.0053, 'INDUS'), (0.0036, 'RAD'), (0.0005, 'CHAS'), (0.0003, 'ZN')]

在这个例子当中,LSTAT和RM这两个特征对模型的性能有着很大的影响,打乱这两个特征的特征值使得模型的性能下降了75%和57%。注意,尽管这些我们是在所有特征上进行了训练得到了模型,然后才得到了每个特征的重要性测试,这并不意味着我们扔掉某个或者某些重要特征后模型的性能就一定会下降很多,因为即便某个特征删掉之后,其关联特征一样可以发挥作用,让模型性能基本上不变

5.4 总结

鉴于本人对特征降维和特征选择学的不是很好,再加上网上有很多好的文章,因此,本文主要是进行了整理工作,有参考的地方都已经标明了出处。
特征选择主要参考基于模型的特征选择详解 (Embedded & Wrapper)这篇文章进行的,里面对各个方法的说明和对比写的非常好,建议多看几遍。
贴上一张来自使用sklearn进行数据挖掘的表。
在这里插入图片描述

参考文献

https://blog.csdn.net/zgcr654321/article/details/88419585
https://blog.csdn.net/youhuakongzhi/article/details/90166634
https://blog.csdn.net/xiongpai1971/article/details/79915047
https://www.cnblogs.com/pinard/p/6249328.html

发布了9 篇原创文章 · 获赞 18 · 访问量 4973

猜你喜欢

转载自blog.csdn.net/AvenueCyy/article/details/104480191