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
Exhaustive cross-validated search over every point of a parameter grid.
__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
-
Expand
param_gridinto the cartesian product of its value lists. - For each combination, copy the estimator, set the parameters on the
cv-fold cross-validation (stratified for classification targets, plain k-fold otherwise).
- Record the mean and standard deviation of the fold scores, plus the
cv_results_.
-
Keep the combination with the highest mean score in
best_params_
cv_results_['rank_test_score'] (rank 1 is best).
-
If
refit=True, refit a fresh copy of the estimator with the winning
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 1281Parameters
estimator
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
ParameterGrid.
scoring
'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
refit
best_params_ after the search. Required for predict/score.
verbose
0 is silent; any value above 0 prints one line per candidate plus a final summary.
n_jobs
joblib. 1 runs sequentially; other values fall back to sequential execution with a warning when joblib is not installed.
random_seed
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
'type', 'iteration', 'total', 'params', 'mean_score', 'std_score' and 'best_score'.
Attributes
param_grid
len(self.param_grid) is the number of candidates the search will evaluate.
best_params_
None before fit is called.
best_score_
best_params_.
best_estimator_
estimator refitted on the full training data with best_params_. Only set when refit=True.
cv_results_
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_
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.
See Also
RandomSearchCV
Samples a fixed budget of configurations from distributions instead of enumerating a grid.
BayesianSearchCV
Models the score surface with a Gaussian Process to pick each next configuration.
ParameterGrid
The grid expansion used here.
TuningResult
Structured result returned by get_results.
Sweep a two-value grid for Naive Bayes on iris:
>>> 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:
>>> y_pred = search.predict(X)
>>> y_pred.shape
(150,)
Methods
fit
(self, X: np.ndarray, y: np.ndarray) -> 'GridSearchCV'
fit
(self, X: np.ndarray, y: np.ndarray) -> 'GridSearchCV'
Evaluate every grid point and keep the best-scoring one.
Parameters
X
y
Returns
self
best_params_, best_score_, cv_results_ and total_time_ populated (and best_estimator_ when refit=True).
get_results
(self) -> TuningResult
get_results
(self) -> TuningResult
Bundle the fitted search state into a TuningResult.
Returns
result
best_params, best_score, best_estimator, cv_results, the number of candidates evaluated, and the total search time in seconds.
Raises
TypeError
fit, because cv_results_ is still None.