特征工程是一个关键环节,它涉及从原始数据中提炼出有益于机器学习模型建立的有价值特征。这一过程可能涵盖了多种操作,如筛选原始数据中的关键特征、对特征进行转换以使其更易于处理,或是构造全新的特征,从而增强模型的预测准确性。特征工程往往被视作机器学习模型构建的初步工作,因为它对模型的表现具有至关重要的影响,所使用的特征的质量直接决定了模型的性能。
1.特征工程是什么
特征工程是一个将原始数据转化为更能反映问题核心特征的过程,这样做能够显著提高预测模型在不可见数据上的预测准确性。简而言之,特征工程就是寻找那些对目标变量y具有显著影响的特征,我们通常称这些影响目标变量的因素为自变量x,即特征。特征工程的根本目标就在于发掘这些重要的特征。
分解和聚合原始数据,以更精准地表达问题的实质,这正是特征工程所要达成的目标。在数据挖掘模型的开发过程中,特征工程往往是最耗时但也是最为关键的一步。特征处理作为特征工程的核心组成部分,包含了数据预处理、Feature Extraction(特征提取)和Feature construction(特征构造)、特征选择、降维等多个环节。对于初次接触sklearn的人来说,尽管其丰富的算法模型库极具吸引力,但不可忽视的是,sklearn所提供的特征处理工具库同样强大而全面。
2 数据预处理
通过特征提取,我们可以获取到原始、未经处理的特征,但这些特征可能存在以下问题:首先,特征的度量单位可能不一致,使得它们难以直接进行比较,这一问题可以通过无量纲化来解决;其次,某些特征可能包含冗余信息,例如学习成绩,若我们仅关心及格与否,则需将具体的分数转化为二值化的表示;再者,定性特征通常无法直接用于机器学习算法中,因此我们需要将它们转化为定量特征,常见的方式是为每种定性值指定一个定量值,但这种方式灵活度过高,可能增加参数调整的工作量,而哑编码则是一种更为有效的转换方法,它将定性特征扩展为多个定量特征,根据原始特征值的不同而赋予不同的值;另外,数据中可能存在缺失值,需要进行适当的补充;最后,不同的机器学习算法对数据的利用方式各异,因此有时我们需要对特征进行转换或多项式化等操作,以提高信息利用率。幸运的是,我们可以利用sklearn中的preprocessing库来进行数据预处理,该库提供了上述问题的解决方案。
2.1 无量纲化
无量纲化是一种数据转换技术,旨在将不同规格或度量单位的数据统一到一个共同的规格或尺度上。实现无量纲化的常见方法包括标准化和区间缩放法。标准化特别适用于那些特征值服从正态分布的情况,经过标准化处理后,数据将被转换为标准正态分布的形式。而区间缩放法则充分利用了数据的边界值信息,通过这种方法,可以将特征的取值范围缩放到特定的区间内,比如[0, 1]等,从而使数据更加易于处理和比较。
2.1.1 标准化
标准化是一种数据预处理技术,它要求计算特征的均值和标准差。标准化的数学公式表达为:
x
′
=
x
−
X
ˉ
S
x' = \frac{x- \bar{X}}{S}
x′=Sx−Xˉ
将每个特征值减去其均值,再除以其标准差。这一过程有助于将原始数据转化为均值为0、标准差为1的标准正态分布形式。在Python的sklearn库中,我们可以使用preprocessing模块的StandardScaler类来方便地实现数据的标准化处理。
from sklearn.preprocessing import StandardScaler
#标准化,返回值为标准化后的数据
StandardScaler().fit_transform(iris.data)
2.1.2 区间缩放法
区间缩放法是一种灵活的数据预处理技术,其思路多种多样。其中,一种常见的方法是利用特征数据集中的最小值和最大值进行缩放,即将每个特征值减去最小值后,再除以该特征的最大值与最小值之差。通过这种方式,可以将原始数据的取值范围缩放到一个特定的区间,例如[0, 1]。在Python的sklearn库中,preprocessing模块的MinMaxScaler类提供了一个方便的接口,用于实现数据的区间缩放处理。
from sklearn.preprocessing import MinMaxScaler
#区间缩放,返回值为缩放到[0, 1]区间的数据
MinMaxScaler().fit_transform(iris.data)
2.1.3 标准化与归一化的区别
标准化主要是根据特征矩阵的列来对数据进行处理,它采用计算z-score的方法,将不同特征的值转换到相同的量纲下,以便进行比较和分析。而归一化则是基于特征矩阵的行来处理数据,其主要目的是在进行点积运算或应用其他核函数计算样本相似性时,确保所有样本向量都遵循统一的标准,即转化为“单位向量”。遵循L2范数的归一化公式是一种常用的转换方法,它能够将样本特征向量的长度缩放为1,从而消除不同样本在特征规模上的差异。
使用preproccessing库的Normalizer类对数据进行归一化的代码如下:
from sklearn.preprocessing import Normalizer
#归一化,返回值为归一化后的数据
Normalizer().fit_transform(iris.data)
2.2 对定量特征二值化
定量特征二值化的关键在于确定一个合适的阈值。在二值化过程中,如果某个特征值大于设定的阈值,就将其赋值为1;若小于等于该阈值,则赋值为0。这样,原始特征就被转化为了仅有0和1两种取值的二值特征。在Python的sklearn库中,我们可以利用preprocessing模块的Binarizer类来实现这一操作,方便地对定量数据进行二值化处理。
from sklearn.preprocessing import Binarizer
#二值化,阈值设置为4,返回值为二值化后的数据
Binarizer(threshold=4).fit_transform(iris.data)
2.3 对定性特征哑编码
IRIS数据集的特征都是定量性质的,因此实际上并不需要进行哑编码处理,因为哑编码通常用于定性特征的转换。然而,如果您误用目标值(即分类标签)进行了哑编码,这并不符合数据预处理的标准流程。正确的做法是使用preprocessing库中的OneHotEncoder类对定性特征(如果有的话)进行哑编码,将每个定性特征转换为多个二进制特征。但由于IRIS数据集的特征是定量的,这里不需要使用OneHotEncoder进行哑编码。如果需要处理定性特征,那么OneHotEncoder类将是正确的选择。
from sklearn.preprocessing import OneHotEncoder
#哑编码,对IRIS数据集的目标值,返回值为哑编码后的数据
OneHotEncoder().fit_transform(iris.target.reshape((-1,1)))
2.4 缺失值计算
IRIS数据集原本不包含任何缺失值,但为了演示如何处理缺失数据,我们可以人为地向数据集中新增一个样本,并为其4个特征都赋予NaN值,以此模拟数据缺失的情况。随后,我们可以利用sklearn库中的preprocessing模块中的Imputer类(注意:在较新版本的sklearn中,Imputer类已被IterativeImputer或SimpleImputer替代)来计算并填充这些缺失值。以下代码展示了如何使用此类对数据集中的缺失值进行处理。
from numpy import vstack, array, nan
from sklearn.preprocessing import Imputer
#缺失值计算,返回值为计算缺失值后的数据
#参数missing_value为缺失值的表示形式,默认为NaN
#参数strategy为缺失值填充方式,默认为mean(均值)
Imputer().fit_transform(vstack((array([nan, nan, nan, nan]), iris.data)))
3、特征构造
3.1 数据变换
常见的数据变换有基于多项式的、基于指数函数的、基于对数函数的。4个特征,度为2的多项式转换公式如下:
使用preproccessing库的PolynomialFeatures类对数据进行多项式转换的代码如下:
from sklearn.preprocessing import PolynomialFeatures
#多项式转换
#参数degree为度,默认值为2
PolynomialFeatures().fit_transform(iris.data)
基于单变元函数的数据变换可以使用一个统一的方式完成,使用preproccessing库的FunctionTransformer对数据进行对数函数转换的代码如下:
from numpy import log1p
from sklearn.preprocessing import FunctionTransformer
#自定义转换函数为对数函数的数据变换
#第一个参数是单变元函数
FunctionTransformer(log1p).fit_transform(iris.data)
3.2 时间类数据转换
在时间序列分析的问题处理中,时间类特征的构造是非常关键的一步。时间序列数据往往涉及到按照时间顺序排列的观测值,如股票价格、销售数据、天气记录等。时间类特征不仅可以帮助我们捕捉数据中的时间依赖性,还能揭示隐藏在时间维度上的模式和趋势。我们可以从原始时间序列数据中提取基本的时间信息,如年、月、日、小时、分钟等。这些时间戳可以直接作为特征使用,尤其是在那些与时间周期密切相关的应用场景中。例如,在零售分析中,季度性的销售变化可能受到特定月份或节日的影响。
3.3 交叉特征构造
特征工程中,交叉特征构造是一种重要的技术,它通过将两个或多个原始特征进行组合,以产生新的特征,从而捕捉数据中潜在的复杂关系。这种方法有助于模型更好地理解和利用特征之间的关联性,提升预测性能。交叉特征构造通常基于业务理解或数据探索的结果,通过特定的组合逻辑或运算规则来实现。例如,在电商领域,可以将用户浏览次数和购买次数进行交叉,得到一个新的特征表示用户的购买转化率。这样的交叉特征能够更全面地反映用户的行为模式,对于预测用户的购买意向非常有价值。通过合理地构造交叉特征,我们可以丰富特征空间,增强模型的表达能力,从而得到更准确的预测结果。
3.3 其他类型的数据转换
除了常见的标准化、归一化、二值化、哑编码等数据转换方法外,在某些特殊领域或复杂数据分析场景中,还会使用到小波变换和傅里叶变换等高级技术。这些转换方法主要应用于信号处理、图像处理以及时间序列分析等领域,它们能够帮助我们从原始数据中提取出更深层次的特征和信息。
小波变换是一种时间-频率分析方法,通过伸缩平移运算对信号(函数)逐步进行多尺度细化,最终达到高频处时间细分,低频处频率细分,能自动适应时频信号分析的要求。在信号处理中,小波变换能够有效地提取出信号中的局部特征,如突变点、奇异点等,从而揭示信号的内在结构和规律。
傅里叶变换则是一种将信号从时间域转换到频率域的数学工具。通过傅里叶变换,我们可以将复杂的时域信号分解为一系列简单的正弦波或余弦波的组合,从而便于分析信号的频率成分和特性。在图像处理中,傅里叶变换常被用于图像滤波、图像压缩和特征提取等方面,能够显著提高图像处理的效率和准确性。后续将给出一些详细的代码
4.特征选择
当数据预处理工作完成后,选取具有意义的特征并将其输入到机器学习算法和模型中进行训练是至关重要的。在选择特征时,我们通常会从两个维度来考虑:
首先是特征是否具备发散性。如果一个特征的方差接近于0,意味着样本在该特征上几乎没有差异,这样的特征对于区分样本并无太大帮助。
其次,我们考虑特征与目标之间的相关性。显然,与目标高度相关的特征应当被优先选取。除了利用方差法外,本文还将介绍其他从相关性角度出发的特征选择方法。
根据特征选择的不同形式,我们可以将其分为三种主要方法:
-
过滤法(Filter),它基于特征的发散性或相关性对各个特征进行评分,然后设定一个阈值或选择特定数量的特征。
-
包装法(Wrapper),它依据目标函数(如预测效果的评分)来每次选择或排除一些特征。
-
集成法(Embedded),它首先利用某些机器学习的算法和模型进行训练,然后根据训练过程中得到的各特征权值系数,从大到小选择特征。这种方法类似于过滤法,但它是通过实际训练来确定特征的优劣。
为了简化特征选择的过程,我们可以利用sklearn中的feature_selection库来实现这些功能。
4.1 Filter
4.1.1 方差选择法
使用方差选择法时,首先需要计算数据集中每个特征的方差。接着,根据预设的阈值,我们可以筛选出那些方差大于该阈值的特征,因为这些特征在样本之间具有较大的变化,更有可能包含对模型训练有用的信息。为了简化这一操作过程,我们可以利用sklearn库中的feature_selection模块中的VarianceThreshold类来自动执行特征选择。通过调用这个类并设置合适的阈值,我们可以方便地从数据集中选出满足条件的特征,进而用于后续的机器学习模型训练。
from sklearn.feature_selection import VarianceThreshold
#方差选择法,返回值为特征选择后的数据
#参数threshold为方差的阈值
VarianceThreshold(threshold=3).fit_transform(iris.data)
4.1.2 相关系数法
使用相关系数法时,我们首先计算数据集中每个特征与目标值之间的相关系数,并获取这些相关系数的P值。相关系数衡量了特征与目标之间的线性关联程度,而P值则帮助我们确定这种关联是否显著。为了根据这些统计量选择特征,我们可以结合使用sklearn库中的feature_selection模块的SelectKBest类。通过指定使用相关系数作为评分函数,并设定需要选择的特征数量或设定一个分数阈值,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)
4.1.3 卡方检验
经典的卡方检验是一种用于检验定性自变量与定性因变量之间相关性的统计方法。当自变量具有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)
4.1.4 互信息法
经典的互信息同样是一种衡量定性自变量与定性因变量之间相关性的方法。通过特定的计算公式,我们可以得到互信息的值,从而评估自变量和因变量之间的关联性。然而,当处理定量数据时,互信息可能并不适用。为了解决这个问题,提出了最大信息系数法,它能够有效地评估定量自变量与因变量之间的相关性。在sklearn的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)
4.2 Wrapper
4.2.1 递归特征消除法
递归消除特征法是一种基于基模型进行多轮训练的特征选择方法。在每一轮的训练过程中,该方法会评估每个特征的权值系数,并据此消除若干权值系数较低的特征。随后,基于更新后的特征集,它会进行下一轮的训练。这个过程会反复进行,直到满足特定的停止条件。在sklearn的feature_selection库中,我们可以使用RFE类来实现这种递归消除特征法的特征选择过程。通过调用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)
4.3 Embedded
4.3.1 基于惩罚项的特征选择法
使用带惩罚项的基模型进行特征选择时,不仅可以筛选出对模型性能贡献最大的特征,还能同时实现降维的效果。这是因为惩罚项能够使得模型在训练过程中倾向于选择较少的特征,从而避免过拟合并简化模型。在sklearn的feature_selection库中,我们可以使用SelectFromModel类与带L1惩罚项的逻辑回归模型相结合来实现这一目的。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)
4.3.2 基于树模型的特征选择法
梯度提升决策树(GBDT)作为一种强大的树模型,同样可以作为基模型用于特征选择。在sklearn的feature_selection库中,我们可以利用SelectFromModel类结合GBDT模型来进行特征选择。通过训练GBDT模型,我们可以得到每个特征的重要性评分,进而使用SelectFromModel类来筛选出对模型性能贡献显著的特征。这种方式不仅可以帮助我们理解数据中的关键特征,还能实现数据的降维,提高后续机器学习模型的效率和性能。
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import GradientBoostingClassifier
#GBDT作为基模型的特征选择
SelectFromModel(GradientBoostingClassifier()).fit_transform(iris.data, iris.target)
5 降维
特征选择完成后,虽然可以开始训练模型,但实际操作中可能会遇到特征矩阵过于庞大导致的计算量大、训练时间长的问题。因此,降低特征矩阵的维度是一个关键的步骤。除了之前提到的利用带L1惩罚项的模型进行降维,还有两种常见的降维方法:主成分分析法(PCA)和线性判别分析(LDA)。LDA本身也是一个用于分类的模型。
PCA和LDA在原理上有很多相似点,它们都是将原始样本映射到维度更低的样本空间中。然而,它们的映射目标有所不同:PCA的目标是使映射后的样本具有最大的发散性,即保留数据中的主要变化方向;而LDA的目标则是使映射后的样本具有最好的分类性能,即最大化类间差异并最小化类内差异。因此,PCA被视为一种无监督的降维方法,因为它不需要利用样本的标签信息;而LDA则是一种有监督的降维方法,因为它在降维过程中充分利用了样本的类别信息。
5.1 主成分分析法(PCA)
使用decomposition库的PCA类选择特征的代码如下:
from sklearn.decomposition import PCA
#主成分分析法,返回降维后的数据
#参数n_components为主成分数目
PCA(n_components=2).fit_transform(iris.data)
4.2 线性判别分析法(LDA)
使用lda库的LDA类选择特征的代码如下:
from sklearn.lda import LDA
#线性判别分析法,返回降维后的数据
#参数n_components为降维后的维数
LDA(n_components=2).fit_transform(iris.data, iris.target)
6、一个简单地例子
# 导入所需的库
import numpy as np
from sklearn.preprocessing import OneHotEncoder, StandardScaler
# 定义原始数据
data = [[1000, "3rd Ave", "apartment"],
[1500, "5th Ave", "house"],
[2000, "1st Ave", "house"],
[2500, "10th Ave", "apartment"],
]
# 定义特征列索引
price_index = 0
address_index = 1
type_index = 2
# 提取特征
prices = [row[price_index] for row in data]
addresses = [row[address_index] for row in data]
types = [row[type_index] for row in data]
# 转换特征
# 对地址进行 one-hot 编码
encoder = OneHotEncoder()
encoder.fit(np.array(addresses).reshape(-1, 1))
encoded_addresses = encoder.transform(np.array(addresses).reshape(-1, 1))
# 对价格进行标准化
scaler = StandardScaler()
scaler.fit(np.array(prices).reshape(-1, 1))
scaled_prices = scaler.transform(np.array(prices).reshape(-1, 1))
# 构造新的特征
num_bedrooms = [2, 3, 4, 1]
# 将所有特征组合起来
features = []
for i in range(len(data)):
features.append([
scaled_prices[i],
encoded_addresses[i],
types[i],
num_bedrooms[i]
])
7、总结
特征工程是机器学习模型构建的初步工作,但它的重要性却贯穿整个模型生命周期。在进行特征工程时,我们需要充分利用数据科学的知识和技巧,结合具体的应用场景和业务需求,来制定出最合适的特征提取和转换策略。只有这样,我们才能为机器学习模型提供最有价值的输入,从而使其发挥出最大的潜力。
总之,特征工程是机器学习中的一项关键任务,它需要我们用心去探索、去实践、去创新。只有不断地优化和完善特征工程的过程,我们才能构建出更加准确、更加可靠的机器学习模型,为实际应用带来更大的价值。