0Pricing
Machine Learning Academy · Lesson

Cross-Validating and Grid-Searching a Full Pipeline

Learners will pass a Pipeline to cross_val_score and GridSearchCV, using double-underscore notation to specify hyperparameters of individual steps.

Cross-Validating and Grid-Searching a Full Pipeline is a free Machine Learning Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Machine Learning Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why Grid-Search a Full Pipeline?

Hyperparameter tuning should always be combined with cross-validation to prevent overfitting the validation set. When your preprocessing steps have their own parameters (e.g., PCA's n_components, OneHotEncoder's drop), those must be tuned simultaneously with the model's parameters. Wrapping everything in a Pipeline and passing it to GridSearchCV ensures all of this is done correctly without leakage.

Passing a Pipeline to cross_val_score

The simplest way to evaluate a Pipeline fairly is cross_val_score(pipeline, X, y, cv=5). Each fold re-fits the entire pipeline — including scaler and classifier — on the training portion, then evaluates on the held-out fold. The mean and standard deviation of the returned array give you a reliable generalisation estimate.

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_wine
import numpy as np

X, y = load_wine(return_X_y=True)

pipe = Pipeline([
    ('sc', StandardScaler()),
    ('lr', LogisticRegression(C=1.0, max_iter=300))
])

scores = cross_val_score(pipe, X, y, cv=5, scoring='accuracy')
print(f'CV accuracy: {np.mean(scores):.4f} +/- {np.std(scores):.4f}')

Double-Underscore Notation for Pipeline Params

To reference a parameter of a named step inside the pipeline, use stepname__paramname. For nested structures like ColumnTransformer inside a Pipeline, chain the names: preprocessor__num__scaler__with_std. This convention is used both in set_params calls and in the param_grid dictionary passed to GridSearchCV.

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC

pipe = Pipeline([('sc', StandardScaler()), ('svm', SVC())])

# Print all tunable parameters
params = pipe.get_params()
for k, v in params.items():
    print(f'  {k}: {v}')

Defining a param_grid for GridSearchCV

Create a dictionary where keys are pipeline parameter names (using double-underscore notation) and values are lists of candidates to try. GridSearchCV trains and evaluates the pipeline for every combination in the Cartesian product of all lists. The total number of fits equals len(combinations) * cv_folds.

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV
from sklearn.datasets import load_wine

X, y = load_wine(return_X_y=True)

pipe = Pipeline([('sc', StandardScaler()), ('svm', SVC())])

param_grid = {
    'svm__C': [0.1, 1.0, 10.0, 100.0],
    'svm__kernel': ['rbf', 'linear'],
    'svm__gamma': ['scale', 'auto']
}

grid = GridSearchCV(pipe, param_grid, cv=5, n_jobs=-1, scoring='accuracy')
grid.fit(X, y)
print('Best params:', grid.best_params_)
print('Best CV score:', grid.best_score_.round(4))

Tuning Preprocessor Parameters Too

You can include preprocessor parameters in the same grid. For example, tune n_components of a PCA step alongside the classifier's regularisation. This finds the optimal compression level and model complexity simultaneously, which is more rigorous than tuning them in separate steps.

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GridSearchCV
from sklearn.datasets import load_digits

X, y = load_digits(return_X_y=True)

pipe = Pipeline([
    ('sc', StandardScaler()),
    ('pca', PCA()),
    ('lr', LogisticRegression(max_iter=500))
])

param_grid = {
    'pca__n_components': [10, 20, 30, 40],
    'lr__C': [0.1, 1.0, 10.0]
}

grid = GridSearchCV(pipe, param_grid, cv=5, n_jobs=-1)
grid.fit(X, y)
print('Best:', grid.best_params_)
print('Score:', grid.best_score_.round(4))

Inspecting GridSearchCV Results

The cv_results_ attribute is a dictionary (convertible to a DataFrame) containing mean test scores, standard deviations, and fit times for every hyperparameter combination. Inspecting this DataFrame helps you understand the performance landscape and identify whether the best result is significantly better than the second-best, or if many parameter combinations perform similarly.

import pandas as pd

results = pd.DataFrame(grid.cv_results_)
results_sorted = results.sort_values('rank_test_score')
print(results_sorted[['param_pca__n_components', 'param_lr__C',
                       'mean_test_score', 'std_test_score']].head(6).to_string())

RandomizedSearchCV for Large Parameter Spaces

When the parameter space is large, GridSearchCV becomes computationally prohibitive. RandomizedSearchCV samples a fixed number of combinations at random (controlled by n_iter), often finding near-optimal results in a fraction of the time. Use scipy.stats distributions for continuous parameters to sample from a range rather than discrete values.

from sklearn.model_selection import RandomizedSearchCV
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.datasets import load_wine
from scipy.stats import loguniform, uniform

X, y = load_wine(return_X_y=True)

pipe = Pipeline([('sc', StandardScaler()), ('svm', SVC())])

param_dist = {
    'svm__C': loguniform(0.01, 100),
    'svm__gamma': loguniform(1e-4, 1),
    'svm__kernel': ['rbf', 'linear']
}

rs = RandomizedSearchCV(pipe, param_dist, n_iter=30, cv=5, random_state=42, n_jobs=-1)
rs.fit(X, y)
print('Best params:', rs.best_params_)
print('Best score:', rs.best_score_.round(4))

Nested CV: Evaluation and Selection Together

Standard grid search with cross-validation slightly overfits the validation set — after choosing the best hyperparameters, you have implicitly used the validation data. Nested cross-validation solves this: the outer loop evaluates the tuned model's generalisation, the inner loop selects hyperparameters. The outer loop score is an unbiased estimate of the final model's true test performance.

from sklearn.model_selection import GridSearchCV, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.datasets import load_iris
import numpy as np

X, y = load_iris(return_X_y=True)

pipe = Pipeline([('sc', StandardScaler()), ('svm', SVC())])
param_grid = {'svm__C': [0.1, 1.0, 10.0], 'svm__gamma': ['scale', 'auto']}

inner_cv = GridSearchCV(pipe, param_grid, cv=5, n_jobs=-1)
# Outer CV evaluates the tuning procedure itself
outer_scores = cross_val_score(inner_cv, X, y, cv=5)
print(f'Nested CV score: {np.mean(outer_scores):.4f} +/- {np.std(outer_scores):.4f}')

Using the Best Estimator

After GridSearchCV.fit, the best_estimator_ attribute is the pipeline re-fitted on the entire training dataset using the best hyperparameters. This is the model you should use for final predictions. You can call predict, predict_proba, or score on it directly.

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.datasets import load_wine

X, y = load_wine(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

pipe = Pipeline([('sc', StandardScaler()), ('svm', SVC())])
param_grid = {'svm__C': [0.1, 1.0, 10.0], 'svm__kernel': ['rbf', 'linear']}

grid = GridSearchCV(pipe, param_grid, cv=5, n_jobs=-1)
grid.fit(X_train, y_train)

best = grid.best_estimator_
print('Test accuracy:', best.score(X_test, y_test).round(4))

Scoring Options in GridSearchCV

By default GridSearchCV uses the estimator's default score (accuracy for classifiers). You can specify any metric with the scoring parameter: 'roc_auc', 'f1', 'neg_mean_squared_error', or a custom scorer made with make_scorer. For imbalanced datasets, 'f1_macro' or 'roc_auc' are better choices than raw accuracy.

from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_breast_cancer

X, y = load_breast_cancer(return_X_y=True)

pipe = Pipeline([('sc', StandardScaler()), ('lr', LogisticRegression(max_iter=300))])
param_grid = {'lr__C': [0.01, 0.1, 1.0, 10.0]}

grid = GridSearchCV(pipe, param_grid, cv=5, scoring='roc_auc', n_jobs=-1)
grid.fit(X, y)
print('Best C:', grid.best_params_)
print('Best ROC-AUC:', grid.best_score_.round(4))

Saving the Best Pipeline

After grid search, save the best pipeline to disk. This single file contains the scaler (with its fitted means and variances), the PCA (with its components), and the classifier (with its weights) — everything needed to reproduce predictions on new data. Load it in production and call predict directly.

import joblib

# Save the best estimator
joblib.dump(grid.best_estimator_, '/tmp/best_pipeline.pkl')

# Load and verify
loaded = joblib.load('/tmp/best_pipeline.pkl')
print('Loaded pipeline test score:', loaded.score(X_test, y_test).round(4))

Quick Check

Test your understanding of cross-validating and grid-searching a full pipeline from this lesson.

Lesson Recap

In this lesson you learned: cross_val_score on a Pipeline re-fits all steps per fold and gives honest generalisation estimates, double-underscore notation references nested hyperparameters in param_grid, and RandomizedSearchCV efficiently explores large parameter spaces by sampling a fixed number of random combinations. Next up we save and load a full pipeline with joblib for production deployment.

Frequently asked questions

Is the “Cross-Validating and Grid-Searching a Full Pipeline” lesson free?

Yes — the full text of “Cross-Validating and Grid-Searching a Full Pipeline” is free to read here on the web, and the Machine Learning Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Machine Learning Academy course, upgrade to CoddyKit PRO.

What will I learn in “Cross-Validating and Grid-Searching a Full Pipeline”?

Learners will pass a Pipeline to cross_val_score and GridSearchCV, using double-underscore notation to specify hyperparameters of individual steps. You practise Machine Learning Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Machine Learning Academy?

No prior experience is required. Machine Learning Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Cross-Validating and Grid-Searching a Full Pipeline” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Machine Learning Academy lesson?

Yes. Every Machine Learning Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Building Your First Pipeline: Scaler Plus Classifier
  2. ColumnTransformer Inside a Pipeline
  3. Cross-Validating and Grid-Searching a Full Pipeline
  4. Saving and Loading a Pipeline with joblib
← Back to Machine Learning Academy