Introduction
Model Subclassing is the third and most flexible way to build a model in Keras, defining a model as a fully custom Python class that inherits from keras.Model, with layers created in the constructor and the actual forward pass logic written explicitly in a call() method. Unlike the Sequential and Functional APIs, which describe a model's structure declaratively (defining what the architecture looks like), Subclassing is fully imperative — you write the exact Python code that determines how data flows through the model, step by step.
This approach trades some of the conciseness and built-in visualization support of the other two APIs for complete, unrestricted control, making it the go-to choice when a model's behavior involves genuinely dynamic logic — loops, conditionals, or custom computations — that can't be cleanly expressed as a static graph of layers.
Why Does Model Subclassing Matter?
Model Subclassing helps to:
- Provide complete, unrestricted flexibility for defining custom model behavior
- Support genuinely dynamic architectures involving loops, conditionals, or custom logic
- Allow full control over the forward pass, not just the arrangement of layers
- Enable custom training logic when standard
.fit()behavior isn't sufficient - Serve as the standard approach for cutting-edge research and highly novel architectures
- Complete the full spectrum of Keras model-building approaches alongside Sequential and Functional
How Model Subclassing Works
Model Subclassing follows standard Python object-oriented
patterns: layers are created once in the constructor
(__init__), and the actual computation — how those layers
are connected and used — is written explicitly in the call()
method, giving you the full expressiveness of regular Python
code (loops, conditionals, intermediate variables) to define
exactly how data flows through the model.A Basic Subclassed Model
Notice that layers are defined once in __init__, but the actual computation — how those layers connect and in what order — is written explicitly as regular Python code inside call().
Adding Genuinely Dynamic Logic
This example demonstrates something the Sequential and Functional APIs can't easily express: genuinely conditional logic (an if statement) determining exactly how the forward pass behaves differently between training and inference.
Custom Training Loops with Subclassed Models
Overriding train_step() allows complete customization of exactly what happens during each training iteration — useful for implementing specialized training procedures (like custom loss weighting, adversarial training, or other advanced techniques) that go beyond what the default .fit() behavior provides.
Sequential vs Functional vs Subclassing
| Aspect | Sequential | Functional | Subclassing |
|---|---|---|---|
| Style | Declarative | Declarative | Imperative (regular Python code) |
| Structure | Linear stack only | Flexible graph | Fully custom, including dynamic logic |
| Multiple Inputs/Outputs | No | Yes | Yes |
| Conditional/Loop-Based Logic | No | No | Yes |
| Built-in Visualization (plot_model) | Yes | Yes | Limited |
| Verbosity | Lowest | Moderate | Highest |
| Best For | Simple, single-path models | Complex but static architectures | Dynamic, research-focused, highly custom models |
When Subclassing Is the Right Choice
Model Subclassing becomes necessary (rather than just an
option) when your model needs:
- Genuinely dynamic control flow (if statements, loops)
based on the input or training state
- Custom training procedures beyond what .fit() supports
by default (via overriding train_step())
- Highly novel, research-oriented architectures that don't
fit a clean, static graph structure
- Fine-grained control over exactly how and when each
computation happensKey Properties of Model Subclassing
- Model Subclassing defines a model as a Python class inheriting from
keras.Model. - Layers are created once in
__init__, while the forward pass logic is written explicitly incall(). - It supports genuinely dynamic behavior — loops, conditionals — that the other two APIs cannot express.
- Overriding
train_step()enables fully custom training logic beyond the default.fit()behavior. - Subclassing trades some conciseness and built-in tooling support for maximum flexibility and control.
Where Is Model Subclassing Used?
| Field | Application |
|---|---|
| Cutting-Edge Research | Implementing novel, experimental architectures not yet standardized |
| Custom Training Procedures | Techniques like adversarial training or custom loss weighting schemes |
| Dynamic Neural Networks | Architectures where structure depends on the input itself |
| Reinforcement Learning | Custom models with non-standard forward pass and training logic |
| Advanced Generative Models | Highly custom GAN or diffusion model training loops |
Advantages
- Provides complete, unrestricted flexibility for defining any model behavior expressible in Python
- Supports genuinely dynamic control flow that static graph-based APIs cannot represent
- Enables full customization of training logic via
train_step()overriding - Well-suited to research contexts requiring highly novel or experimental architectures
- Familiar to anyone comfortable with standard Python object-oriented programming
Limitations
- More verbose and requires more code than Sequential or Functional for equivalent simple models
- Loses some built-in tooling support, like automatic model visualization
- Easier to introduce bugs, since the forward pass isn't validated as a static graph upfront
- Debugging can be more involved, since errors may only surface when the model is actually run
- Generally unnecessary for the many tasks that Sequential or Functional can already handle cleanly
Real-World Examples
| Application | Model Subclassing Use |
|---|---|
| Research Paper Implementations | Reproducing novel architectures described in academic papers |
| Custom GAN Training | Implementing generator/discriminator training loops with custom logic |
| Reinforcement Learning Agents | Models with non-standard, dynamic forward pass behavior |
| Adversarial Training Pipelines | Custom train_step() logic incorporating adversarial loss terms |
| Highly Dynamic Sequence Models | Architectures where computation depends on runtime input properties |
Best Practices
- Reserve Model Subclassing for cases that genuinely require its flexibility, not as a default choice.
- Keep
__init__focused purely on creating and storing layers; keep computation logic incall(). - Override
train_step()only when the default.fit()behavior truly doesn't meet your needs. - Test subclassed models thoroughly, since issues may not surface until the model actually runs.
- Document custom
call()andtrain_step()logic clearly, since it's less self-explanatory than a declarative graph.
Interview Tip
A common interview question is:
"When would you choose Model Subclassing over the Functional API, given that the Functional API already supports complex architectures?"
A strong answer is:
I'd choose Model Subclassing specifically when a model needs genuinely dynamic behavior that can't be expressed as a static graph — for example, conditional logic that changes the forward pass based on the input or training state, loops with a variable number of iterations, or custom training procedures that require overriding
train_step()beyond what.fit()provides by default. The Functional API is excellent for complex but ultimately static architectures — multiple inputs, branching, skip connections — but it still describes a fixed graph of layer connections, whereas Subclassing gives you the full expressiveness of regular Python code to define exactly how the model computes its output.
Clearly distinguishing "complex but static" from "genuinely dynamic" makes your answer stronger.
Conclusion
Model Subclassing completes the full spectrum of Keras model-building approaches, offering complete, code-level flexibility for genuinely dynamic architectures and custom training procedures that the declarative Sequential and Functional APIs cannot express. With all three approaches now covered — Sequential for simplicity, Functional for flexible static graphs, and Subclassing for full custom control — the next topic explores Custom Layers, which extend this same flexibility down to the level of individual, reusable building blocks.