Fork me on GitHub

特征选择方法及其不同应用场景

作者: 沐瑶 MLOps工程实践 稿

你是否曾坐在屏幕前思考过什么样的特征可以帮助你的机器学习模型更好地学习它的任务?你大概率有过这样的经历。数据预处理往往会消耗大量数据科学家和机器学习工程师的时间和精力,将数据准备好,输入学习算法是一项不小的壮举。

数据预处理Pipeline中的关键步骤之一是 特征选择 。你可能知道一句流行的格言:Garbage Input Garbage Output(根不正,苗不红)。你为模型提供的内容的重要性并不比模型本身弱。

在本文中,我们将:

  • 查看数据预处理Pipeline中与特征相关的其他任务中特征选择的位置
  • 并讨论它对任何想要成功的机器学习项目都很重要的多个原因。
  • 接下来,我们将介绍特征选择的不同方法,并讨论一些技巧,以改进其结果。
  • 然后,我们将对Boruta(最先进的特征选择算法)进行剖析,希望能找出一种巧妙的方法来组合不同的特征选择方法。
  • 我们还将研究如何在行业中利用特征选择。

01 什么是特征选择?

什么是特征选择?简而言之,它是选择用于训练机器学习模型的特征子集的过程。这就是特征选择。但同样重要的是要理解特征选择不是什么,它既不是特征提取/特征工程,也不是降维。

特征提取和特征工程这两个术语,都是描述基于领域知识从现有特征创建新特征过程的。这会产生比原来更多的特征,并且应该在特征选择之前完成。我们可以进行特征提取,得出许多潜在有用的特征,然后我们可以执行特征选择来选出确实可以提高模型性能的最佳子集。

降维是另一个概念了。它与特征选择有些相似,因为两者都旨在减少特征的数量。然而,他们在如何实现这一目标的方式上存在显著差异。特征选择选取保留原始特征的子集和丢弃其他特征;降维则是将原始特征投影到低维空间,从而创建一组全新的特征。如果需要,降维应该在特征选择后进行,但实际上,它俩并不会一起使用。

现在我们知道了什么是特征选择,以及它如何与其他与特征相关的数据准备任务相对应。但我们为什么需要它呢?

02 关于为什么需要特征选择的7个原因

主流的说法是,现代机器学习技术在没有特征选择的情况下表现良好。毕竟,一个模型应该能够知道哪些特征是无用的,并且应该关注其他特征,对吧?

这个推理在某种程度上是有道理的。理论上,线性模型可以为无用的特征赋予零权重,而基于树的模型应该很快学会不要对其进行分割。然而,在实践中,当输入不相关或冗余时,训练可能会出现许多问题——稍后详细介绍这两个术语。

除此之外,还有许多其他原因,还有许多其他原因可以解释为什么简单地将所有可用功能转储到模型中可能不是一个好主意。让我们看看七个最突出的例子。

1.不相关和冗余特征

有些特征可能与当下的问题无关。这意味着它们与目标变量无关,并且与模型要解决的任务完全无关。丢弃不相关的特征可以防止模型得到无关特征上可能携带的虚假相关性,从而避免过拟合。

然而,冗余特征是另一种解释。冗余意味着两个或多个特征共享相同的信息,并且除了一个以外,其他所有特征都可以安全地丢弃,但不会丢失信息。请注意,在存在其他相关特征时,重要特征也可能是冗余的。冗余特征应该删除,因为它们可能会在训练期间带来许多问题,例如线性模型中的多重共线性。

2. 维度诅咒 (维度之咒, Curse of Dimensionality

特征选择技术在具有许多特征但训练示例很少的场景中尤为重要。这种情况受到所谓的维度诅咒的影响:在高维空间中,每个训练示例都与其他所有示例相距甚远,因此模型无法学习任何有用的模式。解决方案是降低特征空间的维度,例如,通过特征选择。

3. 训练时间

特征越多,训练时间就越长。这种权衡的具体细节取决于所使用的特定学习算法,但在需要实时进行再训练的情况下,可能需要限制在几个最佳特征中进行。

4. 部署工作

特征越多,机器学习系统在生产中就越复杂。这会带来多种风险,包括但不限于高维护工作量、纠纷、未申报的使用者或纠正级联。

5. 可解释性

由于特征太多,我们失去了模型的可解释性。虽然对模型结果的解释并不总是主要的建模目标,但通常很重要,在某些受监管的领域,甚至可能是法律要求。

6. 奥卡姆剃刀定律(Occam’s Razor)

根据这个所谓的简约定律,只要性能相同,就应该优先选择较简单的模型,而不是较复杂的模型。这也与机器学习工程师的克星-过拟合有关。不太复杂的模型不太可能过拟合数据。

7. 数据-模型兼容性

最后,还有数据-模型兼容性问题。虽然原则上,方法应该是数据优先(这意味着收集和准备高质量的数据)然后选择对这些数据有效的模型,但现实生活可能会反过来。

你可能试图复制一篇特定的研究论文,或者你的老板可能建议你使用一个特定的模型。在这种模型优先方法中,您可能会被迫选择与要训练的模型兼容的特征。例如,许多模型无法处理数据中的缺失值。除非你很了解你的插补方法,否则你可能需要去掉不完整的特征。

03 特征选择的不同方法

所有不同的特征选择方法都可以分为四类,每个方法都有其优缺点。方法同时也分为无监督和有监督的方法。后者可以进一步分为包装器、过滤器和嵌入式方法。我们逐一讨论。

图片

特征选择方法

1. 无监督的特征选择方法

正如无监督学习是在未标记数据中寻找模式的学习类型一样,无监督特征选择方法是不使用任何标签的方法。换句话说,他们不需要访问机器学习模型的目标变量。

你可能会问,如果不分析某个特征与模型目标的关系,我们怎么能确定该特征对模型不重要呢?在某些情况下,这是可能的。我们可能希望放弃以下特征:

  • 方差为零或接近零。(几乎)不变的特征提供的信息很少,因此不相关。
  • 许多缺失值。虽然删除不完整的特征不是处理丢失数据的首选方法,但这通常是一个好的开始。如果丢失了太多条目,这可能是唯一明智的做法,因为这些特征可能无关紧要。
  • 高度多重共线性。多重共线性意味着不同特征之间的强相关性,这可能是冗余问题的信号。

无监督方法的实际实现

现在,我们来讨论一下无监督特征选择方法的实际实现。就像大多数其他机器学习任务一样,scikit-learn软件包很好地提供了特征选择,特别是其“sklearn.feature_selection”模块。然而,在某些情况下,人们需要联系其他地方。在这里,以及在本文的剩余部分,让我们用“X”表示一个数组或数据帧,所有潜在的特征都是列和行中的观察值,目标向量用“y”表示。

  • “sklearn.feature_selection.varianceThreshold”转换器将默认删除所有零方差特征。我们还可以传递一个阈值作为参数,使其删除方差低于阈值的特征。
from sklearn.feature_selection import VarianceThreshold
sel = VarianceThreshold(threshold=0.05)
X_selection = sel.fit_transform(X)
  • 为了删除缺失值的列,可以在数据框上使用 pandas 的“.dropna(axis=1)”方法。
X_selection = X.dropna(axis=1)
  • 要去除具有高度多重共线性的特征,我们首先需要对其进行测量。常见的多重共线性度量是方差通货膨胀因子或VIF。而它可以在statsmodels包中被实现。
from statsmodels.stats.outliers_influence import variance_inflation_factor
vif_scores = [variance_inflation_factor(X.values, feature)for feature in range(len(X.columns))]

按照惯例,VIF大于10的列被视为具有多重共线性,但如果看起来更合理,可以选择另一个阈值。

2. 包装器特征选择方法

包装器方法是指一系列有监督的特征选择方法,它使用一个模型对不同的特征子集进行评分,最终选择最佳的特征。每个新子集用于训练一个模型,该模型的性能随后在保持集上进行评估。选择产生最佳模型性能的特征子集。包装器方法的一个主要优点是,它们往往为特定类型的模型提供性能最佳的特征集。

然而,与此同时,它也有局限性。包装器方法可能会过度适用于模型类型,如果希望使用不同的模型尝试它们,则它们生成的特征子集可能不会泛化。

包装器方法的另一个显著缺点是计算量大。他们需要训练大量的模型,这可能需要一些时间和计算能力。

流行的包装方法包括:

  • 反向选择 ,我们从包含所有可用特征的完整模型开始。在随后的迭代中,我们一次删除一个特征,始终是在模型性能度量中获得最大增益的特哼,直到我们达到所需的特征数量。
  • 正向选择 ,其工作方向相反:我们从零特征的空模型开始,一次添加一个,以最大限度地提高模型的性能。
  • 递归特征消除(RFE) ,在逻辑上类似于反向选择。它还从一个完整的模型开始,并逐个迭代地消除特征。不同之处在于选择要丢弃的特征的方式。RFE根据从模型中提取的特征重要性做出决策,而不是依赖于来自保持集的模型性能度量。这可以是线性模型中的特征权重、基于树的模型中的杂质减少或置换重要性(适用于任何模型类型)。

包装器方法的实际实现

在包装方法方面,scikit-learn为我们提供了帮助:

  • 使用SequentialFeatureSelector转换器可以实现向后和向前特征选择。例如,为了在正向选择中使用k-Nearest-Neighbor分类器作为评分模型,我们可以使用以下代码片段:
from sklearn.feature_selection import SequentialFeatureSelector


knn = KNeighborsClassifier(n_neighbors=3)
sfs = SequentialFeatureSelector(knn, n_features_to_select=3, direction=”forward”)
sfs.fit(X, y) 
X_selection = sfs.transform(X)
  • 递归特征消除是以非常相似的方式实现的。下面是基于支持向量分类器的特征重要性实现RFE的片段。
from sklearn.feature_selection import RFE
svc = SVC(kernel="linear")
rfe = RFE(svc, n_features_to_select=3)
rfe.fit(X, y) 
X_selection = rfe.transform(X)

3. 过滤器特征选择方法

有监督家族的另一个成员是过滤方法。它们可以被认为是包装器的一种更简单、更快的替代品。

为了评估每个特征的有用性,他们只分析其与模型目标的统计关系,使用相关性或相互的信息等度量作为模型性能度量的代理。

过滤方法不仅比包装器快,而且由于它们与模型无关,因此它们也更通用;它们不会对任何特定的算法进行过度匹配。它们也很容易解释:如果一个特征与目标没有统计关系,它就会被丢弃。

然而,另一方面,过滤方法有一个主要缺点。他们分别单独查看每个特征,评估其与目标的关系。这使得他们很容易放弃一些有用的特征,而这些特征本身是目标的弱预测因子,但与其他特征结合后会为模型增加很多价值。

过滤器方法的实际实现

现在让我们看看各种过滤方法的实现。这些将需要更多的粘合代码(Glue Code)来实现。首先,我们需要计算每个特征和目标之间所需的相关性度量。然后,我们将根据结果对所有特征进行排序,并在相关性最强的特征中保留所需的数量(top-K或top-30%)。

幸运的是,scikit-learn提供了一些实用程序来帮助这项工作。

  • 为了保持与目标变量皮尔逊相关性(Pearson Correlation)最强的前两个特征,我们可以运行:
from sklearn.feature_selection import r_regression, SelectKBest
X_selection = SelectKBest(r_regression, k=2).fit_transform(X, y)
  • 同样,为了保留前30%的特征,我们可以运行:
from sklearn.feature_selection import r_regression, SelectPercentile
X_selection = SelectPercentile(r_regression, percentile=30).fit_transform(X, y)

“SelectKBest”和“SelectPercentile”方法也适用于自定义或非scikit学习相关性度量,只要它们返回的向量长度等于特征数。每个特征的数字则表示了其与目标关联的强度。现在让我们看看如何计算所有不同的相关性度量(我们稍后将讨论它们的含义以及何时选择)。

  • scipy软件包中提供了斯皮尔曼等级相关性(Spearman’s Rho)、Kendall Tau相关性和二列相关性(point-biserial correlation)。下面的过程是如何获得X中每个特性的值。
from scipy import stats
rho_corr = [stats.spearmanr(X[:, f], y).correlation for f in range(X.shape[1])]
tau_corr = [stats.kendalltau(X[:, f], y).correlation for f in range(X.shape[1])]
pbs_corr = [stats.pointbiserialr(X[:, f], y).correlation for f in range(X.shape[1])]
  • Chi-Squared、互信息法(Mutual Information)和方差分析F-score都在scikit-learn中。请注意,相互信息有一个单独的实现,具体取决于目标是否是名义(nominal)型的。
from sklearn.feature_selection import chi2
from sklearn.feature_selection import mutual_info_regression
from sklearn.feature_selection import mutual_info_classif
from sklearn.feature_selection import f_classif
chi2_corr = chi2(X, y)[0]
f_corr = f_classif(X, y)[0]
mi_reg_corr = mutual_info_regression(X, y)
mi_class_corr = mutual_info_classif(X, y)
  • Cramer的V可以从最近的scipy版本(1.7.0或更高版本)获得。
from scipy.stats.contingency import association
v_corr = [association(np.hstack([X[:, f].reshape(-1, 1), y.reshape(-1, 1)]), method="cramer") for f in range(X.shape[1])]

4. 嵌入式特征选择方法

我们将讨论的特征选择的最后一种方法是将其嵌入学习算法本身。这种方法的目的是结合两个方面的优点:过滤器的速度,同时从包装器中获取特定模型的最佳子集。

嵌入式方法的实际实现

最主要的例子是LASSO回归。它基本上只是正则化线性回归,其中特征权重在损失函数中收缩为零。因此,许多特征最终的权重为零,这意味着在模型中,它们将被丢弃,而其他具有非零权重的特征将包含在内。

嵌入式方法的问题是,没有那么多内置特征选择的算法。与LASSO接近的另一个例子是计算机视觉的:具有瓶颈层的自动编码器迫使网络忽略图像的一些最无用的特征,并将注意力集中在最重要的特征上。除此之外,没有多少有用的例子。

04 过滤器特征选择方法:

有用的方法和小技巧

正如我们所看到的,包装器方法速度慢,计算量大,并且特定于模型,而且嵌入式方法不多。因此,过滤器通常是特征选择方法的首选系列。

同时,使用这种方法,是要求使用者要有最专业的知识,并且对细节有一定的关注。虽然嵌入式方法是开箱即用的,包装器实现起来相当简单(特别是当只调用scikit学习函数时),但过滤器需要一些统计复杂性。现在,我们详细讨论一下过滤器方法。

过滤器方法需要评估每个特征和目标之间的统计关系。虽然听起来很简单,但事实并非如此。衡量两个变量之间关系的统计方法有很多。为了知道在特定情况下选择哪一个,我们需要回想一下我们的第一个STATS101类,并复习一下数据测量级别。

1. 数据测量级别

简而言之,变量的测量级别描述了数据的真实含义以及对这些数据有意义的数学运算类型。有四个测量级别:定类(Nominal)、定序(Ordinal)、定距(Interval)、定比(Ratio)。

图片

数据测量级别

  • 定类,例如颜色(“红色”、“绿色”或“蓝色”)在值之间没有顺序;他们只是根据这些数据对观察结果进行分组。
  • 定序,例如教育水平(“初级”、“中级”、“高等”)表示顺序,但不表示特定级别之间的差异(我们不能说“初级”和“中级”之间的差异与“中级”和“高等”之间的区别相同)。
  • 定距,例如温度(摄氏度),使间隔保持相等(25到20度之间的差值与30到25度之间的差相同)。
  • 最后,定比(如美元价格)的特征是一个有意义的零,这允许我们计算两个数据点之间的比率:我们可以说4美元是2美元的两倍。

为了选择正确的统计工具来测量两个变量之间的关系,我们需要考虑它们的测量水平。

2. 测量各种数据类型的相关性

当我们比较的两个变量,即特征和目标,都是间隔(Interval)或比率(Ratio)时,我们可以使用最流行的相关性度量: 皮尔逊相关性 (Pearson Correlation) ,也称为 皮尔逊r

这很好,但皮尔逊相关性有两个缺点:它假设两个变量都是正态分布的,并且只测量它们之间的线性相关性。当相关性为非线性时,皮尔逊r将无法检测到它,即使它真的很强。

您可能听说过Alberto Cairo编译的Datasaurus数据集。它由13对变量组成,每个变量都具有相同的非常弱的Pearson相关性,即-0.06。当一旦我们绘制了它们,它们很快就会变得很明显。所以尽管是以非线性的方式,但是这些变量对实际上具有很强的相关性。

图片

Alberto Cairo的Datasaurus数据集

当预期存在非线性关系时,应考虑皮尔逊相关性的替代方案。最受欢迎的两个是:

1. 斯皮尔曼等级相关性(Spearman’s rank correlation,Spearman’s Rho)

对于比率/间隔变量,斯皮尔曼等级相关是皮尔逊相关的替代方法。顾名思义,它只查看等级值,即根据变量中特定数据点的相对位置比较两个变量。它能够捕获非线性关系,但由于只考虑排名而不是确切的数据点,我们丢失了一些信息。

2. Kendall等级相关性(Kendall rank correlation,Kendall Tau)

另一个基于等级的相关性度量是Kendall等级相关性。它在意义上与Spearman的相关性相似,但形式稍有不同(Kendall的计算基于一致和不一致的值对,而Spearman是基于偏差的计算)。Kendall通常被认为对数据中的异常值更为稳健。

如果所比较的变量中至少有一个是序数类型的,则可以使用Spearman或Kendall等级相关性。由于序数数据只包含等级信息,因此它们都是完美的拟合,而皮尔逊的线性相关几乎没有用处。

另一种情况是两个变量都是定类变量(nominal)。在这种情况下,我们可以从几个不同的相关性度量中进行选择:

  • Cramer的V系数 ,它将两个变量之间的关联捕获为从0(无关联)到1(一个变量完全由另一个变量决定)的数字。
  • 卡方统计量 ,通常用于测试两个变量之间的相关性。缺乏依赖性表明该特定特征没有用处。
  • 互信息 ,是两个变量之间相互依赖的度量,旨在量化一个变量可以从另一个变量中提取的信息量。

选择哪一个?没有放之四海而皆准的答案。像往常一样,每种方法都有一些优点和缺点。众所周知,Cramer的V系数高估了关联的强度。互信息是一种非参数方法,需要较大的数据样本才能得出可靠的结果。最后,卡方不提供关系强度的信息,而只提供关系是否存在的信息。

我们已经讨论了这样的场景:我们比较的两个变量都是间隔或比率,当其中至少一个是序数时,以及当我们比较两个定类变量时。最后可能遇到的情况是比较定类变量和非定类变量。

在这种情况下,两种最广泛使用的相关性度量是:

  • 方差分析F-score ,当一个变量为连续变量而另一个为名义变量时的卡方等效值,
  • 点二列相关(Point-biserial Correlation) ——一种专门用于评估二进制变量和连续变量之间关系的相关度量。

再强调一次,没有一刀切的方法。F-score只是反映了线性关系,而点二列相关性做出了一些可能在实践中不成立的强正态性假设,破坏了其结果。

综上所述,在特定情况下应该选择哪种方法?希望下表的总结对这个问题有一定的帮助。

图片

不同方法的比较

05 Boruta:不需要人工投入

谈到特征选择,我们不得不提Boruta。早在2010年,当它以R包的形式发布首次时,它迅速成为一种革命性的特征选择算法。

为什么Boruta是一个游戏改变者?

到目前为止,我们讨论的所有其他方法都需要人类做出任意决定。无监督方法需要我们为特征删除设置方差或VIF阈值。包装器要求我们决定要提前保留的特征的数量。过滤器需要我们选择相关性度量和要保留的特征数量。嵌入式方法让我们选择正则化强度。Boruta不需要这些。

Boruta是一种简单但统计上很优雅的算法。它使用来自随机森林模型的特征重要性度量来选择特征的最佳子集,并通过引入两个极好的思路来实现。

  1. 首先,特征的重要性得分没有相互比较。相反,每个特征的重要性与其随机版本的重要性相竞争。为了实现这一点,Boruta随机排列每个特性,以构建其“影子”版本。

然后,在整个特征集上训练一个随机森林,包括新的阴影特征。阴影特征中的最大特征重要性用作阈值。在原始特征中,只有重要性高于此阈值的特征才能得分。换句话说,只有比随机向量更重要的特征才能获得分数。

此过程以迭代方式重复多次。由于每次随机排列都不同,因此阈值也不同,因此不同的特征可能会得分。多次迭代后,每个原始要素的名称都有一些点。

  1. 最后一步是根据每个特征的得分决定是保留还是放弃。以下是Boruta的另一个极好的思路:我们可以使用二项式分布对分数进行建模。

假设每次迭代都是一个单独的试验。如果在给定的迭代中对该特性进行评分,则投票决定保留该特性;如果没有,就投票放弃它。,我们事先不知道某个特征是否重要,因此该特征得分的预期试验百分比为50%。因此,我们可以用p=0.5的二项式分布来模拟得分的数量。如果我们的特征得分明显高于此值,则认为它很重要并予以保留。如果分数明显减少,则被视为无关紧要,并被丢弃。如果它在大约50%的试验中得分,它的状态尚未解决,但为了保守起见,我们可以保留它。

例如,如果我们让Boruta运行100次试验,每个特性的预期分数将是50。如果接近零,我们将放弃它,如果接近100,我们将保留它。

图片

Boruta 示例

Boruta在许多Kaggle比赛中都非常成功,一直值得一试。它还成功地用于预测建筑供暖能耗或预测空气污染。

有一个可以非常方便的Python包来实现Boruta,名为BorutaPy(现在是scikit-learn-contrib的一部分)。在该包的GitHub文件中,展示了使用Boruta可以很容易地进行特征选择。

06 选择哪种特征选择方法?

为自己打造一个投票选择器

我们讨论了许多不同的特征选择方法。每种方法都有自己的优缺点,做出自己的假设,并以不同的方式得出结论。选择哪一个?还是我们必须选择?在许多情况下,将所有这些不同的方法组合在一起,会使生成的特征选择器比其每个子部分更强大。

思路

这种方法是受到集成决策树的启发。在集成决策树中,包括随机森林和许多流行的梯度提升算法,让其中一个方法训练多个不同的模型,并让它们对最终预测进行决策。按照同样的思路,我们可以为自己打造一个投票选择器。

思路很简单:实现我们讨论过的几种特征选择方法。您的选择可能取决于时间、计算资源和数据度量级别等因素。只要运行尽可能多的不同方法就可以了。然后,对于每个特征,记下建议将此特征保留在数据集中的选择方法的百分比。如果超过50%的方法投票赞成保留,则保留它该特征,否则,请丢弃它。

这种方法背后的思想是,虽然一些方法可能由于其内在的偏见而对某些特征做出错误的判断,但多种方法的集合应该可以正确地获得有用的特征集。让我们看看如何在实践中实现它!

实现

让我们构建一个简单的投票选择器,它集成了三种不同的特征选择方法:

  1. 基于Pearson Correlation的滤波方法。
  2. 基于多重共线性的无监督方法。
  3. 包装器,递归特征消除法。

让我们看看这样的投票选择器可能是什么样子的。

先进行导入:

from itertools import compress
import pandas as pd
from sklearn.feature_selection import RFE, r_regression, SelectKBest
from sklearn.svm import SVR
from statsmodels.stats.outliers_influence import variance_inflation_factor

接下来,我们的VotingSelector类在init构造函数之上包含四个方法。其中三个实现了我们想要集成的三种特征选择技术:

  1. 用于pearson相关过滤的_select_pearson()
  2. 基于方差通货膨胀因子的无监督方法的_select_vif()用于
  3. RBF包装器的_select_rbf()

这些方法都以特征矩阵X和目标y作为输入。基于VIF的方法不会使用目标,但无论如何,我们都会使用这个参数来保持所有方法之间的接口一致,以便稍后在循环中调用它们。除此之外,每个方法都接受一个关键字参数字典,我们将使用它来传递依赖于方法的参数。解析输入后,每个方法都会调用我们之前讨论过的适当sklearn或statsmodels函数,以返回要保留的特征名称列表。

投票流程发生在select()方法中。在这里,我们简单地迭代三种选择方法,对于每个特征,我们记录根据该方法应该保留(1)还是丢弃(0)。最后,我们对这些投票采取平均值。对于每个特征,如果这个平均值大于0.5的投票阈值(这意味着至少有三分之二的方法投票决定保留一个特征),我们就保留它。

这是整个类的代码。

class VotingSelector():
   def __init__(self):
       self.selectors = {
           "pearson": self._select_pearson,
           "vif": self._select_vif,
           "rfe": self._select_rfe,
       }
       self.votes = None




   @staticmethod
   def _select_pearson(X, y, **kwargs):
       selector = SelectKBest(r_regression, k=kwargs.get("n_features_to_select", 5)).fit(X, y)
       return selector.get_feature_names_out()




   @staticmethod
   def _select_vif(X, y, **kwargs):
       return [
           X.columns[feature_index]
           for feature_index in range(len(X.columns))
           if variance_inflation_factor(X.values, feature_index) <= kwargs.get("vif_threshold", 10)
       ]




   @staticmethod
   def _select_rfe(X, y, **kwargs):
       svr = SVR(kernel="linear")
       rfe = RFE(svr, n_features_to_select=kwargs.get("n_features_to_select", 5))
       rfe.fit(X, y)
       return rfe.get_feature_names_out()




   def select(self, X, y, voting_threshold=0.5, **kwargs):
       votes = []
       for selector_name, selector_method in self.selectors.items():
           features_to_keep = selector_method(X, y, **kwargs)
           votes.append(
               pd.DataFrame([int(feature in features_to_keep) for feature in X.columns]).T
           )
       self.votes = pd.concat(votes)
       self.votes.columns = X.columns
       self.votes.index = self.selectors.keys()
       features_to_keep = list(compress(X.columns, self.votes.mean(axis=0) > voting_threshold))
       return X[features_to_keep]

让我们看看它在实践中的效果。我们将加载臭名昭著的波士顿住房数据,该数据内置于scikit-learn中。

from sklearn.datasets import load_boston
boston = load_boston()
X = pd.DataFrame(boston["data"], columns=boston["feature_names"])
y = boston["target"]

现在,运行特征选择非常简单:

vs = VotingSelector()
X_selection = vs.select(X, y)

结果,我们得到了只剩下三个特征的特征矩阵。

ZN CHAS RM
0   18.0 0.0 6.575 
1    0.0  0.0 6.421 
2    0.0  0.0 7.185 
3    0.0  0.0 6.998 
4    0.0  0.0 7.147 
.. ... ... ...
501 0.0 0.0 6.593 
502 0.0 0.0 6.120 
503 0.0 0.0 6.976 
504 0.0 0.0 6.794 
505 0.0 0.0 6.030 
[506 rows x 3 columns]

我们还可以通过打印和投票来了解我们的每种方法是如何投票的。

CRIM ZN INDUS CHAS NOX RM AGE DIS RAD TAX PTRATIO B LSTAT
pearson  0 1 0 1 0 1 0 1 0 0 0 1 0 
vif      1 1 0 1 0 0 0 0 0 0 0 0 0 
rfe      0 0 0 1 1 1 0 0 0 0 1 0 1

我们可能不太满意最初的13列中只剩下3列。不过,我们可以通过修改特定方法的参数轻松地减少选择上的限制。这可以通过简单地向select调用添加适当的参数来实现。

Pearson和RFE方法需要保留预定义数量的特征。默认值为5,但我们可能希望将其增加到8。我们还可以修改VIF阈值,即方差通胀因子的值,高于该值时,由于多重共线性,我们将放弃一个特征。按照惯例,这个阈值设置为10,但如果将其增加到15,则会保留更多特征。

vs = VotingSelector()
X_selection = vs.select(X, y, n_features_to_select=8, vif_threshold=15)

这样,我们还剩下七个特征。

CRIM ZN INDUS CHAS NOX RM AGE DIS RAD TAX PTRATIO B LSTAT
pearson 1 1 0 1 0 1 1 1 1 0 0 1 0 
vif      1 1 1 1 0 0 0 1 0 0 0 0 1 
Rfe      1 0 1 1 1 1 0 1 0 0 1 0 1

我们的VotingSelector类是一个简单但通用的模板,可以扩展到任意数量的特征选择方法。作为一种可能的扩展,您还可以将传递给select()的所有参数视为建模Pipeline的超参数,并对其进行优化,以最大限度地提高下游模型的性能。

07 大型科技公司的特征选择

像GAFAM(谷歌、苹果、Meta(原Facebook)、亚马逊、微软)这样的有数千种机器学习模型的大型科技公司,是特征选择在实际应用绝佳的场地。让我们看看这些科技巨头对此有何评论!

1. 谷歌

ML规则是谷歌机器学习最佳实践的便捷汇编。谷歌的工程师在报告中指出,模型可以学习的参数数量大致与它可以访问的数据量成正比。因此,我们拥有的数据越少,我们需要丢弃的特征就越多。他们粗略的准则(源自基于文本的模型)是使用十几个特征和1000个训练示例,或者使用10万个特征和1000万个训练示例。

文档中的另一个关键点涉及模型部署问题,这也会影响特征选择。

  • 首先,您要选择的特征集可能会受到推理时生产中可用特征的限制。如果模型上线时没有很好的特征,您可能会被迫放弃训练。
  • 其次,某些特征可能容易出现数据漂移。虽然处理漂移很复杂,但有时最好的解决方案可能是从模型中完全删除有问题的特征。

2. Facebook(Meta)

2019年,Facebook(Meta)提出了自己的适合神经网络的特征选择算法,以便在训练大规模模型时节省计算资源。为了尽可能高效地对Facebook News Feed数据集中相关条目进行排序,同时使用更少的维度输入,他们在Facebook News Feed数据集上试用了这个算法。你可以在这里https://research.facebook.com/publications/feature-selection-for-facebook-feed-ranking-system-via-a-group-sparsity-regularized-training-algorithm/ 阅读所有相关信息。

参考资料

  1. https://scikit-learn.org/stable/modules/feature_selection.html
  2. https://github.com/scikit-learn-contrib/boruta_py/blob/master/README.md
  3. https://martin.zinkevich.org/rules_of_ml/rules_of_ml.pdf

来源

本文翻译自neptune.ai

原文链接为:

https://neptune.ai/blog/feature-selection-methods

图片

欢迎关注公众号,获取最新的MLOps信息


本文地址:https://www.6aiq.com/article/1666361141615
本文版权归作者和AIQ共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出