证明在选择特征时使用整个数据集(即在拆分为训练集/测试集之前)可能会导致错误并不是真正困难的事情。以下是使用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
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)
X_selected = selector.fit_transform(X,y)
X_selected_train, X_selected_test, y_train, y_test = train_test_split(X_selected, y, test_size=0.25, random_state=42)
lr = LogisticRegression()
lr.fit(X_selected_train,y_train)
y_pred = lr.predict(X_selected_test)
accuracy_score(y_test, y_pred)
哇!我们在一个二元问题上获得了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)
我们的模型在面对这些真正未曾涉及的数据时,将会表现如何?检查起来并不困难:
我们的模型在面对这些真正未曾涉及的数据时,将会表现如何?检查起来并不困难:
X_new_selected = selector.transform(X_new)
y_new_pred = lr.predict(X_new_selected)
accuracy_score(y_new, y_new_pred)
好的,事实如此:我们把我们的模型送去参加比赛,认为它能够获得约76%的准确率,但实际上它只像随机猜测一样表现...
因此,现在让我们看看正确的程序(即首先进行拆分,并仅基于训练集选择特征):
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
selector = SelectKBest(k=25)
X_train_selected = selector.fit_transform(X_train,y_train)
lr.fit(X_train_selected,y_train)
X_test_selected = selector.transform(X_test)
y_pred = lr.predict(X_test_selected)
accuracy_score(y_test, y_pred)
在这种情况下(即实际上是随机猜测),测试准确度为0.528,接近理论预测值0.5。
向Jacob Schreiber致敬,他提供了一个简单的想法(查看所有的线程,其中包含其他有用的示例),尽管在一个略微不同的上下文中(交叉验证):
![输入图像描述](https://istack.dev59.com/l1lgA.webp)