Three steps, every time
The Functional API follows a simple three-step pattern, and once it clicks it never changes no matter how complex the graph gets:
- Define inputs with
keras.Input(shape=...)— this creates a symbolic tensor, a placeholder that carries shape and dtype but no data yet. - Chain layers by calling them on tensors:
x = Layer()(x). Each call records an edge in the graph. - Create model with
keras.Model(inputs, outputs)— Keras traces backward from outputs to inputs and freezes the DAG.
The double-duty parentheses
The single idea that makes this whole API feel natural: every layer is a callable. The Code block below shows the parentheses doing double duty — layers.Dense(256, activation="relu") constructs a layer object, and the second (inputs) calls it on a tensor. Construction allocates the weights; calling wires the layer into the graph and returns a new symbolic tensor.
That separation is the payoff. Because constructing and calling are two distinct steps, you can hold a layer instance in a variable and call it more than once — and every call reuses the same weights. That single fact is what makes shared layers (next lessons) and weight tying possible at all. Notice too that nothing here runs a forward pass on real data: you're describing a graph, not executing it, which is exactly why the result is serializable.