人工智能的知识体系–特征工程(feature engineering)

【声明】本文为AdamsLee原创,转载请注明出自围炉网并保留本文有效链接:人工智能的知识体系–特征工程(feature engineering), 转载请保留本声明!
  • 特征 Feature 指的是机器学习模型(算法、模型函数)的输入数据。特征工程的目的就是获取更好的训练数据

  • 特征提取是指将机器学习算法不能识别的原始数据转化为算法可以识别的特征的过程。来自自然界的特征本身可能并不是数学形式,或者原始值虽是数值但特征值之间没有任何偏序关系。特征提取这个过程类似于编码的过程。

  • 特征处理也可以称作特征预处理,处理的手段有很多,比如 scaling, centering, normalization, binarization, imputation。

  • 特征选择是指去掉无关特征,保留相关特征的过程,也可以认为是从所有的特征中选择一个最好的特征子集。特征选择本质上可以认为是降维的过程。

  • 不管是图像像素、声音比特、汉语文字,还是表格数据,最后要交给机器学习算法来训练的“特征”总是一个矩阵,叫特征矩阵。其每一行是数据集的一条数据,叫作特征向量。其每一列是众多特征中的一个特征。

  • one-hot encoding

    • 特征并不总是连续值,有可能是分类值。比如:["male", "female"];["from Europe", "from US", "from Asia"];["uses Firefox", "uses Chrome", "uses Safari", "uses Internet Explorer"]如果将上述特征用数字表示,效率会高很多。例如:["male", "from US", "uses Internet Explorer"] 表示为[0, 1, 3]。但是,转化为数字表示后,上述数据不能直接用在我们的分类器中。因为,分类器往往默认数据数据是连续的,并且是有序的。但按上述表示的数字并不有序的,而是随机分配的。

    • 使用N位状态寄存器来对N个状态进行编码,每个状态都有它独立的寄存器位,并且在任意时候,其中只有一位有效。

    • 在sklearn中对应的方法是preprocessing.OneHotEncoder

  • 特征提取 Feature Extraction

    • Feature hashing 特征哈希法的目标是把原始的高维特征向量压缩成较低维特征向量,且尽量不损失元素特征值的表达能力。

      • 相比较与one-hot encoding,不需要预先维护一个变量表;

      • 通过哈希转换后学习的模型变得很难检验,我们很难对训练出的模型参数做出合理解释;

from sklearn.feature_extraction import FeatureHasher
h = FeatureHasher(n_features=10,input_type='dict')
D = [{'dog': 1, 'cat':2, 'elephant':4},{'dog': 2, 'run': 5}]
f = h.transform(D)
f.toarray()
>>>array([[ 0.,  0., -4., -1.,  0.,  0.,  0.,  0.,  0.,  2.],
[ 0.,  0.,  0., -2., -5.,  0.,  0.,  0.,  0.,  0.]])
    • The Bag of Words 词袋模型

      • 要了解一段文本的主要内容,最行之有效的策略是抓取文本中的关键词,根据关键词出现的频率确定这段文本的中心思想。比如:如果一则新闻中经常出现「iraq」、「terrorists」,那么,我们可以认为这则新闻应该跟伊拉克的恐怖主义有关。而如果一则新闻中出现较多的关键词是「soviet」、「cuba」,我们又可以猜测这则新闻是关于冷战的

      • 基于文本的BoW模型的一个简单例子,文档1:John likes to watch movies. Mary likes too. 文档2:John also likes to watch football games. 基于上述两个文档中出现的单词,构建如下一个词典 (dictionary):Vocabulary=  {"John": 1, "likes": 2,"to": 3, "watch": 4, "movies": 5,"also": 6, "football": 7, "games": 8,"Mary": 9, "too": 10} 每个文本我们可以使用一个10维的向量来表示。(用整数数字0~n(n为正整数)表示某个单词在文档中出现的次数)文档1:   [1, 2, 1, 1, 1, 0, 0, 0, 1, 1] 该向量与原来文本中单词出现的顺序没有关系,而是词典中每个单词在文本中出现的频率。

    • Feature Embedding

      • 在机器学习领域,embedding指将数据转换为合适的特征表示,合适是指样本特定的属性可以用距离的概念表示出来。通过embeding可以将原始样本数据转换为适合机器学习的特征数据。

      • embedding在NLP领域出现更多些。我们就拿NLP领域的一个神器word2vec举个例子,word2vec可以把单词转换成一个很神奇的特征空间中的点(向量): 假设Φ(X)是X对应的embedding features, 那么Φ(Germany) – Φ(Berlin) ≈ Φ(France) – Φ(Paris)! 首都这个概念也能用embedding features表示!

  • 特征处理 Feature Processing

    • 特征缩放 Feature scaling ("标准化"和"归一化"长期被混用)

      • 进行特征缩放的原因:

        • 在现实生活中,一个目标变量(y)可以认为是由多个特征变量(x)影响和控制的,那么这些特征变量的量纲和数值的量级就会不一样,比如x1 = 10000,x2 = 1,x3 = 0.5 可以很明显的看出特征x1和x2、x3存在量纲的差距;x1对目标变量的影响程度将会比x2、x3对目标变量的影响程度要大(可以这样认为目标变量由x1掌控,x2,x3影响较小,一旦x1的值出现问题,将直接的影响到目标变量的预测,把目标变量的预测值由x1独揽大权,会存在高风险的预测)而通过特征处理,可以使得不同的特征变量具有相同的尺度(也就是说将特征的值控制在某个范围内),这样目标变量就可以由多个相同尺寸的特征变量进行控制,这样,在使用梯度下降法学习参数的时候,不同特征对参数的影响程度就一样了。

        • 在训练神经网络的过程中,通过将数据标准化,能够加速权重参数的收敛。

      • Rescaling(min-max normalization) 有时简称归一化将数值范围缩放到(0,1),但没有改变数据分布

        • 在sklearn中对应的方法是preprocessing.MinMaxScaler

      • Mean normalization

      • 标准化 Standardization (Z-score Normalization) (将数据变换为均值为0,标准差为1的分布)

        • z-score可以回答这样一个问题:"一个给定分数距离平均数多少个标准差?"在平均数之上的分数会得到一个正的标准分数,在平均数之下的分数会得到一个负的标准分数。

        • 在sklearn中对应的方法是preprocessing.scale

      • Scaling to unit length

    • 什么时候标准化,什么时候归一化?

      • 如果你对处理后的数据范围有严格要求,那肯定是归一化,

      • 个人经验,标准化是ML中更通用的手段,如果你无从下手,可以直接使用标准化;

      • 如果数据不为稳定,存在极端的最大最小值,不要用归一化。

      • 在分类、聚类算法中,需要使用距离来度量相似性的时候、或者使用PCA技术进行降维的时候,标准化表现更好;

      • 在不涉及距离度量、协方差计算的时候,可以使用归一化方法。

    • 二值化 Binarization

      • 定量特征二值化的核心在于设定一个阈值,大于阈值的赋值为1,小于等于阈值的赋值为0.

      • 在sklearn中对应的方法是preprocessing.Binarizer

    • 缺失值计算 Imputation

      • 也称作是 Matrix Completion。 来自真实世界经常是不完整的,有些特征在某一行可能为空。“空”本身是不能被模型所理解的,我们需要做一定的推测,给这些缺失位置赋予一个估计的数值,比如一个平均值或中位数值。

      • XGBoost会通过loss的计算过程自适应地判断特征值缺失的样本被划分为左子树还是右子树更优。

      • Missing value layer 美团机器学习实践中提到一种方法,用一个网络层来学习缺失值的权重。通过这一层,缺失的特征根据对应特征的分布来自适应的学习出一个合理的取值。

      • 在sklearn中对应的方法是preprocessing.Imputer

    • 对定性特征/离线特征编码

      • 我们的算法不能把离散型特征作为输入,需要先把离散型特征编码成为连续型特征。 OneHotEncoder是最常用的一种编码方式。

      • 一种方法叫 Label Encoder: 将文本形式的类别特征转换为数值型。相当于给文本形式的类别编号(数字编号1,2,3,4…)。 这个方法的缺点是编号之后,默认可能会误认为这些类别之间具有偏序关系(其实并没有)。

from sklearn.preprocessing import LabelEncoder
labelencoder = LabelEncoder()
x[:, 0] = labelencoder.fit_transform(x[:, 0])
      • 另一种方法叫 One Hot Encoder:为了不加入类别编号的偏序关系,对每一个类别都加一列,是这个类别则为1,非这个类别则为0.

from sklearn.preprocessing import OneHotEncoder
onehotencoder = OneHotEncoder(categorical_features = [0])
x = onehotencoder.fit_transform(x).toarray()
    • 特征离散化

      • 将连续性特征转换为离散特征、或者二值特征。 有些特征虽然也是数值型的,但是该特征的取值相加相减是没有实际意义的,那么该数值型特征也要看成离散特征,采用离散化的技术。

      • 在sklearn中对应的方法是preprocessing.KBinsDiscretizer

    • 多项式特征扩展

      • PolynomialFeatures 以多项式运算的方式将2个以上的特征以多项式形式组合,产生新的扩展特征。

  • 特征选择 Feature Selection & 降维 Dimension Reduction

    • 降维本质上是特征的处理或抽取,从一个维度空间映射到另一个维度空间,特征的多少并没有减少,在映射的过程中特征值也会相应的变化。

    • 为什么会有维度灾难(curse of dimensionality)?

      • 维度灾难表现为“给定数量的训练样本,其预测性能随着维度的增加而减少”。换个说法,“要达到同样的预测性能,高维数据比低维数据所要求的训练样本数量要大得多,一般呈几何级数增长”。

      • 维度灾难涉及到三方:训练样本数量,预测性能,维度。在三方中任何一方给定,则其他两方的变化是相互影响的,需要权衡折中。

      • 特征维度越高,所需要的样本点数据越多。当数据量一定,要达到预期性能,为什么维度增加会导致样本不足?

        • 从统计学角度理解,机器学习本质上逆概问题,即假定数据符合某个模型,用训练样本求解模型最优参数。

        • 在统计学习中,这叫做参数估计,即从总体中抽取的随机样本来估计总体分布中未知参数的过程。

        • 参数估计,对于样本要求是足够随机以及数量足够大。

        • 可以想象,随着维度增加,特征空间呈几何式增长,给定的样本数量必然不能够满足随机和数量足够大,而且会越来越不足,表现为样本在特征空间中越来越稀疏。

      • 当数据量一定,为什么维度增加,模型会表现为过拟合?

        • 离散化增加特征维度同时会引入非线性信息。非线性信息固然好,能够增加拟合能力,但这并不是完全正相关的。根据奥卡姆剃刀原则,一个模型在满足预测性能条件下尽量简单,才能够有比较好的泛化能力。

    • 怎么避免维度灾难?

      • 特征工程是为了弥补特征不足以表达数据的所有信息而做的特征的交叉和组合,其目标是为了得到更多的特征。而特征太多,又会造成维度灾难。如何避免维度灾难,很遗憾没有固定的规则来指定应该使用多少特征,只能有一些经验方式

      • 不同算法维度权衡

        • 对于复杂模型,维度不要太高;简单模型,维度可以较高。本质上考虑“是否需要增加维度,提供非线性”。

        • 非线性决策边界的分类器(例如神经网络、KNN分类器、决策树等)分类效果好但是泛化能力差且容易发生过拟合。因此,维度不能太高。

        • 使用泛化能力好的分类器(例如贝叶斯分类器、线性分类器),可以使用更多的特征,因为分类器模型并不复杂。

      • 特征选择

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

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

          • 特征与目标的相关性:这点比较显见,与目标相关性高的特征,应当优选选择。

        • 使用sklearn中的IRIS(鸢尾花)数据集来对特征处理功能进行说明。IRIS数据集由Fisher在1936年整理,包含4个特征(Sepal.Length(花萼长度)、Sepal.Width(花萼宽度)、Petal.Length(花瓣长度)、Petal.Width(花瓣宽度)),特征值都为正浮点数,单位为厘米。目标值为鸢尾花的分类(Iris Setosa(山鸢尾)、Iris Versicolour(杂色鸢尾),Iris Virginica(维吉尼亚鸢尾))。导入IRIS数据集的代码如下:

from sklearn.datasets import load_iris
#导入IRIS数据集
iris = load_iris()
#特征矩阵
iris.data
#目标向量
iris.target
        • 根据特征选择形式可以分为三大类:

          • Filter:过滤法,按照发散性或者相关性对各个特征进行评分,设定阈值或者待选择阈值的个数,选择特征。

            • 方差选择法

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

from sklearn.feature_selection import VarianceThreshold
#方差选择法,返回值为特征选择后的数据
#参数threshold为方差的阈值
VarianceThreshold(threshold=3).fit_transform(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的样本频数的观察值与期望的差距,构建统计量,这个统计量的含义简而言之就是自变量对因变量的相关性。用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)
            • 互信息法  

              • 经典的互信息也是评价定性自变量对定性因变量的相关性的,为了处理定量数据,最大信息系数法被提出,使用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

            • 递归消除特征法使用一个基模型来进行多轮训练,每轮训练后,消除若干权值系数的特征,再基于新的特征集进行下一轮训练。使用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

          • 基于惩罚项的特征选择法

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

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中的权值,故需要构建一个新的逻辑回归模型:

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惩罚项的逻辑回归模型,来选择特征的代码如下:

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

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

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类选择特征的代码如下:

from sklearn.decomposition import PCA

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

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

from sklearn.lda import LDA

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

此条目发表在未分类分类目录,贴了标签。将固定链接加入收藏夹。