Skip to content

Tuners

Synalinks-aware wrappers around the standard keras_tuner tuners.

keras_tuner.RandomSearch (and friends) are designed for Keras models: their default run_trial builds a model via hypermodel(hp) and calls model.fit(...). Synalinks programs work the same way but their fit is async, and program.fit returns a synalinks.callbacks.History that keras_tuner does not recognize (its isinstance check is against the stubbed keras.callbacks.History).

The wrappers here override run_trial to:

  1. Build the program by calling tuner.hypermodel.build(hp) — the hypermodel may return either a synalinks.Program directly or a coroutine that resolves to one.
  2. Run await program.fit(*args, **kwargs) with whatever the user passed to tuner.search(...).
  3. Reduce the resulting History into a flat metrics dict keyed by metric name and return it. The oracle's objective(s) get the best-across-epochs value (max for "max" direction, min for "min"); non-objective metrics get their final-epoch value (informational).

Everything else (oracle plumbing, trial persistence, get_best_hyperparameters) comes for free from keras_tuner.

Usage:

import synalinks
synalinks.disable_keras_backend()   # must come before kt is imported

async def build_program(hp):
    inputs = synalinks.Input(data_model=Query)
    outputs = await synalinks.Generator(
        data_model=Answer,
        language_model=language_model,
        temperature=hp.Float("temperature", 0.0, 1.0),
    )(inputs)
    program = synalinks.Program(inputs=inputs, outputs=outputs)
    program.compile(reward=synalinks.rewards.ExactMatch())
    return program

tuner = synalinks.tuners.RandomSearch(
    hypermodel=build_program,
    objective=synalinks.tuners.Objective("val_reward", direction="max"),
    max_trials=5,
    directory="runs",
    project_name="my_search",
)
tuner.search(
    x=x_train,
    y=y_train,
    validation_data=(x_val, y_val),
    epochs=3,
    batch_size=4,
)
best_hp = tuner.get_best_hyperparameters(num_trials=1)[0]

keras_tuner is an optional dependency. Importing this module does not import keras_tuner — the kt subclasses are built lazily on the first instantiation of RandomSearch / BayesianOptimization / Hyperband / GridSearch. If keras_tuner (and either real Keras or the disable_keras_backend() stub) cannot be loaded then, a clear error is raised pointing the user at the fix.

Objective(name, direction=None)

Re-export of keras_tuner.Objective for ergonomic access.

When direction is omitted, it's inferred from the synalinks metrics registry: shipped metrics declare direction at class scope (e.g. Accuracy.direction == "up", ProgramCost.direction == "down"), and "reward" is special-cased as "max". Unknown names fall through to keras-tuner's own inference (which handles "loss" etc.). Pass direction= explicitly if neither table covers your metric.

Loads keras_tuner lazily on first call so importing synalinks.tuners never pulls kt into the process by itself.

Source code in synalinks/src/utils/keras_tuner_utils.py
@synalinks_export(
    [
        "synalinks.tuners.Objective",
        "synalinks.utils.tuners.Objective",
    ]
)
def Objective(name, direction=None):
    """Re-export of `keras_tuner.Objective` for ergonomic access.

    When ``direction`` is omitted, it's inferred from the synalinks metrics
    registry: shipped metrics declare ``direction`` at class scope (e.g.
    ``Accuracy.direction == "up"``, ``ProgramCost.direction == "down"``), and
    ``"reward"`` is special-cased as ``"max"``. Unknown names fall through
    to keras-tuner's own inference (which handles ``"loss"`` etc.). Pass
    ``direction=`` explicitly if neither table covers your metric.

    Loads ``keras_tuner`` lazily on first call so importing
    ``synalinks.tuners`` never pulls kt into the process by itself.
    """
    try:
        import keras_tuner as kt
    except ModuleNotFoundError as e:
        missing = e.name or ""
        if missing == "keras" or missing.startswith("keras."):
            raise RuntimeError(
                "`synalinks.tuners.Objective` could not import "
                "`keras_tuner`: call `synalinks.disable_keras_backend()` "
                "first, or install Keras."
            ) from e
        raise ImportError(
            "`synalinks.tuners.Objective` requires `keras-tuner`. "
            "Install with `pip install keras-tuner`."
        ) from e
    _patch_kt_inference()
    if direction is None:
        from keras_tuner.src.engine import metrics_tracking

        direction = metrics_tracking.infer_metric_direction(name)
        if direction is None:
            raise ValueError(
                f"Could not infer optimization direction for objective {name!r}. "
                "Pass it explicitly: "
                f"synalinks.tuners.Objective({name!r}, direction='max')."
            )
    return kt.Objective(name=name, direction=direction)