In Keras, how do you wire a multi-input, multi-output Functional model?
answer
- one keras.Input per entry point
- branches stay separate until a merge
- Concatenate versus elementwise Add
- name every head you build
- dicts bind by name, lists by position
basics
~20 sCreate one keras.Input per input, build each branch, merge them with a layer such as Concatenate or Add, then create one head per output. Pass the lists (or dicts) to keras.Model(inputs=[...], outputs=[...]); name the inputs and output layers so they can be referred to later.
solid answer
~40 sEach entry point is its own `keras.Input(shape=..., name=...)`. You process each branch independently with ordinary layer calls on its symbolic tensor, then join the branches with a merge layer — `keras.layers.Concatenate()([a, b])` when the features differ in width, `keras.layers.Add()([a, b])` when the shapes match exactly and you want an elementwise sum. From the merged tensor you build as many heads as you need, giving each final layer a `name`. Then `keras.Model(inputs=[image, meta], outputs=[price, label])` closes the graph. Keras supports dicts as well as lists in both positions, and dicts are usually safer because they match by name instead of by position, which removes a whole class of silent ordering bugs when the data is later fed in. `model.summary()` will show every branch, and `model.output_names` reports the names Keras derived from the output layers.
code
python · 20 linesimport keras
image = keras.Input(shape=(64, 64, 3), name="image")
tokens = keras.Input(shape=(32,), dtype="int32", name="tokens")
v = keras.layers.Conv2D(16, 3, activation="relu")(image)
v = keras.layers.GlobalAveragePooling2D()(v)
t = keras.layers.Embedding(5000, 16)(tokens)
t = keras.layers.GlobalAveragePooling1D()(t)
merged = keras.layers.Concatenate()([v, t])
price = keras.layers.Dense(1, name="price")(merged)
label = keras.layers.Dense(3, activation="softmax", name="label")(merged)
model = keras.Model(
inputs={"image": image, "tokens": tokens},
outputs={"price": price, "label": label},
)
print(model.output_names)go deeper
Know that each input needs its own keras.Input and that keras.Model takes lists of inputs and outputs. Be able to sketch two branches joined by a Concatenate layer.
Explain the merge-layer choice — Concatenate for different feature spaces, Add for identical shapes — and why shapes must be reduced to matching rank before merging. Mention naming heads and what output_names gives you.
Talk about the failure modes: positional binding of inputs, silently disconnected branches, shared layer instances across towers, and auxiliary heads used to shape gradient flow through a shared trunk.
Own the interface design. Decide whether a multi-task trunk should be one model with several heads or separate models, and weigh coupling, retraining cadence and per-team ownership against the efficiency of a shared representation.
## The shape of a multi-input, multi-output model A multi-input model is one where information arrives in genuinely different formats — an image plus a tabular record, a title plus a body plus a category id, two views of the same item. A multi-output model is one trunk serving several predictions — a price and a category, a bounding box and a class, a main task and an auxiliary task used to regularise the trunk. The Functional API expresses both because it builds an explicit graph rather than a chain. ## Declaring the entry points Every input needs its own `keras.Input`: - `shape=` is the per-sample shape, **excluding the batch dimension**. An image branch is `shape=(64, 64, 3)`, a tabular branch of eight features is `shape=(8,)`. - `name=` is optional but worth always setting; it is how you address that input later. - `dtype=` matters for non-float inputs — token ids are typically integer inputs feeding an embedding. These are symbolic placeholders. Nothing is allocated and nothing is computed. ## Building and merging the branches Each branch is ordinary Functional wiring: call layers on the branch's tensor and keep the returned tensor. Branches stay completely independent until you merge them, and they can have utterly different architectures — convolutions on one side, an embedding plus pooling on the other. The merge layer choice is a real decision: - `keras.layers.Concatenate()` stacks features along the last axis. All other axes must match. This is the default when the branches carry different kinds of information. - `keras.layers.Add()`, `Subtract()`, `Multiply()`, `Average()`, `Maximum()` combine elementwise and require **identical** shapes. `Add` is what residual connections use. - `keras.layers.Dot()` takes an inner product along chosen axes, which is how two-tower retrieval models score a pair. A very common bug is trying to concatenate a rank-4 convolutional feature map with a rank-2 tabular vector. You have to reduce the map first — `GlobalAveragePooling2D` or `Flatten` — so both tensors are rank 2. ## Declaring the outputs After the merge you build one head per output and give each final layer an explicit `name`. Those names are not cosmetic: Keras derives `model.output_names` from them, and the names are how per-output losses, metrics and target arrays are keyed when the model is later trained. Naming heads `"price"` and `"label"` is far more legible than relying on the order in a list. ## Lists versus dicts `keras.Model(inputs=..., outputs=...)` accepts a single tensor, a list, or a dict at each position: - A list binds by **position**. If the data pipeline later yields the two arrays in the other order, nothing errors — shapes may even be compatible — and the model silently learns garbage. - A dict binds by **name**. `inputs={"image": image, "meta": meta}` matches a dict of arrays or a dataset yielding dicts, and a missing or misspelled key raises instead of misfeeding. For anything beyond two obviously distinct shapes, prefer dicts. ## What you get once the graph exists Because the whole topology is declared before any data flows, Keras propagates shapes through both branches at construction time. `model.summary()` prints every layer with its output shape and, for merged graphs, which layers feed it. `keras.utils.plot_model(model)` draws the branch structure, which is genuinely useful for reviewing a two-tower design. `model.get_layer("...")` reaches any node by name. Shape errors — the flatten you forgot, the mismatched `Add` — surface the moment you write the merge, not on the first batch of training. ## Practical notes - An input that is never reachable from any output is a bug: the graph traversal starts at the outputs, so a dangling input will be reported or silently ignored depending on how the model is assembled. If a branch appears to do nothing, check it actually reaches a merge. - Layer names must be unique within a model. If you build two identical branches in a loop, pass distinct names or let Keras auto-name them; do not hardcode the same name twice. - Reusing the *same* layer instance on both branches is how you build a shared-weight twin tower, which is a deliberate design and not a mistake — but it is easy to do by accident when refactoring, so be explicit about which layers are shared. - Auxiliary outputs are a legitimate multi-output pattern: a second head partway up the trunk that exists only to push gradient into the early layers during training and is ignored at inference.
- When would you use Add instead of Concatenate to merge two branches?`Add` performs an elementwise sum and therefore requires the two tensors to have identical shapes; it keeps the width constant and is what residual and skip connections use. `Concatenate` stacks along the feature axis, so widths can differ and the merged tensor grows. Use `Add` when the branches represent the same feature space, `Concatenate` when they represent different information.
- Why is passing inputs as a dict safer than as a list?A list binds by position, so if the data pipeline yields the arrays in a different order the model still runs and silently learns from swapped inputs. A dict binds by the names you gave each `keras.Input`, so a wrong or missing key raises immediately. With two same-shaped towers this difference is the gap between a caught error and a mysterious accuracy drop.
- What happens if one declared input never reaches any of the outputs?The model is built by walking backwards from the outputs, so a branch that never merges contributes nothing. Depending on how it is assembled you either get an error about a disconnected graph or a model that quietly ignores that data. If a branch's parameter count never changes during training, check that its tensor actually reaches a merge layer.
saying these in an interview costs you the question
- Concatenating a 4-D feature map with a 2-D vector without pooling
- Believing Add and Concatenate are interchangeable
- Passing several inputs as one stacked array instead of separate Inputs
- Leaving heads unnamed and then relying on list order
- Including the batch dimension in keras.Input's shape argument