Pipeline vs GridSearchCV: Predicting after cross‑validation when refit=False
0 reputation · 24 Aug 2022, 07:04 UTC
0 reputation · 24 Aug 2022, 07:04 UTC
Determine whether predictions can be produced after fitting GridSearchCV with refit=False when a Pipeline is used.
When refit=False, GridSearchCV does not refit the best estimator on the full training set, so the predict() method raises NotFittedError. This limits accessibility for exploratory workflows that require predictions during or immediately after cross‑validation.
Scikit‑learn documentation currently states that predictions are only available when refit=True, and no public plan exists to expose the best estimator from cross‑validation without refitting. The trade‑off between avoiding a full refit and providing usable predictions remains unresolved.
33775 reputation · 24 Aug 2022, 17:17 UTC
No. With refit=False, GridSearchCV never fits an estimator on the full dataset, so predict() raises NotFittedError and best_estimator_ is None. There is no flag that hands you a usable model from that run. The supported paths are: (1) clone the pipeline, apply best_params_, and fit it yourself, or (2) use cross_val_predict if you only need out-of-fold predictions for evaluation. Using a Pipeline changes nothing here — it is simply the estimator that gets cloned and fitted per fold.
refit=False, only best_params_, best_score_, and cv_results_ are populated. best_score_ is the mean cross-validated score of the winning parameter set, not a full-data score.best_params_ keys already carry step prefixes (e.g., clf__C), so they can be passed straight to the pipeline's set_params().from sklearn.base import clone
grid = GridSearchCV(pipe, param_grid, refit=False, cv=5)
grid.fit(X_train, y_train)
# grid.predict(X_test) # NotFittedError
assert grid.best_estimator_ is None
best_model = clone(grid.estimator).set_params(**grid.best_params_)
best_model.fit(X_train, y_train)
preds = best_model.predict(X_test)grid.estimator is the original, unfitted pipeline, so cloning it gives a clean slate. The cost of this extra fit is exactly the final fit that refit=True would have run, so total runtime is comparable — you've just made it explicit. One caveat: for estimators with stochastic components, fix random_state, or this manual model may differ slightly from what an internal refit would have produced.
If the goal is predictions during cross-validation (e.g., building a stacked feature or evaluating per-sample errors), cross_val_predict is the right tool — but it performs no hyperparameter search, so run it after choosing parameters:
oof = cross_val_predict(
clone(grid.estimator).set_params(**grid.best_params_),
X_train, y_train, cv=grid.cv,
)The result has one prediction per training sample and is valid only for evaluating that cross-validation split — it is not a model and cannot score new data. Also note that reusing grid.cv reproduces the same folds only for deterministic splitters (an int, or KFold without shuffle=True); otherwise fold assignments will differ between runs.
A "best estimator without refitting" cannot exist without changing semantics: a full-data model requires a full-data fit by definition. The only extra memory refit=False saves is the final fitted model; cv_results_ remains and can grow large with many candidates or multi-metric scoring. If you never call predict afterward, refit=False is a genuine saving; if you do, you pay the refit either way, so refit=True is usually simpler.
refit=False and confirm best_estimator_ is None and that predict raises NotFittedError.best_model.score(X_test, y_test) with grid.best_score_ — they should be close but not identical, since best_score_ is a CV mean.The deciding detail between the two paths is whether predictions must come from a model trained on all data (manual refit) or only describe the CV folds (cross_val_predict) — pick based on which of those you actually need.
Use comments to ask for clarification. Post a solution as an answer.
33,775 reputation · 24 Aug 2022, 13:17 UTC
When GridSearchCV is instantiated with refit=False, it records the estimator that achieved the best cross‑validation score in best_estimator_. That estimator is only fitted on the training folds of the last CV split, not on the entire training set. Consequently, calling predict on unseen data raises a NotFittedError because the model never saw those samples.
The simplest, fully supported workaround is to extract the best hyper‑parameters, rebuild the pipeline, and fit it on the full data set:
# After GridSearchCV with refit=False
best_params = grid_search.best_params_
# Re‑create the pipeline with the same steps
pipe = Pipeline([('scaler', StandardScaler()),
('clf', RandomForestClassifier(**best_params))])
pipe.fit(X, y) # full refit
# Now predictions are available
pred = pipe.predict(X_test)
This adds a single fit operation, which is typically comparable in cost to the refit=True case.
If you only need out‑of‑fold predictions for the training data, cross_val_predict can be used directly on the original pipeline:
from sklearn.model_selection import cross_val_predict
preds = cross_val_predict(pipe, X, y, cv=grid_search.cv)
These predictions are not from a fully trained model, but they avoid an extra refit and can be useful for model selection diagnostics.