Validation and Hyperparameter Search
Optuna
Optuna runs the whole tuning loop for you — suggesting settings, remembering every result, and abandoning hopeless trials early — behind a three-line API.
- 8 min read
- 3 reading levels
- Published
Read these first
On this page 5
One lesson, three depths. Pick the one that fits you today — you can switch any time.
Beginner — No maths. Plain English.
Optuna is a Python library that runs your tuning experiments for you.
It proposes the next settings, records every result, and cancels trials that are going nowhere.
Think of a meticulous lab assistant with a logbook. You describe the experiment once. The assistant then runs it again and again. Each next mixture is chosen from everything in the logbook. Every outcome is written down. The burner goes off early whenever a batch is visibly ruined.
You already know the assistant's two skills from earlier lessons: choosing informed next trials is Bayesian optimisation, and cancelling doomed trials early is successive halving. Optuna packages both behind a tiny interface.
Why this had to be invented
By 2018, the ideas of smart tuning were settled and the pain had moved to plumbing. Teams were hand-writing loops to sample settings and track histories in spreadsheets. They wrote their own code to resume crashed searches and spread work across machines. The same scaffolding was rebuilt at every company, with the same bugs.
Optuna's design insight was the define-by-run interface: you do not declare a search space in a config file. You write an ordinary Python function that trains your model, and inside it you ask for values — "give me a learning rate between these bounds". The search space is whatever your code asks for, so loops, conditions and branching spaces come free.
How it works
You write one function, called the objective: settings in, score out. Optuna calls it repeatedly.
you write: objective(trial) → score
optuna: trial 1 settings A → 0.84 ┐
trial 2 settings B → 0.79 │ every result
trial 3 settings C → 0.91 │ goes in the
trial 4 near C's recipe → 0.92 │ logbook
trial 5 looking bad halfway → cancelled
...
best after N trials: settings, score, full historyEarly trials are near-random. Later trials cluster around what the logbook says works — the borewell-drilling behaviour, automated.
A real example you have seen
Optuna came out of Preferred Networks, a Japanese ML lab. It became the default tuner across the Kaggle community and many industry teams. Most tabular competition write-ups since 2020 mention it by name. If a model at your workplace was tuned recently, there is a fair chance this exact library ran the loop overnight.
Remember this
- Optuna = smart suggestions + a logbook of every trial + early cancelling, in one library.
- You write one objective function; the search space lives inside your code.
- The same ideas as the last two lessons — packaged so you actually use them.
What to learn next
- Tuning gradient-boosted trees — the highest-value search spaces to point Optuna at.
- Learning curves — diagnosing whether tuning or more data is the bottleneck.
- Experiment tracking — where tuning logbooks fit in the wider MLOps picture.
Developer — Code and libraries.
Setup
pip install optunaA light install: the wheel is under half a megabyte, plus a few small dependencies (SQLAlchemy for storage among them). Outputs verified with optuna 4.9.0 and scikit-learn 1.7.2 on CPU; the script runs in a few seconds.
The three-lesson rematch
Same dataset and pipeline as the grid search and random search lessons, same budget of 80 model fits (16 trials × 5 folds). Grid scored 0.916, random 0.926.
import optuna
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
optuna.logging.set_verbosity(optuna.logging.WARNING) # keep the console quiet
X, y = make_classification(n_samples=500, n_features=10, n_informative=5,
flip_y=0.05, random_state=4)
def objective(trial):
pipe = Pipeline([
("scale", StandardScaler()),
("svm", SVC(C=trial.suggest_float("C", 1e-2, 1e3, log=True),
gamma=trial.suggest_float("gamma", 1e-4, 1e1, log=True))),
])
return cross_val_score(pipe, X, y, cv=5).mean()
study = optuna.create_study(direction="maximize",
sampler=optuna.samplers.TPESampler(seed=2))
study.optimize(objective, n_trials=16)
print("trials run:", len(study.trials))
print("best settings:", {k: round(v, 4) for k, v in study.best_params.items()})
print("best CV accuracy:", round(study.best_value, 3))trials run: 16
best settings: {'C': 32.3871, 'gamma': 0.0309}
best CV accuracy: 0.928The scoreboard at equal budget: grid 0.916, random 0.926, Optuna 0.928. An honest reading: on a small, cheap problem the smart methods win by whiskers — their advantage compounds as trials get expensive and spaces grow. What you always gain is the workflow: history, resumability, and pruning.
The walkthrough
trial.suggest_float("C", 1e-2, 1e3, log=True) is the define-by-run move: a request for a value, made mid-code. log=True gives the log-uniform sampling that scale-type settings need — the same lesson as loguniform. Integers use suggest_int, choices use suggest_categorical, and an if on one suggestion can gate another — conditional spaces with no special syntax.
TPESampler(seed=2) — Optuna's default smart sampler. Its first ~10 trials are random startup; after that it proposes settings resembling past winners. The seed makes the run reproducible.
Pruning is one integration away. For iterative models, report progress inside the objective — trial.report(score, step) then if trial.should_prune(): raise optuna.TrialPruned() — and add pruner=optuna.pruners.MedianPruner() to the study. Trials below the running median get cancelled mid-training: successive halving's economics per trial. Note pruning needs stepwise scores, so it pairs with epoch-style training loops rather than a single opaque cross_val_score call.
Resumable, shareable studies. optuna.create_study(storage="sqlite:///tuning.db", study_name="svm", load_if_exists=True) persists every trial. A crashed overnight search resumes where it stopped; several machines pointing at one database parallelise the same study.
Common mistakes
No seed on the sampler. Rerun the script, get different trials, best score wobbles — and yesterday's result cannot be reproduced. Seed the sampler for experiments; drop the seed only for long production searches where extra diversity helps.
Judging TPE after ten trials. The first ~10 trials are deliberate random startup; stopping there means you paid TPE's overhead for random search's answer. Give it 30–100 trials to earn its keep.
Optimising a metric you do not care about. The objective returns one number and Optuna will maximise exactly that. Accuracy on imbalanced data is the classic wrong number — decide the metric before the search, not after.
Reporting best_value as final performance. Sixteen attempts at the same validation folds — the winner is flattered, as in every search method. Confirm the chosen settings once on an untouched test set.
Try it yourself
Add a third suggestion — kernel=trial.suggest_categorical("kernel", ["rbf", "poly"]) — and, when the kernel is polynomial, a conditional degree=trial.suggest_int("degree", 2, 4). The branching space needs zero configuration. Then bump n_trials to 60 and watch study.best_value — where does the improvement flatten?
What to learn next
- Tuning gradient-boosted trees — the highest-value search spaces to point Optuna at.
- Learning curves — diagnosing whether tuning or more data is the bottleneck.
- Experiment tracking — where tuning logbooks fit in the wider MLOps picture.
Researcher — Mathematics and papers.
Design contributions
Akiba, Sano, Yanase, Ohta and Koyama (2019), Optuna: a next-generation hyperparameter optimization framework (KDD), name three design goals: define-by-run search-space construction (by analogy to define-by-run autodiff frameworks), efficient sampling and pruning as co-equal primitives, and easy scalability through storage-backed studies. The architectural separation matters: samplers decide what to try; pruners decide how long to run it — orthogonal axes matching the model-based versus bandit-based split of the two previous lessons.
The default sampler: TPE
The tree-structured Parzen estimator (Bergstra et al., 2011) models the configuration densities $\ell(x)$ — fitted to trials in the best $\gamma$ quantile — and $g(x)$, fitted to the rest, then proposes $x$ maximising $\ell(x)/g(x)$, which is monotone in expected improvement under the quantile construction. The tree structure mirrors define-by-run's conditional branches: each suggest call is a node whose density is estimated where the branch was active. Multivariate TPE (multivariate=True) models joint densities to capture parameter interactions the independent default misses.
Other bundled samplers: RandomSampler (the honest baseline), CmaEsSampler — CMA-ES (Hansen and Ostermeier, 2001) for continuous, non-conditional spaces — QMCSampler (Sobol' low-discrepancy startup), GPSampler (GP-based BO), and NSGAIISampler (Deb et al., 2002) as the multi-objective default.
Pruners as bandit schedulers
MedianPruner cancels a trial whose intermediate value trails the median of completed trials at the same step — an aggressive approximation of successive halving. SuccessiveHalvingPruner implements ASHA-style asynchronous promotion (Li et al., 2020); HyperbandPruner adds the bracket structure over minimum resources (Li et al., 2018). The pruning contract requires that intermediate metrics be comparable across trials at equal steps — the same rankability assumption analysed in the Hyperband lesson, inherited unchanged.
Multi-objective and constrained search
create_study(directions=["maximize", "minimize"]) runs NSGA-II by default, returning a Pareto front (study.best_trials) — the standard formulation for accuracy-versus-latency trades in deployment-bound models. Constraints enter via constraints_func on supported samplers, implementing feasibility-aware selection.
Ecosystem position
Peer frameworks: Ray Tune (distributed-first, scheduler-rich), Hyperopt (TPE's original home, now largely dormant), SMAC3 (random-forest surrogates, strong on algorithm configuration), Ax/BoTorch (GP-based, research-grade acquisition functions). Optuna's niche is the pragmatic middle: minimal API, storage-backed durability, first-class pruning, and integrations (LightGBM callbacks, PyTorch, sklearn) that wire trial.report into existing training loops. Benchmark caution transfers verbatim from earlier lessons: at large budgets on cheap objectives, tuned random search remains within noise of sophisticated samplers (Li and Talwalkar, 2020) — the framework's durable value is the workflow, with sampling gains concentrated in expensive, low-budget regimes.
What to learn next
- Tuning gradient-boosted trees — the highest-value search spaces to point Optuna at.
- Learning curves — diagnosing whether tuning or more data is the bottleneck.
- Experiment tracking — where tuning logbooks fit in the wider MLOps picture.