特征选择
特征工程在工业上有这么一句广为流传的话:数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。接下来将给你介绍特征工程的第一个分支,特征选择。
对于一个学习任务来说,如果某一个特征和我们的学习任务没有太大关系,我们把它称之为无关特征(irrelevant feature),如个人长相和财富自由;如果某一个特征和我们的学习任务有太大关系,我们把它称之为相关特征(relevant feature),而我们接下来要讲的特征选择(feature select)则是要从给定的原生特征集合汇总选出相关特征的子集,即筛选掉无关特征。
筛选掉无关特征一般是在训练模型之前,所以特征选择也属于构建机器学习系统中的数据预处理。
特征选择学习目标
- 特征选择的用处
- 过滤式特征选择
- 包裹式特征选择
- 嵌入式特征选择
- 高级特征选择
特征选择引入
如果我们有一个学习任务是为了分析一个人具备什么条件更可能财富自由,让我们想象一下,虽然你可能想象不到,我就直说了,这个社会上财富自由的人个人长相一般般的有很多;个人长相挺漂亮的如明星也会有很;个人长相相比较不随时代潮流的也会有,这可以一定程度上表明个人是否能财富自由和个人长相没有关系。
但是我们可能再对个人条件数据挖掘的时候,把这一个特征也当做影响模型准确度的条件之一,如果只有几个维度的特征,或几个无关特征,我们计算机可以应付的过来,但是如果出现维数灾难的时候,每多一个无关特征对我们的计算机来说,计算量都是庞大的,这个时候我们就得考虑过滤掉这些可能无意义的特征了,毕竟若将纷繁复杂的因素抽丝剥茧,只留下关键因素,真相往往容易看清。
上述所说的长相可能是通过我们认知判断出来的,工业上可没有这么多可以通过认知判断出来的无关特征,而我们所要讲述的特征选择干的就是这件事。
特征选择详解
无关特征和冗余特征
上一节讲到了特征选择的任务是尽可能的过滤掉无关特征,但是除了无关特征之外,有时候我们还会碰到冗余特征(redundant feature)。
冗余特征可以理解为可以从其他特征中推演出来的特征,比如我们正在考虑立方体对象,如果我们有了立方体地面宽$a$和底面长$b$这两个特征,如果再给我们立方体底面的面积$S$的话,则这个面积$S$即为冗余特征。但是如果我们的学习任务是为了计算一个立方体的体积$V$,通过立方体体积公式可以知道给我们$S$比给我们$a$和$b$更好,因为给了$a$和$b$我们还需要计算出$S$。对于这种冗余特征可以考虑特征与特征之间的相关性。
还有一种冗余特征,如学生的成绩只分为及格和不及格,有时候我们可以只考虑及格的学生情况,因为没考虑到及格情况的学生就是不及格,这样也可以适当的进行特征选择。
过滤式特征选择
过滤式选择主要是,通过不同的方法去检测特征与样本之间的相关度,然后依据不同方法的检验规则筛选特征。对特征过滤之后再训练模型。
卡方检验
卡方检验可以检验某个特征分布与输出值分布之间的相关性,我们可以给定卡方值阈值,选择过滤掉卡方值较大的部分特征。通过也可以通过p值判断,p值为结果为可信程度的一个递减指标,p值越大,我们越不能认为样本中变量的关联是总体中各变量关联的可靠指标。
# 卡方检验示例
import numpy as np
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
X = [[0, 2, 0, 3], [0, 1, 4, 3], [0, 1, 1, 3]]
y = np.array([[2], [4], [5]])
# 使用SelectKBest进行特征选择
selector = SelectKBest(score_func=chi2, k=4)
selector = selector.fit(X, y)
print('卡方值:{}'.format(selector.scores_))
卡方值:[nan 0.5 5.2 0. ]
# 直接使用chi2类获取chi_values值
chi_value, p_value = chi2(X, y)
print('卡方值:{}\np值:{}'.format(chi_value, p_value))
卡方值:[nan 0.5 5.2 0. ]
p值:[ nan 0.77880078 0.07427358 1. ]
方差过滤
首先我们可以计算出所有特征的方差$D(x_i)\quad(i=1,2,\cdots,n)$,然后给定一个方差阈值$\epsilon$,如果$D(x_i)>\epsilon$,则该保留该特征,反之,过滤掉该特征。一般方差小于1,特征对模型的作用可能并不大;如果方差为0,即所有样本的特征取值都是一样的,则该特征一定是无用的,可以直接舍弃。同理可以使用from sklearn.feature_selection import SelectKBest
选择特征,此处不多赘述。
# 方差过滤示例
from sklearn.feature_selection import VarianceThreshold
X = [[0, 2, 0, 3], [0, 1, 4, 3], [0, 1, 1, 3]]
selector = VarianceThreshold()
print('通过方差过滤特征:\n{}'.format(selector.fit_transform(X, y)))
通过方差过滤特征:
[[2 0]
[1 4]
[1 1]]
相关系数过滤
曾经在线性回归代码中,我们使用过该方法。我们可以计算训练集中样本特征与输出值之间的相关度,选择相关度较大的部分特征,此处我么选择pearsonr系数举例。
对于pearsonr系数,它衡量的是变量之间的线性相关性,取值范围为$[-1,1]$,其中1代表变量完全正相关,-1代表完全负相关。
# 相关系数过滤示例
import numpy as np
import scipy as sc
a = [1, 2, 3, 4]
b = [2, 3, 6, 9]
c = [-2, -4, -6, -7]
a = np.array(a)
b = np.array(b)
c = np.array(c)
print('a和b之间的皮尔逊相关系数:{}'.format(sc.stats.pearsonr(a, b)[0]))
print('a和c之间的皮尔逊相关系数:{}'.format(sc.stats.pearsonr(a, c)[0]))
a和b之间的皮尔逊相关系数:0.9797958971132713
a和c之间的皮尔逊相关系数:-0.9897782665572894
F检验
F检验,最常用的别名叫做联合假设检验(joint hypotheses test),此外也称方差比率检验、方差齐性检验,通产如卡方检验,给定F值阈值,选择F值较大的部分特征。F检验可以检验分类和回归问题,在scikit-learn库中分别对应from sklearn.feature_selection import f_classif
和from sklearn.feature_selection import f_regression
此处只给出回归问题的代码,并且同理可以使用from sklearn.feature_selection import SelectKBest
选择特征,此处不多赘述。
# F检验示例
import numpy as np
from sklearn.feature_selection import f_classif
from sklearn.feature_selection import f_regression
X = [[1, 2, 4, 3], [2, 1, 4, 3], [3, 1, 1, 4]]
y = np.array([[2], [4], [5]]).ravel()
f_value, p_value = f_regression(X, y)
print('f_value:{}\np_value:{}'.format(f_value, p_value))
f_value:[27. 8.33333333 1.33333333 1.33333333]
p_value:[0.12103772 0.21229562 0.45437105 0.45437105]
互信息过滤
互信息即信息增益,互信息值越大,说明该特征和输出值之间的相关性越大,即筛选掉互信息值较小的特征。一般可以计算分类和回归问题中特征与输出之间的互信息,在scikit-learn库中分别对应from sklearn.feature_selection import mutual_info_classif
和from sklearn.feature_selection import mutual_info_regression
,此处只给出回归问题的代码,并且同理可以使用from sklearn.feature_selection import SelectKBest
选择特征,此处不多赘述。
# 互信息过滤示例
import numpy as np
from sklearn.feature_selection import mutual_info_regression
from sklearn.feature_selection import mutual_info_classif
X = [[1., 2., 4., 3.], [2., 1., 4., 3.], [3., 1., 1., 4.]]
y = np.array([[2.], [4.], [5.]]).ravel()
mi = mutual_info_regression(X, y, n_neighbors=1)
print('mi:{}'.format(mi))
mi:[0.5 0.16666667 0. 0.16666667]
包裹式特征选择
包裹式选择并不是直接对特征进行选择,而是把最终将要使用的学习器的性能作为特征子集的评价标准,即选择最有利于学习器性能的特征子集。
包裹式选择从学习器性能上看,较于过滤式特征选择会更好,但是由于是从不同的学习器中选择最优的学习器,需要训练多个学习器,因此计算机的计算开销较于过滤式特征选择会大很多。
递归特征消除
递归特征消除可以理解成,没训练一次模型,消除若干个权值系数较小的特征,然后基于新的特征值进行下一轮训练,不断重复上述的过程。
我们以较为经典的SVM-REF举例,SVM在第一轮训练的时候,得到超平面$S=w^Tx+b=0$,如果每个训练数据有$n$个特征,则SVM-REF会消除$n$个特征中${w_i}^2,\quad(i=1,2,\cdots,n)$最小的特征,因此特征数将变成$n-1$个,然后对训练集继续训练,使用上述方法将特征变成$n-2$个……
在scikit-learn库中可以使用from sklearn.feature_selection import RFE
和from sklearn.feature_selection import RFECV
方法用于递归特征消除,此处使用CV方法让模型自动消除指定个数的特征。
# 递归特征消除示例
from sklearn.datasets import make_friedman1
from sklearn.feature_selection import RFECV
from sklearn.svm import SVR
# 生成10维特征的50个样本
X, y = make_friedman1(n_samples=50, n_features=10, random_state=0)
estimator = SVR(kernel="linear")
selector = RFECV(estimator, step=1, cv=4)
selector = selector.fit(X, y)
print('特征个数:{}'.format(selector.n_features_))
特征个数:6
print('被选定的特征:{}'.format(selector.support_))
被选定的特征:[ True True True True True False False False True False]
print('特征排名:{}'.format(selector.ranking_))
特征排名:[1 1 1 1 1 5 3 2 1 4]
嵌入式特征选择
嵌入式特征选择是将特征选择过程与学习器训练过程融为一体,即两者在同一个优化过程中完成。嵌入式特征一般需要能得到特征权重系数或特征重要度的学习器,也就是说如线性回归、逻辑回归、决策树、随机森林都可以作为嵌入式特征选的学习器。
通常使用最多的是L1正则化和L2正则化,在线性回归的正则化中讲到,L1和L2正则化可以解决过拟合问题,但是L1和L2正则化有时候也被用来做特征选择。当正则化系数越大的时候,对模型的惩罚力度也越大,因此会导致特征的权重系数变成0,这样特征对于模型而言就没有意义了,因此便达到了特征选择的目的。
在scikit-learn库中可以使用from sklearn.feature_selection import SelectFromModel
来选择特征,此处使用L1正则化和随机森林举例。
# 嵌入式选择示例
from sklearn.datasets import make_friedman1
from sklearn.linear_model import Lasso
from sklearn.ensemble import RandomForestRegressor
from sklearn.feature_selection import SelectFromModel
X, y = make_friedman1(n_samples=100, n_features=10, random_state=0)
# 有系数的线性模型中,L1正则化可生成一个稀疏矩阵
lasso = Lasso()
lasso.fit(X, y)
model = SelectFromModel(lasso)
new_X = model.fit(X, y)
print('L1正则化:{}'.format(new_X.get_support()))
# 无系数的模型中,根据重要性筛选无关特征
rf = RandomForestRegressor(n_estimators=10)
rf.fit(X, y)
model = SelectFromModel(rf)
new_X = model.fit(X, y)
print('随机森林:{}'.format(new_X.get_support()))
L1正则化:[False False False True False False False False False False]
随机森林:[ True False False True False False False False False False]
高级特征寻找
高级特征可以理解成,训练数据特征没有给我们的信息,就那冗余特征中的例子举例,如果得到了正方体的边长$a$,进而可以得到正方体的某一个面的表面积$S=a^2$,进而又可以得到立方体的体积$V=a^3$,进而可以得到…也就是说,高级特征可以一直找下去。这也一定程度说明了冗余特征有时候也可能是有益特征,所以对冗余特征做处理也需要多多考虑。
在Kaggle之类的算法竞赛中,高分团队主要使用的方法除了集成学习算法,剩下的主要就是在寻找高级特征。所以寻找高级特征一般是模型优化的必要步骤之一。但是需要注意的是,在第一次建立模型的时候,我们可以先不寻找高级特征,得到以后基准模型后,再寻找高级特征进行优化。
寻找高级特征的方法
- 若干项特征加和: 股票交易中得到某只股票的每周的总价格
- 若干项特征之差: 股票交易中得到某只股票的两天的差额
- 若干项特征乘积: 股票交易中得到某只股票的成交量*成交金额
- 若干项特征除商: 股票交易中得到某只股票的交易金额/A股市场总交易金额
上述只是简单的举例可以如此寻找高级特征,工业上可能用的就不是如此简单方法找高级特征,不同的问题有不同的寻找高级特征的方法。然后在找高级特征的时候需要注意的是,寻找高级特征的过程会极大的增加特征维度,这个也是需要考虑的。如果可以的话,解决聚类问题时高级特征尽量少一点,解决分类回归问题时高级特征可以多一点。
小结
特征选择是特征工程中很重要的一步,不仅能很大程度的获取重要特征,并在一定程度上降低维数灾难带来的风险,细心地同学会发现我们特征选择的基础是建立在,原始数据都比较完美的情况,接下来我们将聊一聊数据不完美的情况该如何处理,我知道你可能想问:什么是数据不完美呢?下一篇特征预处理将告诉你答案。