应该在训练测试拆分之前还是之后,使用基于树的算法的特征重要性评分进行特征选择?

时间:2019-05-25 19:38:28

标签: machine-learning feature-selection train-test-split

实际上,存在两个事实的矛盾,这是对该问题的可能答案:

  1. 常规的答案是在拆分后执行此操作,因为如果以前这样做,可能会导致从测试集中泄漏信息。

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

2 个答案:

答案 0 :(得分: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上都包含其他有用的示例),尽管在这里您所询问的内容(交叉验证)略有不同: >

enter image description here

答案 1 :(得分:1)

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

如果有这样的疑问,可以想象一下,在模型拟合过程中(包括功能重要性),您只是没有在任何测试集中都没有任何访问权限;您应该将测试集视为字面上看不见的数据(并且由于看不见,因此无法用于特征重要性评分)。

Hastie&Tibshirani很久以前就已经明确地论证了执行此类过程的正确和错误方法;我已经在博客文章How NOT to perform feature selection!中总结了这个问题-尽管讨论是关于交叉验证的,但很容易看出,论点也适用于训练/测试拆分的情况。

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

  

未分析整体历史数据

尽管如此,这是为了拥有一个独立的性能评估测试集而必须付出的代价,否则,按照相同的逻辑,我们也应该将测试集用于 training 不是吗?


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

更新(在评论后):

  

测试集中的趋势可能不同

这里的一个标准(但通常是隐含的)假设是训练和测试集在质量上相似。正是由于这种假设,我们觉得只使用简单的随机分割来获得它们就可以了。如果我们有理由相信我们的数据发生了重大变化(不仅在训练与测试之间,而且在模型部署期间),那么整个原理就会崩溃,因此需要完全不同的方法。 / p>

  

此外,这样做可能会导致过度拟合

过分拟合的唯一确定方式是在管道中以任何方式使用测试集(包括您所建议的功能选择)。可以说,链接的博客文章具有足够的论点(包括引号和链接)来令人信服。经典示例,The Dangers of Overfitting or How to Drop 50 spots in 1 minute中的证词:

  

随着比赛的进行,我开始使用更多的功能选择和预处理。但是,我在交叉验证方法中犯了一个经典错误,即没有在交叉验证折叠中包含此错误(有关此错误的更多信息,请参见this short descriptionThe Elements of Statistical Learning中的7.10.2节)。这导致越来越乐观的交叉验证估计。

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

  

训练集M1的测试集的用途是什么

这不是正确的问题;真正感兴趣的问题是:模型M2在性能评估中是否会具有不公平的优势,因为用于模型M2的测试集已在上一步中用于特征选择,从而导致估计偏差?答案是肯定的;只要在管道的任何阶段都使用了测试集,执行此功能选择的确切方式(即通过M2本身或通过任何其他方式)就无关紧要。

也就是说,问题本身并非没有道理。如果没有独立的测试集,您将如何精确评估M1的特征选择过程?

  

功能选择应以增强模型性能的方式进行

当然,没有人可以对此争论!问题是-我们在说什么确切的性能?因为上面引用的Kaggler确实在执行过程中(应用错误的程序)获得了更好的“性能”,直到他的模型面临真实的看不见的数据(关键时刻!),并且失败也就不足为奇了。

诚然,这不是一件小事,您将它们内部化可能要花一些时间(正如Hastie和Tibshirani所演示的那样,甚至有研究论文在其中执行该过程也不是偶然的)错误地)。在此之前,为确保您的安全,我的建议是:在模型构建的所有 阶段(包括功能选择),假装您无权访问测试集,并且仅在需要评估最终模型的性能时才可用。