# Tuning Fit and Compile Arguments

## Introduction

While `kerasnip` makes it easy to tune the architecture of a Keras model (e.g., the number of layers or the number of units in a layer), it is often just as important to tune the parameters that control the training process itself. `kerasnip` exposes these parameters through special `fit_*` and `compile_*` arguments in the model specification.

This vignette provides a comprehensive example of how to tune these arguments within a `tidymodels` workflow. We will tune:

- `fit_epochs`: The number of training epochs.
- `fit_batch_size`: The number of samples per gradient update.
- `compile_optimizer`: The optimization algorithm (e.g., “adam”, “sgd”).
- `compile_loss`: The loss function used for training.
- `learn_rate`: The learning rate for the optimizer.

Fit arguments can be written either with the `fit_` prefix (`fit_epochs`) or without it (`epochs`), matching the names used by other tidymodels neural-network engines. When both are supplied, the `fit_` version takes precedence.

## Setup

First, we load the necessary packages.

    library(kerasnip)
    library(tidymodels)
    #> ── Attaching packages ───────────────── tidymodels 1.5.0 ──
    #> ✔ broom        1.0.13     ✔ recipes      1.3.3
    #> ✔ dials        1.4.4      ✔ rsample      1.3.2
    #> ✔ dplyr        1.2.1      ✔ tailor       0.1.0
    #> ✔ ggplot2      4.0.3      ✔ tidyr        1.3.2
    #> ✔ infer        1.1.0      ✔ tune         2.1.0
    #> ✔ modeldata    1.5.1      ✔ workflows    1.3.0
    #> ✔ parsnip      1.6.0      ✔ workflowsets 1.1.1
    #> ✔ purrr        1.2.2      ✔ yardstick    1.4.0
    #> ── Conflicts ──────────────────── tidymodels_conflicts() ──
    #> ✖ purrr::discard()         masks scales::discard()
    #> ✖ dplyr::filter()          masks stats::filter()
    #> ✖ parsnip::get_model_env() masks kerasnip::get_model_env()
    #> ✖ dplyr::lag()             masks stats::lag()
    #> ✖ recipes::step()          masks stats::step()
    library(keras3)
    #>
    #> Attaching package: 'keras3'
    #> The following object is masked from 'package:yardstick':
    #>
    #>     get_weights
    #> The following object is masked from 'package:infer':
    #>
    #>     generate

## Data Preparation

We will use the classic `iris` dataset for this example. It’s a simple, small dataset, which is ideal for demonstrating the tuning process without long training times.

    # Split data into training and testing sets
    set.seed(123)
    iris_split <- initial_split(iris, prop = 0.8, strata = Species)
    iris_train <- training(iris_split)
    iris_test <- testing(iris_split)

    # Create cross-validation folds for tuning
    iris_folds <- vfold_cv(iris_train, v = 3, strata = Species)

## Define a `kerasnip` Model

We’ll create a very simple sequential model with a single dense layer. This keeps the focus on tuning the `fit_*` and `compile_*` arguments rather than the model architecture.

    # Define layer blocks
    input_block <- function(model, input_shape) {
      keras_model_sequential(input_shape = input_shape)
    }
    dense_block <- function(model, units = 10) {
      model |> layer_dense(units = units, activation = "relu")
    }
    output_block <- function(model, num_classes) {
      model |> layer_dense(units = num_classes, activation = "softmax")
    }

    # Create the kerasnip model specification function
    create_keras_sequential_spec(
      model_name = "iris_mlp",
      layer_blocks = list(
        input = input_block,
        dense = dense_block,
        output = output_block
      ),
      mode = "classification"
    )

## Define the Tunable Specification

Now, we create an instance of our `iris_mlp` model. We set the arguments we want to optimize to `tune()`.

    # Define the tunable model specification
    tune_spec <- iris_mlp(
      dense_units = 16, # Keep architecture fixed for this example
      fit_epochs = tune(),
      fit_batch_size = tune(),
      compile_optimizer = tune(),
      compile_loss = tune(),
      learn_rate = tune()
    ) |>
      set_engine("keras")

    print(tune_spec)
    #> iris mlp Model Specification (classification)
    #>
    #> Main Arguments:
    #>   num_input = structure(list(), class = "rlang_zap")
    #>   num_dense = structure(list(), class = "rlang_zap")
    #>   num_output = structure(list(), class = "rlang_zap")
    #>   dense_units = 16
    #>   learn_rate = tune()
    #>   fit_batch_size = tune()
    #>   fit_epochs = tune()
    #>   fit_callbacks = structure(list(), class = "rlang_zap")
    #>   fit_validation_split = structure(list(), class = "rlang_zap")
    #>   fit_validation_data = structure(list(), class = "rlang_zap")
    #>   fit_shuffle = structure(list(), class = "rlang_zap")
    #>   fit_class_weight = structure(list(), class = "rlang_zap")
    #>   fit_sample_weight = structure(list(), class = "rlang_zap")
    #>   fit_initial_epoch = structure(list(), class = "rlang_zap")
    #>   fit_steps_per_epoch = structure(list(), class = "rlang_zap")
    #>   fit_validation_steps = structure(list(), class = "rlang_zap")
    #>   fit_validation_batch_size = structure(list(), class = "rlang_zap")
    #>   fit_validation_freq = structure(list(), class = "rlang_zap")
    #>   fit_verbose = structure(list(), class = "rlang_zap")
    #>   fit_view_metrics = structure(list(), class = "rlang_zap")
    #>   compile_optimizer = tune()
    #>   compile_loss = tune()
    #>   compile_metrics = structure(list(), class = "rlang_zap")
    #>   compile_loss_weights = structure(list(), class = "rlang_zap")
    #>   compile_weighted_metrics = structure(list(), class = "rlang_zap")
    #>   compile_run_eagerly = structure(list(), class = "rlang_zap")
    #>   compile_steps_per_execution = structure(list(), class = "rlang_zap")
    #>   compile_jit_compile = structure(list(), class = "rlang_zap")
    #>   compile_auto_scale_loss = structure(list(), class = "rlang_zap")
    #>
    #> Computational engine: keras

## Create Workflow and Tuning Grid

Next, we create a `workflow` and define the search space for our hyperparameters using `dials`. `kerasnip` provides special `dials` parameter functions for `optimizer` and `loss`.

    # Create a simple recipe
    iris_recipe <- recipe(Species ~ ., data = iris_train) |>
      step_normalize(all_numeric_predictors())

    # Create the workflow
    tune_wf <- workflow() |>
      add_recipe(iris_recipe) |>
      add_model(tune_spec)

    # Define the tuning grid
    params <- extract_parameter_set_dials(tune_wf) |>
      update(
        fit_epochs = epochs(c(10, 30)),
        fit_batch_size = batch_size(c(16, 64), trans = NULL),
        compile_optimizer = optimizer_function(values = c("adam", "sgd", "rmsprop")),
        compile_loss = loss_function_keras(values = c("categorical_crossentropy", "kl_divergence")),
        learn_rate = learn_rate(c(0.001, 0.01), trans = NULL)
      )

    set.seed(456)
    tuning_grid <- grid_regular(params, levels = 2)

    tuning_grid
    #> # A tibble: 32 × 5
    #>    learn_rate fit_batch_size fit_epochs compile_optimizer compile_loss
    #>         <dbl>          <int>      <int> <chr>             <chr>
    #>  1      0.001             16         10 adam              categorical_crossentropy
    #>  2      0.01              16         10 adam              categorical_crossentropy
    #>  3      0.001             64         10 adam              categorical_crossentropy
    #>  4      0.01              64         10 adam              categorical_crossentropy
    #>  5      0.001             16         30 adam              categorical_crossentropy
    #>  6      0.01              16         30 adam              categorical_crossentropy
    #>  7      0.001             64         30 adam              categorical_crossentropy
    #>  8      0.01              64         30 adam              categorical_crossentropy
    #>  9      0.001             16         10 sgd               categorical_crossentropy
    #> 10      0.01              16         10 sgd               categorical_crossentropy
    #> # ℹ 22 more rows

## Tune the Model

With the workflow and grid defined, we can now run the hyperparameter tuning using `tune_grid()`.

    tune_res <- tune_grid(
      tune_wf,
      resamples = iris_folds,
      grid = tuning_grid,
      metrics = metric_set(accuracy, roc_auc),
      control = control_grid(save_pred = FALSE, save_workflow = TRUE, verbose = FALSE)
    )
    #> 3/3 - 0s - 51ms/step
    #> 2/2 - 0s - 68ms/step
    #> 2/2 - 0s - 27ms/step
    #> 3/3 - 0s - 38ms/step
    #> 2/2 - 0s - 100ms/step
    #> 2/2 - 0s - 55ms/step
    #> 3/3 - 0s - 47ms/step
    #> 2/2 - 0s - 69ms/step
    #> 2/2 - 0s - 31ms/step
    #> 3/3 - 0s - 43ms/step
    #> 2/2 - 0s - 110ms/step
    #> 2/2 - 0s - 72ms/step
    #> 3/3 - 0s - 49ms/step
    #> 2/2 - 0s - 56ms/step
    #> 2/2 - 0s - 31ms/step
    #> 3/3 - 0s - 36ms/step
    #> 2/2 - 0s - 51ms/step
    #> 2/2 - 0s - 22ms/step
    #> 3/3 - 0s - 48ms/step
    #> 2/2 - 0s - 74ms/step
    #> 2/2 - 0s - 30ms/step
    #> 3/3 - 0s - 100ms/step
    #> 2/2 - 0s - 40ms/step
    #> 2/2 - 0s - 24ms/step
    #> 3/3 - 0s - 35ms/step
    #> 2/2 - 0s - 48ms/step
    #> 2/2 - 0s - 26ms/step
    #> 3/3 - 0s - 42ms/step
    #> 2/2 - 0s - 59ms/step
    #> 2/2 - 0s - 31ms/step
    #> 3/3 - 0s - 37ms/step
    #> 2/2 - 0s - 56ms/step
    #> 2/2 - 0s - 38ms/step
    #> 3/3 - 0s - 37ms/step
    #> 2/2 - 0s - 79ms/step
    #> 2/2 - 0s - 32ms/step
    #> 3/3 - 0s - 37ms/step
    #> 2/2 - 0s - 62ms/step
    #> 2/2 - 0s - 31ms/step
    #> 3/3 - 0s - 37ms/step
    #> 2/2 - 0s - 54ms/step
    #> 2/2 - 0s - 26ms/step
    #> 3/3 - 0s - 45ms/step
    #> 2/2 - 0s - 65ms/step
    #> 2/2 - 0s - 26ms/step
    #> 3/3 - 0s - 36ms/step
    #> 2/2 - 0s - 48ms/step
    #> 2/2 - 0s - 25ms/step
    #> 3/3 - 0s - 38ms/step
    #> 2/2 - 0s - 51ms/step
    #> 2/2 - 0s - 28ms/step
    #> 3/3 - 0s - 55ms/step
    #> 2/2 - 0s - 60ms/step
    #> 2/2 - 0s - 38ms/step
    #> 3/3 - 0s - 35ms/step
    #> 2/2 - 0s - 55ms/step
    #> 2/2 - 0s - 24ms/step
    #> 3/3 - 0s - 36ms/step
    #> 2/2 - 0s - 53ms/step
    #> 2/2 - 0s - 23ms/step
    #> 3/3 - 0s - 39ms/step
    #> 2/2 - 0s - 56ms/step
    #> 2/2 - 0s - 25ms/step
    #> 3/3 - 0s - 39ms/step
    #> 2/2 - 0s - 56ms/step
    #> 2/2 - 0s - 25ms/step
    #> 3/3 - 0s - 39ms/step
    #> 2/2 - 0s - 50ms/step
    #> 2/2 - 0s - 20ms/step
    #> 3/3 - 0s - 35ms/step
    #> 2/2 - 0s - 62ms/step
    #> 2/2 - 0s - 22ms/step
    #> 3/3 - 0s - 39ms/step
    #> 2/2 - 0s - 53ms/step
    #> 2/2 - 0s - 29ms/step
    #> 3/3 - 0s - 36ms/step
    #> 2/2 - 0s - 58ms/step
    #> 2/2 - 0s - 25ms/step
    #> 3/3 - 0s - 41ms/step
    #> 2/2 - 0s - 66ms/step
    #> 2/2 - 0s - 22ms/step
    #> 3/3 - 0s - 39ms/step
    #> 2/2 - 0s - 63ms/step
    #> 2/2 - 0s - 24ms/step
    #> 3/3 - 0s - 57ms/step
    #> 2/2 - 0s - 52ms/step
    #> 2/2 - 0s - 30ms/step
    #> 3/3 - 0s - 36ms/step
    #> 2/2 - 0s - 52ms/step
    #> 2/2 - 0s - 24ms/step
    #> 3/3 - 0s - 33ms/step
    #> 2/2 - 0s - 51ms/step
    #> 2/2 - 0s - 27ms/step
    #> 3/3 - 0s - 36ms/step
    #> 2/2 - 0s - 54ms/step
    #> 2/2 - 0s - 22ms/step
    #> 3/3 - 0s - 28ms/step
    #> 2/2 - 0s - 45ms/step
    #> 2/2 - 0s - 21ms/step
    #> 3/3 - 0s - 44ms/step
    #> 2/2 - 0s - 52ms/step
    #> 2/2 - 0s - 24ms/step
    #> 3/3 - 0s - 39ms/step
    #> 2/2 - 0s - 57ms/step
    #> 2/2 - 0s - 27ms/step
    #> 3/3 - 0s - 36ms/step
    #> 2/2 - 0s - 47ms/step
    #> 2/2 - 0s - 23ms/step
    #> 3/3 - 0s - 33ms/step
    #> 2/2 - 0s - 57ms/step
    #> 2/2 - 0s - 25ms/step
    #> 3/3 - 0s - 38ms/step
    #> 2/2 - 0s - 52ms/step
    #> 2/2 - 0s - 25ms/step
    #> 3/3 - 0s - 34ms/step
    #> 2/2 - 0s - 47ms/step
    #> 2/2 - 0s - 24ms/step
    #> 3/3 - 0s - 45ms/step
    #> 2/2 - 0s - 53ms/step
    #> 2/2 - 0s - 28ms/step
    #> 3/3 - 0s - 35ms/step
    #> 2/2 - 0s - 58ms/step
    #> 2/2 - 0s - 26ms/step
    #> 3/3 - 0s - 37ms/step
    #> 2/2 - 0s - 55ms/step
    #> 2/2 - 0s - 24ms/step
    #> 3/3 - 0s - 36ms/step
    #> 2/2 - 0s - 56ms/step
    #> 2/2 - 0s - 27ms/step
    #> 3/3 - 0s - 28ms/step
    #> 2/2 - 0s - 50ms/step
    #> 2/2 - 0s - 21ms/step
    #> 3/3 - 0s - 37ms/step
    #> 2/2 - 0s - 53ms/step
    #> 2/2 - 0s - 28ms/step
    #> 3/3 - 0s - 35ms/step
    #> 2/2 - 0s - 61ms/step
    #> 2/2 - 0s - 26ms/step
    #> 3/3 - 0s - 76ms/step
    #> 2/2 - 0s - 88ms/step
    #> 2/2 - 0s - 32ms/step
    #> 3/3 - 0s - 36ms/step
    #> 2/2 - 0s - 61ms/step
    #> 2/2 - 0s - 23ms/step
    #> 3/3 - 0s - 40ms/step
    #> 2/2 - 0s - 49ms/step
    #> 2/2 - 0s - 23ms/step
    #> 3/3 - 0s - 36ms/step
    #> 2/2 - 0s - 50ms/step
    #> 2/2 - 0s - 19ms/step
    #> 3/3 - 0s - 35ms/step
    #> 2/2 - 0s - 51ms/step
    #> 2/2 - 0s - 22ms/step
    #> 3/3 - 0s - 34ms/step
    #> 2/2 - 0s - 49ms/step
    #> 2/2 - 0s - 29ms/step
    #> 3/3 - 0s - 36ms/step
    #> 2/2 - 0s - 52ms/step
    #> 2/2 - 0s - 22ms/step
    #> 3/3 - 0s - 34ms/step
    #> 2/2 - 0s - 47ms/step
    #> 2/2 - 0s - 28ms/step
    #> 3/3 - 0s - 35ms/step
    #> 2/2 - 0s - 51ms/step
    #> 2/2 - 0s - 23ms/step
    #> 3/3 - 0s - 35ms/step
    #> 2/2 - 0s - 50ms/step
    #> 2/2 - 0s - 26ms/step
    #> 3/3 - 0s - 30ms/step
    #> 2/2 - 0s - 53ms/step
    #> 2/2 - 0s - 26ms/step
    #> 3/3 - 0s - 31ms/step
    #> 2/2 - 0s - 49ms/step
    #> 2/2 - 0s - 20ms/step
    #> 3/3 - 0s - 39ms/step
    #> 2/2 - 0s - 49ms/step
    #> 2/2 - 0s - 25ms/step
    #> 3/3 - 0s - 30ms/step
    #> 2/2 - 0s - 54ms/step
    #> 2/2 - 0s - 23ms/step
    #> 3/3 - 0s - 33ms/step
    #> 2/2 - 0s - 51ms/step
    #> 2/2 - 0s - 22ms/step
    #> 3/3 - 0s - 38ms/step
    #> 2/2 - 0s - 51ms/step
    #> 2/2 - 0s - 19ms/step
    #> 3/3 - 0s - 36ms/step
    #> 2/2 - 0s - 50ms/step
    #> 2/2 - 0s - 22ms/step
    #> 3/3 - 0s - 38ms/step
    #> 2/2 - 0s - 39ms/step
    #> 2/2 - 0s - 26ms/step
    #> 3/3 - 0s - 37ms/step
    #> 2/2 - 0s - 51ms/step
    #> 2/2 - 0s - 24ms/step
    #> 3/3 - 0s - 42ms/step
    #> 2/2 - 0s - 55ms/step
    #> 2/2 - 0s - 23ms/step
    #> 3/3 - 0s - 36ms/step
    #> 2/2 - 0s - 46ms/step
    #> 2/2 - 0s - 27ms/step
    #> 3/3 - 0s - 37ms/step
    #> 2/2 - 0s - 54ms/step
    #> 2/2 - 0s - 23ms/step
    #> 3/3 - 0s - 36ms/step
    #> 2/2 - 0s - 57ms/step
    #> 2/2 - 0s - 30ms/step
    #> 3/3 - 0s - 39ms/step
    #> 2/2 - 0s - 53ms/step
    #> 2/2 - 0s - 22ms/step
    #> 3/3 - 0s - 36ms/step
    #> 2/2 - 0s - 55ms/step
    #> 2/2 - 0s - 25ms/step
    #> 3/3 - 0s - 38ms/step
    #> 2/2 - 0s - 58ms/step
    #> 2/2 - 0s - 25ms/step
    #> 3/3 - 0s - 36ms/step
    #> 2/2 - 0s - 49ms/step
    #> 2/2 - 0s - 25ms/step
    #> 3/3 - 0s - 38ms/step
    #> 2/2 - 0s - 65ms/step
    #> 2/2 - 0s - 23ms/step
    #> 3/3 - 0s - 35ms/step
    #> 2/2 - 0s - 54ms/step
    #> 2/2 - 0s - 27ms/step
    #> 3/3 - 0s - 40ms/step
    #> 2/2 - 0s - 56ms/step
    #> 2/2 - 0s - 20ms/step
    #> 3/3 - 0s - 35ms/step
    #> 2/2 - 0s - 54ms/step
    #> 2/2 - 0s - 20ms/step
    #> 3/3 - 0s - 35ms/step
    #> 2/2 - 0s - 54ms/step
    #> 2/2 - 0s - 19ms/step
    #> 3/3 - 0s - 27ms/step
    #> 2/2 - 0s - 50ms/step
    #> 2/2 - 0s - 24ms/step
    #> 3/3 - 0s - 31ms/step
    #> 2/2 - 0s - 69ms/step
    #> 2/2 - 0s - 23ms/step
    #> 3/3 - 0s - 34ms/step
    #> 2/2 - 0s - 47ms/step
    #> 2/2 - 0s - 18ms/step
    #> 3/3 - 0s - 33ms/step
    #> 2/2 - 0s - 46ms/step
    #> 2/2 - 0s - 22ms/step
    #> 3/3 - 0s - 34ms/step
    #> 2/2 - 0s - 45ms/step
    #> 2/2 - 0s - 21ms/step
    #> 3/3 - 0s - 34ms/step
    #> 2/2 - 0s - 52ms/step
    #> 2/2 - 0s - 19ms/step
    #> 3/3 - 0s - 36ms/step
    #> 2/2 - 0s - 62ms/step
    #> 2/2 - 0s - 25ms/step
    #> 3/3 - 0s - 33ms/step
    #> 2/2 - 0s - 53ms/step
    #> 2/2 - 0s - 22ms/step
    #> 3/3 - 0s - 33ms/step
    #> 2/2 - 0s - 49ms/step
    #> 2/2 - 0s - 23ms/step
    #> 3/3 - 0s - 39ms/step
    #> 2/2 - 0s - 48ms/step
    #> 2/2 - 0s - 21ms/step
    #> 3/3 - 0s - 38ms/step
    #> 2/2 - 0s - 51ms/step
    #> 2/2 - 0s - 33ms/step
    #> 3/3 - 0s - 36ms/step
    #> 2/2 - 0s - 65ms/step
    #> 2/2 - 0s - 24ms/step
    #> 3/3 - 0s - 40ms/step
    #> 2/2 - 0s - 54ms/step
    #> 2/2 - 0s - 29ms/step
    #> 3/3 - 0s - 38ms/step
    #> 2/2 - 0s - 51ms/step
    #> 2/2 - 0s - 32ms/step
    #> 3/3 - 0s - 36ms/step
    #> 2/2 - 0s - 52ms/step
    #> 2/2 - 0s - 25ms/step
    #> 3/3 - 0s - 59ms/step
    #> 2/2 - 0s - 37ms/step
    #> 2/2 - 0s - 21ms/step
    #> 3/3 - 0s - 37ms/step
    #> 2/2 - 0s - 55ms/step
    #> 2/2 - 0s - 20ms/step
    #> 3/3 - 0s - 35ms/step
    #> 2/2 - 0s - 59ms/step
    #> 2/2 - 0s - 22ms/step

## Inspect the Results

Let’s examine the results to see how the different combinations of fitting and compilation parameters performed.

    # Show the best performing models based on accuracy
    show_best(tune_res, metric = "accuracy")
    #> # A tibble: 5 × 11
    #>   learn_rate fit_batch_size fit_epochs compile_optimizer compile_loss     .metric .estimator  mean     n std_err .config
    #>        <dbl>          <int>      <int> <chr>             <chr>            <chr>   <chr>      <dbl> <int>   <dbl> <chr>
    #> 1       0.01             16         30 adam              categorical_cro… accura… multiclass 0.951     3 0.0132  pre0_m…
    #> 2       0.01             16         30 adam              kl_divergence    accura… multiclass 0.951     3 0.0132  pre0_m…
    #> 3       0.01             64         30 adam              categorical_cro… accura… multiclass 0.942     3 0.00672 pre0_m…
    #> 4       0.01             64         30 adam              kl_divergence    accura… multiclass 0.918     3 0.0305  pre0_m…
    #> 5       0.01             16         10 adam              kl_divergence    accura… multiclass 0.901     3 0.0220  pre0_m…

    # Plot the results
    autoplot(tune_res) + theme_minimal()

plot of chunk inspect-results

    # Select the best hyperparameters
    best_params <- select_best(tune_res, metric = "accuracy")
    print(best_params)
    #> # A tibble: 1 × 6
    #>   learn_rate fit_batch_size fit_epochs compile_optimizer compile_loss             .config
    #>        <dbl>          <int>      <int> <chr>             <chr>                    <chr>
    #> 1       0.01             16         30 adam              categorical_crossentropy pre0_mod21_post0

The results show that `tune` has successfully explored different optimizers, loss functions, learning rates, epochs, and batch sizes, identifying the combination that yields the best accuracy.

## Finalize and Fit

Finally, we finalize our workflow with the best-performing hyperparameters and fit the model one last time on the full training dataset.

    # Finalize the workflow
    final_wf <- finalize_workflow(tune_wf, best_params)

    # Fit the final model
    final_fit <- fit(final_wf, data = iris_train)
    #> 4/4 - 0s - 27ms/step

    print(final_fit)
    #> ══ Workflow [trained] ═════════════════════════════════════
    #> Preprocessor: Recipe
    #> Model: iris_mlp()
    #>
    #> ── Preprocessor ───────────────────────────────────────────
    #> 1 Recipe Step
    #>
    #> • step_normalize()
    #>
    #> ── Model ──────────────────────────────────────────────────
    #> $fit
    #> Model: "sequential_98"
    #> ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┓
    #> ┃ Layer (type)                                        ┃ Output Shape                           ┃               Param # ┃
    #> ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━┩
    #> │ dense_195 (Dense)                                   │ (None, 16)                             │                    80 │
    #> ├─────────────────────────────────────────────────────┼────────────────────────────────────────┼───────────────────────┤
    #> │ dense_196 (Dense)                                   │ (None, 3)                              │                    51 │
    #> └─────────────────────────────────────────────────────┴────────────────────────────────────────┴───────────────────────┘
    #>  Total params: 395 (1.55 KB)
    #>  Trainable params: 131 (524.00 B)
    #>  Non-trainable params: 0 (0.00 B)
    #>  Optimizer params: 264 (1.04 KB)
    #>
    #> $keras_bytes
    #>     [1] 50 4b 03 04 14 00 00 00 00 00 00 00 21 00 10 9a 9e 5c 40 00 00 00 40 00 00 00 0d 00 00 00 6d 65 74 61 64 61 74
    #>    [38] 61 2e 6a 73 6f 6e 7b 22 6b 65 72 61 73 5f 76 65 72 73 69 6f 6e 22 3a 20 22 33 2e 31 35 2e 31 22 2c 20 22 64 61
    #>    [75] 74 65 5f 73 61 76 65 64 22 3a 20 22 32 30 32 36 2d 30 39 2d 30 33 40 31 37 3a 32 30 3a 33 35 22 7d 50 4b 03 04
    #>   [112] 14 00 00 00 00 00 00 00 21 00 28 a1 52 af d0 0b 00 00 d0 0b 00 00 0b 00 00 00 63 6f 6e 66 69 67 2e 6a 73 6f 6e
    #>   [149] 7b 22 6d 6f 64 75 6c 65 22 3a 20 22 6b 65 72 61 73 22 2c 20 22 63 6c 61 73 73 5f 6e 61 6d 65 22 3a 20 22 53 65
    #>   [186] 71 75 65 6e 74 69 61 6c 22 2c 20 22 63 6f 6e 66 69 67 22 3a 20 7b 22 6e 61 6d 65 22 3a 20 22 73 65 71 75 65 6e
    #>   [223] 74 69 61 6c 5f 39 38 22 2c 20 22 74 72 61 69 6e 61 62 6c 65 22 3a 20 74 72 75 65 2c 20 22 64 74 79 70 65 22 3a
    #>   [260] 20 7b 22 6d 6f 64 75 6c 65 22 3a 20 22 6b 65 72 61 73 22 2c 20 22 63 6c 61 73 73 5f 6e 61 6d 65 22 3a 20 22 44
    #>   [297] 54 79 70 65 50 6f 6c 69 63 79 22 2c 20 22 63 6f 6e 66 69 67 22 3a 20 7b 22 6e 61 6d 65 22 3a 20 22 66 6c 6f 61
    #>   [334] 74 33 32 22 7d 2c 20 22 72 65 67 69 73 74 65 72 65 64 5f 6e 61 6d 65 22 3a 20 6e 75 6c 6c 2c 20 22 73 68 61 72
    #>   [371] 65 64 5f 6f 62 6a 65 63 74 5f 69 64 22 3a 20 31 33 31 34 30 35 33 32 34 31 37 31 36 30 30 7d 2c 20 22 6c 61 79
    #>   [408] 65 72 73 22 3a 20 5b 7b 22 6d 6f 64 75 6c 65 22 3a 20 22 6b 65 72 61 73 2e 6c 61 79 65 72 73 22 2c 20 22 63 6c
    #>   [445] 61 73 73 5f 6e 61 6d 65 22 3a 20 22 49 6e 70 75 74 4c 61 79 65 72 22 2c 20 22 63 6f 6e 66 69 67 22 3a 20 7b 22
    #>   [482] 62 61 74 63 68 5f 73 68 61 70 65 22 3a 20 5b 6e 75 6c 6c 2c 20 34 5d 2c 20 22 64 74 79 70 65 22 3a 20 22 66 6c
    #>   [519] 6f 61 74 33 32 22 2c 20 22 73 70 61 72 73 65 22 3a 20 66 61 6c 73 65 2c 20 22 72 61 67 67 65 64 22 3a 20 66 61
    #>   [556] 6c 73 65 2c 20 22 6e 61 6d 65 22 3a 20 22 69 6e 70 75 74 5f 6c 61 79 65 72 5f 39 38 22 2c 20 22 6f 70 74 69 6f
    #>   [593] 6e 61 6c 22 3a 20 66 61 6c 73 65 7d 2c 20 22 72 65 67 69 73 74 65 72 65 64 5f 6e 61 6d 65 22 3a 20 6e 75 6c 6c
    #>   [630] 7d 2c 20 7b 22 6d 6f 64 75 6c 65 22 3a 20 22 6b 65 72 61 73 2e 6c 61 79 65 72 73 22 2c 20 22 63 6c 61 73 73 5f
    #>   [667] 6e 61 6d 65 22 3a 20 22 44 65 6e 73 65 22 2c 20 22 63 6f 6e 66 69 67 22 3a 20 7b 22 6e 61 6d 65 22 3a 20 22 64
    #>   [704] 65 6e 73 65 5f 31 39 35 22 2c 20 22 74 72 61 69 6e 61 62 6c 65 22 3a 20 74 72 75 65 2c 20 22 64 74 79 70 65 22
    #>   [741] 3a 20 7b 22 6d 6f 64 75 6c 65 22 3a 20 22 6b 65 72 61 73 22 2c 20 22 63 6c 61 73 73 5f 6e 61 6d 65 22 3a 20 22
    #>   [778] 44 54 79 70 65 50 6f 6c 69 63 79 22 2c 20 22 63 6f 6e 66 69 67 22 3a 20 7b 22 6e 61 6d 65 22 3a 20 22 66 6c 6f
    #>   [815] 61 74 33 32 22 7d 2c 20 22 72 65 67 69 73 74 65 72 65 64 5f 6e 61 6d 65 22 3a 20 6e 75 6c 6c 7d 2c 20 22 75 6e
    #>   [852] 69 74 73 22 3a 20 31 36 2c 20 22 61 63 74 69 76 61 74 69 6f 6e 22 3a 20 22 72 65 6c 75 22 2c 20 22 75 73 65 5f
    #>   [889] 62 69 61 73 22 3a 20 74 72 75 65 2c 20 22 6b 65 72 6e 65 6c 5f 69 6e 69 74 69 61 6c 69 7a 65 72 22 3a 20 7b 22
    #>   [926] 6d 6f 64 75 6c 65 22 3a 20 22 6b 65 72 61 73 2e 69 6e 69 74 69 61 6c 69 7a 65 72 73 22 2c 20 22 63 6c 61 73 73
    #>   [963] 5f 6e 61 6d 65 22 3a 20 22 47 6c 6f 72 6f 74 55 6e 69 66 6f 72 6d 22 2c 20 22 63 6f 6e 66 69 67 22 3a 20 7b 22
    #>  [1000] 73 65 65 64 22 3a 20 6e 75 6c 6c 2c 20 22 69 6e 70 75 74 5f 61 78 65 73 22 3a 20 6e 75 6c 6c 2c 20 22 6f 75 74
    #>  [1037] 70 75 74 5f 61 78 65 73 22 3a 20 6e 75 6c 6c 7d 2c 20 22 72 65 67 69 73 74 65 72 65 64 5f 6e 61 6d 65 22 3a 20
    #>  [1074] 6e 75 6c 6c 7d 2c 20 22 62 69 61 73 5f 69 6e 69 74 69 61 6c 69 7a 65 72 22 3a 20 7b 22 6d 6f 64 75 6c 65 22 3a
    #>  [1111] 20 22 6b 65 72 61 73 2e 69 6e 69 74 69 61 6c 69 7a 65 72 73 22 2c 20 22 63 6c 61 73 73 5f 6e 61 6d 65 22 3a 20
    #>  [1148] 22 5a 65 72 6f 73 22 2c 20 22 63 6f 6e 66 69 67 22 3a 20 7b 7d 2c 20 22 72 65 67 69 73 74 65 72 65 64 5f 6e 61
    #>  [1185] 6d 65 22 3a 20 6e 75 6c 6c 7d 2c 20 22 6b 65 72 6e 65 6c 5f 72 65 67 75 6c 61 72 69 7a 65 72 22 3a 20 6e 75 6c
    #>  [1222] 6c 2c 20 22 62 69 61 73 5f 72 65 67 75 6c 61 72 69 7a 65 72 22 3a 20 6e 75 6c 6c 2c 20 22 6b 65 72 6e 65 6c 5f
    #>  [1259] 63 6f 6e 73 74 72 61 69 6e 74 22 3a 20 6e 75 6c 6c 2c 20 22 62 69 61 73 5f 63 6f 6e 73 74 72 61 69 6e 74 22 3a
    #>
    #> ...
    #> and 1106 more lines.

We can now use this `final_fit` object to make predictions on the test set.

    # Make predictions
    predictions <- predict(final_fit, new_data = iris_test)
    #> 1/1 - 0s - 57ms/step

    # Evaluate performance
    bind_cols(predictions, iris_test) |>
      accuracy(truth = Species, estimate = .pred_class)
    #> # A tibble: 1 × 3
    #>   .metric  .estimator .estimate
    #>   <chr>    <chr>          <dbl>
    #> 1 accuracy multiclass     0.967

## Conclusion

This vignette demonstrated how to tune the crucial `fit_*` and `compile_*` arguments of a Keras model within the `tidymodels` framework using `kerasnip`. By exposing these as tunable parameters, `kerasnip` gives you full control over the training process, allowing you to optimize not just the model’s architecture, but also how it learns.