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

Whiteboard
Whiteboard diagram
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

AspectSequentialFunctionalSubclassing
StyleDeclarativeDeclarativeImperative (regular Python code)
StructureLinear stack onlyFlexible graphFully custom, including dynamic logic
Multiple Inputs/OutputsNoYesYes
Conditional/Loop-Based LogicNoNoYes
Built-in Visualization (plot_model)YesYesLimited
VerbosityLowestModerateHighest
Best ForSimple, single-path modelsComplex but static architecturesDynamic, 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 happens

Key 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 in call().
  • 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?

FieldApplication
Cutting-Edge ResearchImplementing novel, experimental architectures not yet standardized
Custom Training ProceduresTechniques like adversarial training or custom loss weighting schemes
Dynamic Neural NetworksArchitectures where structure depends on the input itself
Reinforcement LearningCustom models with non-standard forward pass and training logic
Advanced Generative ModelsHighly 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

ApplicationModel Subclassing Use
Research Paper ImplementationsReproducing novel architectures described in academic papers
Custom GAN TrainingImplementing generator/discriminator training loops with custom logic
Reinforcement Learning AgentsModels with non-standard, dynamic forward pass behavior
Adversarial Training PipelinesCustom train_step() logic incorporating adversarial loss terms
Highly Dynamic Sequence ModelsArchitectures 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 in call().
  • 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() and train_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.