我正在尝试以一种比在cross_val_score
中使用GridSearchCV
更加手动的方式实现嵌套交叉验证,这样我可以更好地控制我的操作,考虑数据泄漏,观察我的变量和超参数如何变化,并记录它们,同时也为了更清楚地理解一切是如何工作的。
我想问一下下面的实现看起来是否合理。
处理流程:数据泄漏
分类变量
cat_pipe = Pipeline([("encoder",OneHotEncoder(drop="first"))])
可能为分类的数值特征
num_pipe = Pipeline([("encoder",OneHotEncoder(drop="first"))])
连续变量
num_cont_pipe = Pipeline([("scaler",StandardScaler())])preprocessor = ColumnTransformer(transformers=[('cat', cat_pipe, cat_var), ('num', num_pipe, num_cat_vars), "num_cont",num_cont_pipe,num_cont)],n_jobs=-1)#,sparse_threshold=0)
嵌套交叉验证
cv_outer = StratifiedKFold(n_splits=5, random_state=0)cv_inner = StratifiedKFold(n_splits=10,random_state=0) # RepeatedStratifiedKFold(n_repeats=3,n_splits=10,random_state=0)
估计器和模型
estimator = RandomForestClassifier(class_weight="balanced")model = XGBClassifier(eval_metric="logloss")
特征选择和超参数优化
rfe = RFECV(estimator,cv=cv_inner, scoring="roc_auc",n_jobs=-1)
实现
for train_ix, test_ix in cv_outer.split(X,y): time_start_outer = time.time() X_train = X.iloc[train_ix,:] X_test = X.iloc[test_ix,:] y_train = y.iloc[train_ix] y_test = y.iloc[test_ix] # 数据准备 X_train_enc = preprocessor.fit_transform(X_train) X_test_enc = preprocessor.transform(X_test) # 特征选择 X_train_enc_var = rfe.fit(X_train_enc,y_train) print(f"The number of features selected is {X_train_enc_var.n_features_}", sep="/n") X_train_enc_var = rfe.transform(X_train_enc) X_test_enc_var = rfe.transform(X_test_enc) # 超参数调优 counter = Counter(y_train) weight = counter[0] / counter[1] hyper_params = { # XGBClassifier "scale_pos_weight":[1,2,3,4,5,6,weight,7,8,9,10,11,12] } grid_search = GridSearchCV(model,cv=cv_inner,param_grid=hyper_params,n_jobs=-1,scoring="roc_auc") X_train_enc_var_hyp = grid_search.fit(X_train_enc_var,y_train) best_model = X_train_enc_var_hyp.best_estimator_ yhat = best_model.predict(X_test_enc_var) # 评估模型 score = roc_auc_score(y_test, yhat) # 存储结果 outer_results.append(score) outer_params.append(X_train_enc_var_hyp.best_params_) # 报告进度 print(f' test_score= {score}, validation_score= {X_train_enc_var_hyp.best_score_}')# 总结模型的估计性能print(f'best_score: {np.mean(outer_results)}, {np.std(outer_results)}')
回答:
我注意到了三件事。
首先,不重要:
# 特征选择 X_train_enc_var = rfe.fit(X_train_enc,y_train) print(f"The number of features selected is {X_train_enc_var.n_features_}", sep="/n") X_train_enc_var = rfe.transform(X_train_enc) X_test_enc_var = rfe.transform(X_test_enc)
您将拟合的rfe
保存到X_train_enc_var
中,然后在两行后将其覆盖为转换后的数据集。这一切都按您希望的方式进行,但为了更忠实于变量名称:
X_train_enc_var = rfe.fit_transform(X_train_enc,y_train) X_test_enc_var = rfe.transform(X_test_enc) print(f"The number of features selected is {rfe.n_features_}", sep="/n")
其次,您对递归特征消除和网格搜索分别使用了相同的cv_inner
。这意味着选定的特征数量包含了(内部)测试折叠的信息,因此网格搜索得分可能存在乐观偏见。这可能不是什么大问题,因为您只关心搜索的相对得分。但是,可能某些超参数组合在不同数量的特征下表现得更好,从而被惩罚了。
最后,最严重但也是最容易修复的问题:您调用best_model.predict(...)
,但由于您使用的是roc_auc_score
,您应该改为使用best_model.predict_proba(...)[:, 1]
来获取正类别的概率得分。
另一个建议:由于您进行了大量计算,请保存更多信息:例如rfe的grid_scores_
和网格搜索的cv_results_
。