特征选择
特征选择是机器学习任务中的关键步骤。在本课程中,我们将介绍一些常用的特征选择方法。
什么是特征选择?
在机器学习中,特征选择(feature selection)是指从原始特征集合中挑选出一组相关的特征(变量、预测因子等),用于模型构建。这是机器学习项目流程中的关键步骤,也是特征工程的一部分。进行特征选择的重要性体现在以下几个方面:
- 减少训练时间:训练时间与特征空间大小呈正相关。
- 避免维度灾难(curse of dimensionality)。
- 简化模型结构。
- 提高泛化能力,降低过拟合风险。
- 减少共线性问题,增强模型可解释性。
当你获得一个数据集(表格型数据)时,每一列代表一个特征,但并非所有列都具有实际价值或相关性。因此,花时间进行特征选择是值得的。使用特征选择技术的基本前提是:数据中包含一些冗余或无关的特征,这些特征可以在不显著损失信息的前提下被移除。
实现特征选择的方法有很多。sklearn 提供了多种函数来完成这一任务,我们将在接下来的部分中详细介绍。
移除低方差特征
某个特征的方差为零意味着什么?这意味着该特征在所有样本中取值相同,即该特征没有任何变化。换句话说,它不携带任何有效信息,对目标变量的预测毫无贡献。类似地,方差很低的特征所包含的信息量也很少,移除它们通常不会明显影响模型性能。
sklearn 提供了 VarianceThreshold 工具用于移除低方差特征。其中,threshold 参数允许你设定方差阈值。
1import sklearn.feature_selection as fs 2 3# X 是你的特征矩阵 4var = fs.VarianceThreshold(threshold=0.2) 5var.fit(X) 6X_trans = var.transform(X)
你可以尝试以下代码示例。可以看到,第一个特征只有一个值不同,但由于其整体方差低于阈值,因此整列被移除。
1import sklearn.feature_selection as fs 2import numpy as np 3 4X = np.array([[0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 1, 1], [0, 1, 0], [0, 1, 5 1]]) 6var = fs.VarianceThreshold(threshold=0.2) 7var.fit(X) 8X_trans = var.transform(X) 9print("The original data") 10print(X) 11print("The processed data by variance threshold") 12print(X_trans)
-
第3行创建了一个6行3列的矩阵。
-
第6行通过
VarianceThreshold创建了一个方差阈值对象,参数threshold=0.2表示将移除方差小于0.2的列。 -
可在第12行对比原始矩阵和变换后的矩阵。
选择K个最优特征
sklearn 提供了一个通用函数 SelectKBest,可以根据指定的评估指标选择表现最好的 k 个特征,你只需提供一个评分函数来定义该指标。幸运的是,sklearn 内置了一些常用的评分函数,包括:
f_classif:用于分类任务的标签与特征之间的ANOVA F值。mutual_info_classif:用于离散型目标变量的互信息。chi2:适用于非负特征的卡方统计量,常用于分类任务。f_regression:用于回归任务的标签与特征之间的F值。mutual_info_regression:用于连续型目标变量的互信息。SelectFpr:基于假阳性率检验的特征选择方法。
核心思想是:计算每个特征与目标变量之间的某种度量值,按得分排序后选出前 K 个最佳特征。
在下面的例子中,我们选用 f_classif 作为评分标准,并设置 K=3。
1import sklearn.datasets as datasets 2 3X, y = datasets.make_classification(n_samples=300, n_features=10, n_informative=4) 4# 选择 f_classif 作为评分标准,K 设为 3 5bk = fs.SelectKBest(fs.f_classif, 3) 6bk.fit(X, y) 7X_trans = bk.transform(X)
一个关键问题是:减少特征数量会对模型性能产生怎样的影响?在下例中,我们将比较使用不同 K 值选出的特征子集时,逻辑回归模型的表现差异。
如下图所示,当仅移除少量特征时,模型性能指标变化不大。
你可以运行以下代码进行实验,尝试修改生成数据集时的特征数量,或调整 K 的取值。
1import sklearn.feature_selection as fs 2import sklearn.datasets as datasets 3from sklearn.model_selection import train_test_split 4from sklearn.linear_model import LogisticRegression 5import sklearn.metrics as metrics 6import matplotlib.pyplot as plt 7 8X, y = datasets.make_classification(n_samples=500, 9 n_features=20, 10 n_informative=8, 11 random_state=42) 12 13f1_list = [] 14for k in range(1, 15): 15 bk = fs.SelectKBest(fs.f_classif, k) 16 bk.fit(X, y) 17 X_trans = bk.transform(X) 18 train_x, test_x, train_y, test_y = train_test_split(X_trans, 19 y, 20 test_size=0.2, 21 random_state=42) 22 lr = LogisticRegression() 23 lr.fit(train_x, train_y) 24 y_pred = lr.predict(test_x) 25 f1 = metrics.f1_score(test_y, y_pred) 26 f1_list.append(f1) 27 28fig, axe = plt.subplots(dpi = 300) 29axe.plot(range(1, 15), f1_list) 30axe.set_xlabel("best k features") 31axe.set_ylabel("F1-score") 32fig.savefig("output/img.png") 33plt.close(fig)
-
第8行使用
make_classification创建了一个分类数据集。 -
第14至26行为一个循环
for k in range(1, 15)。每次迭代传入不同的K值给SelectKBest,目的是观察不同K对模型性能的影响。在每次迭代中(第22–25行),都会构建、训练并评估一个逻辑回归模型,使用的特征数为当前K值对应的选中特征。评估结果(F1分数)被保存到列表f1_list中。 -
第28至33行绘制了不同
K值与其对应F1分数的关系图。
基于其他模型的特征选择
SelectFromModel 是一种元变换器(meta-transformer),可与任何训练后具有 coef_ 或 feature_importances_ 属性的估计器结合使用。本文我们主要关注基于树的模型。你可能记得,决策树是通过在单个特征上基于某种指标进行分裂的。根据这一过程,我们可以得到各个特征的重要性程度。这是树模型的一个特性,因此可以通过树模型了解不同特征对模型的贡献程度。
注意:文中提到的模型(GBDT)将在后续课程中详细讲解。
sklearn 提供了 SelectFromModel 来实现此类特征选择。从以下代码可以看出,第一个参数 gb 是一个 GBDT 模型,利用其 feature_importances_ 属性来进行特征筛选。树模型在特征选择方面表现优异。
1import sklearn.feature_selection as fs 2 3model = fs.SelectFromModel(gb, prefit=True) 4# X 是你的特征矩阵,X_trans 是转换后的特征矩阵 5X_trans = model.transform(X)
1import sklearn.feature_selection as fs 2import sklearn.datasets as datasets 3from sklearn.model_selection import train_test_split 4from sklearn.ensemble import GradientBoostingClassifier 5import sklearn.metrics as metrics 6 7X, y = datasets.make_classification(n_samples=500, 8 n_features=20, 9 n_informative=6, 10 random_state=21) 11 12gb = GradientBoostingClassifier(n_estimators=20) 13gb.fit(X, y) 14print("The feature importances of GBDT") 15print(gb.feature_importances_) 16 17model = fs.SelectFromModel(gb, prefit=True) 18X_trans = model.transform(X) 19print("The shape of original data is {}".format(X.shape)) 20print("The shape of transformed data is {}".format(X_trans.shape))
-
第7行创建了一个数据集。
-
第12行通过
GradientBoostingClassifier创建了一个 GBDT 模型,并在第13行进行了训练(fit)。 -
第15行输出了 GBDT 模型的特征重要性:数值越大,表示该特征越重要。
-
第17行展示了如何使用
SelectFromModel结合其他模型进行特征选择。只需传入已训练好的 GBDT 模型即可,prefit=True表示该模型已经完成训练。