本文主要包含两大部分,第1部分为对特征数据的变换,尤其对复杂度较低的模型,例如线性模型非常有用,第2部分为特征选择。
特征类型 | 英文名 | 别名 | 英文名 |
---|---|---|---|
数值特征 | numerical feature | 连续特征 | continuous feature |
分类特征 | categorical feature | 离散特征 | discrete feature |
用正确的方式表示数据,对监督模型性能的影响比所选择的精确参数还要大。
编码方式 | 英文名 | 别名1 | 别名2 |
---|---|---|---|
one-hot 编码 | one-hot-encoding | N 取一编码(one-out-of-N encoding) | 虚拟变量(dummy variable) |
虚拟变量背后的思想是将一个分类变量替换为一个或多个新特征,新特征取值为 0 和 1。
pandas 的 get_dummies 函数将所有数字看作是连续的,不会为其创建虚拟变量。
为了解决这个问题,可以使用 scikit-learn 的 OneHotEncoder,指定哪些变量是连续的、哪些变量是离散的,你也可以将数据框中的数值列转换为字符串。
demo_df = pd.DataFrame({'Integer Feature': [0, 1, 2, 1],'Categorical Feature': ['socks', 'fox', 'socks', 'box']})
demo_df['Integer Feature'] = demo_df['Integer Feature'].astype(str)
pd.get_dummies(demo_df, columns=['Integer Feature', 'Categorical Feature']) # 显示给出想要编码的列
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder(sparse=False)
encoder.fit(demo_df)
encoder.transform(demo_df)
使用特征分箱(binning,也叫离散化,即 discretization)将其划分为多个特征。
bins = np.linspace(-3, 3, 11) # 生成[-3,3]的10个区间
which_bin = np.digitize(X, bins=bins) # 返回相同形状的保存X位于区间的数组,区间从1计数
分箱特征对基于树的模型通常不会产生更好的效果,因为这种模型可以学习在任何位置划分数据。从某种意义上来看,决策树可以学习如何分箱对预测这些数据最为有用。
对于特定的数据集,如果有充分的理由使用线性模型——比如数据集很大、维度很高,但有些特征与输出的关系是非线性的——那么分箱是提高建模能力的好方法。
想要丰富特征表示,特别是对于线性模型而言,可以添加原始数据的交互特征(interaction feature)和多项式特征(polynomial feature)。
X_combined = np.hstack([X, X_binned]) # 将原特征和分箱编码后的特征作为输入特征,横向拼接
X_product = np.hstack([X_binned, X * X_binned])
from sklearn.preprocessing import PolynomialFeatures
# 默认的"include_bias=True"添加恒等于1的常数特征
# degree是针对所有特征
poly = PolynomialFeatures(degree=10, include_bias=False)
X = np.array([1,2]).reshape(-1,1)
poly.fit(X)
X_poly = poly.transform(X)
可以通过调用 get_feature_names 方法来获取特征的语义,给出每个特征的指数
poly.get_feature_names()
大部分模型都在每个特征(在回归问题中还包括目标值)大致遵循高斯分布时表现最好。
虽然基于树的模型只关注特征的顺序,但线性模型和神经网络依赖于每个特征的尺度和分布。
对于回归问题。log 和 exp 函数可以帮助调节数据的相对比例,从而改进线性模型或神经网络的学习效果。
在处理具有周期性模式的数据时,sin 和 cos 函数非常有用。
上图这种类型的数值分布(许多较小的值和一些非常大的值)在实践中非常常见。一般对数据集做如下变换:
X_train_log = np.log(X_train + 1)
X_test_log = np.log(X_test + 1)
变换之后,数据分布的不对称性变小,也不再有非常大的异常值。
分箱、多项式和交互项都对模型在给定数据集上的性能有很大影响,对于复杂度较低的模型更是这样,比如线性模型和朴素贝叶斯模型。
基于树的模型通常能够自己发现重要的交互项,大多数情况下不需要显式地变换数据。
其他模型,比如 SVM、最近邻和神经网络,有时可能会从使用分箱、交互项或多项式中受益,但其效果通常不如线性模型那么明显。
在添加新特征或处理一般的高维数据集时,最好将特征的数量减少到只包含最有用的那些特征,并删除其余特征。这样会得到泛化能力更好、更简单的模型。
基本策略 | 英文名 |
---|---|
单变量统计 | univariate statistics |
基于模型的选择 | model-based selection |
迭代选择 | iterative selection |
将遮罩(特征选择结果)可视化的函数:plt.matshow(mask.reshape(1,-1), cmap=‘gray_r’)
在单变量统计中,我们计算每个特征和目标值之间的关系是否存在统计显著性,然后选择具有最高置信度的特征。
对于分类问题,这也被称为方差分析(analysis of variance,ANOVA)。这些测试的一个关键性质就是它们是单变量的(univariate),即它们只单独考虑每个特征。
计算阈值的方法:
方法 | 使用说明 |
---|---|
SelectKBest | 选择固定数量的 k 个特征 |
SelectPercentile | 选择固定百分比的特征 |
from sklearn.feature_selection import SelectPercentile
select = SelectPercentile(percentile=50)
select.fit(X_train, y_train)
基于模型的特征选择使用一个监督机器学习模型来判断每个特征的重要性,并且仅保留最重要的特征。
用于特征选择的监督模型不需要与用于最终监督建模的模型相同。
特征选择模型需要为每个特征提供某种重要性度量,以便用这个度量对特征进行排序。
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier
select = SelectFromModel(RandomForestClassifier(n_estimators=100, random_state=42), threshold="median") # 用包含 100 棵树的随机森林分类器来计算特征重要性,参数 threshold 指定阈值
在迭代特征选择中,将会构建一系列模型,每个模型都使用不同数量的特征。有两种基本方法:
开始时没有特征,然后逐个添加特征,直到满足某个终止条件;
从所有特征开始,然后逐个删除特征,直到满足某个终止条件。
方法: 递归特征消除(recursive feature elimination,RFE)
使用说明:它从所有特征开始构建模型,并根据模型舍弃最不重要的特征,然后使用除被舍弃特征之外的所有特征来构建一个新模型,如此继续,直到仅剩下预设数量的特征。
from sklearn.feature_selection import RFE
select = RFE(RandomForestClassifier(n_estimators=100, random_state=42), n_features_to_select=40)
不确定何时选择使用哪些特征作为机器学习算法的输入,那么自动化特征选择可能特别有用。
特征选择有助于减少所需要的特征数量,加快预测速度,或允许可解释性更强的模型。在大多数现实情况下,使用特征选择不太可能大幅提升性能,但它仍是特征工程工具箱中一个非常有价值的工具。