特征选择应该在训练测试拆分之前还是之后完成?
Posted
技术标签:
【中文标题】特征选择应该在训练测试拆分之前还是之后完成?【英文标题】:Should Feature Selection be done before Train-Test Split or after? 【发布时间】:2019-10-11 23:16:39 【问题描述】:实际上,这个问题的可能答案有 2 个事实相互矛盾:
传统的答案是在拆分后执行,因为如果之前执行过,可能会从测试集中泄漏信息。
矛盾的答案是,如果仅使用从整个数据集中选择的训练集进行特征选择,那么特征选择或特征重要性得分顺序很可能会随着 Train_Test_Split 的 random_state 的变化而动态变化.如果任何特定工作的特征选择发生变化,则无法进行特征重要性的泛化,这是不可取的。其次,如果仅使用训练集进行特征选择,则测试集可能包含某些实例集,这些实例与仅在训练集上进行的特征选择相矛盾/矛盾,因为未分析整体历史数据。此外,只有在给定一组实例而不是单个测试/未知实例时,才能评估特征重要性分数。
【问题讨论】:
这篇文章的标题很清楚,但不清楚第 2 点在说什么。如果可以将第二点重写为更清晰,将投票赞成。 这个问题不应该关闭,还是归档?因为它是关于 ML 方法而不是编程。我看到很多 ML 问题经常关闭... 【参考方案1】:传统的答案#1在这里是正确的;矛盾的答案 #2 中的论点实际上并不成立。
当有这样的疑问时,想象一下在模型拟合过程中您只是没有在任何测试集中有任何访问权(包括特征重要性)是有用的;您应该将测试集视为字面上看不到的数据(并且,由于看不到,它们不能用于特征重要性分数)。
Hastie 和 Tibshirani 很久以前就已经明确争论过执行此类过程的正确和错误方式;我在一篇博文中总结了这个问题,How NOT to perform feature selection! - 虽然讨论是关于交叉验证,但很容易看出这些论点也适用于训练/测试分割的情况。
在您的矛盾答案 #2 中真正成立的唯一论点是
整体历史数据未分析
尽管如此,这是为了拥有一个独立的测试集进行性能评估所付出的必要代价,否则,按照相同的逻辑,我们也应该使用测试集进行训练不是吗?
总结:测试集仅用于评估模型的性能,它不应该用于模型构建的任何阶段,包括特征选择。
更新(在 cmets 之后):
测试集中的趋势可能不同
这里的一个标准(但通常是隐含的)假设是训练集和测试集在质量上相似;正是由于这个假设,我们觉得可以只使用简单的随机拆分来获得它们。如果我们有理由相信我们的数据会以显着方式发生变化(不仅在训练和测试之间,而且在模型部署期间也是如此),那么整个基本原理就会失效,并且需要完全不同的方法。
此外,这样做可能会出现过度拟合
过拟合的唯一某些方法是在管道期间以任何方式使用测试集(包括用于特征选择,正如您所建议的那样)。可以说,链接的博客文章有足够的论据(包括引号和链接)来令人信服。经典例子,The Dangers of Overfitting or How to Drop 50 spots in 1 minute中的证词:
随着比赛的进行,我开始使用更多的特征选择和预处理。但是,我在交叉验证方法中犯了一个经典错误,没有将其包含在交叉验证折叠中(有关此错误的更多信息,请参阅this short description 或The Elements of Statistical Learning 中的第 7.10.2 节)。这导致了越来越乐观的交叉验证估计。
正如我已经说过的,虽然这里的讨论是关于交叉验证,但应该不难说服自己它也完全适用于训练/测试用例。
特征选择应该以提高模型性能的方式完成
好吧,当然,没有人可以反驳这一点!关键是 - 我们在谈论哪个确切的性能?因为上面引用的 Kaggler 在他前进的过程中确实得到了更好的“性能”(应用错误的程序),直到他的模型面临真正的未见数据(关键时刻!),不出所料失败。
诚然,这不是微不足道的事情,可能需要一些时间才能将它们内化(正如 Hastie 和 Tibshirani 所证明的那样,甚至有研究论文执行了该程序,这绝非巧合错)。在此之前,我为确保您的安全的建议是:在模型构建的所有阶段(包括特征选择),假装您无权访问测试集,并且仅在您需要评估最终模型的性能时才可用。
【讨论】:
如果特征选择是通过只考虑训练集实例的趋势来完成的,那么可能不只是将特征选择强加于测试集,因为测试集中的趋势可能是不同的。此外,这样做时,过度拟合的可能性很高。相反,如果整个数据集仅用于选择重要特征并保持模型训练过程独立于测试集(未训练的模型初始化,然后仅使用具有选定特征的训练集进行训练),则有助于实现具有更高的统计潜力。 保留一个单独的测试集的用处是分析模型的性能对吧?因此,假设机器学习模型,M1(随机森林分类器)使用训练集进行训练,并根据特征重要性得分,在训练集和测试集上进行特征选择。然后使用训练集训练另一个机器学习模型 M2(AdaBoost 分类器),并使用测试集评估其性能。那么,既然我们对模型 M1 的性能评估不感兴趣,那么测试集对训练模型 M1 的效用是什么......? 总结:特征选择和模型性能应该相互独立。换句话说,基于模型性能,特征选择并不是要完成的。相反,应该以增强模型性能的方式进行特征选择。 实际上,我不相信您关于过度拟合的论点,如果仅在训练集上进行特征选择,可能会发生这种情况,并且有时是不可避免的。 另一点是:假设我有一个数据集,D。我想通过删除数据集中的行或列来执行一些过滤。只是过滤或数据清理,例如删除不需要的功能......在这种情况下,我肯定可以使用整个数据集进行评估吗?在删除一些特征之后,我可以拆分数据集并在训练集上使用完全未经训练的模型并在测试集上进行验证。未经训练的模型没有关于测试集的信息。在数据集过滤步骤中,我认为没有涉及这样的训练集或测试集......【参考方案2】:实际上并不难演示为什么使用整个数据集(即在拆分到训练/测试之前)来选择特征会导致您误入歧途。这是一个使用 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% 的准确率,但实际上它的表现只是随机猜测......
那么,现在让我们看看正确过程(即先拆分,然后仅根据training集选择特征):
# 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
在这种情况下,测试精度 0f 0.528 足够接近理论上预测的 0.5 之一(即实际上是随机猜测)。
感谢 Jacob Schreiber 提供了这个简单的想法(检查所有 thread,它包含其他有用的示例),尽管与您在此处询问的上下文略有不同(交叉验证):
【讨论】:
以上是关于特征选择应该在训练测试拆分之前还是之后完成?的主要内容,如果未能解决你的问题,请参考以下文章
应该在神经网络中的 dropout 层之前还是之后进行特征嵌入?
如何使用内置的张量流方法对特征和标签张量执行 sklearn 风格的训练测试拆分?