3.6 Grid, Random, and Bayesian Search Strategies
Key Takeaways
- Grid search evaluates the full Cartesian product of candidate values — exhaustive, reproducible, and exponential in the number of hyperparameters.
- Random search samples from distributions for a fixed budget and finds good configurations faster when only a few hyperparameters actually matter.
- Bayesian optimisation (Hyperopt's TPE) models the relationship between hyperparameters and loss from completed trials and concentrates later trials in promising regions.
- In Spark ML, grid search is expressed with `ParamGridBuilder().addGrid(...).build()` and executed by `CrossValidator` or `TrainValidationSplit`.
- Grid search is preferable for a handful of discrete values you must cover exhaustively; Bayesian search wins when evaluations are expensive and the space is large.
3.6 Grid, Random, and Bayesian Search Strategies
Hyperparameter optimization is the systematic process of identifying the optimal combination of model hyperparameters that minimizes validation loss. While model parameters (such as tree split thresholds or linear coefficients) are learned directly during training, hyperparameters (such as learning rate, tree depth, and regularization penalties) govern the learning algorithm itself. Databricks natively bundles Hyperopt, an open-source Python library for Sequential Model-Based Optimization (SMBO) using Bayesian reasoning.
Hyperparameter Optimization Paradigms Compared
+-----------------------------------------------------------------------------+
| HYPERPARAMETER TUNING STRATEGY COMPARISON |
| |
| GRID SEARCH RANDOM SEARCH BAYESIAN OPTIMIZATION |
| (ParamGridBuilder) (Random Sampling) (Hyperopt / TPE) |
| +-----------------+ +-----------------+ +-----------------+ |
| | . . . . . . . . | | . . . | | . . . . | |
| | . . . . . . . . | | . . . | | . . . . . . | |
| | . . . . . . . . | | . . . | | . . . | |
| | . . . . . . . . | | . . . | | | |
| +-----------------+ +-----------------+ +-----------------+ |
| - Exhaustive grid - Random distributions - Learns from past trials |
| - Combinatorial blowup - Better coverage - Focuses on optimal space|
| - Wastes compute - Uninformed trials - Max Expected Improvement|
+-----------------------------------------------------------------------------+
Grid Search
- Mechanics: Exhaustively evaluates every point in the Cartesian product of predefined parameter candidate lists.
- Limitations: Suffers from the curse of dimensionality. If 5 hyperparameters each have 4 candidate values, Grid Search evaluates $4^5 = 1,024$ full training runs. Furthermore, if only 2 of the 5 hyperparameters strongly influence performance, Grid Search wastes 95% of its computational budget re-evaluating identical values of the important parameters across uninformative variations of unimportant ones.
Random Search
- Mechanics: Randomly draws candidate parameter combinations from specified continuous or discrete distributions over a fixed budget of $N$ evaluations.
- Advantages: Discovers superior parameter configurations faster than Grid Search because it tests unique values across continuous dimensions on every single trial.
- Limitations: Independent and memoryless; it fails to leverage results from previous trials to guide future exploration.
Bayesian Optimization & Tree of Parzen Estimators (TPE)
- Mechanics: Hyperopt implements the Tree of Parzen Estimators (TPE) algorithm. Rather than modeling the objective function response directly $P(y|x)$ (as in Gaussian Processes), TPE uses Bayes' theorem to model two conditional parameter distributions: where $y^*$ is a quantile cutoff (e.g., top 15% best performing trials), $\ell(x)$ is the density of hyperparameters associated with low validation loss, and $g(x)$ is the density associated with the remaining evaluations.
- Acquisition Function: TPE chooses candidate hyperparameter vectors $x$ that maximize the Expected Improvement (EI): Maximizing $\frac{\ell(x)}{g(x)}$ naturally samples parameters that have high probability under good models $\ell(x)$ and low probability under poor models $g(x)$.
Expressing Each Strategy on Databricks
Grid search — ParamGridBuilder with a Spark ML tuner
from pyspark.ml.tuning import ParamGridBuilder, CrossValidator
param_grid = (ParamGridBuilder()
.addGrid(lr.regParam, [0.01, 0.1, 1.0])
.addGrid(lr.elasticNetParam, [0.0, 0.5, 1.0])
.build()) # 3 x 3 = 9 parameter combinations
ParamGridBuilder builds the full Cartesian product; the count is the product of the
list lengths. Section 3.9 covers how that number combines with the fold count.
Random search — Hyperopt with rand.suggest
from hyperopt import fmin, rand, hp
best = fmin(fn=objective, space=search_space, algo=rand.suggest, max_evals=40)
Every trial draws fresh values from the distributions, so continuous hyperparameters are never re-tested at the same point — the key advantage over a grid.
Bayesian search — Hyperopt with tpe.suggest
from hyperopt import fmin, tpe
best = fmin(fn=objective, space=search_space, algo=tpe.suggest, max_evals=40)
TPE builds density models of the good and the poor trials and proposes candidates that maximise the ratio between them, so later trials exploit what earlier trials revealed.
Choosing a Strategy
| Situation | Strategy | Reason |
|---|---|---|
| A few discrete values that must each be evaluated (e.g. 3 regularisation strengths × 3 mixing ratios) | Grid | Exhaustive, deterministic, easy to audit |
| Many hyperparameters, most of them unimportant | Random | Spends the budget on distinct values of the parameters that matter instead of re-testing them |
| Continuous ranges spanning orders of magnitude | Random or Bayesian | A grid cannot cover a continuum without exploding |
| Each evaluation is expensive (minutes to hours) | Bayesian (TPE) | Uses every completed trial to choose the next one, so the budget goes further |
| Full reproducibility and exhaustive coverage are contractual requirements | Grid | The only strategy that provably evaluates every specified combination |
Tuning a distributed pyspark.ml model | Grid via CrossValidator | Spark ML tuners take a ParamGrid; see Section 3.7 for why SparkTrials is wrong here |
The dimensionality argument
With 5 hyperparameters at 4 values each, a grid is $4^5 = 1{,}024$ evaluations. If only 2 of the 5 genuinely affect the score, the grid tests each important value 64 times under irrelevant variations of the others. Random search with the same budget tests 1,024 distinct values of every parameter — which is why random search dominates grid search on high-dimensional spaces at equal cost.
Defining the Search Space Correctly
The strategy only matters if the space it samples is well posed, and Hyperopt's space primitives are themselves examinable:
| Primitive | Draws | Use for |
|---|---|---|
hp.uniform(label, low, high) | A continuous value, uniformly | Parameters whose sensible range spans one order of magnitude, such as subsample between 0.5 and 1.0 |
hp.loguniform(label, low, high) | A continuous value, uniform in log space | Parameters spanning orders of magnitude, such as a learning rate from 0.001 to 0.3 or regParam from 1e-4 to 1e2 |
hp.quniform(label, low, high, q) | A value rounded to a multiple of q | Integer-like parameters such as max_depth; cast the result with int() before passing it to the estimator |
hp.choice(label, options) | One element of a list | Genuinely categorical parameters such as the kernel or the boosting type |
The log-uniform case is the one that changes results. Sampling a learning rate
uniformly between 0.001 and 0.3 puts roughly 97% of the draws above 0.01, so the small
values that often win are almost never tried. hp.loguniform spreads the budget evenly
across each decade instead. Note that its bounds are given in natural-log units, so a
range of 0.001 to 0.3 is expressed as hp.loguniform("lr", np.log(0.001), np.log(0.3)).
import numpy as np
from hyperopt import hp, fmin, tpe, space_eval
search_space = {
"learning_rate": hp.loguniform("learning_rate", np.log(0.001), np.log(0.3)),
"max_depth": hp.quniform("max_depth", 3, 12, 1),
"booster": hp.choice("booster", ["gbtree", "dart"]),
}
best = fmin(fn=objective, space=search_space, algo=tpe.suggest, max_evals=40)
best_params = space_eval(search_space, best) # {'booster': 'gbtree', ...}
The hp.choice return-value trap
fmin returns the index into the option list for any hp.choice parameter, not the
value itself. Reading best["booster"] therefore yields 0, and passing that straight
into an estimator either fails or silently trains something else.
hyperopt.space_eval(search_space, best) translates the returned dictionary back into
real hyperparameter values, and it is the step scenario questions leave out.
Budget
max_evals is the total number of trials, and it is the only real cost control. A
useful default is roughly 10 to 20 evaluations per hyperparameter for TPE, which needs
some completed trials before its density models are informative — the first several
trials are effectively random regardless of the algorithm chosen.
A data scientist is configuring Hyperopt to tune the learning rate of a gradient boosting model across three orders of magnitude between 0.0001 and 0.1. Which hyperopt.hp distribution is mathematically best suited for this parameter?
A team must tune a Spark ML logistic regression over exactly three values of regParam and three values of elasticNetParam, and an auditor requires proof that every combination was evaluated. Which approach fits?
Why does random search typically outperform grid search at an equal evaluation budget when a model has many hyperparameters but only a few matter?
A search space defines "booster": hp.choice("booster", ["gbtree", "dart"]). After fmin completes, best["booster"] is 0. What does that mean and how is the value recovered?