How to create a functional model in Keras

The Keras Functional API builds a model from symbolic input tensors and layer calls instead of a plain layer list. It fits models that need named inputs, branches, merges, shared layers, or several outputs while keeping the result compatible with normal Keras model APIs.

A functional model starts with keras.Input(), passes those symbolic tensors through layers, and finishes with keras.Model(inputs=…, outputs=…). The resulting graph can be inspected with model.summary() and plotting while still accepting the same predict(), compile(), fit(), and save APIs used by other model types.

A two-input scoring model makes the branch wiring visible. Numeric features and one-hot category features pass through separate branches, merge through Concatenate, and produce one sigmoid score that can be checked with a small prediction batch.

Steps to create a functional Keras model:

  1. Create functional_model.py with backend selection and imports.
    functional_model.py
    import os
     
    os.environ["KERAS_BACKEND"] = "jax"
     
    import keras
    import numpy as np
    from keras import layers
     
    keras.utils.set_random_seed(42)

    Set KERAS_BACKEND before import keras. Use tensorflow or torch only when those backend packages are installed for the project.
    Related: How to set the Keras backend

  2. Define the symbolic input tensors for each branch.
    numeric_input = keras.Input(shape=(4,), name="numeric_features")
    category_input = keras.Input(shape=(3,), name="category_features")

    The shape value excludes the batch dimension. A shape of (4,) accepts batches of four numeric features.

  3. Pass each input through its own layer stack.
    numeric_branch = layers.Dense(8, activation="relu", name="numeric_projection")(numeric_input)
    category_branch = layers.Dense(4, activation="relu", name="category_projection")(category_input)
  4. Merge the branches and create the model output.
    features = layers.Concatenate(name="combined_features")(
        [numeric_branch, category_branch]
    )
    features = layers.Dense(6, activation="relu", name="hidden_features")(features)
    score = layers.Dense(1, activation="sigmoid", name="risk_score")(features)
  5. Instantiate the functional model from the input and output tensors.
    model = keras.Model(
        inputs=[numeric_input, category_input],
        outputs=score,
        name="risk_score_functional",
    )

    keras.Model() can receive lists, tuples, or dictionaries of tensors for multi-input or multi-output models.

  6. Add a prediction batch and print the summary details.
    model.summary()
     
    numeric_batch = np.array(
        [
            [0.10, 0.80, 0.35, 0.20],
            [0.90, 0.15, 0.55, 0.70],
        ],
        dtype="float32",
    )
    category_batch = np.array(
        [
            [1.0, 0.0, 0.0],
            [0.0, 1.0, 0.0],
        ],
        dtype="float32",
    )
     
    prediction = model.predict([numeric_batch, category_batch], verbose=0)
     
    print(f"backend: {keras.backend.backend()}")
    print(f"inputs: {[tensor.name for tensor in model.inputs]}")
    print(f"output layer: {model.layers[-1].name}")
    print(f"prediction shape: {prediction.shape}")
    print(f"first prediction: {float(prediction[0, 0]):.4f}")
  7. Run the script and confirm the graph plus prediction shape.
    $ python functional_model.py
    Model: "risk_score_functional"
    ┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓
    ┃ Layer (type)        ┃ Output Shape      ┃    Param # ┃ Connected to      ┃
    ┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩
    │ numeric_features    │ (None, 4)         │          0 │ -                 │
    │ (InputLayer)        │                   │            │                   │
    ├─────────────────────┼───────────────────┼────────────┼───────────────────┤
    │ category_features   │ (None, 3)         │          0 │ -                 │
    │ (InputLayer)        │                   │            │                   │
    ├─────────────────────┼───────────────────┼────────────┼───────────────────┤
    │ numeric_projection  │ (None, 8)         │         40 │ numeric_features… │
    │ (Dense)             │                   │            │                   │
    ├─────────────────────┼───────────────────┼────────────┼───────────────────┤
    │ category_projection │ (None, 4)         │         16 │ category_feature… │
    │ (Dense)             │                   │            │                   │
    ├─────────────────────┼───────────────────┼────────────┼───────────────────┤
    │ combined_features   │ (None, 12)        │          0 │ numeric_projecti… │
    │ (Concatenate)       │                   │            │ category_project… │
    ├─────────────────────┼───────────────────┼────────────┼───────────────────┤
    │ hidden_features     │ (None, 6)         │         78 │ combined_feature… │
    │ (Dense)             │                   │            │                   │
    ├─────────────────────┼───────────────────┼────────────┼───────────────────┤
    │ risk_score (Dense)  │ (None, 1)         │          7 │ hidden_features[… │
    └─────────────────────┴───────────────────┴────────────┴───────────────────┘
     Total params: 141 (564.00 B)
     Trainable params: 141 (564.00 B)
     Non-trainable params: 0 (0.00 B)
    backend: jax
    inputs: ['numeric_features', 'category_features']
    output layer: risk_score
    prediction shape: (2, 1)
    first prediction: 0.4898

    The summary should show both input layers, the Concatenate layer, and the named risk_score output layer. The prediction shape should match two rows with one score per row.