Last modified: Oct 24, 2026
Hyperparameter Tuning with Optuna
Machine learning models have two kinds of settings. Parameters are learned from data. Hyperparameters are set by you before training starts.
Examples include learning rate, tree depth, and regularization strength. The right values can make or break a model.
Optuna is a modern Python library for hyperparameter tuning. It is fast, flexible, and easy to learn. This guide walks you through it from start to finish.
Why Hyperparameter Tuning Matters
A model with poor hyperparameters can underfit or overfit. A model with good ones often performs far better.
Manual tuning is slow and unreliable. Grid search tries every combination, which explodes in cost. Random search is better but still wastes time.
Optuna solves these problems. It learns from past trials and focuses on promising regions of the search space.
What Is Optuna?
Optuna is an open-source framework for automatic hyperparameter optimization. It was created by Preferred Networks and is widely used in research and industry.
Optuna defines search spaces dynamically. You write a Python function, and Optuna samples values inside it.
The library also supports pruning. It stops weak trials early to save compute. This makes tuning much faster.
Installing Optuna
Install Optuna with pip. It works well with scikit-learn, XGBoost, LightGBM, PyTorch, and more.
pip install optuna
If you plan to visualize results, also install plotting tools.
pip install optuna plotly matplotlib
Core Concepts of Optuna
Optuna has a few simple building blocks. Once you know them, everything else clicks.
Study: a collection of trials for one optimization task. Each study has a direction, such as minimize or maximize.
Trial: a single run of your objective function. Each trial suggests a set of hyperparameters.
Objective function: the function Optuna calls. It receives a trial object and returns a score.
Sampler: the algorithm that picks the next hyperparameters. The default is the Tree-structured Parzen Estimator (TPE).
Pruner: the component that stops unpromising trials early.
Your First Optuna Study
Let's tune a simple model. We will use scikit-learn's breast cancer dataset and a random forest classifier.
First, import the libraries and load the data.
import optuna
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
# Load a small dataset
data = load_breast_cancer()
X, y = data.data, data.target
Next, define the objective function. Inside it, use trial.suggest_int for integer values and trial.suggest_float for floats.
def objective(trial):
# Suggest hyperparameters for this trial
n_estimators = trial.suggest_int("n_estimators", 50, 300)
max_depth = trial.suggest_int("max_depth", 2, 20)
min_samples_split = trial.suggest_int("min_samples_split", 2, 10)
model = RandomForestClassifier(
n_estimators=n_estimators,
max_depth=max_depth,
min_samples_split=min_samples_split,
random_state=42,
)
# 5-fold cross-validation accuracy
score = cross_val_score(model, X, y, cv=5, n_jobs=-1).mean()
return score
Now create a study and run it. We want to maximize accuracy.
# Create a study that maximizes the objective
study = optuna.create_study(direction="maximize")
# Run 50 trials
study.optimize(objective, n_trials=50)
print("Best accuracy:", study.best_value)
print("Best params:", study.best_params)
The output shows the best score and the winning configuration.
Best accuracy: 0.9649122807017543
Best params: {'n_estimators': 187, 'max_depth': 9, 'min_samples_split': 3}
That is the entire workflow. Define a function, suggest values, and optimize. Optuna handles the rest.
Understanding the Search Space
The search space defines which values Optuna can try. A well-designed space matters as much as the optimizer.
Use trial.suggest_categorical for fixed choices like model type or activation function.
def objective(trial):
# Categorical choice between two solvers
solver = trial.suggest_categorical("solver", ["liblinear", "lbfgs"])
C = trial.suggest_float("C", 1e-4, 1e2, log=True)
return C # placeholder return
Use log=True for values that span several orders of magnitude. Learning rate and regularization strength are common examples.
Keep spaces realistic. Huge ranges slow down the search. Tiny ranges limit discovery.
Pruning Bad Trials Early
Many trials are clearly weak after a few steps. Pruning stops them before they finish.
Use trial.report to send intermediate scores and trial.should_prune to check whether to stop.
import optuna
from sklearn.model_selection import cross_val_score
def objective(trial):
n_estimators = trial.suggest_int("n_estimators", 50, 300)
model = RandomForestClassifier(n_estimators=n_estimators, random_state=42)
# Report scores fold by fold
scores = cross_val_score(model, X, y, cv=5, n_jobs=-1)
for step, score in enumerate(scores):
trial.report(score, step)
if trial.should_prune():
raise optuna.TrialPruned()
return scores.mean()
study = optuna.create_study(
direction="maximize",
pruner=optuna.pruners.MedianPruner(n_startup_trials=5, n_warmup_steps=1),
)
study.optimize(objective, n_trials=50)
Pruning saves time and lets you run more trials. More trials usually mean better results.
Visualizing Results
Optuna ships with built-in plots. They help you understand which parameters matter.
optuna.visualization.plot_param_importances shows which hyperparameters influence the score most.
import optuna
# Show which parameters matter most
optuna.visualization.plot_param_importances(study).show()
# Show the optimization history
optuna.visualization.plot_optimization_history(study).show()
The history plot shows how the best score improves over time. The importance plot ranks parameters by impact.
These visuals are great for reports. They also guide the next round of tuning.
Persisting Studies with Storage
By default, studies live in memory. For long runs, save them to a database.
Use optuna.create_study with a storage URL and a study name. This lets you pause, resume, and share results.
study = optuna.create_study(
study_name="rf_tuning",
storage="sqlite:///optuna.db",
direction="maximize",
load_if_exists=True,
)
study.optimize(objective, n_trials=50)
You can also run parallel workers against the same storage. Each worker adds trials to the shared study.
Integrating with Real Pipelines
Optuna fits into any Python workflow. If your pipeline calls APIs, tune those settings too.
For example, you can tune prompt temperature when working with OpenAI function calling in Python. You can also tune batch sizes and retry limits.
Text preprocessing has knobs as well. You might tune vocabulary size or n-gram ranges when doing tokenization with NLTK in Python.
Data pipelines benefit too. If you produce and consume Kafka messages in Python, you can tune consumer fetch sizes and producer linger times with Optuna.
Tips for Better Tuning
Start with a small number of trials. Look at the plots. Then refine the search space.
Use log scale for learning rates. Use integers for counts. Avoid overlapping parameters that do the same job.
Set a fixed random seed for reproducibility. Log every trial so you can compare runs later.
Finally, validate the best configuration on a holdout set. Cross-validation scores can be optimistic.
Conclusion
Optuna makes hyperparameter tuning simple and effective. You define an objective function. Optuna suggests values, prunes weak trials, and finds strong configurations.
The workflow is short. Create a study, suggest parameters, optimize, and inspect results.
Start with the example in this guide. Then adapt it to your own model. Small experiments lead to big gains.