关于机器学习:特征选择应该在Train-Test Split之前还是之后进行?

Should Feature Selection be done before Train-Test Split or after?

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

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

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


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

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

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

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

    the overall historical data is not analyzed

    但是,这是要有一个独立的测试集进行绩效评估所必须付出的代价,否则,按照同样的逻辑,我们也应该将测试集用于培训,不是吗?

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

    更新(在评论后):

    the trends in the Test Set may be different

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

    Also, on doing so, there can be a high probability of Over-fitting

    过度拟合的唯一确定方法是在管道中以任何方式使用测试集(如您所建议的那样,用于功能选择)。可以说,链接的博客文章具有足够的论点(包括引号和链接)来令人信服。经典示例,《过拟合的危险或如何在一分钟内下降50个点》中的证词:

    as the competition went on, I began to use much more feature selection and preprocessing. However, I made the classic mistake in my cross-validation method by not including this in the cross-validation folds (for more on this mistake, see this short description or section 7.10.2 in The Elements of Statistical Learning). This lead to increasingly optimistic cross-validation estimates.

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

    what is the utility of the test set to the Trained Model M1

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

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

    feature selection should be done in such a way that Model Performance is enhanced

    好吧,当然,没有人可以对此争论!问题是-我们在谈论哪种确切的表现?因为上面引用的Kaggler确实在前进(应用错误的过程)时获得了更好的"性能",所以直到他的模型面临着真正的看不见的数据(真实的时刻!),并且毫无意外地失败了。

    不可否认,这并不是一件小事,将它们内部化可能要花一些时间(正如Hastie和Tibshirani所论证的,甚至有研究论文错误地执行了该过程也不是巧合)。在此之前,我为您提供的安全建议是:在模型构建的所有阶段(包括功能选择),假装您根本无法使用测试集,并且只有在需要评估时才可用最终模型的性能。


    实际上,不难证明为什么使用整个数据集(即在分割以训练/测试之前)来选择特征会导致您误入歧途。这是一个使用随机虚拟数据和Python和scikit-learn的演示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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 %。让我们看看在分割之前应用错误的过程来使用整个数据集进行特征选择时会发生什么:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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%的东西!

    当然,事实是,我们仅仅因为犯了一个非常基本的错误就能够获得这样的测试准确性:我们错误地认为我们的测试数据是看不见的,但是实际上测试数据已经在模型构建过程中看到了在选择特征时,尤其是在这里:

    1
    X_selected = selector.fit_transform(X,y)

    现实中我们的境遇有多严重?好了,再次看到它并不难:假设在完成模型并进行了部署之后(使用新的看不见的数据,在实践中期望达到76%的准确性),我们得到了一些真正的新数据:

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

    当然,这里没有任何质的变化,即没有新的趋势或其他任何变化-这些新数据是由完全相同的基础过程生成的。假设我们碰巧知道真正的标签y,如上生成:

    1
    y_new = np.random.choice(2, size=500)

    面对这些真正看不见的数据,我们的模型将在这里表现如何?不难检查:

    1
    2
    3
    4
    5
    6
    # 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%,但实际上它的表现就像一个随机的猜测...

    因此,现在让我们看一下正确的过程(即先拆分,然后仅根据训练集选择功能):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # 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提供了简单的想法(在所有线程中检查,其中包含其他有用的示例),这一点非常荣幸,尽管与您在此处询问的内容(交叉验证)略有不同:

    enter image description here