我一直在使用波士顿数据集进行RFECV实验。
据我目前所知,为了防止数据泄露,重要的是仅在训练数据上而不是整个数据集上执行此类操作。
我仅在训练数据上进行了RFECV操作,结果显示14个特征中有13个是最优的。然而,当我对整个数据集运行相同的过程时,这次结果显示只有6个特征是最优的——这看起来更合理。
为了说明这一点:
import numpy as npimport pandas as pdfrom sklearn.model_selection import train_test_splitfrom sklearn.feature_selection import RFECVfrom sklearn.linear_model import LinearRegressionfrom sklearn.datasets import load_boston### CONSTANTSTARGET_COLUMN = 'Price'TEST_SIZE = 0.1RANDOM_STATE = 0### LOAD THE DATA AND ASSIGN TO X and ydata_dict = load_boston()data = data_dict.datafeatures = list(data_dict.feature_names)target = data_dict.targetdf = pd.DataFrame(data=data, columns=features)df[TARGET_COLUMN] = targetX = df[features]y = df[TARGET_COLUMN]### PERFORM TRAIN TEST SPLITX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=TEST_SIZE, random_state=RANDOM_STATE)#### DETERMINE THE DATA THAT IS PASSED TO RFECV## Just the Training dataX_input = X_trainy_input = y_train## All the data# X_input = X# y_input = y### IMPLEMENT RFECV AND GET RESULTSrfecv = RFECV(estimator=LinearRegression(), step=1, scoring='neg_mean_squared_error')rfecv.fit(X_input, y_input)rfecv.transform(X_input)print(f'Optimal number of features: {rfecv.n_features_}')imp_feats = X.drop(X.columns[np.where(rfecv.support_ == False)[0]], axis=1)print('Important features:', list(imp_feats.columns))
运行上述代码将得到以下结果:
Optimal number of features: 13Important features: ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT']
现在,如果我更改代码,让RFECV适应所有数据:
#### DETERMINE THE DATA THAT IS PASSED TO RFECV## Just the Training data# X_input = X_train # NOW COMMENTED OUT# y_input = y_train # NOW COMMENTED OUT## All the dataX_input = X # NOW UN-COMMENTEDy_input = y # NOW UN-COMMENTED
并运行它,我得到以下结果:
Optimal number of features: 6Important features: ['CHAS', 'NOX', 'RM', 'DIS', 'PTRATIO', 'LSTAT']
我不明白为什么在整个数据集上的结果与仅在训练集上的结果差异如此明显(而且似乎更准确)。
我尝试通过将TEST_SIZE
常量设置为极小值,使训练集的规模接近整个数据集,但仍然得到了这种看似不太可能的差异。
我错过了什么?
回答:
这种行为确实看起来很出乎意料,尤其是当你提到即使将测试集大小减少到10%甚至5%,仍然会发现类似的差异,这似乎非常违反直觉。理解这里发生的事情的关键是认识到,对于这个特定的数据集,每列的值在各行之间不是随机分布的(例如,尝试运行X['CRIM'].plot()
)。你用来分割数据的train_test_split
函数有一个默认值为True
的shuffle
参数。所以如果你查看X_train
数据集,你会发现索引是打乱的,而在X
中索引是顺序的。这意味着当RFECV
类在内部进行交叉验证时,它在X
的每次分割中得到的是一个有偏见的数据子集,而在X_train
的每次分割中得到的是一个更具代表性/随机的数据子集。如果你将shuffle=False
传递给train_test_split
,你会发现这两个结果更加接近(或者更好的方法,可能是在X
上打乱索引)。