clone, get_params and fitted state

scikit-learn · Expert · 10 min

Knowing what clone does explains why search results, CV and nested pipelines behave as they do.

Meta-estimators (pipelines, searches, cross-validation, ensembles) never fit the object you passed. They call sklearn.base.clone(est), which builds a new, unfitted estimator with the same parameters: it reads get_params(deep=False), recursively clones nested estimators, and calls the constructor again.

Consequences worth knowing:

  • After cross_val_score(model, X, y), model is still unfitted. Use cross_validate(..., return_estimator=True) to get the fitted fold models.
  • GridSearchCV leaves your original estimator untouched; the trained model is search.best_estimator_.
  • Fitted attributes (coef_, classes_) are never copied by clone; use copy.deepcopy if you really need a fitted copy.
  • A random_state given as an integer makes clones reproducible; a RandomState instance is shared and advanced between clones, so results differ.

get_params(deep=True) returns nested parameters with __ paths, exactly the names grid search accepts. set_params(**{"step__param": v}) writes them and returns the estimator.

To check fitted state use check_is_fitted(est), which looks for attributes ending in _. Estimators also carry tags (est.__sklearn_tags__()) describing capabilities such as NaN support or whether they need y, which meta-estimators consult.

Example

from sklearn.base import clone
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score, cross_validate
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

X, y = load_iris(return_X_y=True)
pipe = make_pipeline(StandardScaler(), LogisticRegression(C=0.5))

cross_val_score(pipe, X, y, cv=3)
print(hasattr(pipe[-1], "coef_"))          # False (CV fitted clones, not pipe)

res = cross_validate(pipe, X, y, cv=3, return_estimator=True)
print(res["estimator"][0][-1].coef_.shape)  # (3, 4)

copy = clone(pipe.fit(X, y))
print(hasattr(copy[-1], "coef_"), copy.get_params()["logisticregression__C"])  # False 0.5
print([k for k in pipe.get_params() if k.startswith("standardscaler__")])
# ['standardscaler__copy', 'standardscaler__with_mean', 'standardscaler__with_std']

Remember

  • Meta-estimators fit clones, never your original object
  • clone copies parameters, not fitted state
  • Use integer random_state for reproducible clones

Watch out for

  • Expecting a model passed to cross_val_score to be fitted afterwards.

Check yourself

  1. After GridSearchCV(est, grid).fit(X, y), where is the trained best model?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: scikit-learn.org/stable/modules/generated/sklearn.base.clone.html