API Reference / evaluation / tuning /

grid_search.py

Exhaustive grid search over a discrete hyperparameter grid.

This module provides GridSearchCV, the "try everything" tuner. It expands a mapping of parameter name to a list of candidate values into the full cartesian product, scores every combination with k-fold cross-validation, ranks them, and optionally refits the winner on the whole training set.

Reach for grid search when the space is small, discrete, and you want a complete, deterministic sweep that is trivial to explain and reproduce (for example criterion x max_depth with three values each). The cost is the product of the list lengths times the number of folds, so it degrades badly in higher dimensions: swap to RandomSearchCV when parameters are continuous or unequally important, and to BayesianSearchCV when each fit is expensive enough that the choice of the next candidate is worth modelling.

Classes

GridSearchCV

class evaluation.tuning.grid_search.GridSearchCV(BaseTuner)

Exhaustive cross-validated search over every point of a parameter grid.

Constructor
__init__(
    self,
    estimator,
    param_grid: Union[Dict, List[Dict]],
    scoring: Union[str, Callable] = 'accuracy',
    cv: int = 5,
    refit: bool = True,
    verbose: int = 0,
    n_jobs: int = 1,
    random_seed: Optional[int] = None,
    progress_callback: Optional[Callable] = None,
    **kwargs,
)

Overview

  1. Expand param_grid into the cartesian product of its value lists.
  2. For each combination, copy the estimator, set the parameters on the
copy, and run cv-fold cross-validation (stratified for classification targets, plain k-fold otherwise).
  1. Record the mean and standard deviation of the fold scores, plus the
mean per-fold fit time, in cv_results_.
  1. Keep the combination with the highest mean score in best_params_
and rank every combination in cv_results_['rank_test_score'] (rank 1 is best).
  1. If refit=True, refit a fresh copy of the estimator with the winning
parameters on the full training set and store it as best_estimator_, which then backs predict and score.

Search space

param_grid maps a parameter name to the list of values to try. Scalars are treated as one-element lists. A list of dictionaries defines disjoint sub-grids, which lets you avoid invalid combinations:

# single grid: 3 x 2 = 6 candidates
{'max_depth': [3, 5, 10], 'criterion': ['gini', 'entropy']}

# disjoint grids: 2 + 3 = 5 candidates
[{'kernel': ['linear'], 'C': [1, 10]},
 {'kernel': ['rbf'], 'gamma': [0.01, 0.1, 1.0]}]

Cost

The search performs n_{\text{candidates}} \times \text{cv} model fits, plus one more when refit=True. n_candidates is the product of the list lengths (summed over sub-grids), so it grows exponentially in the number of tuned parameters:

parameters   values each   candidates   fits at cv=5
----------   -----------   ----------   ------------
         2             3            9             46
         3             3           27            136
         4             4          256           1281

Parameters

estimator
object
Estimator to tune. Must expose fit(X, y) and predict(X) and accept the grid's parameter names as writable attributes. The instance is never modified; every evaluation works on a deep copy.
param_grid
dict or list of dict
Search space. Keys are parameter names, values are lists of candidate settings. A list of dicts defines disjoint sub-grids. Wrapped in ParameterGrid.
scoring
str or callable = 'accuracy'
Metric maximized by the search. Built-ins are 'accuracy', 'neg_mse' and 'r2'; a callable must have the signature scorer(y_true, y_pred) -> float and follow the higher-is-better convention. An unrecognized string falls back to 'accuracy'.
cv
int = 5
Number of cross-validation folds per candidate.
refit
bool = True
Whether to refit the estimator on the full training data with best_params_ after the search. Required for predict/score.
verbose
int = 0
Verbosity. 0 is silent; any value above 0 prints one line per candidate plus a final summary.
n_jobs
int = 1
Number of folds evaluated in parallel via joblib. 1 runs sequentially; other values fall back to sequential execution with a warning when joblib is not installed.
random_seed
int
Seed controlling the cross-validation shuffling. If None, the global TuiML seed is used, falling back to 42. The legacy keyword random_state is still accepted as an alias and is stored on the instance as self.random_state.
progress_callback
callable
Called after each candidate with a dictionary containing 'type', 'iteration', 'total', 'params', 'mean_score', 'std_score' and 'best_score'.

Attributes

param_grid
ParameterGrid
The expanded search space. len(self.param_grid) is the number of candidates the search will evaluate.
best_params_
dict
Parameter combination with the highest mean cross-validation score. None before fit is called.
best_score_
float
Mean cross-validation score of best_params_.
best_estimator_
object
Copy of estimator refitted on the full training data with best_params_. Only set when refit=True.
cv_results_
dict

Per-candidate log with parallel lists, one entry per candidate:

  • 'params' : list of dict, the parameter combination.
  • 'mean_test_score' : list of float, mean score across folds.
  • 'std_test_score' : list of float, standard deviation of the

fold scores.

  • 'mean_fit_time' : list of float, mean seconds per fold.
  • 'rank_test_score' : list of int, 1 for the best candidate.
total_time_
float
Wall-clock seconds consumed by the whole search, including the refit.

Notes

Complexity. O(n_{\text{candidates}} \cdot \text{cv} \cdot C) where C is the cost of a single estimator fit; memory is O(n_{\text{candidates}}) for the result log.

When to use. Small discrete spaces where a complete sweep is affordable and reproducibility matters. For continuous parameters, more than three or four tuned parameters, or expensive fits, prefer RandomSearchCV or BayesianSearchCV.

Sweep a two-value grid for Naive Bayes on iris:

python
>>> from tuiml.evaluation.tuning import GridSearchCV
>>> from tuiml.algorithms.bayesian import NaiveBayesClassifier
>>> from tuiml.datasets import load_iris
>>> data = load_iris()
>>> X, y = data.X, data.y
>>> search = GridSearchCV(
...     estimator=NaiveBayesClassifier(),
...     param_grid={'use_kernel_estimator': [True, False]},
...     cv=3,
...     scoring='accuracy',
...     random_seed=0,
... )
>>> search = search.fit(X, y)
>>> sorted(search.best_params_)
['use_kernel_estimator']
>>> round(float(search.best_score_), 2)
0.96
>>> len(search.cv_results_['params'])
2
>>> search.cv_results_['rank_test_score']
[1, 2]

The refitted best estimator backs predict:

python
>>> y_pred = search.predict(X)
>>> y_pred.shape
(150,)

Methods

get_parameter_schema (cls) -> dict

Return JSON Schema for constructor parameters.

fit (self, X: np.ndarray, y: np.ndarray) -> 'GridSearchCV'

Evaluate every grid point and keep the best-scoring one.

Parameters
X
np.ndarray of shape (n_samples, n_features)
Training features.
y
np.ndarray of shape (n_samples,)
Target values. Integer, boolean, or object dtype (or few unique values) selects stratified folds; otherwise plain k-fold is used.
Returns
self
GridSearchCV
The fitted searcher, with best_params_, best_score_, cv_results_ and total_time_ populated (and best_estimator_ when refit=True).
get_results (self) -> TuningResult

Bundle the fitted search state into a TuningResult.

Returns
result
TuningResult
Container holding best_params, best_score, best_estimator, cv_results, the number of candidates evaluated, and the total search time in seconds.
Raises
TypeError
If called before fit, because cv_results_ is still None.
__repr__ (self) -> str

Return a short summary of the searcher's configuration.

Returns
text
str
String of the form GridSearchCV(estimator=..., cv=..., scoring='...').