Building Pipelines with Preprocessing Steps
Keys such as classifier__C use the double underscore to point at a parameter inside a named pipeline step, while keys such as preprocessing list the step's possible objects directly.
Searching Models and Preprocessing Together
Earlier in a machine learning workflow, you often tune the settings of one model and compare the results by hand. You try a few values, note which one scores best, and move on. That approach answers a narrow question: which hyperparameter values work best for this one model?
This article covers a more complete question. Instead of asking only which hyperparameter values work best, you ask which combination of model, preprocessing step, and hyperparameter values works best. The source works through this with the cancer dataset. It compares a Support Vector Classifier (SVC) against a RandomForestClassifier, and it also chooses between scaling the data with StandardScaler or leaving it unchanged.
The key idea is that GridSearchCV does not care what kind of thing it is searching over. A number, a model object, and a preprocessing step are all treated the same way. The search tries each candidate in the grid, evaluates it, and reports which one scored best. Once you understand how the grid is written, the rest of the workflow follows from it.
Reading the Parameter Grid
A parameter grid is a Python list in which each entry is a dictionary describing one family of candidates. Each dictionary maps a key to a list of values to try. Because the grid is a list of dictionaries, GridSearchCV can search over models and preprocessing steps at the same time.
The central object in this workflow is the parameter grid. The source defines a grid with two entries. The first entry covers the SVC branch, and the second covers the RandomForestClassifier branch. Within each entry, the key 'classifier' holds the model objects to try, and the key 'preprocessing' holds the preprocessing choices. Other keys hold hyperparameter values for that model.
- The first dictionary is the SVC family: one model, two preprocessing choices, and two hyperparameters with three values each.
- The second dictionary is the RandomForestClassifier family. It only lists keys that apply to its own model.
- The numeric values in this grid are an illustration written for this article, not the source's exact values.
Notice that the two entries are independent. The search does not force the RandomForestClassifier to be combined with the SVC's gamma and C values. Each entry defines only the keys that apply to its own model. This is why a single grid can hold several different model families without mixing their hyperparameters together.
Double Underscore Keys
Keys such as classifier__gamma and classifier__C use a double underscore to point at a setting inside a pipeline step. The part before the double underscore names the step. The part after it names the parameter of that step. So classifier__C means the C parameter of whatever object is stored under the 'classifier' step.
The preprocessing key works differently. It is not a parameter of a model at all. It names a step, and its possible objects are listed directly. In the grid above, those objects are StandardScaler() and None. So a key with no double underscore lists whole step objects, while a key with a double underscore reaches into a step and names one of its parameters.
Counting Candidates Before Running
Before you run a grid search, it helps to know how much work it will do. Count the candidates in each entry of the grid, then add the entries together. The number of candidates in one entry is the product of the lengths of its value lists. Each key contributes one factor, and a key with a single value contributes a factor of one.
In this formula, N is the total number of candidates, e runs over the entries of the grid, k runs over the keys inside one entry, and |V_{e,k}| is the number of values listed under key k in entry e. Multiplication happens within an entry, because every combination of values inside that family is a candidate. Addition happens between entries, because the families do not combine with each other.
What do you think happens?
Using the illustrative grid in the previous section, how many candidate combinations will GridSearchCV evaluate in total? Count the SVC entry and the RandomForest entry separately before adding them.
Reveal answer
24 candidates: the SVC entry gives 1 x 2 x 3 x 3 = 18, and the RandomForest entry gives 1 x 2 x 3 = 6, so 18 + 6 = 24.
Within the SVC entry, the model has one value, preprocessing has two, gamma has three, and C has three. Those lengths multiply. The RandomForest entry has one model, two preprocessing choices, and three max_features values. The two entries are then added.
Counting the fits for a five-fold search
The grid has 24 candidates, as counted above. GridSearchCV is run with cv=5. How many model fits does the search perform?
- 1Count the candidates
The grid holds 24 candidate combinations across both entries.
- 2Apply the fold count
The source notes that cv=5 multiplies the number of fits. Each candidate is fitted once per fold, so each candidate is fitted five times.
- 3Multiply
24 candidates multiplied by 5 folds gives 120 fits.
The search performs 120 fits. Adding more values to any list in the grid increases this number, and so does increasing cv.
Running the Search with a Pipeline
Once the grid is defined, the source runs the search in the usual way. The data is first split into training and test sets with train_test_split, using random_state=0 so the split is repeatable. The grid is then built around a pipeline object, which the source refers to as pipe. GridSearchCV is asked to use five cross-validation folds. Calling fit runs the whole search on the training data only. The test set is held back until the end.
- X and y are assumed to hold the cancer dataset's features and labels.
- The pipe object is assumed to already exist, with a step named 'preprocessing' and a step named 'classifier'. The source uses pipe without defining it here.
- fit uses only the training split. The test split is used only by the final score call.
It helps to trace what happens to the data when a candidate is evaluated. For each candidate, the data first passes through the preprocessing step, which is either StandardScaler or no change at all. The output of that step is what the classifier step receives. The classifier is then fitted and scored. This sequence is repeated for each fold, so every candidate passes through the same pipeline structure five times in this setup.
Reading the Winning Combination
The printed output reports three different things, and each one answers a different question. The best_params_ attribute reports the winning combination across both models and the preprocessing step, not just the best hyperparameter value. In the source's run, the selected classifier is an SVC, the selected preprocessing is a StandardScaler, and the selected values are the ones stored under classifier__C and classifier__gamma. Reading this dictionary tells you which branch of the grid won, not only which number was best.
The best_score_ attribute reports the best cross-validation score from the training data. This is the score the search used to pick the winner. The score method, called on the held-out test set, reports how the chosen model performs on data the search never saw.
| Question | Score used | Data it is computed on | Role in the workflow |
|---|---|---|---|
| Which candidate was selected? | best_score_ | Training data, across folds | Used by the search to choose the winner, so it is somewhat optimistic about the chosen model |
| How does the chosen model perform on unseen data? | score on the test set | Test data, never used during the search | The final performance check |
The two scores answer different questions and come from different data.
The two scores should be read together. When the cross-validation score and the test-set score are close, as they are in the source's results, that suggests the search did not overfit the cross-validation folds. A large gap between them would be a reason to be cautious about the reported performance, because the chosen model would look better on the data used to select it than on new data.
Mistakes Beginners Make with Grids
Common mistake Treating the whole grid as one family of combinations
What it looks likePutting SVC's gamma and C values and RandomForestClassifier's settings into one dictionary, so the search tries mixed combinations.
Why it failsEach entry in the list is a separate family. Keys that do not apply to a model have no place in that model's entry.
Do this insteadGive each model its own dictionary, and list only the keys that apply to that model.
Common mistake Writing a hyperparameter key without the step name
What it looks likeUsing a key such as C where the pipeline expects classifier__C.
Why it failsThe double underscore is how the pipeline finds the step that owns the parameter. Without the step name, the pipeline does not know where to look.
Do this insteadWrite the key as step name, double underscore, then parameter name, such as classifier__C.
Common mistake Expecting best_params_ to contain only a number
What it looks likeReading best_params_ and assuming it only tells you the best C value.
Why it failsbest_params_ reports the winning combination across models and preprocessing, so it can name a different classifier entirely.
Do this insteadRead the whole dictionary to see which branch of the grid won, then read the hyperparameter values inside it.
Common mistake Reporting the best cross-validation score as the final result
What it looks likeClaiming the chosen model reaches the best_score_ value on new data.
Why it failsThe cross-validation score was used to select the winner, so it is somewhat optimistic. Only the test-set score uses data the search never saw.
Do this insteadReport the test-set score as the final performance check, and compare it with the cross-validation score.
Common mistake Forgetting that the candidate count multiplies with cv, so a grid that looks small can produce many fits
Practice Predicting the Search Size
A grid has two entries. The first entry lists one classifier, two preprocessing choices, four values for classifier__C, and three values for classifier__gamma. The second entry lists one classifier, two preprocessing choices, and five values for classifier__max_features. Before running anything, predict how many candidates GridSearchCV evaluates, and then how many fits it performs with cv=5.
Hints
- Count the candidates in the first entry by multiplying the lengths of its value lists: one classifier, two preprocessing choices, four C values, and three gamma values.
- Count the second entry the same way, then add the two entries together.
- Multiply the total number of candidates by 5 to get the number of fits.
Explain in one or two sentences why best_score_ and the test-set score should be compared, and what a large gap between them would suggest.
Hints
- Think about which data was used to choose the winning candidate.
- Think about what the test set was held back for.
Key Takeaways
- A parameter grid is a list of dictionaries, and each dictionary describes one family of candidates that GridSearchCV explores.
- Keys with a double underscore, such as classifier__C, point at a parameter inside a named pipeline step, while keys such as preprocessing list that step's possible objects directly.
- The number of candidates is found by multiplying the value-list lengths inside each entry and then adding the entries together, and cv multiplies the number of fits.
- best_params_ names the winning combination across models and preprocessing, best_score_ is the cross-validation score used for selection, and the test-set score is the final check on data the search never saw.
- When the cross-validation score and the test-set score are close, the search is less likely to have overfit the folds; a large gap calls for caution.