特征选择和特征生成问题初探

1. 为什么要进行特征选择?

0x1:好的模型 = 好的数据 + 好的特征 + 好的算法

以文字图像识别为例,运行机器学习算法的结果可以被表示为一个函数 y(x),它以一个新的数字的图像 x 为输入, 产生向量y,与目标向量的形式相同。

函数 y(x) 的精确形式在训练(training)阶段被确定,这 个阶段也被称为学习(learning)阶段,以训练数据为基础。一旦模型被训练出来,它就能确定新的数字的图像集合中图像的标签。这些新的数字的图像集合组成了测试集(test set)。

正确分类与训练集不同的新样本的能力叫做泛化(generalization)。在实际应用中,输入向量的变化性是相当大的,以至于训练数据只所有可能的输入向量中相当小得一部分,所以泛化是模式识别的一个中心问题。

需要特别注意的是,“泛化(generalization)”这个词在中文中可能会引起一些误会,很多人可能会将其理解为可以自动发现“未知的未知(unknown unknown)”,实际上不然。这里所谓的泛化,主要取决于2点:

  • 具体的问题场景。
  • 提供的训练数据集。

还是以MNIST文字图像识别为例,问题场景就是通过输入一个【x,y】的像素矩阵到模型,然后输出一个【z】的离散数字向量结果,提供的数据集是一个带标注的相同大小的文字图片。

在这个背景前提下,泛化能力具体指:

通过提供有限数量的,包含各种数字类别的标注图像,模型具备对该类别数字所对应图像的识别能力,即使在一定程度的变形情况下,仍然能成功识别出对应数字图像的正确类别标注

泛化的本质是信息论本身所决定的,它本身是一种信息理论的扩展,不可能超出信息论的范畴,要求模型还具备我们所谓的泛化能力

例如,我们不能准备一个【1-10】的数字图像进行训练,然后要求模型能准确识别出【11】这个完全未知的新数字。

回到泛化这个话题上来,如果要提供模型对数据的信息抽取能力,特征工程就是一个十分重要的环节!

对于大部分实际应用,原始输入向量通常被预处理(pre-processed),变换到新的变量空间。人们期望在新的变量空间中模式识别问题可以更容易地被解决。

例如,在数字识别的问题中,数字的图像通常被转化缩放,使得每个数字能够被包含到一个固定大小的盒子中。这极大地减少了每个数字类别的变化性,因为现在所有数字的位置和大小现在相同,这使得后续的区分不同类别的模式识别算法变得更加容易。这个预处理阶段有时被叫做特征抽取(feature extraction)。

0x2:为了更有效利用先验知识

我们知道,一个算法学习可以成功预测,一个很重要的关键是目标问题隐含了特定模式的先验假设(先验知识),而算法要做的唯一的事情,就是捕获这种先验假设,即通过我们熟悉的模型参数的形式来固话一个生成式或者一个判别式。

从一个具体的虚拟的例子开始说起,但是这个例子和我们再实际工程项目中遇到的所有问题,本质上是一样的。

有一个广告公司,每月会投入一定的广告费,用于提升其销售额,同时,财务部门收集了历史上每个月投入的广告费和销售额的结果:

这是一个非常具体的问题,项目的目标是需要开发一个预测器,帮助决策层判断将来需要投入多少广告费,并且对预期的销售额收入有一个预期判断。

1. 特征数字化编码 - 如何数值化编码是第一个影响最终效果的因素

算法是一个数学的运算和求导得极值过程,要求模型输入的是一个数字化的向量,但是现实生活中的例子不一定都是纯数字化的,当然这个例子本身足够简单,输入和要预测的结果本身已经是数字了,那我们可以直接进行数字化编码,但是在其他领域利于NLP领域,如何进行数字化特征编码就是一个足够引起慎重思考的问题了。

在这个销售预测的例子中,我们将输入样本编码为 1 维向量 X,数值即广告费的数值。预测的结果编码为 1 维的向量 Y,即销售额。

我们用 X 表示广告费,用 Y 表示销售额,这种从现实世界的具体物体,到用数值表示的抽象数值化变换,我们称之为特征函数,简称特征。

如果 X 是向量空间的一个子集,每一个有时候称为特征向量。

如何将现实世界物体编码为输入空间 X 的方式,对理解我们如何利用问题相关的先验知识,是非常重要的。

2. 特征选择 - 对特征进行筛选 and 扭曲变换

在完成特征数字化编码之后,我们可能会遇到一些问题

1)特征数量太多

当然,在本文的销售额预测的例子中没有这个情况,但是我们假设我们引入的特征还有例如当月季节、当月气温、当月流行热点等上千个维度,因为其实这个现象在实际项目中是很常见的,由于物体本身(例如NLP文本)或者我们出于“多多益善”的目的经常会尽可能地将我们认为对最终预测有帮助的特征维度都抽取出来,作为物体的特征向量。

这本身也没有太多问题,但是过多的特征会增大算法运算过程中的CPU和内存需求。

而且很可能还会导致模型欠拟合,这是因为在一些场景下,最终决定结果判断的可能只能其中3-5个核心特征,输入太多的无关特征,可能导致输入了一些“假关联”,这些“假关联”只是恰好在 这批训练样本集中呈现出了某种相关性,但其实其背后并没有隐含任何真实世界的统计规律。

如果模型在训练的过程中,“不小心”错误地引入了这些“假关联”特征,那么可以想象,模型的预测泛化能力是会大打折扣的。

2)在当前特征空间中特征向量线性不可分

这也是一个很常见的问题,线性可分看起来像是实验课上做demo的场景,在大多数的工程项目中,我们的特征向量都是在当前空间线性不可分的。例如下面这张图:

红星和黑圈无法通过一个线性函数直接进行二分类区分。这个时候其实有两个办法:

  • 选择用非线性分类器进行训练:例如adaboost、decision tree、dnn这些,但其实本质上,这些模型在训练生成模型的过程中,已经隐含了基于线性分类器组合得到非线性分类器的步骤了。
  • 对原始特征进行扭曲变化,将其转化到另一个高纬度的新特征空间中:例如基于kernel trick的SVM就是这种情况,通过在原空间样本 X 到希尔伯特空间的特征映射上学习每一个二分类器来实现分类

特征选择,是另一种应用先验知识来处理问题的方法。这是我们本文要重点讨论的一个主题。

3)直接穷举所有特征子集组合可行吗?

在理情况下,我们可以尝试 d 个特征的所有 k 个特征组合,然后选择最优预测结果对应的特征子集。然而,这种穷尽搜索方法通常在计算上是不可行的。

在实际工程中使用的算法中,我们常常采用一些启发式,迭代式的搜索方法来尽可能地获得一个足够好的特征子集。

0x3:避免或缓解过拟合问题

多数机器学习算法都被设计为要学习哪些属性最适用于做决策。

例如,决策树是在每个结点挑选最有希望成功的属性进行分裂,从理论上讲,决策树绝不会选择无关的或者无用的属性。属性越多,理论上会导致更强而不是更差的识别能力

但是遗憾的是,”从理论上看,理论和实践没有差别,但是在实践中有差别“。在实践中,往数据集中添加无关或干扰属性,会显著降低机器学习系统的能力。

在C4.5的实验中,发现了下面这个现象:

往标准数据集中添加一个随机的二元属性,属性值由抛掷无偏硬币产生,这会直接导致决策树的性能下降5%~10%。这是为什么呢?

变差的原因是在树的某些结点处,这个无关的属性被不可避免地选择为决定分支的属性,导致使用测试数据测试时产生随机误差。

问题来了,决策树的设计这么巧妙,能在理论上保证在每个结点挑选最适合的属性进行分裂,怎么还会发生这种情形呢?原因也很微妙。

随着程序逐渐向树的下层运行,能对属性选择决策有帮助的数据变得越来越少。然后一定会在某个结点时,数据极少,随机属性碰巧看起来很好。那么决策树没理论不选择这个”看起来很好“的随机噪声属性了。

由于每层的结点数量是随着层数按指数增加的,所以这个随机属性在某处看起来较好的概率也随着树的深度成倍增加。

真正的问题是树总是会达到某个深度,那里只存在少量的数据可用于属性选择,即使数据集较大,也不能避免这个问题,只是树可能更深而已。

0x4:避免误导属性

所谓误导属性,是指对于一组固定的属性值来说,同时包含了不同预测类的情况。

换句话就是,一个数据集中某一个属性子集,存在一组完全相同属性值的实例,除了预测类值不同之外,其他的所有属性值都相同。可以想象在属性中存在一个两类交叠的灰色地带。

使用标准数据集进行试验的结果表明,这会造成分类正确率下降1%~5%。

以决策树为例,问题出在这个误导属性在决策树的上层(或中间的某一层)就被(自然地)选中用于分裂。受此影响,此后的下层结点就被误导到了一个稀疏数据的属性空间中,拟合的效果自然会下降。

Relevant Link:

https://www.jianshu.com/p/f485dc676e1c

 

2. 特征选择的四象限分类

属性选择的大多数方法概括来说就是:在属性空间中搜索最有可能做出最好类预测的属性子集

按照评估预测性能方式、以及搜索顺序这2个维度,可以有两种不同的分类方法,它们分别分布在四个象限中。

0x1:按照评估预测性能方式分类

按照评估预测性能方式分类,选择一个好的属性子集,有两种完全不同的方法:

  • 根据数据的普遍特性做出一个独立估计:也被称为过滤(filtering)方法,因为它是要在学习开始之前,先过滤属性集,产生一个最好的属性子集。例如
    • 基于PCA降维的主成分特征提取就是一种属性过滤方法
    • 相关性统计分析也是一种属性过滤方法
  • 采用将要用于最终机器学习的算法来评估子集:被被称为包装(wrapper)方法,因为学习方法被包裹在选择过程中。

0x2:按照搜素顺序分类

以天气数据预测为例,

天气数据集的属性空间

可以看到,可能的属性子集数目随属性数量的增加而呈指数增长,这使得穷举搜索不切实际,穷举搜索只适合最简单的问题。

基本上,搜索方式可以分为:

  • 正向选择(forward selection):从上到下,开始时不含任何属性,然后每次增加一个。
  • 反向删除(backward elimination):从下到上,开始时包含了所有属性,然后每次减少一个

一般来说,反向删除比正向选择生成的属性集更大,但是在某些情况下分类准确率更高。原因是正向选择往往是在搜索的早期就早早提前停止。但是如果重点是要理解所涉及的决策结构,那么正向选择是很有用的,因为它有效减少了属性数目而对分类准确率的影响却很小。

0x3:四象限之外的其他搜索方法

我们学习主要的目的是学习其核心思想,前面2小节讨论的四象限方法笔者认为是属性特征的最核心的需要理解的概念。但是我们同时也需要明白,除此之外,还有一些更为精细复杂的搜索方法,例如:

  • 正向选择和反向删除可以结合成双向搜索,此时,算法开始时可以包含所有属性也可以不包含任何属性
  • 最佳优先搜索(best-first search),该方法不是在性能开始下降时停止搜索,而是保存到目前为止已经评估过的所有属性子集列表,并按照性能好坏排序,因此它可以重访先前的配置
  • 束搜索(beam search),只在每个阶段截取属性子集列表,因此它只含有固定数目的(束宽)最有希望的候选对象
  • 遗传算法驱动的属性选择,遗传算法松散地基于自然选择原理,使用对当前的候选子集的随机扰动,逐步”进化出“好的属性子集

 

2. 滤波器特征选择 - 不依赖具体算法模型的feature selection

滤波器方法可能是最简单的特征选择方法,在滤波方法中,我们将某些特征看作独立于其他特征,然后根据一些质量度量标准来估计这些独立特征,然后选择 k 个获得最高评分的特征(此外也可以依据最好的评分确定特征的数量)。

直观上,我们总是选择能区分所有实例的最小的属性集,即MDL原则。

0x1:线性相关系数(皮尔森相关系数)- 评价单个特征和预测结果标签的相关性

1. 皮尔森相关系数 - 评价单个特征在一定的评价标准前提下和待预测标签的相关性

一种最直接的方法是依据预测期的错误率来获得特征的评分。

为了说明这个问题,我们考虑采用平方损失的线性回归问题。令

表示 m 个训练样本第 j 个特征值形成的向量,令表示 m 个样本的目标值。

仅仅使用第 j 个特征的经验风险最小化线性预测期的经验平方损失是:

为了求解这个最小化问题,令表示特征的平均值,令表示目标的平均值。显然:

等式右边对 b 求导,令导数等于0,我们得到 b = 0。

同样,对 a 求导,当 b = 0时,我们得到

将 a,b 的值代入目标函数,我们得到通过选择右式的特征组合,我们的目的是让左式的损失函数值最小

依据最小平方损失对特征排序,等同于依据下面评分的绝对值进行排序(高分表示好的特征):

上面的表达式被称为皮尔森相关系数。分子表示第 j 个特征和目标值方差的经验估计,而分母表示第 j 个特征方差乘上目标值所得方差经验估计的均方根。

皮尔森相关系数的取值范围为【-1,1】,这里如果皮尔森相关系数等于 1 或者 -1,表示 v 和 y 之间有线性相关映射关系,且经验风险等于0。

2. 单个特征和待预测标签线性相关性很低,一定意味着这个特征不好吗?

如果皮尔森相关系数等于0,表示 v 到 y 的最优线性映射为各个维度都等于0,这就是说单独只用 v 不足以预测 y。

但是这并不一定意味着 v 是一个坏的特征,比如可能出现这种情况,v 和其他特征组合起来能够很好地预测 y。

考虑一个简单的例子,目标通过函数来产生。

假定 x1 是由上的均匀分布产生,而,这里 z 也是由上的均匀分布产生。

那么,,我们可以得到

因此,对于足够大的训练集,第一个特征的皮尔森相关系数可能等于0,因此它可能不被选择,然而,如果不知道第一个特征,没有函数能够很好地预测目标值。

3. Pearson相关系数计算的Scikit-learn版本

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

#!/usr/bin/python

import numpy as np
from scipy.stats import pearsonr

np.random.seed(0)
size = 300
x = np.random.normal(0, 1, size)
print "Lower noise", pearsonr(x, x + np.random.normal(0, 1, size))
print "Higher noise", pearsonr(x, x + np.random.normal(0, 10, size))

这个例子中,我们比较了变量在加入噪音之前和之后的差异。

当噪音比较小的时候,p-value接近1,改变后的变量和改变前的变量相关性很强;

当噪音比较大的时候,p-value降低,改变后的变量和改变前的变量相关性降低

Pearson相关系数的一个明显缺陷是,作为特征排序机制,他只对线性关系敏感。如果关系是非线性的,即便两个变量具有一一对应的关系,Pearson相关性也可能会接近0。

#!/usr/bin/python

import numpy as np
from scipy.stats import pearsonr

x = np.random.uniform(-1, 1, 100000)
print pearsonr(x, x**2)[0]

0x2:卡方检验 - 变量之间相关性(这里的变量包括特征或标签值)

卡方检验是一种用途很广的计数资料的假设检验方法。它属于非参数检验的范畴,主要是比较两个及两个以上样本率( 构成比)以及两个分类变量的关联性分析。

其根本思想就是在于比较理论频数和实际频数的吻合程度或拟合优度问题。

0. 卡方检验概念

卡方检验(chi-square test),也叫作χ2检验。卡方检验有多种方法,最著名的就是皮尔逊卡方检验,也是卡尔·皮尔森提出。我们通常用卡方来检测两个变量or样本的独立性。

卡方检验的理论假设是:一个样本中已发生事件的次数分配会遵守某个特定的理论分配

通常的讲:观测频数跟实际频数应该没有区别,除非两个变量不是独立的。举个例子:XX公司不同部门职员的性别跟部门两者是独立的么?很明显不是哦。这儿我们的期望男女比例是1.05:1,但是在IT研发部分,比例可能是3:1,在HR、客服等部门,比率可能是1:3了。因此这儿的观测值就跟实际值不一致,我们就可以说两个不是独立的。

1. 通过一个四格卡方检验来阐述卡方检验

下面的表格将实验者分成了2组,随机变量分别为:是否和牛奶、感冒人数。我们现在希望通过卡方检验来揭示这两个变量之间是否具有相关性,即它们之间是否存在依赖推导。

  感冒人数 未感冒人数 合计 感冒率
喝牛奶组 43 96 139 30.94%
不喝牛奶组 28 84 112 25.00%
合计 71 180 251 28.29%

通过简单的统计我们得出喝牛奶组和不喝牛奶组的感冒率为30.94%和25.00%,两者的差别可能是抽样误差导致,也有可能是牛奶对感冒率真的有影响。

1)建立理论假设统计

为了确定真实原因,我们先假设喝牛奶对感冒发病率是没有影响的,即喝牛奶喝感冒时独立无关的。

所以我们可以得出感冒的发病率实际是(43+28)/(43+28+96+84)= 28.29%

这一步实际上里隐含了条件独立性假设,即将另一个随机变量视为无任何影响,无任何依赖推导,直接计算边缘条件概率。

2)根据理论假设统计重新得到新的概率分布 - 有点类似EM的过程

所以,理论的四格表应该如下表所示:

  感冒人数 未感冒人数 合计
喝牛奶组 139*0.2829 = 39.3231 139*(1-0.2829) = 99.6769 139
不喝牛奶组 112*0.2829 = 31.6848 112*(1-0.2829) = 80.3152 112
合计 71 180 251

如果喝牛奶喝感冒真的是独立无关的,那么四格表里的理论值和实际值差别应该会很小。 

3)计算理论值和实际值之间的差距

卡方检验的计算公式为:,其中,A为实际值,T为理论值。

x2用于衡量实际值与理论值的差异程度(也就是卡方检验的核心思想),包含了以下两个信息:

1. 实际值与理论值偏差的绝对大小(由于平方的存在,差异是被放大的)
2. 差异程度与理论值的相对大小

上面牛奶的的例子我们计算得:

卡方 = (43 - 39.3231)平方 / 39.3231 + (28 - 31.6848)平方 / 31.6848 + (96 - 99.6769)平方 / 99.6769 + (84 - 80.3152)平方 / 80.3152 = 1.077

4)卡方分布的临界值

上一步我们得到了卡方的值 = 1.077,但是这个值意味着什么呢?如何通过卡方的值来判断喝牛奶和感冒是否真的是独立无关的?也就是说,怎么知道无关性假设是否可靠?

基本上来说:

卡方值越大,假设成立的概率就越小,即随机变量间相关性越大;

卡方值越小,假设成立的概率就越大,即随机变量间相关性越小。

下面是卡方分布的临界值表。

表格里的值代表着:如果卡方值大于对应的阈值,则至少有对应的概率,我们的假设是成立的。

而具体应该查表格的哪一行,这里需要用到一个自由度的概念,自由度等于:F = (行数 - 1) * (列数 - 1)。对这个例子来说,自由度F = 1 * 1 = 1。

在牛奶的这个例子中,对应的是F = 1,即我们需要查第一行。

我们计算得到的卡方检验值为 1.077,介于P(0.1)和P(0.5)这两栏之间,所以我们可以说:

喝牛奶和感冒之间有至少50%的概率,是独立不相关的;

但是1.077超过了和P(0.9)和和P(0.95)的阈值,所以,“喝牛奶和感冒之间独立性假设有95%的概率成立”这句话是错了(有点绕,理解一下)。

所以,综上,所以喝牛奶和感冒独立不相关的假设不成立。

Relevant Link:

https://blog.csdn.net/snowdroptulip/article/details/78770088
https://blog.csdn.net/gdanskamir/article/details/54913233

0x3:互信息估计

以上就是经典的互信息公式了。想把互信息直接用于特征选择其实不是太方便:

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

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

反过头来看y=x^2这个例子,MIC算出来的互信息值为1(最大的取值)。

#!/usr/bin/python

import numpy as np
from minepy import MINE

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

0x4:接受操作特征(ROC)曲线的面积

总体来说,滤波评分方法的思想是:在一定的评价标准(损失函数)下,评估特征对最终预测标签结果准确性的贡献度

Relevant Link:

https://blog.csdn.net/gdanskamir/article/details/54913233
https://www.cnblogs.com/hhh5460/p/5186226.html

 

3. 包装器特征选择方法,需要依赖具体算法模型进行属性子集评价

包装器特征选择方法是另一个比较常用的特征选择方法。和滤波方法不同,包装器特征选择方法伴随着学习算法的过程,迭代地进行。

机器学习算法可用于属性选择,本章列举几个典型的评估方法。

0x1:基于决策树的属性选择方法

可以在整个数据集上应用决策树算法,然后选择那些在决策树中真正用到的属性。

这个属性选择可能会对其他的学习算法产生影响。例如,最近邻算法很容易受到无关属性的影响,它可以通过先创建一棵用于过滤属性的决策树而使性能得到提高。

0x2:基于线性模型的属性选择方法

一种可行的方法是建立一个线性模型,比如一个线性支持向量机,然后根据系数的大小来进行属性重要性排序。

0x3:基于有监督学习模型的特征排序 (Model based ranking)

这种方法的思路是直接使用你要用的有监督机器学习算法,针对每个单独的特征和响应变量(标签值y)建立预测模型。

假如某个特征和响应变量之间的关系是非线性的,可以用基于树的方法(决策树、随机森林)、或者扩展的线性模型等。基于树的方法比较易于使用,因为他们对非线性关系的建模比较好,并且不需要太多的调试。但要注意过拟合问题,因此树的深度最好不要太大,再就是运用交叉验证。

我们在著名的波士顿房价数据集上使用sklearn的随机森林回归给出一个单变量选择的例子:

#!/usr/bin/python

import numpy as np
from sklearn.cross_validation import cross_val_score, ShuffleSplit
from sklearn.datasets import load_boston
from sklearn.ensemble import RandomForestRegressor

#Load boston housing dataset as an example
boston = load_boston()
X = boston["data"]
Y = boston["target"]
names = boston["feature_names"]

rf = RandomForestRegressor(n_estimators=20, max_depth=4)
scores = []
for i in range(X.shape[1]):
     score = cross_val_score(rf, X[:, i:i+1], Y, scoring="r2",
                              cv=ShuffleSplit(len(X), 3, .3))
     scores.append((round(np.mean(score), 3), names[i]))
res = sorted(scores, reverse=True)
for i in res:
    print i

 

4. 特征操作与归一化

特征操作或归一化包括在每一个源特征上的简单变换。这些变换可能使得我们假设类的近似误差或估计误差更低或者能够得到一个更快的算法。

与特征选择的问题类似,特征操作与归一化充满了“玄学”,它需要我们具体问题具体分析,这里没有绝对好或绝对坏的变换,而是每一个特征变换与在这些特征矢量上的学习算法,以及这个问题相关的先验假设密切相关。

0x1:常用的特征变换

接下来,我们用表示在 m 个训练样本上的特征 f,同样,我们用表示所有样本特征的经验均值。

1. 中心化

通过变换,这个变换使得特征有 0 均值。

2. 归一化范围

这个变换使得每一个特征的范围都是【0,1】,

当然,很容易将范围变换为【0,b】或【-b,b】

3. 标准化

这个变换使得所有特征有 0 均值和 1 方差。形式上,令表示特征的经验方差,那么设置

4. 裁剪变换

这个变换裁剪特征的高值或低值

5. sigmoidal变换

这个变换在特征上用到了sigmoid函数,例如,这里 b 是用户自定义参数。这个变换可以认为是一种软版本的裁剪变换。它对接近于 0 的值值有一些作用,并且与远离 0 的裁剪变换很相似。

6. 对数变换

这个变换是,这里 b 是用户自定义参数。这个变换广泛地用于当特征是计数型特征的情况下。例如,假定特征表示在一个文档中某个词出现的次数。那么,某个词出现一次与没有出现的区别,要比一个词出现1000次还是1001次更为重要。

简单来说,对数变换对值较小时的变化更敏感,值越大,敏感度越低。从本质上看,和标准化和归一化的思想很类似。

Relevant Link:

https://www.cnblogs.com/hhh5460/p/5186226.html 

 

posted @ 2018-07-29 11:10  郑瀚Andrew  阅读(3342)  评论(0编辑  收藏  举报