1 特征工程是什么?
有这么一句话在业界广泛流传:数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。那特征工程到底是什么呢?顾名思义,其本质是一项工程活动,目的是最大限度地从原始数据中提取特征以供算法和模型使用。通过总结和归纳,人们认为特征工程包括以下方面:
通过特征提取,我们能得到未经处理的特征,这时的特征可能有以下问题:
我们使用sklearn中的preproccessing库来进行数据预处理,可以覆盖以上问题的解决方案。
无量纲化使不同规格的数据转换到同一规格。常见的无量纲化方法有标准化和区间缩放法。标准化的前提是特征值服从正态分布,标准化后,其转换成标准正态分布。区间缩放法利用了边界值信息,将特征的取值区间缩放到某个特点的范围,例如[0, 1]等。
标准化需要计算特征的均值和标准差,公式表达为:
使用preproccessing库的StandardScaler类对数据进行标准化的代码如下:
1 from sklearn.preprocessing import StandardScaler
3 #标准化,返回值为标准化后的数据
4 StandardScaler().fit_transform(iris.data)
区间缩放法的思路有多种,常见的一种为利用两个最值进行缩放,公式表达为:
使用preproccessing库的MinMaxScaler类对数据进行区间缩放的代码如下:
from sklearn.preprocessing import MinMaxScaler
#区间缩放,返回值为缩放到[0, 1]区间的数据
MinMaxScaler().fit_transform(iris.data)
简单来说,标准化是依照特征矩阵的列处理数据,其通过求z-score的方法,将样本的特征值转换到同一量纲下。归一化是依照特征矩阵的行处理数据,其目的在于样本向量在点乘运算或其他核函数计算相似性时,拥有统一的标准,也就是说都转化为“单位向量”。规则为l2的归一化公式如下:
使用preproccessing库的Normalizer类对数据进行归一化的代码如下:
1 from sklearn.preprocessing import Normalizer
3 #归一化,返回值为归一化后的数据
4 Normalizer().fit_transform(iris.data)
归一化 不同的特征有不同的取值范围,在有些算法中,例如线性模型或者距离相关的模型像聚类模型、knn模型等,特征的取值范围会对最终的结果产生较大影响,例如二元特征的取值范围为[0,1],而距离特征取值可能是[0,正无穷),在实际使用中会对距离进行截断,例如[0,],但是这两个特征由于取值范围不一致导致了模型可能会更偏向于取值范围较大的特征,为了平衡取值范围不一致的特征,需要对特征进行归一化处理,将特征取值归一化到[0,1]区间。常用的归一化方法包括1.函数归一化,通过映射函数将特征取值映射到[0,1]区间,例如最大最小值归一化方法,是一种线性的映射。还有通过非线性函数的映射,例如log函数等。2.分维度归一化,可以使用最大最小归一化方法,但是最大最小值选取的是所属类别的最大最小值,即使用的是局部最大最小值,不是全局的最大最小值。3.排序归一化,不管原来的特征取值是什么样的,将特征按大小排序,根据特征所对应的序给予一个新的值。
定量特征二值化的核心在于设定一个阈值,大于阈值的赋值为1,小于等于阈值的赋值为0,公式表达如下:
使用preproccessing库的Binarizer类对数据进行二值化的代码如下:
1 from sklearn.preprocessing import Binarizer
3 #二值化,阈值设置为3,返回值为二值化后的数据
4 Binarizer(threshold=3).fit_transform(iris.data)
在这个例子中,我们假设你有一个输出序列如下3个标签:
Cold,warm,hot
10个时间序列的示例顺序可以是:
Cold,cold,warm,cold,hot,hot,warm,cold,warm,hot
这将首先需要一个整数编码,如1,2,3,然后是整数到one hot编码具有3个值的二进制向量,例如[1,0,0]。
这个情况下提供序列中每个可能值的至少一个示例。因此,我们可以使用自动方法来定义整数到二进制向量的映射。
在这个例子中,我们将使用scikit学习库的编码器。具体来说,LabelEncoder创建标签的整数编码,OneHotEncoder用于创建整数编码值的one hot编码。
from numpy import array
from numpy import argmax
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
# define example
data = ['cold', 'cold', 'warm', 'cold', 'hot', 'hot', 'warm', 'cold', 'warm', 'hot']
values = array(data)
print(values)
# integer encode
label_encoder = LabelEncoder()
integer_encoded = label_encoder.fit_transform(values)
print(integer_encoded)
# binary encode
onehot_encoder = OneHotEncoder(sparse=False)
integer_encoded = integer_encoded.reshape(len(integer_encoded), 1)
onehot_encoded = onehot_encoder.fit_transform(integer_encoded)
print(onehot_encoded)
# invert first example
inverted = label_encoder.inverse_transform([argmax(onehot_encoded[0, :])])
print(inverted)
运行示例首先打印标签序列。之后是标签的整数编码,最后是one hot编码。训练数据包含所有可能示例的集合,因此我们可以依靠整数和one hot编码变换来创建标签到编码的完整映射。
默认情况下,OneHotEncoder类将返回更高效的稀疏编码,这可能不适用于某些应用程序。例如使用Keras深度学习库。在这种情况下,我们通过设置sparse = False这个参数来禁用稀疏返回类型。
首先,我们可以使用argmax()NumPy函数来找到具有最大值的列的索引。然后可以将其输入到LabelEncoder,以计算反向变换回文本标签。运行结果为:
['cold' 'cold' 'warm' 'cold' 'hot' 'hot' 'warm' 'cold' 'warm' 'hot'] [0 0 2 0 1 1 2 0 2 1] [[ 1. 0. 0.] [ 1. 0. 0.] [ 0. 0. 1.] [ 1. 0. 0.] [ 0. 1. 0.] [ 0. 1. 0.] [ 0. 0. 1.] [ 1. 0. 0.] [ 0. 0. 1.] [ 0. 1. 0.]] ['cold'] |
在下一个例子中,我们来看一下如何直接对一个整数值进行one hot编码。
您可能有一个已经是整数编码的序列。
经过处理后,您可以直接使用整数。或者,您可以直接对整数进行one hot 编码。这是非常重要的如果整数没有真正的顺序关系并且只是标签的占位符。
Keras提供了一个名为to_categorical()的函数,它可以帮助你使用one hot编码整数数据。接下来我们看一个小例子:
在这个例子中,我们有4个整数值[0,1,2,3],我们有以下10个数字的输入序列:
Data = [1,3,2,0,3,2,2,1,0,1]
该序列具有已知了所有值,因此我们可以直接使用to_categorical()函数。以下列出了此功能的完整示例。
from numpy import array
from numpy import argmax
from keras.utils import to_categorical
# define example
data = [1, 3, 2, 0, 3, 2, 2, 1, 0, 1]
data = array(data)
print(data)
# one hot encode
encoded = to_categorical(data)
print(encoded)
# invert encoding
inverted = argmax(encoded[0])
print(inverted)
运行示例并打印输入序列。
然后将整数编码为二进制向量并打印。我们可以看到,正如我们预期的那样,第一个整数值1被编码为[0,1,0,0]。
然后,我们使用NumPy argmax()函数反转编码,该函数返回第一个整数的期望值1的序列中的第一个值。
[1 3 2 0 3 2 2 1 0 1] [[ 0. 1. 0. 0.] [ 0. 0. 0. 1.] [ 0. 0. 1. 0.] [ 1. 0. 0. 0.] [ 0. 0. 0. 1.] [ 0. 0. 1. 0.] [ 0. 0. 1. 0.] [ 0. 1. 0. 0.] [ 1. 0. 0. 0.] [ 0. 1. 0. 0.] 1 |
缺省值处理的一般步骤:确定缺省值范围、去除不需要的字段、填充缺省值内容(经验值、均值、中位数、众数、推测值)、重新获取数据。
1、缺失值剔除
python实现:data.dropna()
python实现
from sklearn.preprocessing import Imputer
imputer=Imputer(strategy='mean') #mean, median, most_frequent
data['累计票房']=imputer.fit_transform(data[['累计票房']])
data['豆瓣评分']=imputer.fit_transform(data[['豆瓣评分']])
或者:
df['distance'] = df['Distance'].fillna(0).astype(int)#填充成固定值
data['累计票房'] = data['累计票房'].fillna(data['累计票房'].mean()))#均值
data['累计票房'] = data['累计票房'].fillna(data['累计票房']. mode ()))#众数
data['累计票房'] = data['累计票房'].fillna(method='pad')#用前一个数进行填充
data['累计票房'] = data['累计票房'].fillna(method=’bfill’)#用后一个数进行填充
data['累计票房'] = data['累计票房'].interpolate()#用插值法进行填充
#用KNN进行填充
from fancyimpute import BiScaler, KNN, NuclearNormMinimization, SoftImpute
dataset = KNN(k=3).complete(dataset)
#用随机森林进行填充
from sklearn.ensemble import RandomForestRegressor
zero_columns_2 = ['机构查询数量', '直接联系人数量', '直接联系人在黑名单数量', '间接联系人在黑名单数量',
'引起黑名单的直接联系人数量', '引起黑名单的直接联系人占比']
#将出现空值的除了预测的列全部取出来,不用于训练
dataset_list2 = [x for x in dataset if x not in zero_columns_2]
dataset_2 = dataset[dataset_list2]
# 取出灰度分不为空的全部样本进行训练
know = dataset_2[dataset_2['灰度分'].notnull()]
print(know.shape) #26417, 54
# 取出灰度分为空的样本用于填充空值
unknow = dataset_2[dataset_2['灰度分'].isnull()]
print(unknow.shape) #2078, 54
y = ['灰度分']
x = [1]
know_x2 = know.copy()
know_y2 = know.copy()
print(know_y2.shape)
#
know_x2.drop(know_x2.columns[x], axis=1, inplace=True)
print(know_y2.shape)
print(know_x2.shape)
#
know_y2 = know[y]
# RandomForestRegressor
rfr = RandomForestRegressor(random_state=666, n_estimators=2000, n_jobs=-1)
rfr.fit(know_x2, know_y2)
# 填充为空的样本
unknow_x2 = unknow.copy()
unknow_x2.drop(unknow_x2.columns[x], axis=1, inplace=True)
print(unknow_x2.shape) #(2078, 53)
unknow_y2 = rfr.predict(unknow_x2)
unknow_y2 = pd.DataFrame(unknow_y2, columns=['灰度分'])
在上面介绍过连续值的取值空间可能是无穷的,为了便于表示和在模型中处理,需要对连续值特征进行离散化处理。常用的离散化方法包括等值划分和等量划分。等值划分是将特征按照值域进行均分,每一段内的取值等同处理。例如某个特征的取值范围为[0,10],我们可以将其划分为10段,[0,1),[1,2),...,[9,10)。等量划分是根据样本总数进行均分,每段等量个样本划分为1段。例如距离特征,取值范围[0,],现在需要切分成10段,如果按照等比例划分的话,会发现绝大部分样本都在第1段中。使用等量划分就会避免这种问题,最终可能的切分是[0,100),[100,300),[300,500),..,[10000,],前面的区间划分比较密,后面的比较稀疏。
常见的数据变换有基于多项式的、基于指数函数的、基于对数函数的。4个特征,度为2的多项式转换公式如下:
使用preproccessing库的PolynomialFeatures类对数据进行多项式转换的代码如下:
1 from sklearn.preprocessing import PolynomialFeatures
3 #多项式转换
4 #参数degree为度,默认值为2
5 PolynomialFeatures().fit_transform(iris.data)
基于单变元函数的数据变换可以使用一个统一的方式完成,使用preproccessing库的FunctionTransformer对数据进行对数函数转换的代码如下:
1 from numpy import log1p
2 from sklearn.preprocessing import FunctionTransformer
3
4 #自定义转换函数为对数函数的数据变换
5 #第一个参数是单变元函数
6 FunctionTransformer(log1p).fit_transform(iris.data)
类 |
功能 |
说明 |
StandardScaler |
无量纲化 |
标准化,基于特征矩阵的列,将特征值转换至服从标准正态分布 |
MinMaxScaler |
无量纲化 |
区间缩放,基于最大最小值,将特征值转换到[0, 1]区间上 |
Normalizer |
归一化 |
基于特征矩阵的行,将样本向量转换为“单位向量” |
Binarizer |
二值化 |
基于给定阈值,将定量特征按阈值划分 |
OneHotEncoder |
哑编码 |
将定性数据编码为定量数据 |
Imputer |
缺失值计算 |
计算缺失值,缺失值可填充为均值等 |
PolynomialFeatures |
多项式数据转换 |
多项式数据转换 |
FunctionTransformer |
自定义单元数据转换 |
使用单变元的函数来转换数据 |
当数据预处理完成后,我们需要选择有意义的特征输入机器学习的算法和模型进行训练。通常来说,从两个方面考虑来选择特征:
根据特征选择的形式又可以将特征选择方法分为3种:
我们使用sklearn中的feature_selection库来进行特征选择。
假设某特征的特征值只有0和1,并且在所有输入样本中,95%的实例的该特征取值都是1,那就可以认为这个特征作用不大。如果100%都是1,那这个特征就没意义了。当特征值都是离散型变量的时候这种方法才能用,如果是连续型变量,就需要将连续变量离散化之后才能用。而且实际当中,一般不太会有95%以上都取某个值的特征存在,所以这种方法虽然简单但是不太好用。可以把它作为特征选择的预处理,先去掉那些取值变化小的特征,然后再从接下来提到的的特征选择方法中选择合适的进行进一步的特征选择。
使用方差选择法,先要计算各个特征的方差,然后根据阈值,选择方差大于阈值的特征。使用feature_selection库的VarianceThreshold类来选择特征的代码如下:
1 from sklearn.feature_selection import VarianceThreshold
3 #方差选择法,返回值为特征选择后的数据
4 #参数threshold为方差的阈值
5 VarianceThreshold(threshold=3).fit_transform(iris.data)
单变量特征选择的原理是分别单独的计算每个变量的某个统计指标,根据该指标来判断哪些指标重要,剔除那些不重要的指标。
对于分类问题(y离散),可采用:
卡方检验,f_classif, mutual_info_classif,互信息
对于回归问题(y连续),可采用:
皮尔森相关系数,f_regression, mutual_info_regression,最大信息系数
这种方法比较简单,易于运行,易于理解,通常对于理解数据有较好的效果(但对特征优化、提高泛化能力来说不一定有效)。这种方法有许多改进的版本、变种。
单变量特征选择基于单变量的统计测试来选择最佳特征。它可以看作预测模型的一项预处理。==Scikit-learn将特征选择程序用包含 transform 函数的对象来展现==:
将特征输入到评分函数,返回一个单变量的f_score(F检验的值)或p-values(P值,假设检验中的一个标准,P-value用来和显著性水平作比较),注意SelectKBest 和 SelectPercentile只有得分,没有p-value。
Notice:
The methods based on F-test estimate the degree of linear dependency between two random variables. (F检验用于评估两个随机变量的线性相关性)On the other hand, mutual information methods can capture any kind of statistical dependency, but being nonparametric, they require more samples for accurate estimation.(另一方面,互信息的方法可以捕获任何类型的统计依赖关系,但是作为一个非参数方法,估计准确需要更多的样本)
Feature selection with sparse data:
If you use sparse data (i.e. data represented as sparse matrices), chi2, mutual_info_regression, mutual_info_classif will deal with the data without making it dense.(如果你使用稀疏数据(比如,使用稀疏矩阵表示的数据), 卡方检验(chi2)、互信息回归(mutual_info_regression)、互信息分类(mutual_info_classif)在处理数据时可保持其稀疏性.)
Pearson相关系数法
使用相关系数法,先要计算各个特征对目标值的相关系数以及相关系数的P值。用feature_selection库的SelectKBest类结合相关系数来选择特征的代码如下:
1 from sklearn.feature_selection import SelectKBest
2 from scipy.stats import pearsonr
3
4 #选择K个最好的特征,返回选择特征后的数据
5 #第一个参数为计算评估特征是否好的函数,该函数输入特征矩阵和目标向量,输出二元组(评分,P值)的数组,数组第i项为第i个特征的评分和P值。在此定义为计算相关系数
6 #参数k为选择的特征个数
7 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类结合卡方检验来选择特征的代码如下:
1 from sklearn.feature_selection import SelectKBest
2 from sklearn.feature_selection import chi2
3
4 #选择K个最好的特征,返回选择特征后的数据
5 SelectKBest(chi2, k=2).fit_transform(iris.data, iris.target)
互信息法
经典的互信息也是评价定性自变量对定性因变量的相关性的,互信息计算公式如下:
为了处理定量数据,最大信息系数法被提出,使用feature_selection库的SelectKBest类结合最大信息系数法来选择特征的代码如下:
1 from sklearn.feature_selection import SelectKBest
2 from minepy import MINE
4 #由于MINE的设计不是函数式的,定义mic方法将其为函数式的,返回一个二元组,二元组的第2项设置成固定的P值0.5
5 def mic(x, y):
6 m = MINE()
7 m.compute_score(x, y)
8 return (m.mic(), 0.5)
10 #选择K个最好的特征,返回特征选择后的数据
11 SelectKBest(lambda X, Y: array(map(lambda x:mic(x, Y), X.T)).T, k=2).fit_transform(iris.data, iris.target)
递归消除特征法使用一个基模型来进行多轮训练,每轮训练后,移除若干权值系数的特征,再基于新的特征集进行下一轮训练。
sklearn官方解释:对特征含有权重的预测模型(例如,线性模型对应参数coefficients),RFE通过递归减少考察的特征集规模来选择特征。首先,预测模型在原始特征上训练,每个特征指定一个权重。之后,那些拥有最小绝对值权重的特征被踢出特征集。如此往复递归,直至剩余的特征数量达到所需的特征数量。
RFECV 通过交叉验证的方式执行RFE,以此来选择最佳数量的特征:对于一个数量为d的feature的集合,他的所有的子集的个数是2的d次方减1(包含空集)。指定一个外部的学习算法,比如SVM之类的。通过该算法计算所有子集的validation error。选择error最小的那个子集作为所挑选的特征。
使用feature_selection库的RFE类来选择特征的代码如下:
1 from sklearn.feature_selection import RFE
2 from sklearn.linear_model import LogisticRegression
4 #递归特征消除法,返回特征选择后的数据
5 #参数estimator为基模型
6 #参数n_features_to_select为选择的特征个数
7 RFE(estimator=LogisticRegression(), n_features_to_select=2).fit_transform(iris.data, iris.target)
使用带惩罚项的基模型,除了筛选出特征外,同时也进行了降维。使用feature_selection库的SelectFromModel类结合带L1惩罚项的逻辑回归模型,来选择特征的代码如下:
1 from sklearn.feature_selection import SelectFromModel
2 from sklearn.linear_model import LogisticRegression
4 #带L1惩罚项的逻辑回归作为基模型的特征选择
5 SelectFromModel(LogisticRegression(penalty="l1", C=0.1)).fit_transform(iris.data, iris.target)
L1惩罚项降维的原理在于保留多个对目标值具有同等相关性的特征中的一个,所以没选到的特征不代表不重要。故,可结合L2惩罚项来优化。具体操作为:若一个特征在L1中的权值为1,选择在L2中权值差别不大且在L1中权值为0的特征构成同类集合,将这一集合中的特征平分L1中的权值,故需要构建一个新的逻辑回归模型:
1 from sklearn.linear_model import LogisticRegression
2
3 class LR(LogisticRegression):
4 def __init__(self, threshold=0.01, dual=False, tol=1e-4, C=1.0,
5 fit_intercept=True, intercept_scaling=1, class_weight=None,
6 random_state=None, solver='liblinear', max_iter=100,
7 multi_class='ovr', verbose=0, warm_start=False, n_jobs=1):
8
9 #权值相近的阈值
10 self.threshold = threshold
11 LogisticRegression.__init__(self, penalty='l1', dual=dual, tol=tol, C=C,
12 fit_intercept=fit_intercept, intercept_scaling=intercept_scaling, class_weight=class_weight,
13 random_state=random_state, solver=solver, max_iter=max_iter,
14 multi_class=multi_class, verbose=verbose, warm_start=warm_start, n_jobs=n_jobs)
15 #使用同样的参数创建L2逻辑回归
16 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)
18 def fit(self, X, y, sample_weight=None):
19 #训练L1逻辑回归
20 super(LR, self).fit(X, y, sample_weight=sample_weight)
21 self.coef_old_ = self.coef_.copy()
22 #训练L2逻辑回归
23 self.l2.fit(X, y, sample_weight=sample_weight)
24
25 cntOfRow, cntOfCol = self.coef_.shape
26 #权值系数矩阵的行数对应目标值的种类数目
27 for i in range(cntOfRow):
28 for j in range(cntOfCol):
29 coef = self.coef_[i][j]
30 #L1逻辑回归的权值系数不为0
31 if coef != 0:
32 idx = [j]
33 #对应在L2逻辑回归中的权值系数
34 coef1 = self.l2.coef_[i][j]
35 for k in range(cntOfCol):
36 coef2 = self.l2.coef_[i][k]
37 #在L2逻辑回归中,权值系数之差小于设定的阈值,且在L1中对应的权值为0
38 if abs(coef1-coef2) < self.threshold and j != k and self.coef_[i][k] == 0:
39 idx.append(k)
40 #计算这一类特征的权值系数均值
41 mean = coef / len(idx)
42 self.coef_[i][idx] = mean
43 return self
使用feature_selection库的SelectFromModel类结合带L1以及L2惩罚项的逻辑回归模型,来选择特征的代码如下:
1 from sklearn.feature_selection import SelectFromModel
3 #带L1和L2惩罚项的逻辑回归作为基模型的特征选择
4 #参数threshold为权值系数之差的阈值
5 SelectFromModel(LR(threshold=0.5, C=0.1)).fit_transform(iris.data, iris.target)
树模型中GBDT也可用来作为基模型进行特征选择,使用feature_selection库的SelectFromModel类结合GBDT模型,来选择特征的代码如下:
1 from sklearn.feature_selection import SelectFromModel
2 from sklearn.ensemble import GradientBoostingClassifier
4 #GBDT作为基模型的特征选择
5 SelectFromModel(GradientBoostingClassifier()).fit_transform(iris.data, iris.target)
类 |
所属方式 |
说明 |
VarianceThreshold |
Filter |
方差选择法 |
SelectKBest |
Filter |
可选关联系数、卡方校验、最大信息系数作为得分计算的方法 |
RFE |
Wrapper |
递归地训练基模型,将权值系数较小的特征从特征集合中消除 |
SelectFromModel |
Embedded |
训练基模型,选择权值系数较高的特征 |
参考资料:
特征工程 - 缺省值填充:
https://www.jianshu.com/p/d01dac
使用sklearn做单机特征工程:
http://www.cnblogs.com/jasonfreak/p/5448385.html
训练模型填充空值(fill null)的几种方法:
人工智能——特征工程总结