Summary

数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。由此可见,特征工程在机器学习中占有相当重要的地位。在实际应用当中,可以说特征工程是机器学习成功的关键。

那特征工程是什么?

​ 特征工程是利用数据领域的相关知识来创建能够使机器学习算法达到最佳性能的特征的过程。

特征工程又包含了Feature Selection(特征选择)、Feature Extraction(特征提取)和Feature construction(特征构造)等子问题,本章内容主要讨论特征选择相关的方法及实现。

在实际项目中,我们可能会有大量的特征可使用,有的特征携带的信息丰富,有的特征携带的信息有重叠,有的特征则属于无关特征,如果所有特征不经筛选地全部作为训练特征,经常会出现维度灾难问题,甚至会降低模型的准确性。因此,我们需要进行特征筛选,排除无效/冗余的特征,把有用的特征挑选出来作为模型的训练数据。

特征选择介绍

  1. 特征按重要性分类

    • 相关特征

      对于学习任务(例如分类问题)有帮助,可以提升学习算法的效果

    • 无关特征

      对于我们的算法没有任何帮助,不会给算法的效果带来任何提升

    • 冗余特征

      不会对我们的算法带来新的信息,或者这种特征的信息可以由其他的特征推断出

  2. 特征选择的目的

    对于一个特定的学习算法来说,哪一个特征是有效的是未知的。因此,需要从所有特征中选择出对于学习算法有益的相关特征。而且在实际应用中,经常会出现维度灾难问题。如果只选择所有特征中的部分特征构建模型,那么可以大大减少学习算法的运行时间,也可以增加模型的可解释性

  3. 特征选择的原则

    获取尽可能小的特征子集,不显著降低分类精度、不影响分类分布以及特征子集应具有稳定、适应性强等特点

特征选择的方法

  1. Filter 方法(过滤式)

    先进行特征选择,然后去训练学习器,所以特征选择的过程与学习器无关。相当于先对特征进行过滤操作,然后用特征子集来训练分类器。

    **主要思想:**对每一维特征“打分”,即给每一维的特征赋予权重,这样的权重就代表着该特征的重要性,然后依据权重排序。

    主要方法:

    • 卡方检验
    • 信息增益
    • 相关系数

    优点: 运行速度快,是一种非常流行的特征选择方法。

    **缺点:**无法提供反馈,特征选择的标准/规范的制定是在特征搜索算法中完成,学习算法无法向特征搜索算法传递对特征的需求。另外,可能处理某个特征时由于任意原因表示该特征不重要,但是该特征与其他特征结合起来则可能变得很重要。

  2. Wrapper 方法 (封装式)

    直接把最后要使用的分类器作为特征选择的评价函数,对于特定的分类器选择最优的特征子集。

    主要思想: 将子集的选择看作是一个搜索寻优问题,生成不同的组合,对组合进行评价,再与其他的组合进行比较。这样就将子集的选择看作是一个优化问题,这里有很多的优化算法可以解决,尤其是一些启发式的优化算法,如GA、PSO(如:优化算法-粒子群算法)、DE、ABC(如:优化算法-人工蜂群算法)等。

    主要方法:

    • 递归特征消除算法

    优点: 对特征进行搜索时围绕学习算法展开的,对特征选择的标准/规范是在学习算法的需求中展开的,能够考虑学习算法所属的任意学习偏差,从而确定最佳子特征,真正关注的是学习问题本身。由于每次尝试针对特定子集时必须运行学习算法,所以能够关注到学习算法的学习偏差/归纳偏差,因此封装能够发挥巨大的作用。

    缺点: 运行速度远慢于过滤算法,实际应用用封装方法没有过滤方法流行。

  3. Embedded 方法(嵌入式)

    将特征选择嵌入到模型训练当中,其训练可能是相同的模型,但是特征选择完成后,还能给予特征选择完成的特征和模型训练出的超参数,再次训练优化。

    主要思想: 在模型既定的情况下学习出对提高模型准确性最好的特征。也就是在确定模型的过程中,挑选出那些对模型的训练有重要意义的特征。

    主要方法: 用带有L1正则化的项完成特征选择(也可以结合L2惩罚项来优化)、随机森林平均不纯度减少法/平均精确度减少法。

    优点: 对特征进行搜索时围绕学习算法展开的,能够考虑学习算法所属的任意学习偏差。训练模型的次数小于Wrapper方法,比较节省时间。

    缺点: 运行速度慢

特征选择的实现方法

从两个方面考虑来选择特征:

特征是否发散: 如果一个特征不发散,例如方差接近于0,也就是说样本在这个特征上基本上没有差异,这个特征对于样本的区分并没有什么用。

假设某特征的特征值只有0和1,并且在所有输入样本中,95%的实例的该特征取值都是1,那就可以认为这个特征作用不大。如果100%都是1,那这个特征就没意义了。

**特征与目标的相关性:**这点比较显见,与目标相关性高的特征,应当优选选择。除方差法外,本文介绍的其他方法均从相关性考虑。

Filter:

  1. 卡方检验

    经典的卡方检验是检验定性自变量对定性因变量的相关性。假设自变量有N种取值,因变量有M种取值,考虑自变量等于i且因变量等于j的样本频数的观察值与期望的差距,构建统计量:

    img img

      不难发现,这个统计量的含义简而言之就是自变量对因变量的相关性。用feature_selection库的SelectKBest类结合卡方检验来选择特征的代码如下:

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

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

    1
    2
    3
    4
    5
    
    from sklearn.feature_selection import VarianceThreshold
    
    #方差选择法,返回值为特征选择后的数据
    #参数threshold为方差的阈值
    VarianceThreshold(threshold=3).fit_transform(iris.data)
    
  3. 相关系数

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

    1
    2
    3
    4
    5
    6
    7
    
    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)
    
  4. 互信息法

    经典的互信息也是评价定性自变量对定性因变量的相关性的,互信息计算公式如下:

    img img

      为了处理定量数据,最大信息系数法被提出,使用feature_selection库的SelectKBest类结合最大信息系数法来选择特征的代码如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    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:

  1. 递归特征消除法

    递归消除特征法使用一个基模型来进行多轮训练,每轮训练后,消除若干权值系数的特征,再基于新的特征集进行下一轮训练。使用feature_selection库的RFE类来选择特征的代码如下:

    1
    2
    3
    4
    5
    6
    7
    
    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 :

  1. 基于惩罚项的特征选择法

    使用带惩罚项的基模型,除了筛选出特征外,同时也进行了降维。使用feature_selection库的SelectFromModel类结合带L1惩罚项的逻辑回归模型,来选择特征的代码如下:

    1
    2
    3
    4
    5
    
    from sklearn.feature_selection import SelectFromModel
    from sklearn.linear_model import LogisticRegression
    
    #带L1惩罚项的逻辑回归作为基模型的特征选择
    SelectFromModel(LogisticRegression(penalty="l1", C=0.1)).fit_transform(iris.data, iris.target)
    

    实际上,L1惩罚项降维的原理在于保留多个对目标值具有同等相关性的特征中的一个,所以没选到的特征不代表不重要。故,可结合L2惩罚项来优化。具体操作为:若一个特征在L1中的权值为1,选择在L2中权值差别不大且在L1中权值为0的特征构成同类集合,将这一集合中的特征平分L1中的权值,故需要构建一个新的逻辑回归模型:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    
    from sklearn.linear_model import LogisticRegression
    
    class LR(LogisticRegression):
        def __init__(self, threshold=0.01, dual=False, tol=1e-4, C=1.0,
                     fit_intercept=True, intercept_scaling=1, class_weight=None,
                     random_state=None, solver='liblinear', max_iter=100,
                     multi_class='ovr', verbose=0, warm_start=False, n_jobs=1):
    
            #权值相近的阈值
            self.threshold = threshold
            LogisticRegression.__init__(self, penalty='l1', dual=dual, tol=tol, C=C,
                     fit_intercept=fit_intercept, intercept_scaling=intercept_scaling, class_weight=class_weight,
                     random_state=random_state, solver=solver, max_iter=max_iter,
                     multi_class=multi_class, verbose=verbose, warm_start=warm_start, n_jobs=n_jobs)
            #使用同样的参数创建L2逻辑回归
            self.l2 = LogisticRegression(penalty='l2', dual=dual, tol=tol, C=C, fit_intercept=fit_intercept, intercept_scaling=intercept_scaling, class_weight = class_weight, random_state=random_state, solver=solver, max_iter=max_iter, multi_class=multi_class, verbose=verbose, warm_start=warm_start, n_jobs=n_jobs)
    
        def fit(self, X, y, sample_weight=None):
            #训练L1逻辑回归
            super(LR, self).fit(X, y, sample_weight=sample_weight)
            self.coef_old_ = self.coef_.copy()
            #训练L2逻辑回归
            self.l2.fit(X, y, sample_weight=sample_weight)
    
            cntOfRow, cntOfCol = self.coef_.shape
            #权值系数矩阵的行数对应目标值的种类数目
            for i in range(cntOfRow):
                for j in range(cntOfCol):
                    coef = self.coef_[i][j]
                    #L1逻辑回归的权值系数不为0
                    if coef != 0:
                        idx = [j]
                        #对应在L2逻辑回归中的权值系数
                        coef1 = self.l2.coef_[i][j]
                        for k in range(cntOfCol):
                            coef2 = self.l2.coef_[i][k]
                            #在L2逻辑回归中,权值系数之差小于设定的阈值,且在L1中对应的权值为0
                            if abs(coef1-coef2) < self.threshold and j != k and self.coef_[i][k] == 0:
                                idx.append(k)
                        #计算这一类特征的权值系数均值
                        mean = coef / len(idx)
                        self.coef_[i][idx] = mean
            return self
    

    使用feature_selection库的SelectFromModel类结合带L1以及L2惩罚项的逻辑回归模型,来选择特征的代码如下:

    1
    2
    3
    4
    5
    
    from sklearn.feature_selection import SelectFromModel
    
    #带L1和L2惩罚项的逻辑回归作为基模型的特征选择
    #参数threshold为权值系数之差的阈值
    SelectFromModel(LR(threshold=0.5, C=0.1)).fit_transform(iris.data, iris.target)
    
  2. 基于树模型的特征选择法

    树模型中GBDT也可用来作为基模型进行特征选择,使用feature_selection库的SelectFromModel类结合GBDT模型,来选择特征的代码如下:

    1
    2
    3
    4
    5
    
    from sklearn.feature_selection import SelectFromModel
    from sklearn.ensemble import GradientBoostingClassifier
    
    #GBDT作为基模型的特征选择
    SelectFromModel(GradientBoostingClassifier()).fit_transform(iris.data, iris.target)
    

降维

当特征选择完成后,可以直接训练模型了,但是可能由于特征矩阵过大,导致计算量大,训练时间长的问题,因此降低特征矩阵维度也是必不可少的。常见的降维方法除了以上提到的基于L1惩罚项的模型以外,另外还有主成分分析法(PCA)和线性判别分析(LDA),线性判别分析本身也是一个分类模型。PCA和LDA有很多的相似点,其本质是要将原始的样本映射到维度更低的样本空间中,但是PCA和LDA的映射目标不一样:PCA是为了让映射后的样本具有最大的发散性;而LDA是为了让映射后的样本有最好的分类性能。所以说PCA是一种无监督的降维方法,而LDA是一种有监督的降维方法。

主成分分析法(PCA)

使用decomposition库的PCA类选择特征的代码如下:

1
2
3
4
5
from sklearn.decomposition import PCA

#主成分分析法,返回降维后的数据
#参数n_components为主成分数目
PCA(n_components=2).fit_transform(iris.data)

线性判别分析法(LDA)

使用lda库的LDA类选择特征的代码如下:

1
2
3
4
5
from sklearn.lda import LDA

#线性判别分析法,返回降维后的数据
#参数n_components为降维后的维数
LDA(n_components=2).fit_transform(iris.data, iris.target)

什么是特征选择,为什么要进行特征选择,以及如何进行?

特征选择是通过选择旧属性的子集得到新属性,是一种维规约方式。

Why:

应用方面:提升准确率,特征选择能够删除冗余不相关的特征并降低噪声,避免维灾难。在许多数据挖掘算法中,维度较低,效果更好;

执行方面:维度越少,运行效率越高,同时内存需求越少。

How:

  1. 过滤方法,独立于算法,在算法运行前进行特征选择。如可以选择属性的集合,集合内属性对之间的相关度尽可能低。常用对特征重要性(方差,互信息,相关系数,卡方检验)排序选择;可结合别的算法(随机森林,GBDT等)进行特征重要性提取,过滤之后再应用于当前算法。
  2. 包装方法,算法作为黑盒,在确定模型和评价准则之后,对特征空间的不同子集做交叉验证,进而搜索最佳特征子集。深度学习具有自动化包装学习的特性。 总之,特征子集选择是搜索所有可能的特性子集的过程,可以使用不同的搜索策略,但是搜索策略的效率要求比较高,并且应当找到最优或近似最优的特征子集。
  3. 嵌入方法,算法本身决定使用哪些属性和忽略哪些属性。即特征选择与训练过程融为一体,比如L1正则、决策树等;

参考: https://blog.csdn.net/Dream_angel_Z/article/details/49388733