Principle:Fastai Fastbook Learner Abstraction
| Knowledge Sources | |
|---|---|
| Domains | Deep Learning, Software Architecture, Training Infrastructure |
| Last Updated | 2026-02-09 17:00 GMT |
Overview
The Learner abstraction is a design pattern that encapsulates a model, data, loss function, optimizer, and learning rate into a single object, with a callback system that allows arbitrary customization of the training loop without modifying its core logic.
Description
As neural network training grows in complexity (learning rate scheduling, mixed precision, gradient clipping, logging, early stopping), the training loop accumulates conditional logic that becomes difficult to maintain. The Learner pattern solves this by:
- Centralizing all training state in a single object that callbacks can read and modify.
- Defining a fixed sequence of events (before_fit, before_epoch, before_batch, after_batch, after_epoch, after_fit) that the loop fires at predictable points.
- Delegating all customization to callbacks that hook into these events, keeping the core loop simple and stable.
This is an application of the Observer pattern (also known as the event-driven or hook-based architecture), where the Learner is the subject and callbacks are observers. The pattern was developed and refined by Jeremy Howard and Sylvain Gugger for the fastai library and is explained from scratch in Chapter 19 of the fastbook.
Usage
Use the Learner abstraction when:
- You want a single unified interface for training any model architecture.
- You need to compose multiple training behaviors (scheduling, logging, augmentation) without modifying the core loop.
- You want to make training experiments reproducible and configurable.
- You are transitioning from a manual training loop to a production-quality training system.
Theoretical Basis
The Core Training Loop (Without Callbacks)
The simplest Learner loop processes epochs and batches:
class Learner:
init(model, data, loss_func, optimizer, lr):
store all components
fit(n_epochs):
for epoch in 1..n_epochs:
one_epoch(train=True)
one_epoch(train=False)
one_epoch(train):
dataloader = data.train if train else data.valid
for batch in dataloader:
one_batch(batch, train)
one_batch(batch, train):
x, y = batch
predictions = model(x)
loss = loss_func(predictions, y)
if train:
loss.backward()
optimizer.step()
optimizer.zero_grad()
The Callback System
Callbacks intercept the training loop at defined events:
Events fired in order:
before_fit
before_epoch
before_batch
after_batch
after_epoch
after_fit
The Learner fires events by iterating over its list of callbacks:
fire_event(name):
for callback in callbacks:
call callback.name() if it exists
A callback can read any attribute of the Learner (model, loss, predictions, learning rate, epoch number) and modify any of them. This gives callbacks the power to implement:
- Metrics computation: Read predictions and targets after each batch, accumulate statistics.
- Learning rate scheduling: Modify the learning rate before each batch or epoch.
- Early stopping: Raise a
CancelFitExceptionduringafter_epochto stop training. - Logging: Record loss values and metrics for later analysis.
- Device management: Move model and data to GPU in
before_fit.
The Callback Base Class
To avoid verbose self.learner.model access patterns, the Callback base class uses Python's __getattr__ to delegate attribute lookups to the Learner:
class Callback:
default_attribute = learner
getattr(name):
return getattr(self.learner, name)
This allows a callback to write self.model instead of self.learner.model, resulting in cleaner, more readable callback code.
Exception-Based Flow Control
Callbacks can alter control flow by raising specific exceptions:
- CancelBatchException: Skip the rest of the current batch.
- CancelEpochException: Skip the rest of the current epoch.
- CancelFitException: Stop training entirely.
The Learner catches these exceptions at the appropriate level, ensuring clean handling without complex boolean flags.