How to Implement Your Callback with OptunaHub

This recipe shows how to implement and register your own callback with OptunaHub.

Callbacks are used when you want to insert custom processing after each trial completes. Typical use cases include:

  • Uploading the current best result to an external server (e.g., W&B, MLflow).

  • Sending a notification when a new best value is found.

  • Stopping the study early based on custom criteria by calling stop().

A callback is simply a callable with the following signature:

def callback(study: optuna.Study, trial: optuna.trial.FrozenTrial) -> None:
    ...

Optuna calls each registered callback once per trial, after the objective function returns and the trial state has been recorded. At that point Study already reflects the updated best value / best params, and trial is a FrozenTrial (immutable).

The simplest way to implement a callback is to define a plain function:

from __future__ import annotations

import optuna


def my_callback(study: optuna.Study, trial: optuna.trial.FrozenTrial) -> None:
    print(f"Trial {trial.number} finished.")
    print(f"  params     : {trial.params}")
    print(f"  value      : {trial.value}")
    if trial.state == optuna.trial.TrialState.COMPLETE:
        print(f"  best_value : {study.best_value}")

If your callback needs to hold internal state (e.g., a connection to an external service), you can implement it as a class with a __call__ method instead.

class MyCallback:
    """A callback that prints trial information after each completed trial.

    Args:
        verbose: If ``True``, also print the full ``params`` dict.
    """

    def __init__(self, verbose: bool = True) -> None:
        self._verbose = verbose

    def __call__(self, study: optuna.Study, trial: optuna.trial.FrozenTrial) -> None:
        # This method is called after every trial regardless of its state.
        if trial.state != optuna.trial.TrialState.COMPLETE:
            return

        print(f"Trial {trial.number} finished.")
        if self._verbose:
            print(f"  params     : {trial.params}")
        print(f"  value      : {trial.value}")
        print(f"  best_value : {study.best_value}")

The callback is passed to optimize() via the callbacks argument. Multiple callbacks can be specified as a list; they are called in order after each trial.

def objective(trial: optuna.trial.Trial) -> float:
    x = trial.suggest_float("x", -10, 10)
    y = trial.suggest_int("y", -5, 5)
    return x**2 + y**2

Run the study with a plain function callback.

study = optuna.create_study()
study.optimize(objective, n_trials=5, callbacks=[my_callback])
Trial 0 finished.
  params     : {'x': 0.28828389547690847, 'y': 0}
  value      : 0.08310760439134109
  best_value : 0.08310760439134109
Trial 1 finished.
  params     : {'x': 3.6813682704050716, 'y': 0}
  value      : 13.55247234234523
  best_value : 0.08310760439134109
Trial 2 finished.
  params     : {'x': -8.132343001161315, 'y': -5}
  value      : 91.13500268853743
  best_value : 0.08310760439134109
Trial 3 finished.
  params     : {'x': -8.836767117283731, 'y': 0}
  value      : 78.08845308510702
  best_value : 0.08310760439134109
Trial 4 finished.
  params     : {'x': -5.2960213305046615, 'y': 0}
  value      : 28.047841933160367
  best_value : 0.08310760439134109

Run another study with the class-based callback.

study = optuna.create_study()
study.optimize(objective, n_trials=5, callbacks=[MyCallback(verbose=True)])
Trial 0 finished.
  params     : {'x': -0.7339450343286931, 'y': 1}
  value      : 1.5386753134157465
  best_value : 1.5386753134157465
Trial 1 finished.
  params     : {'x': 0.8368352061658406, 'y': -5}
  value      : 25.700293162278626
  best_value : 1.5386753134157465
Trial 2 finished.
  params     : {'x': 5.699111017621476, 'y': 5}
  value      : 57.4798663911745
  best_value : 1.5386753134157465
Trial 3 finished.
  params     : {'x': 2.0465627122532446, 'y': 0}
  value      : 4.188418935185357
  best_value : 1.5386753134157465
Trial 4 finished.
  params     : {'x': 0.15431904270280583, 'y': -1}
  value      : 1.0238143669407105
  best_value : 1.0238143669407105

After implementing your own callback, you can register it with OptunaHub. See How to Register Your Package with OptunaHub for how to register your callback with OptunaHub. The category name to use when placing your package in the registry is callbacks:

Total running time of the script: (0 minutes 0.009 seconds)

Gallery generated by Sphinx-Gallery