17

实际上,有两个事实相互矛盾,这些事实是该问题的可能答案:

  1. 传统的答案是在拆分后执行,因为如果之前执行过,可能会从测试集中泄漏信息。

  2. 矛盾的答案是,如果仅使用从整个数据集中选择的训练集进行特征选择,那么特征选择或特征重要性得分顺序很可能会随着 Train_Test_Split 的 random_state 的变化而动态变化。如果任何特定工作的特征选择发生变化,则无法进行特征重要性的泛化,这是不可取的。其次,如果仅使用训练集进行特征选择,则测试集可能包含某些实例集,这些实例与仅在训练集上进行的特征选择相矛盾/矛盾,因为未分析整体历史数据。此外,特征重要性分数只能在给定一组实例而不是单个测试/未知实例的情况下进行评估。

4

2 回答 2

19

传统的答案#1在这里是正确的;矛盾的答案#2中的论点实际上并不成立。

当有这样的疑问时,想象一下在模型拟合过程中您根本无法访问任何测试集(包括特征重要性)是有用的;您应该将测试集视为字面上看不见的数据(并且,由于看不见,它们不能用于特征重要性分数)。

Hastie & Tibshirani 很久以前就已经明确地争论过执行此类过程的正确和错误方式;我在一篇博文中总结了这个问题,如何不执行特征选择!- 尽管讨论是关于交叉验证的,但很容易看出这些论点也适用于训练/测试拆分的情况。

在您的矛盾答案#2中实际存在的唯一论点是

整体历史数据未分析

尽管如此,这是为了拥有一个独立的测试集进行性能评估所付出的必要代价,否则,按照同样的逻辑,我们也应该使用测试集进行训练,不是吗?


总结:测试集仅用于评估模型的性能,不应模型构建的任何阶段使用,包括特征选择。

更新(评论后):

测试集中的趋势可能不同

这里的一个标准(但通常是隐含的)假设是训练集和测试集在质量上是相似的;正是由于这个假设,我们觉得可以只使用简单的随机拆分来获得它们。如果我们有理由相信我们的数据会以显着方式发生变化(不仅在训练和测试之间,而且在模型部署期间也是如此),那么整个基本原理就会失效,并且需要完全不同的方法。

此外,这样做时,过度拟合的可能性很高

唯一确定的过度拟合方法是在管道期间以任何方式使用测试集(包括用于特征选择,正如您所建议的那样)。可以说,链接的博客文章有足够的论据(包括引号和链接)来令人信服。经典例子,The Dangers of Overfitting or How to Drop 50 points in 1 minute 中的证词:

随着比赛的进行,我开始使用更多的特征选择和预处理。但是,我在交叉验证方法中犯了一个经典错误,即未将其包含在交叉验证折叠中(有关此错误的更多信息,请参阅此简短描述或统计学习要素中的第 7.10.2 节)。这导致越来越乐观的交叉验证估计。

正如我已经说过的,虽然这里的讨论是关于交叉验证,但不难说服自己它也完全适用于训练/测试用例。

特征选择应该以增强模型性能的方式完成

好吧,当然,没有人可以反驳这一点!问题是 -我们在谈论哪个确切的性能?因为上面引用的 Kaggler 确实在进行过程中得到了更好的“性能”(应用了错误的程序),直到他的模型面临着真正的看不见的数据(关键时刻!),并且不出所料地失败了。

诚然,这不是微不足道的事情,可能需要一些时间才能将它们内化(正如 Hastie 和 Tibshirani 所证明的那样,甚至有研究论文中的程序执行错误,这绝非巧合)。在那之前,我为保证您的安全的建议是:在模型构建的所有阶段(包括特征选择),假装根本无法访问测试集,并且仅在您需要评估时才可用最终模型的性能。

于 2019-05-26T09:32:06.313 回答
14

实际上并不难证明为什么使用整个数据集(即在拆分到训练/测试之前)来选择特征会导致您误入歧途。这是一个使用 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

在这种情况下,测试精度 0f 0.528 与理论上预测的 0.5 之一足够接近(即实际上是随机猜测)。

感谢 Jacob Schreiber 提供了简单的想法(检查所有线程,它包含其他有用的示例),尽管上下文与您在此处询问的上下文略有不同(交叉验证):

在此处输入图像描述

于 2019-06-11T16:45:25.237 回答