Concepts / Grid-Searching Preprocessing Steps and Model Parameters

Grid-Searching Preprocessing Steps and Model Parameters

A grid search can tune preprocessing parameters and model parameters together when both are steps in one pipeline.

  • Programming

The Search Winner Is a Whole Pipeline

When you tune a model with GridSearchCV, the search tries many parameter combinations and keeps one winner. In many workflows that winner is not just a bare classifier. When the object being searched is a pipeline, the winner is a complete pipeline, with its preprocessing steps and its model, all fitted together. Once the search has finished, a practical question follows: how do you get inside that fitted pipeline and read the numbers the model has learned?

The answer is a short chain of attribute lookups. Each lookup moves you one level deeper into the structure: from the search object, to the best model it found, to one step of that model, to a learned quantity inside that step. Beginners most often go wrong when they try to walk this chain by themselves, because each link is a separate lookup and a wrong key or a skipped level breaks the whole expression.

Walking Down the Attribute Chain

Start with the search object, the one you created with GridSearchCV. The attribute best_estimator_ is found on this search object, not on the pipeline you originally handed to it. The search keeps the record of which configuration won, and it exposes that winning configuration as best_estimator_. The source describes this model as already refit on all of the training data, so it is the version that produced the winning result.

python
Notes on the code
  • GridSearchCV wraps the pipeline and the param_grid; fit runs the whole search
  • best_estimator_ is an attribute of the search object
  • Printing the result is the simplest way to confirm what you got back

The printed representation is your evidence. In the source example, printing best_estimator_ shows a Pipeline object whose steps are listed by name: a StandardScaler first and a LogisticRegression second. That printed text confirms that best_estimator_ is a pipeline with multiple steps, which is the fact everything later in this article depends on.

The next link is named_steps. A pipeline's named_steps attribute gives you each step by its name. You index it like a dictionary, passing the step name as a string, and you get back that step's fitted object. Read the whole expression from left to right: grid is the search object, best_estimator_ moves you to the winning pipeline, named_steps moves you to the dictionary of its steps, and the string key picks one step out of that dictionary.

python
Notes on the code
  • 'logreg' must match the name given in the steps list exactly
  • named_steps is indexed like a dictionary with a string key
  • coef_ is read from the fitted LogisticRegression step

The coef_ attribute belongs to the fitted LogisticRegression step, so the lookup must pass through named_steps before reading it. The coefficients are the weights the model learned for each input feature, and because this step was trained as part of the best pipeline, these weights are the ones that actually produced the search's winning result. The printed output is nested in double brackets, which signals a two-dimensional array with one row of weights and one value per input feature. Reading that structure correctly is part of interpreting the model, not just retrieving it.

GridSearchCV objectholds the search recordbest_estimator_fitted Pipelinenamed_stepsdictionary of stepslogreg stepLogisticRegressioncoef_learned weightsbest_estimator_named_steps['logreg']coef_
What objects does grid.best_estimator_ contain, and how do you reach the LogisticRegression step and its coef_ array from the outer search object?

Writing Keys That Reach Each Step

A grid search can tune preprocessing parameters and model parameters together when both are steps in one pipeline. Consider a pipeline that adds polynomial features before a Ridge regression model. It has two settings that need a decision: the degree of the polynomial features, which controls how far each existing feature is raised to a power, and the alpha parameter of Ridge regression, which controls the regularization strength. Because the pipeline already contains both steps, a single search can vary the degree and the alpha value together and compare every combination on the same cross-validated scores.

The search must know which step each value belongs to. Hyperparameter values are passed to a step by prefixing the parameter name with the step name, separated by a double underscore. Each key in the param_grid is written as the step name followed by the parameter name, such as polynomialfeatures__degree and ridge__alpha. Each key therefore tells the search which step to change and which parameter to change inside that step.

python
Notes on the code
  • The step names polynomialfeatures and ridge must match the keys exactly
  • The double underscore separates the step name from the parameter name
  • The value lists here are illustrative choices, not values from the source
polynomialfeatures__degreepolynomialfeaturespipeline step namedegreeparameter of that stepridge__alpharidgepipeline step namealphaparameter of that stepstep partparameter partstep partparameter part
Which named pipeline step does each param_grid key point to, and how does the double-underscore prefix map a key to the right object's parameter?

Counting the Candidates in a Grid

Before reading any search output, it helps to know how large the search is. A grid search tries every combination of the values listed in the param_grid, so the number of candidates is the product of the number of values for each key. Cross-validation then multiplies that count, because each candidate is evaluated once per fold.

Worked example

Counting candidates for the degree and alpha grid

The param_grid has polynomialfeatures__degree with 3 values and ridge__alpha with 6 values. How many candidate combinations does the search evaluate?

  1. 1
    Count the values for each key

    The degree key has 3 values, and the alpha key has 6 values.

  2. 2
    Multiply the counts

    Every degree is paired with every alpha, so the number of candidates is $3 \times 6 = 18$.

  3. 3
    Check against the source's score count

    The source reshapes 18 mean test scores into a grid with 3 rows, which leaves $18 / 3 = 6$ columns, matching the 6 alpha values.

Result

The grid defines 18 candidates, and with 5 cross-validation folds the search performs 90 fits in total.

What do you think happens?

Before reading the answer, predict how many candidate combinations this param_grid defines: a degree key with 3 values and an alpha key with 6 values.

Reveal answer
Answer:

18 candidates, because 3 times 6 equals 18.

Each candidate is one pairing of a degree value with an alpha value, so the total is the product of the two counts.

Reshaping Scores into a Grid

After the search runs, the results can be visualized as a heat map. The scores are read from the cv_results_ attribute, specifically the mean test score for each candidate. The 18 mean scores are reshaped into a grid with 3 rows and as many columns as needed, so each row corresponds to one polynomial degree and each column to one alpha value. Each cell is one candidate pairing, and the color reflects the averaged cross-validation score for that pairing.

The layout follows directly from the reshape call. The source places degrees on the vertical axis and alpha values on the horizontal axis. The heat map is a way of reading the search result, not a separate calculation, so its shape must always match the grid you defined. The flat array is in candidate order, and the reshape simply cuts it into rows.

candidate 0degree 1, alpha 0.1candidate 1degree 1, alpha 1candidate 2degree 1, alpha 10candidate 3degree 1, alpha 100candidate 4degree 1, alpha 1000candidate 5degree 1, alpha 10000candidate 6degree 2, alpha 0.1candidate 7degree 2, alpha 1candidate 8degree 2, alpha 10candidate 9degree 2, alpha 100candidate 10degree 2, alpha 1000candidate 11degree 2, alpha 10000candidate 12degree 3, alpha 0.1candidate 13degree 3, alpha 1candidate 14degree 3, alpha 10candidate 15degree 3, alpha 100candidate 16degree 3, alpha 1000candidate 17degree 3, alpha 10000
How does each position in the flat mean_test_score array correspond to one combination of parameter values, and where in the reshaped 2D grid does a given score sit?

Where Grid Search Results Diverge

When the heat map or the best score does not match what you expected, the cause is usually found in one of a few specific places. Work through them in order: first the keys of the param_grid, then the step names in the pipeline, then the reshape call, and last the axis labels. Each of these is visible in the code, so you can check them directly rather than guessing.

  • Common mistake

    Using a key without the step-name prefix

    What it looks like
    param_grid = {'degree': [1, 2, 3]}
    Why it fails

    The search needs to know which step owns the parameter, and the step name is the prefix before the double underscore.

    Do this instead

    Use the full key, such as 'polynomialfeatures__degree'.

  • Common mistake

    Misspelling a step name in the param_grid or in named_steps

    What it looks like

    named_steps['Ridge'] when the step was named 'ridge'

    Why it fails

    Each link in the chain is a separate lookup, and a key that does not match exactly fails.

    Do this instead

    Copy the step name from the pipeline's steps list and use it unchanged in every lookup.

  • Common mistake

    Reading coef_ from the search object or the pipeline

    What it looks like
    grid.coef_ or grid.best_estimator_.coef_
    Why it fails

    The learned weights live on the model step, not on the wrapper.

    Do this instead

    Go through named_steps to the LogisticRegression or Ridge step, then read coef_.

  • Common mistake

    Reshaping the scores with the wrong number of rows

    What it looks like

    Reshaping 18 scores into 4 rows

    Why it fails

    The row count must match the number of values in one key, and mismatched shapes produce a grid that does not correspond to the candidates.

    Do this instead

    Set the rows to the length of one key's value list and confirm that the total divides evenly.

Predicting the Lookup Before Running It

What do you think happens?

After grid.fit(...), what type is grid.best_estimator_ when the searched object is a pipeline with a scaler and a model?

Reveal answer
Answer:

A Pipeline object, with its steps listed by name.

The source's printed output shows best_estimator_ as a Pipeline containing a StandardScaler and a LogisticRegression, so the winner is the complete pipeline, not a bare classifier.

MEDIUM

A colleague writes grid.best_estimator_.coef_ and gets an error. Using the chain of lookups, rewrite the expression so it reads the coefficients of the logistic regression step named 'logreg' in the winning pipeline. Then predict the shape of the result: is it one-dimensional or two-dimensional, and how many values does each row hold?

Hints
  • The wrapper does not hold the weights; the model step does.
  • Pass through named_steps with the exact string key before reading coef_.
  • The printed output uses double brackets, which signals a two-dimensional array with one row.

Key Takeaways

  • GridSearchCV exposes the winning configuration as grid.best_estimator_, which is a complete pipeline refit on all of the training data.
  • named_steps[step_name] reaches one step of the fitted pipeline by its exact name, and that step's coef_ attribute holds the learned weights.
  • A param_grid key is the step name followed by a double underscore and the parameter name, so one search can tune preprocessing and model settings together.
  • The number of candidates is the product of the value counts for each key, and cross-validation multiplies that count into the total number of fits.
  • When results diverge, check the param_grid keys, the step names, the reshape call, and the axis labels, in that order.

Related concepts

More concepts starting with G