特征选择应该在训练测试分裂之前还是之后进行?

28

实际上,有两个相互矛盾的事实可以回答这个问题:

  1. 传统答案是在数据分割后进行特征选择,因为如果先进行特征选择会从测试集中泄露信息。

  2. 另一方面,如果仅使用从整个数据集中选出的训练集进行特征选择,则特征选择或特征重要性排序可能会随着Train_Test_Split的random_state变化而动态改变。如果任何特定工作的特征选择发生变化,则无法进行特征重要性的概括,这是不可取的。其次,如果仅针对训练集进行特征选择,则测试集可能包含某些与仅在训练集上进行特征选择相矛盾的实例,因为没有分析整体历史数据。此外,只有在给定一组实例而不是单个测试/未知实例时才能评估特征重要性分数。


1
这篇文章的标题很清楚,但第二点说的不是很明白。如果第二点能够重写得更清晰,我会点赞的。 - benso8
这个问题是否应该关闭或存档?因为它涉及到机器学习方法而不是编程。我经常看到许多关于机器学习的问题被关闭... - Hamish Gibson
2个回答

35

证明在选择特征时使用整个数据集(即在拆分为训练集/测试集之前)可能会导致错误并不是真正困难的事情。以下是使用Python和scikit-learn进行随机虚拟数据的一个示例:

import numpy as np
from sklearn.feature_selection import SelectKBest
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# random data:
X = np.random.randn(500, 10000)
y = np.random.choice(2, size=500)

由于我们的数据X是随机的(500个样本,10,000个特征),而我们的标签y是二进制的,因此我们预计在这种情况下我们永远不应该能够超过基线准确率,即约为0.5或50%。让我们看看当我们在分割之前使用整个数据集进行特征选择的错误过程时会发生什么:

selector = SelectKBest(k=25)
# first select features
X_selected = selector.fit_transform(X,y)
# then split
X_selected_train, X_selected_test, y_train, y_test = train_test_split(X_selected, y, test_size=0.25, random_state=42)

# fit a simple logistic regression
lr = LogisticRegression()
lr.fit(X_selected_train,y_train)

# predict on the test set and get the test accuracy:
y_pred = lr.predict(X_selected_test)
accuracy_score(y_test, y_pred)
# 0.76000000000000001

哇!我们在一个二元问题上获得了76%的测试准确率,根据统计学的基本定律,我们应该得到非常接近50%的结果!有人要打电话给诺贝尔奖委员会,快点...

...当然,事实是我们能够获得如此高的测试准确率只是因为我们犯了一个非常基本的错误:我们错误地认为我们的测试数据是未知的,但实际上,在特征选择期间,模型构建过程已经看到了测试数据,尤其是在这里:

X_selected = selector.fit_transform(X,y)

我们实际上可以处于多么糟糕的状态?嗯,很容易看出来:假设在我们完成模型并部署它(期望新的未见数据在实践中与76%的准确性相似)后,我们获得了一些真正新的数据:

X_new = np.random.randn(500, 10000)

当然,在这里没有任何质的变化,即新趋势或其他内容 - 这些新数据是由完全相同的基础程序生成的。假设我们恰好知道真实标签y,并按上述方式生成:

y_new = np.random.choice(2, size=500)
我们的模型在面对这些真正未曾涉及的数据时,将会表现如何?检查起来并不困难:

我们的模型在面对这些真正未曾涉及的数据时,将会表现如何?检查起来并不困难:

# select the same features in the new data
X_new_selected = selector.transform(X_new)
# predict and get the accuracy:
y_new_pred = lr.predict(X_new_selected)
accuracy_score(y_new, y_new_pred)
# 0.45200000000000001

好的,事实如此:我们把我们的模型送去参加比赛,认为它能够获得约76%的准确率,但实际上它只像随机猜测一样表现...


因此,现在让我们看看正确的程序(即首先进行拆分,并仅基于训练集选择特征):

# split first
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
# then select features using the training set only
selector = SelectKBest(k=25)
X_train_selected = selector.fit_transform(X_train,y_train)

# fit again a simple logistic regression
lr.fit(X_train_selected,y_train)
# select the same features on the test set, predict, and get the test accuracy:
X_test_selected = selector.transform(X_test)
y_pred = lr.predict(X_test_selected)
accuracy_score(y_test, y_pred)
# 0.52800000000000002

在这种情况下(即实际上是随机猜测),测试准确度为0.528,接近理论预测值0.5。

向Jacob Schreiber致敬,他提供了一个简单的想法(查看所有的线程,其中包含其他有用的示例),尽管在一个略微不同的上下文中(交叉验证):

输入图像描述


21
传统答案#1在这里是正确的;反驳答案#2中的论点实际上并不成立。
当有这样的疑问时,想象一下在模型拟合过程中(包括特征重要性),您根本没有任何测试集的访问权限,将非常有用。您应该将测试集视为“从未见过”的数据(因为未被看到,所以它们不能用于特征重要性评分)。
Hastie和Tibshirani很久以前就明确阐述了执行此类过程的正确和错误方式;我在博客文章中总结了这个问题,如何不进行特征选择!-尽管讨论是关于交叉验证的,但可以很容易地看出这些论点也适用于训练/测试拆分的情况。
您反驳答案#2中唯一成立的论点是:

整体历史数据没有被分析

然而,这是为了拥有独立的测试集进行性能评估所必须付出的代价,否则,按照同样的逻辑,我们也应该使用测试集进行训练,不是吗?
总结:测试集仅用于评估模型性能,不应在任何模型构建阶段中使用,包括特征选择。
更新(评论后):
引用:“测试集中的趋势可能不同。”
一个标准(但常常是隐含的)假设是训练集和测试集在质量上相似;正是由于这个假设,我们才觉得可以使用简单的随机分割来获取它们。如果我们有理由相信我们的数据发生了显著变化(不仅是在训练和测试之间,而且在模型部署期间也是如此),整个基础就会崩溃,需要完全不同的方法。
引用:“另外,这样做有很高的过度拟合的可能性。”
唯一确定的过度拟合方式是在管道中以任何方式使用测试集(包括您建议的特征选择)。可以说,链接的博客文章有足够的论据(包括引用和链接)来令人信服。经典例子,过度拟合的危险或如何在1分钟内下降50个名次中的证言。
随着比赛的进行,我开始使用更多的特征选择和预处理。然而,在我的交叉验证方法中,我犯了经典错误,没有将其包含在交叉验证折叠中(有关此错误的更多信息,请参见这个简短的描述统计学习基础第7.10.2节)。这导致了越来越乐观的交叉验证估计。
正如我已经说过的,尽管这里讨论的是交叉验证,但很容易让您相信它也完美地适用于训练/测试案例。
特征选择应该以提高模型性能的方式进行。
当然没有人会反对这个观点!问题在于——我们正在谈论哪种确切的表现?因为上述Kaggler确实在进行(错误的)操作时得到了更好的“表现”,直到他的模型面临真正的未知数据(真相时刻!),并且不出所料地失败了。
承认,这不是琐碎的东西,可能需要一些时间才能内化它们(正如Hastie和Tibshirani所证明的那样,甚至有研究论文在其中错误地执行了该过程)。在此期间,我建议您保持安全:在模型构建的所有阶段(包括特征选择)中,假装您根本没有访问测试集,并且只有在需要评估最终模型的性能时才可用。

保留单独的测试集的实用性在于分析模型的性能,对吧?因此,假设使用训练集训练了一个机器学习模型M1(随机森林分类器),并基于特征重要性得分,在训练集和测试集上进行特征选择。然后,使用训练集训练另一个机器学习模型M2(AdaBoost分类器),并使用测试集评估其性能。那么,对于经过训练的模型M1来说,测试集的实用性是什么呢?因为我们不关心模型M1的性能评估... - Navoneel Chakrabarty
总之,特征选择和模型性能应该相互独立。换句话说,基于模型性能,并不意味着要进行特征选择。相反,应该以提高模型性能为目的进行特征选择。 - Navoneel Chakrabarty
实际上,特征选择是数据准备的一步,如此解释:https://www.analyticsindiamag.com/get-started-preparing-data-machine-learning/ - Navoneel Chakrabarty
@NavoneelChakrabarty 无限扩展问题并提供新的例子和子情况既不是高效的,也不符合SO的工作方式:你已经被一位在机器学习领域有着可靠记录的SO用户给出了充分的论据、证据和建议;你可以选择忽略它,但自己要承担风险... - desertnaut
@NavoneelChakrabarty,我既感到抱歉又感到困惑,你会有这样的感觉;如果你认为提供这样的答案(顺便说一下,这需要时间)是“极其不恰当”的,或者这次交流是“不健康的”,或者你认为“健康”就是在明显已经知道答案的情况下提问(再次强调,这不是SO的工作方式),那我们可以同意不同意见。无法了解你的背景(与我的公开显示相反),但如果你真的认为博客文章可以构成任何类型的“证明”,那么祝你好运(是的,我确实使用了一篇博客文章,但只是作为附加证据,而不是主要证据)... - desertnaut
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接