How to run hyperparameter tuning in Keras

Hyperparameter tuning tests several model configurations against the same validation signal before committing to a final training run. In Keras projects, KerasTuner can drive that search while the model-building function stays close to normal Keras code.

A bounded RandomSearch is enough for a local tuning smoke test. It samples a few values for hidden units, dropout, and learning rate, writes each trial under a project directory, and ranks the trials by val_accuracy.

The script selects the JAX backend before importing Keras so the run does not depend on TensorFlow in a minimal environment. Replace the synthetic arrays with project training and validation splits, keep the held-out test split separate, and raise max_trials only after the script records the expected trial files.

Steps to run Keras hyperparameter tuning:

  1. Save the backend selection, imports, and data split as
    tune_dense_classifier.py

    .

    tune_dense_classifier.py
    import os
    from pathlib import Path
     
    os.environ["KERAS_BACKEND"] = "jax"
     
    import keras
    import keras_tuner
    import numpy as np
     
     
    keras.utils.set_random_seed(17)
     
    rng = np.random.default_rng(17)
    x = rng.normal(size=(240, 4)).astype("float32")
    y = (
        (0.8 * x[:, 0] - 0.4 * x[:, 1] + 0.3 * x[:, 2] + 0.1 * x[:, 3]) > 0
    ).astype("float32")
     
    x_train, x_val, x_test = x[:160], x[160:200], x[200:]
    y_train, y_val, y_test = y[:160], y[160:200], y[200:]

    Install Keras, KerasTuner, NumPy, and the selected backend in the same Python environment before running the script. Put the backend setting before any keras or keras_tuner import.
    Related: How to set the Keras backend

  2. Add the model-building function below the data split.
    def build_model(hp):
        units = hp.Int("units", min_value=4, max_value=16, step=4)
        dropout = hp.Choice("dropout", values=[0.0, 0.2])
        learning_rate = hp.Choice("learning_rate", values=[0.01, 0.03])
     
        model = keras.Sequential(
            [
                keras.layers.Input(shape=(4,)),
                keras.layers.Dense(units, activation="relu"),
                keras.layers.Dropout(dropout),
                keras.layers.Dense(1, activation="sigmoid"),
            ]
        )
        model.compile(
            optimizer=keras.optimizers.Adam(learning_rate=learning_rate),
            loss=keras.losses.BinaryCrossentropy(),
            metrics=[keras.metrics.BinaryAccuracy(name="accuracy")],
            jit_compile=False,
        )
        return model

    hp.Int() and hp.Choice() define the values KerasTuner can sample for each trial. Disable jit_compile for a short CPU smoke test if backend compilation time would hide the tuning result.
    Related: How to compile a model in Keras
    Related: How to disable JIT compilation in Keras

  3. Add the RandomSearch tuner and the bounded search call.
    tuner = keras_tuner.RandomSearch(
        build_model,
        objective="val_accuracy",
        max_trials=3,
        seed=17,
        overwrite=True,
        directory="tuner_runs",
        project_name="dense_classifier",
    )
     
    tuner.search(
        x_train,
        y_train,
        validation_data=(x_val, y_val),
        epochs=4,
        batch_size=16,
        verbose=0,
    )

    overwrite=True starts a fresh demo run when the same directory already exists. Remove it when a real tuning job should resume or preserve earlier trials.

  4. Add best-trial retrieval, final fitting, evaluation, and printed checks.
    best_hp = tuner.get_best_hyperparameters(num_trials=1)[0]
    best_trial = tuner.oracle.get_best_trials(num_trials=1)[0]
     
    best_model = tuner.hypermodel.build(best_hp)
    x_final = np.concatenate([x_train, x_val])
    y_final = np.concatenate([y_train, y_val])
    history = best_model.fit(x_final, y_final, epochs=4, batch_size=16, verbose=0)
    _, accuracy = best_model.evaluate(x_test, y_test, verbose=0)
     
    print(f"backend: {keras.config.backend()}")
    print(f"trials run: {len(tuner.oracle.trials)}")
    print(f"best units: {best_hp.get('units')}")
    print(f"best dropout: {best_hp.get('dropout')}")
    print(f"best learning_rate: {best_hp.get('learning_rate')}")
    print(f"best val_accuracy: {best_trial.score:.4f}")
    print(f"test accuracy: {accuracy:.4f}")
    print(f"history keys: {', '.join(sorted(history.history.keys()))}")
    print(f"trial directory: {Path('tuner_runs/dense_classifier').as_posix()}")

    Rebuilding the model from best_hp keeps the final fit separate from the validation trials. Keep a test split outside the tuner search so the final metric is not part of trial selection.

  5. Run the tuning script.
    $ python3 tune_dense_classifier.py
    backend: jax
    trials run: 3
    best units: 12
    best dropout: 0.0
    best learning_rate: 0.03
    best val_accuracy: 0.9750
    test accuracy: 1.0000
    history keys: accuracy, loss
    trial directory: tuner_runs/dense_classifier
  6. Check the generated tuner directory.
    $ ls tuner_runs/dense_classifier
    oracle.json
    trial_0
    trial_1
    trial_2
    tuner0.json

    Each trial_* directory stores the sampled hyperparameters and checkpoint files for one configuration. Keep this directory with experiment artifacts when the tuning results need to be reviewed later.