特征工程系列:特征预处理(下)
本文为数据茶水间群友原创,经授权在本公众号发表。
关于作者:JunLiang,一个热爱挖掘的数据从业者,勤学好问、动手达人,期待与大家一起交流探讨机器学习相关内容~
0x00 前言
数据预处理包含数据探索、数据清洗和特征预处理三部分,《特征工程系列:特征预处理(上)》介绍了无量纲化和特征分桶相关的处理方法,本章将继续介绍特征预处理中的统计变换和类别特征编码相关内容。
0x01 统计变换
数据分布的倾斜有很多负面的影响。我们可以使用特征工程技巧,利用统计或数学变换来减轻数据分布倾斜的影响。使原本密集的区间的值尽可能的分散,原本分散的区间的值尽量的聚合。
这些变换函数都属于幂变换函数簇,通常用来创建单调的数据变换。它们的主要作用在于它能帮助稳定方差,始终保持分布接近于正态分布并使得数据与分布的平均值无关。
1.Log变换
1)定义
Log变换通常用来创建单调的数据变换。它的主要作用在于帮助稳定方差,始终保持分布接近于正态分布并使得数据与分布的平均值无关。
Log 变换属于幂变换函数簇。该函数用数学表达式表示为
自然对数使用 b=e,e=2.71828,通常叫作欧拉常数。你可以使用通常在十进制系统中使用的 b=10 作为底数。
当应用于倾斜分布时 Log 变换是很有用的,因为Log变换倾向于拉伸那些落在较低的幅度范围内自变量值的范围,倾向于压缩或减少更高幅度范围内的自变量值的范围。从而使得倾斜分布尽可能的接近正态分布。
2)作用
针对一些数值连续特征的方差不稳定,特征值重尾分布我们需要采用Log化来调整整个数据分布的方差,属于方差稳定型数据转换。比如在词频统计中,有些介词的出现数量远远高于其他词,这种词频分布的特征就会现有些词频特征值极不协调的状况,拉大了整个数据分布的方差。这个时候,可以考虑Log化。尤其在分本分析领域,时间序列分析领域,Log化非常常见, 其目标是让方差稳定,把目标关注在其波动之上。
3)变换效果
4)实现代码
fcc_survey_df['Income_log'] = np.log((1+fcc_survey_df['Income']))
2.Box-Cox变换
1)定义
Box-Cox 变换是另一个流行的幂变换函数簇中的一个函数。该函数有一个前提条件,即数值型值必须先变换为正数(与 log 变换所要求的一样)。万一出现数值是负的,使用一个常数对数值进行偏移是有帮助的。
Box-Cox 变换函数:
生成的变换后的输出y是输入 x 和变换参数的函数;当 λ=0 时,该变换就是自然对数 log 变换,前面我们已经提到过了。λ 的最佳取值通常由最大似然或最大对数似然确定。
2)作用
Box-Cox变换是Box和Cox在1964年提出的一种广义幂变换方法,是统计建模中常用的一种数据变换,用于连续的响应变量不满足正态分布的情况。Box-Cox变换之后,可以一定程度上减小不可观测的误差和预测变量的相关性。Box-Cox变换的主要特点是引入一个参数,通过数据本身估计该参数进而确定应采取的数据变换形式,Box-Cox变换可以明显地改善数据的正态性、对称性和方差相等性,对许多实际数据都是行之有效的。
3)变化效果
4)实现代码
import scipy.stats as spstats
# 从数据分布中移除非零值
income = np.array(fcc_survey_df['Income'])
income_clean = income[~np.isnan(income)]
# 计算最佳λ值
l, opt_lambda = spstats.boxcox(income_clean)
print('Optimal lambda value:', opt_lambda)
# 进行Box-Cox变换
fcc_survey_df['Income_boxcox_lambda_opt'] = spstats.boxcox(fcc_survey_df['Income'],lmbda=opt_lambda)
0x02 分类特征(类别特征)编码
在统计学中,分类特征是可以采用有限且通常固定数量的可能值之一的变量,基于某些定性属性将每个个体或其他观察单元分配给特定组或名义类别。
1.标签编码(LabelEncode)
1)定义
LabelEncoder是对不连续的数字或者文本进行编号,编码值介于0和n_classes-1之间的标签。
2)优缺点
优点:相对于OneHot编码,LabelEncoder编码占用内存空间小,并且支持文本特征编码。
缺点:它隐含了一个假设:不同的类别之间,存在一种顺序关系。在具体的代码实现里,LabelEncoder会对定性特征列中的所有独特数据进行一次排序,从而得出从原始输入到整数的映射。所以目前还没有发现标签编码的广泛使用,一般在树模型中可以使用。
例如:比如有[dog,cat,dog,mouse,cat],我们把其转换为[1,2,1,3,2]。这里就产生了一个奇怪的现象:dog和mouse的平均值是cat。
3)实现代码
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
le.fit(["paris", "paris", "tokyo", "amsterdam"])
print('特征:{}'.format(list(le.classes_)))
# 输出 特征:['amsterdam', 'paris', 'tokyo']
print('转换标签值:{}'.format(le.transform(["tokyo", "tokyo", "paris"])))
# 输出 转换标签值:array([2, 2, 1]...)
print('特征标签值反转:{}'.format(list(le.inverse_transform([2, 2, 1]))))
# 输出 特征标签值反转:['tokyo', 'tokyo', 'paris']
2.独热编码(OneHotEncode)
1)定义
OneHotEncoder用于将表示分类的数据扩维。最简单的理解就是与位图类似,设置一个个数与类型数量相同的全0数组,每一位对应一个类型,如该位为1,该数字表示该类型。
OneHotEncode只能对数值型变量二值化,无法直接对字符串型的类别变量编码。
2)为什么要使用独热编码
独热编码是因为大部分算法是基于向量空间中的度量来进行计算的,为了使非偏序关系的变量取值不具有偏序性,并且到圆点是等距的。使用one-hot编码,将离散特征的取值扩展到了欧式空间,离散特征的某个取值就对应欧式空间的某个点。将离散型特征使用one-hot编码,会让特征之间的距离计算更加合理。
为什么特征向量要映射到欧式空间?
将离散特征通过one-hot编码映射到欧式空间,是因为,在回归、分类、聚类等机器学习算法中,特征之间距离的计算或相似度的计算是非常重要的,而我们常用的距离或相似度的计算都是在欧式空间的相似度计算。
3)例子
假如有三种颜色特征:红、黄、蓝。
在利用机器学习的算法时一般需要进行向量化或者数字化。那么你可能想令 红=1,黄=2,蓝=3。那么这样其实实现了标签编码,即给不同类别以标签。然而这意味着机器可能会学习到“红<黄<蓝”,但这并不是我们的让机器学习的本意,只是想让机器区分它们,并无大小比较之意。
所以这时标签编码是不够的,需要进一步转换。因为有三种颜色状态,所以就有3个比特。即红色:1 0 0 ,黄色: 0 1 0,蓝色:0 0 1 。如此一来每两个向量之间的距离都是根号2,在向量空间距离都相等,所以这样不会出现偏序性,基本不会影响基于向量空间度量算法的效果。
4)优缺点
优点:独热编码解决了分类器不好处理属性数据的问题,在一定程度上也起到了扩充特征的作用。它的值只有0和1,不同的类型存储在垂直的空间。
缺点:当类别的数量很多时,特征空间会变得非常大。在这种情况下,一般可以用PCA来减少维度。而且one hot encoding+PCA这种组合在实际中也非常有用。
5)实现代码
使用sklearn实现
注:当特征是字符串类型时,需要先用 LabelEncoder() 转换成连续的数值型变量,再用 OneHotEncoder() 二值化 。
from sklearn.preprocessing import OneHotEncoder
enc = OneHotEncoder()
enc.fit([[0, 0, 3], [1, 1, 0], [0, 2, 1], [1, 0, 2]]) # fit来学习编码
enc.transform([[0, 1, 3]]).toarray() # 进行编码
# 输出:array([[ 1., 0., 0., 1., 0., 0., 0., 0., 1.]])
使用pandas实现
import pandas as pd
import numpy as np
sex_list = ['MALE', 'FEMALE', np.NaN, 'FEMALE', ]
df = pd.DataFrame({'SEX': sex_list})
display(df)
# 输出
SEX
0 MALE
1 FEMALE
2 NaN
3 FEMALE
df = pd.get_dummies(df['SEX'],prefix='IS_SEX')
display(df)
# 输出
IS_SEX_FEMALE IS_SEX_MALE
0 0 1
1 1 0
2 0 0
3 1 0
3.标签二值化(LabelBinarizer)
1)定义
功能与OneHotEncoder一样,但是OneHotEncode只能对数值型变量二值化,无法直接对字符串型的类别变量编码,而LabelBinarizer可以直接对字符型变量二值化。
2)实现代码
from sklearn.preprocessing import LabelBinarizer
lb = LabelBinarizer()
lb.fit([1, 2, 6, 4, 2])
print(lb.classes_)
# 输出 array([1, 2, 4, 6])
print(lb.transform([1, 6]))
# 输出 array([[1, 0, 0, 0],
[0, 0, 0, 1]])
print(lb.fit_transform(['yes', 'no', 'no', 'yes']))
# 输出 array([[1],
[0],
[0],
[1]])
4.多标签二值化(MultiLabelBinarizer)
1)定义
用于label encoding,生成一个(n_examples * n_classes)大小的0~1矩阵,每个样本可能对应多个label。
2)适用情况
-
每个特征中有多个文本单词;
用户兴趣特征(如特征值:”健身 电影 音乐”)适合使用多标签二值化,因为每个用户可以同时存在多种兴趣爱好。
-
多分类类别值编码的情况。
电影分类标签中(如:[action, horror]和[romance, commedy])需要先进行多标签二值化,然后使用二值化后的值作为训练数据的标签值。
3)实现代码
from sklearn.preprocessing import MultiLabelBinarizer
mlb = MultiLabelBinarizer()
print(mlb.fit_transform([(1, 2), (3,)]))
# 输出
array([[1, 1, 0],
[0, 0, 1]])
print(mlb.classes_)
# 输出:array([1, 2, 3])
print(mlb.fit_transform([{'sci-fi', 'thriller'}, {'comedy'}]))
# 输出:array([[0, 1, 1],
[1, 0, 0]])
print(list(mlb.classes_))
# 输出:['comedy', 'sci-fi', 'thriller']
5.平均数编码(Mean Encoding)
1)定义
平均数编码(mean encoding),针对高基数类别特征的有监督编码。当一个类别特征列包括了极多不同类别时(如家庭地址,动辄上万)时,可以采用。
平均数编码(mean encoding)的编码方法,在贝叶斯的架构下,利用所要预测的应变量(target variable),有监督地确定最适合这个定性特征的编码方式。在Kaggle的数据竞赛中,这也是一种常见的提高分数的手段。
算法原理详情可参考:平均数编码:针对高基数定性特征(类别特征)的数据预处理/特征工程。
2)为什么要用平均数编码
如果某一个特征是定性的(categorical),而这个特征的可能值非常多(高基数),那么平均数编码(mean encoding)是一种高效的编码方式。在实际应用中,这类特征工程能极大提升模型的性能。
因为定性特征表示某个数据属于一个特定的类别,所以在数值上,定性特征值通常是从0到n的离散整数。例子:花瓣的颜色(红、黄、蓝)、性别(男、女)、地址、某一列特征是否存在缺失值(这种NA 指示列常常会提供有效的额外信息)。
一般情况下,针对定性特征,我们只需要使用sklearn的OneHotEncoder或LabelEncoder进行编码,这类简单的预处理能够满足大多数数据挖掘算法的需求。定性特征的基数(cardinality)指的是这个定性特征所有可能的不同值的数量。在高基数(high cardinality)的定性特征面前,这些数据预处理的方法往往得不到令人满意的结果。
3)优点
和独热编码相比,节省内存、减少算法计算时间、有效增强模型表现。
4)实现代码
MeanEnocodeFeature = ['item_city_id','item_brand_id'] #声明需要平均数编码的特征
ME = MeanEncoder(MeanEnocodeFeature) #声明平均数编码的类
trans_train = ME.fit_transform(X,y)#对训练数据集的X和y进行拟合
test_trans = ME.transform(X_test)#对测试集进行编码
MeanEncoder实现源码详情可参考:平均数编码:针对高基数定性特征(类别特征)的数据预处理/特征工程。
0x0FF 总结
-
特征预处理是数据预处理过程的重要步骤,是对数据的一个的标准的处理,几乎所有的数据处理过程都会涉及该步骤。
-
由于树模型(Random Forest、GBDT、xgboost等)对特征数值幅度不敏感,可以不进行无量纲化和统计变换处理;
同时,由于树模型依赖于样本距离来进行学习,所以也可以不进行类别特征编码(但字符型特征不能直接作为输入,所以需要至少要进行标签编码)。
-
依赖样本距离来学习的模型(如线性回归、SVM、深度学习等)
-
对于数值型特征需要进行无量纲化处理;
-
对于一些长尾分布的数据特征,可以做统计变换,使得模型能更好优化;
-
对于线性模型,特征分箱可以提升模型表达能力;
对数值型特征进行特征分箱可以让模型对异常数据有很强的鲁棒性,模型也会更稳定。
另外,分箱后需要进行特征编码。
如有错误欢迎指正~
参考文献
[1] sklearn中的数据预处理. http://d0evi1.com/sklearn/preprocessing/
[2] 归一化与标准化. https://ssjcoding.github.io/2019/03/27/normalization-and-standardization/
[3] Preprocessing Data : 類別型特徵_OneHotEncoder & LabelEncoder 介紹與實作. https://medium.com/ai%E5%8F%8D%E6%96%97%E5%9F%8E/preprocessing-data-onehotencoder-labelencoder-%E5%AF%A6%E4%BD%9C-968936124d59
[4] 平均数编码:针对高基数定性特征(类别特征)的数据预处理/特征工程. https://zhuanlan.zhihu.com/p/26308272
[5] 特征工程之分箱. https://blog.csdn.net/Pylady/article/details/78882220
[6] https://www.leiphone.com/news/201801/T9JlyTOAMxFZvWly.html
来源:oschina
链接:https://my.oschina.net/u/4357815/blog/3432190