1.0 what is 特征工程?
关于特征工程比较官方的定义为:特征工程是利用数据领域的相关知识来创建能够使机器学习算法达到最佳性能的特征的过程。
下面讲讲个人理解:“数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。” 这句话在机器学习领域内流传非常广泛。实施上,对于所有基于信息的分析类工作,数据本身决定着分析的结果。前些年大谈特谈的“数据质量”、“Garbage in, garbage out”也是在强调相同的事情。当数据量级较小的时候,强调数据的质量即可。而机器学习通常分析量级较大的数据集,此时需要对数据进行清洗、加工、转换、合并等“处理”,经过处理的数据就是“特征”。因此,特征工程就是一套完整的数据预处理流程、技术和方法等,并且处理后的特征要能保证或者改进原数据的质量。
特征工程的内容包含了Data PreProcessing(数据预处理)、Feature Extraction(特征提取)、Feature Selection(特征选择)和Feature construction(特征构造)等内容。
这一节内容主要讲述数据预处理。特征预处理主要包括无量纲化、特征分桶、统计变换和特征编码四个部分,下文分别阐述。
2.0 无量纲化
数据无量纲化与系列(四)中的归一化本质是相同的,包含标准化、归一化、正态分布化三种方式。无量纲化的优点包括满足模型的数据要求、提高建模精度和训练效率等。
无量纲化方法的基本思想都是一样的,就是通过除以数据的某个特征值(均值或标准差)约掉单位并对数据缩放到某个范围内;有的无量纲化方法还会减去一个特征值,进而对数据平移。
2.1 标准化
标准化的前提是标准化后使特征值服从正态分布,。
优点:Z-Score最大的优点就是简单,容易计算;能够应用于数值型的数据,并且不受数据量级的影响。
缺点:计算Z-Score特征需要总体的均值与标准差,但是实际情况下是用样本的均值与标准差替代(需要假设检验);Z-Score对于数据的分布有一定的要求,正态分布是最有利于Z-Score计算的;Z-Score消除了数据具有的实际意义,Z-Score的结果只能用于比较数据间的结果,数据的真实意义还需要还原原值;在存在异常值时无法保证平衡的特征尺度。
sklearn中的标准化:
from sklearn.preprocessing import StandardScaler
standardScaler = StandardScaler().fit(X_train)
standardScaler.transform(X_train)
2.2 归一化
(1)最小最大值归一化minmax
利用了边界值(最大值和最小值)信息, 将数据区间缩放到[0,1],。
sklearn中的最小最大值归一化:
from sklearn.preprocessing import MinMaxScaler
minMaxScaler = MinMaxScaler().fit(X_train)
minMaxScaler.transform(X_train)
(2)最大绝对值归一化absmax
利用数据集中的最大值,缩放每个数据到[-1,1]。由于没有减项,它不会移动/居中数据,因此不会破坏任何稀疏性,。
sklearn
from sklearn.preprocessing import MaxAbsScaler
maxAbsScaler = MaxAbsScaler().fit(X_train)
maxAbsScaler.transform(X_train)
缺点:两种归一化的方法应对新数据时都需要可能导致max和min的变化,需要重新定义;对异常值比较敏感。
2.3 正态分布化
正态化的过程是将每个样本缩放到单位范数(每个样本的范数为1)。该方法是文本分类和聚类分析中经常使用的向量空间模型(Vector Space Model)的基础,。范数的本质其实就是点x距离空间原点的距离,L1范数就是曼哈顿距离,L2范数就是欧式距离,以L2范数为例:。
sklearn中的正态分布化:
from sklearn.preprocessing import Normalizer
normalizer = Normalizer(norm='l2').fit(X_train)
normalizer.transform(X_train)
2.4 标准化与归一化的比较
相同点:都能取消由于量纲不同引起的误差;都是一种线性变换,都是对原数据按照比例压缩(除以某一特征值)再进行平移(减去某一特征值,maxabs除外)。
不同点:目的不同,归一化是为了消除纲量压缩到[0,1]区间,标准化调整特征整体的分布;关系值不同:归一化与最大,最小值有关,标准化与均值,标准差有关;输出不同:归一化输出在[0,1]之间,标准化无限制。
2.5 标准化与归一化的适用
如果对输出结果范围有要求,用归一化;如果数据较为稳定,不存在极端的最大最小值,用归一化;如果数据存在异常值和较多噪音,用标准化或robustScaler(四分位数归一化),可以间接通过中心化避免异常值和极端值的影响。
大多数机器学习算法中,会选择StandardScaler来进行特征缩放,因为MinMaxScaler对异常值非常敏感。在PCA,聚类,逻辑回归,支持向量机,神经网络这些算法中,StandardScaler往往是最好的选择。
在不涉及距离度量、梯度、协方差计算以及数据需要被压缩到特定区间时使用MinMaxScaler,比如数字图像处理中量化像素强度。在希望压缩数据,却不影响数据的稀疏性时(不影响矩阵中取值为0的个数时),我们会使用MaxAbsScaler。建议先试试看StandardScaler,效果不好再换其他scaler。
3.0 离散化(特征分箱)
离散化是数值型特征非常重要的一个处理,其实就是要将数值型数据转化成类别型数据。连续值的取值空间可能是无穷的,为了便于表示和在模型中处理,需要对连续值特征进行离散化处理。
离散化的优点:离散特征的增加和减少都很容易,易于模型的快速迭代;稀疏向量内积乘法运算速度快,计算结果方便存储,容易扩展;离散化后的特征对异常数据有很强的鲁棒性:比如一个成年男子的特征是体重为>80kg是1,否则0,如果特征没有离散化,一个超重的异常数据会给模型造成很大的干扰;提高模型的表达能力:如单变量离散化为N个后,每个变量有单独的权重,相当于模型引入了非线性,能够提升模型表达能力,加大拟合;离散化后可以进行特征交叉,由M+N个变量变为M*N个变量,进一步引入非线性,提升表达能力;特征离散化后,模型会更稳定:比如如果对用户年龄离散化,20-30作为一个区间,不会因为一个用户年龄长了一岁就变成一个完全不同的人,当然处于区间相邻处的样本会刚好相反,所以怎么划分区间是门学问;特征离散化以后,起到了简化了逻辑回归模型的作用,降低了模型过拟合的风险;可以将缺失作为独立的一类带入模型;将所有变量变换到相似的尺度上。(参考:https://mp.weixin.qq.com/s/qWO9zgKyntvyWfftpGqrHQ)
3.1 无监督分箱法
(1)自定义分箱
自定义分箱,是指根据业务经验或者常识等自行设定划分的区间,然后将原始数据归类到各个区间中。最有价值同时也是风险最高的分箱方法,分箱准确性完全取决于人员的业务经验和理解。
(2)等距分箱
按照相等的区间宽度将数据分成几等份,每个等份里面的实例数量可能不等。
pandas等距分箱:
import pandas as pd
result = pd.cut(data, n) # 将数据data平均分成n等份
(3)等频分箱
根据数据的数量平均分成几等份,每等份数据里面的个数(基本)一样。
pandas等频分箱:
import pandas as pd
result = pd.qcut(data,3) # 将数据data按数量平均分成n份
(4)聚类分箱
就是统计上的聚类分析。常用的方法有k-means,二分k-means。具体算法将在后面专题展开。
(5)二值化
二值化将数值型(numerical)的数据进行阀值化得到boolean型数据。其本质就是设定一个阈值,大于阈值的赋值为1,小于等于阈值的赋值为0。实际中的考试及格判断就符合这种情况。
from sklearn.preprocessing import Binarizer
binarizer = Binarizer(threshold=0.0).fit(X_train)
binarizer.transform(X_train)
3.2 有监督分箱法
常用的有监督分箱法主要有卡方分箱法,这里先提及一下,具体算法将在后面专题展开。
4.0 统计变换
统计分析上对于数据分布较多都会有正态分布的要求,但是并不是所有数据都能满足这一要求,呈现为偏态分布,此时就需要通过统计变换将减弱数据的偏态,使得数据整体上趋于正态分布形式。
统计变换通常使用幂变换函数(单调性较好)对原数据进行处理,变换的主要作用在于它能帮助稳定方差,始终保持分布接近于正态分布并使得数据与分布的平均值无关。
4.1 log变换
使用log函数对数据进行变换处理,。
log 变是处理偏态分布数据的最常用方法之一,因为Log变换倾向于拉伸那些落在较低的幅度范围内自变量值的范围,倾向于压缩或减少更高幅度范围内的自变量值的范围。从而使得倾斜分布尽可能的接近正态分布。
log变换要求所有数据为正数。遇到数据中有负值时,可以先加上一个正数对数据作移动。
log变换的Python实现:
import numpy as np
data_log = np.log(data)
4.2 Box-Cox变换
Box-Cox 变换是另一个常用的幂变换方法,该方法与log变换一样,要求数据值须为正数。该方法需要指定变换参数,根据的取值采取不同的计算方式:
,
,
Box-Cox的python实现:
import scipy.stats as spstats # 导入数据包
l, opt_lambda = spstats.boxcox(data) # 计算最佳lambda,这一步之前要先处理负值和空值
print('Optimal lambda value:', opt_lambda)
data_boxcox = spstats.boxcox(data,lmbda=opt_lambda) # box-cox变换
5.0 分类特征编码
实际数据中,经常能够遇到某些特征是以有限且固定的取值来描述的,就是统计中常说的分类变量或名义变量。而依据变量之间的是否存在大小关系又分为有序分类变量(如大、中、小)和无序分类变量(如红,黄,蓝)。
5.1 标签编码LabelEncoder
LabelEncoder是对不连续的数字或者文本进行编号,编码值介于0和(标签取值的种类数-1)之间的标签。
优点:相对于OneHot编码,LabelEncoder编码占用内存空间小,并且支持文本特征编码。
缺点:它隐含了一个假设:不同的类别之间,存在一种顺序关系。在具体的代码实现里,LabelEncoder会对定性特征列中的所有独特数据进行一次排序,从而得出从原始输入到整数的映射。所以目前还没有发现标签编码的广泛使用,一般在树模型中可以使用。例如:比如有[dog,cat,dog,mouse,cat],我们把其转换为[1,2,1,3,2]。这里就产生了一个奇怪的现象:dog和mouse的平均值是cat。
sklearn中的LabelEncoder:
from sklearn.preprocessing import LabelEncoder
data = ["paris", "paris", "tokyo", "amsterdam"]
le = LabelEncoder() # 实例化
le.fit(data) # 学习编码
list(le.classes_) # 编码类别
le.transform(["tokyo", "tokyo", "paris"]) # 数据编码
list(le.inverse_transform([2, 2, 1]) # 逆转换编码
5.2 独热编码OneHotEncoder
OneHotEncoder用于将表示分类的数据扩维。最简单的理解就是与位图类似,设置一个个数与类型数量相同的全0数组,每一位对应一个类型,如该位为1,该数字表示该类型。
(其实就是统计上的哑变量)
优点:独热编码解决了分类器不好处理属性数据的问题,在一定程度上也起到了扩充特征的作用。它的值只有0和1,不同的类型存储在垂直的空间。
缺点:当类别的数量很多时,特征空间会变得非常大。在这种情况下,一般可以用PCA来减少维度。而且one hot encoding+PCA这种组合在实际中也非常有用。
(为什么要使用独热编码?因为大部分算法是基于向量空间中的度量来进行计算的,为了使非偏序关系的变量取值不具有偏序性,并且到圆点是等距的。使用one-hot编码,将离散特征的取值扩展到了欧式空间,离散特征的某个取值就对应欧式空间的某个点。将离散型特征使用one-hot编码,会让特征之间的距离计算更加合理。 为什么特征向量要映射到欧式空间?将离散特征通过one-hot编码映射到欧式空间,是因为,在回归、分类、聚类等机器学习算法中,特征之间距离的计算或相似度的计算是非常重要的,而我们常用的距离或相似度的计算都是在欧式空间的相似度计算。)
sklearn中的OneHotEncoder:
from sklearn.preprocessing import OneHotEncoder
enc = OneHotEncoder()
enc.fit([[0, 0, 3], [1, 1, 0], [0, 2, 1], [1, 0, 2]]) # 学习编码
enc.transform([[0, 1, 3]]).toarray() # 数据编码
# 输出:array([[ 1., 0., 0., 1., 0., 0., 0., 0., 1.]])
# 每一个数据是个三维向量,第一维有0,1两个取值,第二维有0,1,2三个取值,第三维有0,1,2,3四个取值,因此最后的编码为2+3+4维的向量。
5.3 标签二值化LabelBinarizer
功能与OneHotEncoder一样,但是OneHotEncode只能对数值型变量二值化,无法直接对字符串型的类别变量编码,而LabelBinarizer可以直接对字符型变量二值化。
sklearn的标签二值化:
from sklearn.preprocessing import LabelBinarizer
lb = LabelBinarizer()
lb.fit([1, 2, 6, 4, 2])
lb.classes_ # 标签类别
lb.transform([1, 6]) # 标签二值化转换