Concepts / Grid Search with Cross-Validation

Grid Search with Cross-Validation

A grid search represents this enumeration by placing one loop inside another.

  • Programming

Why the Search Record Matters

Suppose you have written a grid search for an SVC model and it has finished running. The search does not hand you a single answer. It tried many parameter settings, scored each one with cross-validation, and kept a record of everything it did. That record is where the real information lives. It shows how the model's performance changes as the parameters change, and it is the place to go when you want to know whether the search was informative at all.

This article follows the order of operations inside a grid search. As you read, keep asking three questions: which values are being chosen right now, which data is being used, and where is the best result being saved? Most mistakes in grid search code come from losing track of one of those three things. We will trace the enumeration of combinations, the single mean score that cross-validation produces for each one, and the structured record the search leaves behind.

Enumerating Combinations with Nested Loops

Grid search tries every combination of the candidate hyperparameter values. You write down a list of candidate values for each hyperparameter, and the search visits each pairing. The total number of combinations is the product of the lengths of the lists. Two lists of length 2 give 2 times 2, or 4 combinations. Three gamma values and two C values give 6.

A grid search represents this enumeration by placing one loop inside another. The outer loop picks one value of the first hyperparameter. The inner loop then runs through every value of the second hyperparameter for that outer value. When the inner loop finishes, the outer loop moves to its next value and the inner loop starts over from the beginning of its list. The important consequence is that the outer loop completes its first value before the second outer value begins.

Worked example

Tracing the order of a two-by-two grid

The candidate gamma values are [0.1, 1] and the candidate C values are [1, 10]. Gamma is the outer loop and C is the inner loop. List every combination in the order the search visits it, and count the total.

  1. 1
    Count the combinations

    The product of the list lengths is 2 times 2, so there are 4 combinations.

  2. 2
    Start the outer loop at its first value

    The outer loop picks gamma = 0.1. The inner loop then runs through C = 1 and C = 10, producing (0.1, 1) and then (0.1, 10).

  3. 3
    Advance the outer loop

    The outer loop moves to gamma = 1. The inner loop restarts at C = 1, producing (1, 1), then C = 10, producing (1, 10).

  4. 4
    Check the second pair

    The second pair visited is (0.1, 10), not (1, 1). That ordering comes from the outer loop finishing its first value before the second value starts.

Result

The four combinations are visited in this order: (0.1, 1), (0.1, 10), (1, 1), (1, 10). Gamma changes slowly and C changes fast.

gamma = 0.1, C = 1first visitgamma = 0.1, C = 10inner loop advancesgamma = 1, C = 1outer loop advances, innerrestartsgamma = 1, C = 10last visitinner loopouter loopinner loop
In what order does the search visit each combination of parameter values, and how do the outer and inner loops advance?

The diagram shows the traversal as a sequence. Each step either advances the inner loop to its next C value or advances the outer loop to its next gamma value, which resets the inner loop. The search never needs to build the full list of combinations in advance. It simply runs through them in order.

What do you think happens?

With gamma as the outer loop over [0.1, 1] and C as the inner loop over [1, 10], which pair is visited second?

  • (0.1, 1)

  • (0.1, 10)

  • (1, 1)

  • (1, 10)

Reveal answer
Answer:

(0.1, 10)

The inner loop finishes all C values for gamma = 0.1 before the outer loop moves on, so the second pair keeps gamma at 0.1 and moves C to 10.

python
Output (expected)
0.1 1
0.1 10
1 1
1 10
Notes on the code
  • The outer loop variable gamma is fixed while the inner loop runs through every C value.
  • The printed output shows the four pairs in the same order as the worked example.

How cross_val_score Makes One Mean

Once a combination of gamma and C is chosen, the search does not judge the model from a single train and validation split. Cross-validation splits the data into several folds, trains and evaluates the model on different subsets, and records a score for each split. The source code calls cross_val_score with cv=5, which asks for five splits. Each fold is used for validation exactly once.

Training andvalidation dataX_trainval, y_trainvalFive foldscv=5Five split scoresone score per foldnp.mean(scores)average of five scoresOne mean for thissettingcompared to best_scoresplittrain and score each foldaveragerecorded
What happens to the data at each fold, and how do the per-fold scores combine into the single mean that the grid search records?
Rule

Here $s_i$ is the score from split $i$. The source code computes this average with np.mean(scores), and that single number is what the search compares against the best score it has seen so far. This is why a grid search with cross-validation produces one number per combination, even though each combination was evaluated several times underneath. The average prevents the final number from reflecting one lucky or unlucky split.

Reading the cv_results_ Dictionary

After a grid search has been fitted, its results are stored in an attribute called cv_results_. The source describes it as a dictionary that stores all aspects of the search. A dictionary of this kind maps names to lists, with one entry per quantity recorded. It holds a great deal of detail, so it is rarely read directly. The usual approach is to convert it into a pandas DataFrame, which gives you a table you can display, filter, sort, and plot.

python
Notes on the code
  • The first line builds the DataFrame from the fitted search's cv_results_ dictionary.
  • The second line displays only the first five rows, which is a sample of the full table.
Row 0C = 1, gamma = 0.1Row 1C = 10, gamma = 0.1Row 2C = 1, gamma = 1Row 3C = 10, gamma = 1
Which entries in each column belong to the same parameter setting, and how does row i link the parameter values to the scores?

The DataFrame is the same information as the dictionary, arranged so that every parameter setting becomes a row and every recorded quantity becomes a column. Row i holds the parameter values for one setting together with that setting's split scores, mean, and standard deviation. Each row is therefore a summary of every split used to score that setting, not a single cross-validation run. The diagram shows four illustrative rows, each pairing a parameter setting with its position in the table.

Checking One Row for Consistency

Each row contains the individual split scores along with a mean and a standard deviation. Before you trust the summary values, check that they agree with the splits they summarize. The mean should equal the average of the five split scores. The standard deviation should measure the spread of those same five scores around that average. If either disagrees, something in the chain from parameters to scores has gone wrong.

The source gives one real row for context: with C = 0.001 and gamma = 0.001, the mean test score is 0.366 and the standard deviation is 0.011. The individual split values for that row are not reproduced here, so the example below uses its own illustrative numbers to show the check.

Worked example

Checking an illustrative row

A row records five split scores of 0.36, 0.37, 0.35, 0.38, and 0.36. The row's mean is reported as 0.364 and its standard deviation as 0.010. Do the summary values agree with the splits?

  1. 1
    Recompute the mean

    The five scores sum to 1.82, and 1.82 divided by 5 is 0.364. This matches the reported mean.

  2. 2
    Compute each deviation from the mean

    The deviations are -0.004, +0.006, -0.014, +0.016, and -0.004.

  3. 3
    Square and average the deviations

    The squared deviations are 0.000016, 0.000036, 0.000196, 0.000256, and 0.000016. Their sum is 0.00052, and dividing by 5 gives 0.000104.

  4. 4
    Take the square root

    The square root of 0.000104 is about 0.0102, which rounds to 0.010. This matches the reported standard deviation, so the row is internally consistent.

Result

The mean and standard deviation both agree with the five split scores. If the reported mean had been 0.40, the row would be inconsistent and the cause would need to be located.

Formula

Here $s_i$ is a split score and $\bar{s}$ is the mean of the five splits. A useful habit is to read a row this way: first look at the individual split scores, then confirm that the mean is their average, and only then accept the spread as meaningful.

Reading a Two-Parameter Grid as a Heat Map

The source notes that the search covered a two-dimensional grid, varying C and gamma together. A table with one row per combination is accurate, but when two parameters change at once it is hard to see patterns across both axes. For a two-dimensional grid, the source recommends a heat map.

The procedure is to extract the mean validation scores first, then reshape them so that one axis corresponds to C and the other to gamma. Once arranged that way, each cell of the picture is one parameter combination, and the colors show how the score moves as you travel along either parameter. A flat list of scores does not show which pair produced which result, so the reshaping step matters.

python
Notes on the code
  • pivot places gamma on one axis and C on the other, using the mean score as the value in each cell.
  • heatmap colors each cell by its mean score.

The heat map also gives a sanity check on the loop. If the map does not have one cell for each combination you intended to try, the grid lists or the reshaping step are wrong. A two-by-two grid should produce exactly four cells. Missing or duplicated cells point back to the loop structure from earlier in the article.

Mistakes That Make the Search Misleading

When a grid search fails or gives a suspicious answer, the failure usually sits at one of a few specific points. Learning to locate them is more useful than memorizing the code. The items below are worth checking every time you read a search loop.

  • Common mistake

    Initializing best_score to a value that no real score can fail to beat, or never updating it

    What it looks like
    best_score = 1 before a loop whose accuracy scores are all below 1, so no combination ever replaces it and best_parameters is never set.
    Why it fails

    The comparison is made against the starting value. If the starting value is too high, the test 'score > best_score' is never true, and the search reports nothing useful.

    Do this instead

    Initialize best_score so that the first real score becomes the best, and check that best_parameters is set inside the update branch.

  • Common mistake

    Reversing the direction of the comparison

    What it looks like

    Writing if score < best_score: and calling the result the best setting.

    Why it fails

    The loop keeps the lowest score instead of the highest, so the reported combination is the weakest one.

    Do this instead

    Keep the update condition as score > best_score, and confirm the direction whenever you read a search loop.

  • Common mistake

    Passing test data into the search

    What it looks like

    Calling the search on the full dataset, including the portion held back for final testing.

    Why it fails

    The search is meant to run only on training and validation data. Letting it see the test data makes the chosen result look better than it really is.

    Do this instead

    Use only X_trainval and y_trainval inside the loop, and keep the test data for the final evaluation.

  • Common mistake

    Trusting a summary value without checking the splits

    What it looks like

    Reporting a row's mean of 0.366 without looking at its five split scores.

    Why it fails

    A summary can disagree with the values beneath it, and the disagreement can hide a bug in the chain from parameters to scores.

    Do this instead

    Compare the mean and standard deviation against the individual split scores before trusting the row.

  • Common mistake

    Treating the top-scoring combination as a fair estimate of new-data performance

    What it looks like

    Reporting the highest mean across many candidates as the expected accuracy on unseen data.

    Why it fails

    The best score was selected from many candidates, so it is an optimistic summary of the search, not an unbiased measure of how the chosen model will do on new data.

    Do this instead

    Treat the best score as a selection result. Keep a separate held-out test set for the final estimate, and use the full results to judge whether the search was informative.

  • Common mistake

    Reading only the head() sample and deciding the best setting from five rows

python
Notes on the code
  • best_score and best_parameters are initialized before the loops and updated only inside the comparison.
  • Every candidate is scored with cross_val_score on the training and validation data only.
  • The model is rebuilt with the best parameters and trained once on X_trainval and y_trainval after the loop ends.

Practice: Count and Trace the Grid

EASY

A grid search uses gamma values [0.01, 0.1, 1] as the outer loop and C values [1, 10] as the inner loop. How many combinations are there, and what are the first three pairs the search visits, written as (gamma, C)?

Hints
  • Multiply the length of the gamma list by the length of the C list to get the total.
  • The inner loop finishes all C values for the first gamma before the outer loop moves on.
  • The first pair has gamma = 0.01 and C = 1, and the second pair keeps gamma = 0.01 while C moves to 10.

Key Takeaways

  • A grid search tries every combination of candidate hyperparameter values, usually with nested loops, and the total number of combinations is the product of the list lengths. The outer loop completes all inner values before it advances.
  • cross_val_score scores each combination on several folds, and np.mean reduces those split scores to the single number the search compares against best_score.
  • cv_results_ is a dictionary that is usually converted to a pandas DataFrame, where each row is one parameter setting holding its split scores, mean, and standard deviation. Check that the mean and spread agree with the splits, and inspect the full table rather than the head() sample.
  • For a two-parameter grid, extract the mean scores and reshape them into a heat map, which shows patterns across both axes. Misleading results usually come from initialization, the direction of comparison, the data passed to the search, or treating the best score as a fair estimate.

Related concepts

More concepts starting with G