特征工程中的常用方法

数据和特征工程决定了模型的上限,改进算法只不过是逼近这个上限。
数据矩阵:X
标签:y

  1. 探索性数据分析
    博客上写了三个 plot ,我想大概是用画图来看看数据的分布情况。

  2. 预处理
    需要将训练集变成矩阵X,大小为[n_samples,n_features]和矢量y,长度:[n_samples]。矢量y可以表示成矩阵Y,大小为[n_samples,n_classes]。
    常用函数:

  • sklearn.preprocessing.LabelBinarizer
preprocessing.LabelBinarizer(neg_label=0,pos_label=1,sparse_output=False)

输入参数:
neg_label = 0:让负标签为0;
pos_label = 1:让正标签为1;
sparse_output = False:如果希望从transform返回的是CSR格式,就写成True。
输出参数:
classes_:有多少个类,类分别是什么;
y_type_:计算的目标的数据类型,有'continuos(连续)',‘continuous-multioutput(连续多输出)’,'multiclass(多目标)','binary’,'multiclass', 'multiclass-multioutput', 'multilabel-indicator(多标签指示符)????'

lb = preprocessing.LabelBinarizer()
lb.fit([1,2,6,4,2])
print(lb.classes_)
print(lb.transform([1,2,6,4,3,5]))
print(lb.y_type_)

结果:

array([1, 2, 4, 6])
array([[1, 0, 0, 0],
       [0, 1, 0, 0],
       [0, 0, 0, 1],
       [0, 0, 1, 0],
       [0, 0, 0, 0],
       [0, 0, 0, 0]])

感觉类似于one-hot编码
-sklearn.preprocessing.MultiLabelBinarizer(多标签二值化)

sklearn.preprocessing.MultiLabelBinarizer(classes=None, sparse_output=False)

输入参数:
classes:设置classes时候,label从classes里面出,如果没有设置,则从训练集里面统计
sparse_out:同上
返回参数:
classes_:同上

mlb = preprocessing.MultiLabelBinarizer()
print(mlb.fit_transform([(1,2),(3,4),(5,)]))
print(mlb.classes_)
mlb1 = preprocessing.MultiLabelBinarizer(classes=[2,3,4,5,6,1])
print(mlb1.fit_transform([(1,2),(3,4),(5,)]))
print(mlb1.classes_)

结果:

array([[1, 1, 0, 0, 0],
       [0, 0, 1, 1, 0],
       [0, 0, 0, 0, 1]])
array([1, 2, 3, 4, 5])
array([[1, 0, 0, 0, 0, 1],
       [0, 1, 1, 0, 0, 0],
       [0, 0, 0, 1, 0, 0]])
array([2, 3, 4, 5, 6, 1])

-sklearn.preprocessing.LabelEncoder
之前看过这个,可以把male,female这些变成0,1的二值,但是存在值的大小关系,可能会造成机器学习的算法过程中一些偏好。

  1. 处理也有的特征
  • 类别特征
    类别特征,表示某个数据点属于某一个类别,或具有某一种类的特性。一列类别特征,默认用自然数表示(可以用LabelEncoder将字符串转化为自然数)。如果一列类别特征里有K种不同类别,其取值范围是{0,1,2...,K-1}

  • 自然数编码
    默认的编码方式,消耗内存小,训练时间快,但是特征的质量不高。
    这个特征质量不高我感觉是因为LabelEncoder的出来的东西是无序的,经常需要再用one-hot编码

  • 独热编码
    如果类别特征的数据本身是无序的,例如male,female里面不存在male的值大于female,以及female大于male的情况,则需要用one-hot编码。
    它会使一组数据变得只保留一个位置上为1,其余都为0的情况,会特别稀疏。

  • 聚类编码
    (百度也没查到,这玩意儿没看懂太,待补)

  • 平均数编码
    博客上说这是针对高基数类别特征的有监督编码。
    如果一个特征是定性的并且这个特征的可能值非常多(高基数),那么平均数编码是一种高效的编码方式。(比如:家庭住址?,email地址)
    LabelEncoder编码高基数定性特征,虽然只会产生一列,但每个自然数都会产生不同的重要意义,对于y而言线性不可分(?????仅仅是因为维度高了所以不可分,还是因为什么?)
    OneHotEncoder编码高基数定义特征,必然会产生上万列的稀疏矩阵,易消耗大量内存和训练时间。
    这时候平均数编码是一种好的选择。在经验贝叶斯的架构下,利用所要预测的因变量,有监督地确定最适合这个定性特征的编码方式。

基本思路与原理:
平均数编码是一种有监督的编码方式,适用于分类和回归问题。
平均数编码的基本的基本思想是:把特征工程中的每一个变量变成它所估算的label的概率。具体可见
先验概率,后验概率的计算感觉上是通过频率来估计概率!
先验概率的计算:

先验概率计算

后验概率的计算:
后验概率计算

后验概率的理解:用条件概率展开,然后使用频数估计概率。(我是这么感觉的)

权重:
平均编码使用的是先验概率和后验概率的一个凸组合,引入先验概率权重\lambda来计算编码时候所使用的概率\hat P

权重

感觉上这个是手动调节的,不知道可不可以弄成自动调节(根据先验概率和后验概率的比值,那一部分值越大,使得它的比值越大)。

权重函数:
定义一个权重函数,输入是特征类别在训练集中出现的次数n,输出是对于这个特征类别的先验概率的权重\lambda

image.png

  • 只出现一次的类别
    在类别特征里,有时候有一些类别,在训练集和测试集中总共只出现一次,这时候,继续保留这个编码的意义不大,不然将所有只出现一次的编码融合成一个新的类别里面。

数值特征

数值特征可以是连续的,也可以是离散的,一般表示为一个实数值。
不同算法对于数值的要求不一样,有些算法是可以不用标准化/归一化的,例如:树模型;但是神经网络这样的模型是需要标准化的。

  • 标准化
  1. Z-score标准化
preprocessing.StandardScaler(copy = True, with_mean = True, with_stand = True)

计算公式是:
\begin{equation} z = \frac{x-\mu}{s} \end{equation}
\mu是训练样本的均值,s是训练样本的标准差。
输入参数:
copy:默认是True,不会覆盖原数据,(特别是处理一些DataFrame文件时候);
with_mean:默认是True,如果是False,会使得\mu为0;
with_std:默认是True,如果是False,会使得s为0。
返回参数:
scale_:每个功能相对缩放数据;
mean_:训练集的每个特征的平均值;
var_:训练集的每个特征的方差;
n_samples_seen:对每个特征通过估算器处理的样本数。(试了下,这个应该是返回的是处理的多少样本数目)

scaler = preprocessing.StandardScaler()
data = [[0,0],[0,0],[1,1],[1,1]]
scaler.fit(data)
print(scaler.mean_)
print(scaler.var_)
print(scaler.n_samples_seen_)
print(scaler.transformer(data))

结果是:

array([0.5, 0.5])
array([0.25, 0.25])
4
array([[-1., -1.],
       [-1., -1.],
       [ 1.,  1.],
       [ 1.,  1.]])
  1. RobustScaler(稳健的放缩??)
    如果数值特征列中存在数值极大或者极小的异常点outlier(通过EDA发现,e.g. 箱形图,ps.我好像也只能想到箱形图),应该使用更稳健的统计数据:用中位数取代均值(因为均值易受离群点的影响),用分位数而不是方差。
prepocessing.RobustScaler(with_center=True, with_scaling=True, quantile_range=(25.0, 75.0), copy=True)

感觉和Z-score的区别就是使用对离群点更鲁棒的统计学参数。
输入参数:
with_center:默认是True,缩放之前让数据居中。这将导致变换在稀疏矩阵上尝试时引发异常,因为对它们进行居中需要构建一个密集矩阵,在常见的情况下,该矩阵可能太大而无法放入内存中。(意思就是说,对稀疏矩阵要选择False?);
with_scaling:默认是True,将数据放缩到4分位数。
quantile_range:默认是(25.0,75.0),1分位数和3分位数,计算范围;
copy:同之前写的一样。
输出参数:
center_:训练集每个属性的中值;
scale_:训练集中每个特征的(缩放)四分位数范围。

from sklearn.preprocessing import RobustScaler
X =  np.array([[1,2],[2,3]])
scaler = RobustScaler()
scaler.fit(X)
print(scaler.transform(X))

结果为:

array([[-1., -1.],
       [ 1.,  1.]])
  • 归一化
    至少存在一个非0成分的每一行(每一个样本)被分别缩放使得它的L2范数等于1。
    这个transformer对稀疏矩阵,密集矩阵都适用
preprocessing.Normalizer(norm='l2', copy = True)

输入参数:
norm:用于标准化每个非0样本的标准;
copy:同上。

from sklearn.preprocessing import Normalizer
X2 = [[4,1,2,2],[1,3,9,3],[5,7,5,1]]
scaler2 = Normalizer(norm='l2')
scaler2.fit(X2)
print(scaler2.transform(X2))

结果是:

array([[0.8, 0.2, 0.4, 0.4],
       [0.1, 0.3, 0.9, 0.3],
       [0.5, 0.7, 0.5, 0.1]])
  • 区间缩放
  1. MaxAbsScaler
    根据最大绝对值放缩每个特征。
    单独的缩放和转换每个特征,使得训练集上的最大绝对值的值变为1,它不会移动/居中数据,因此不会破坏稀疏性。
prepocessing.MaxAbsScarer(copy = True)

输入参数:
copy:同上。
输出参数:
scale_:数据的每个特征的缩放;
max_abs:每个特征的绝对值最大值;
n_samples_seen_:同上。

from sklearn.preprocessing import MaxAbsScaler
X = np.array([[1,-1,2],[2,0,0],[0,1,-1]])
print(X)
scaler = MaxAbsScaler()
scaler.fit(X)
print(scaler.max_abs_)
print(scaler.scale_)
print(scaler.n_samples_seen_)
print(scaler.transform(X))

结果:

[[ 1 -1  2]
 [ 2  0  0]
 [ 0  1 -1]]
[2. 1. 2.]
[2. 1. 2.]
3
[[ 0.5 -1.   1. ]
 [ 1.   0.   0. ]
 [ 0.   1.  -0.5]]
  1. MinMaxScaler
    把每一个特征放缩到一个区域内。
    \begin{equation} \begin{split} X_{std} &= (X-X_{min})/(X_{max}-X_{min})\\ X_{scaled} &= X_{std}*(max-min)+min \end{split} \end{equation}
    其中,min,max 是特征区域,e.g. [0,1]。
preprocessing.MinMaxScaler(featur_range(0,1), copy = True)

输入参数:
feature_range:期望的转换数据范围;
copy:同上。
输出参数:

min_:为了最小值,每个特征调整,
image.png

scale_:数据的每个特征的缩放;
data_min_:每个特征的最小值;
data_max_:每个特征的最大值;
data_range_:每个特征的范围。
image.png
from sklearn.preprocessing import MinMaxScaler
X1 = np.array([[-1,2],[-0.5,6],[0,10],[1,18]])
print(X1)
scaler1 = MinMaxScaler()
scaler1.fit(X1)
print(scaler1.min_)
print(scaler1.scale_)
print(scaler1.data_min_)
print(scaler1.data_max_)
print(scaler1.data_range_)
print(scaler1.transform(X1))

结果:

[ 0.5   -0.125]
[0.5    0.0625]
[-1.  2.]
[ 1. 18.]
[ 2. 16.]
[[0.   0.  ]
 [0.25 0.25]
 [0.5  0.5 ]
 [1.   1.  ]]
  • 缺失值处理
    LightGBM/XGBoost都能将NaN作为数据的一部分进行学习,所以不需要处理缺失值,但是别的方法一般都不行,需要进行填充。
  1. 用平均值,中值,分位数,众数,随机值等替代,效果一般,因为认为增加了噪声;
  2. 先根据欧氏距离或者Pearson相似度,来确定和缺失数据样本最近的K个样本,将这K个样本的属性加权平均来估计该样本的缺失值;
    (感觉上前面部分是推荐系统里面的东西,找出近邻的样本);
  3. 将变量映射到高维空间
    3.a. 对于离散型变量:男,女,缺失三种情况,采用one-hot编码映射成三个变量;
    3.b. 对于连续型变量:首先对连续型变量进行变量分箱,采用一定数据的数据平滑方式(平均值/中值/箱边界)进行离散化,然后增加是否缺失这种维度。
  • 创造新的特征
  1. 数值特征的简单变换
    1.a. 单独特征列乘以一个常数或者加减一个常数:对于创造新的有用特征毫无用处,只能作为对已有特征的处理;
    1.b. 任何针对单独特征列的单调变换,e.g. 把x变成x^{2};不适合于决策树之类的算法
    1.c. 线性组合;仅适用于决策树以及基于决策树的ensemble算法;
    1.d. 多项式特征;
    生成多项式和相互特征。
    生成一个新的特征矩阵,该特征矩阵由特征的所有多项式组合组成,其度数小于或等于指定的度数。也就是说给定[a,b],度数是2,那么新的特征矩阵是[a,b,a^{2},b^{2},ab,1]。(给我一种集合里的生成元的感觉)
prepocessing.PolynomialFeatures(degree=2, interaction_only=False, include_bias=True,Order='C')

输入参数:
degree:默认是2,多项式特征的度;
interaction_only:(没看懂这个);
include_bias:默认是True,会包含一个偏置列,度数是0,也就是会有0这一项;
Order:密集情况下的输出数组的顺序,'F'顺序计算速度更快,但可能会降低后续估算器的速度。
输出参数:
powers_:powers_[i,j] 是第i个输出中第j个输入的指数;
n_input_features_:输入特征的总数;
n_output_features:输出特征的总数。

from sklearn.preprocessing import PolynomialFeatures
import numpy as np
X = np.arange(6).reshape(3,2)
print(X)
poly = PolynomialFeatures()
poly.fit(X)
print(poly.powers_)
print(poly.n_input_features_)
print(poly.n_output_features_)
print(poly.transform(X))

结果是:

[[0 1]
 [2 3]
 [4 5]]
[[0 0]
 [1 0]
 [0 1]
 [2 0]
 [1 1]
 [0 2]]
2
6
[[ 1.  0.  1.  0.  0.  1.]
 [ 1.  2.  3.  4.  6.  9.]
 [ 1.  4.  5. 16. 20. 25.]]

1.e 比例特征:X_{1}/X_{2}
1.f 绝对值;
1.g max(X_{1},X_{2})min(X_{1},X_{2})

  1. 类别特征与数值特征的组合
    N_{1},N_{2}表示数值特征,用C_{1},C_{2}表示类别特征,利用Pandas的groupby操作,可以创造出以下几种有意思的特征:(知乎上说C_{2}可以是离散化的N_{1},这是不是可以说,我们对一个数值特征分成几个类别,然后对它进行操作,重复使用会不会对数据利用的效果更好。)
    image.png

    仅仅将已有的数值特征和类别特征进行以上有效组合,就能够得到更多的有效特征。
pandas.DataFrame.groubpy(by=None, axis=0, level=None, as_index=True, sort=True, group_keys=True, squeeze=False, observed=False, **Kwargs)

使用映射器或者一系列的列对DataFrame或者Series进行分组。groupby操作涉及拆分对象,应用函数和组合结果的某种组合。 这可用于对这些组上的大量数据和计算操作进行分组。
输入参数:
by:用于确定作为groupby操作的group;
axis:对行操作还是对列操作,axis=0/1;
level:如果轴是MultiIndex(分层),则按特定级别或级别分组;
ax_index:对于聚合输出,返回以组标签作为索引的对象。 仅与DataFrame输入相关。 as_index = False实际上是“SQL风格”的分组输出;
sort:对组键进行排序,关闭它可以获得更好的性能,请注意,这不会影响每组内观察的顺序,groupby保留每个组中的行顺序;
group_keys:调用apply时,将组键添加到索引以标识片段;
squeeze:如果可能,减少返回类型的维度,否则返回一致类型;
observed:这仅适用于任何group是分类的情况。 如果为True:仅显示分l类group的观察值。 如果为False:显示分类group的所有值;
**Kwargs:可选,只接受关键字参数'mutated'并传递给groupby。
输出参数:取决于调用对象并返回包含有关组的信息的groupby对象。

import pandas as pd
data = pd.DataFrame({'Animal':['Falcon','Falcon','Parrot','Parrot'],
                    'Max Speed':[380,370,24,26]})
data.groupby(['Animal']).mean()

data:


image.png

结果:


image.png

将这种方法与线性组合等基础特征工程方法结合(仅适用于决策树),可以得到更多有意义的特征。
e.g.
N1 - median(N1)_by(C1)
N1 - mean(N1)_by(C1)
  1. 用基因编程创造新的特征
    目前,Python环境下最好用的基因编程库是gplearn。
    基因编程的两大用法:
    3.a 转换:将已有特征进行组合转换,组合的方式(一元,二元,多元算子)可以由用户自行定义,也可以使用库中自带的函数(e.g. 加减乘除,min,max,三角函数,对数等)。组合的目的是为了创造出与目标y值最相关的新特征。这种相关程度可以用spearman或者pearson的相关系数进行测量。spearman多用于决策树(免疫单特征单调变换),pearson多用于线性回归等其他算法。
    3.b 回归:原理同上,只不过直接用于回归而已。
    关于gplearn的中文介绍:https://zhuanlan.zhihu.com/p/31185882
    官方文档说明:https://gplearn.readthedocs.io/en/stable/intro.html#initialization
  2. 用决策树创造新的特征
    在决策树系列算法中(单颗决策树,GBDT,随机森林中),每一个样本都会被映射到决策树的一片叶子上。因此,我们可以把样本经过每一棵决策树映射后的index(自然数)或one-hot-vector(哑编码得到的稀疏矢量)作为一项新的特征,加入到模型中。
    具体实现:apply()以及decision_path()方法,在scikit-learn和xgboost里都可以用。
  3. 决策树,基于决策树的ensemble
    5.a spearman correlation coefficient
  • 特征选择
    当数据预处理完后,我们需要选择有意义的特征输入机器学习的算法和模型进行训练。通常来说,从两个方面考虑来选择特征:
  1. 特征是否发散:如果一个特征不发散,例如方差接近于0,也就是说样本在这个特征上基本上没有差异,这个特征对于样本的区分并没有什么用。
  2. 特征与目标的相关性:这点比较显而易见,与目标相关性高的特征,应当优选选择。
    根据特征选择的形式又可以将特征选择方法分为三种。
    2.a Filter:过滤法,按照发散性或者相关性对各个特征进行评分,设定阈值或者待选择阈值的个数,选择特征。
    aa. 方差选择法。使用方差选择法,先要计算各个特征的方差,然后根据阈值,选择方差大于阈值的特征;
    bb. 相关系数法。使用相关系数法,先要计算各个特征对目标值的相关系数以及相关系数的P值。 e.g. 使用pearson相关系数法
    cc. 卡方检验。 (这一部分不太能理解,待补)
    dd. 互信息法。 (这个也是,不太能理解为啥这个能选择出来)。
    补充下个人的其它看法,这个过滤法,感觉只要能算出属性的什么什么值,然后用阈值去截断就好了,本科毕设里面对BP神经网络加了个灰色关联度去截断属性能够获得比BP神经网络更好的效果,现在理解就是这么个意思
    2.b Wrapper:包装法,根据目标函数(通常是预测效果评分),每次选择若干特征,或者排除若干特征。
    aa. 递归消除法:递归消除特征法使用一个基模型来进行多轮训练,每轮训练后,消除若干权值系数的特征,再基于新的特征集进行下一轮训练。
    2.c Embedded:嵌入法,先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据系数从大到小选择特征。类似于Filter方法,但是是通过训练来确定特征的优劣。
    aa. 基于惩罚项的特征选择法

参考资料:
https://zhuanlan.zhihu.com/p/26444240
https://blog.csdn.net/fisherming/article/details/80105891
https://zhuanlan.zhihu.com/p/26308272
https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.PolynomialFeatures.html
https://zhuanlan.zhihu.com/p/31185882
https://www.cnblogs.com/peizhe123/p/7412364.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容