Neural Network Abstractions
Work through examples, try the reasoning yourself, then put it into practice.
View SlidesBefore you start
- Implement scalar autograd and read Linear input/output dimensions.
What you will practice
- Enumerate unique parameters and canonical names across aliased child modules.
- Preserve the graph through Linear and Sequential composition.
- Separate persistent module ownership from a fresh forward graph.
In this lesson
Lesson
5 min readNeural Network Abstractions: Own Parameters, Preserve the Graph
Autograd answers “how does this loss depend on this scalar?” It does not know which scalars belong to a model or which ones should be updated. Module supplies ownership and composition. Linear supplies a trainable transformation. Sequential connects transformations without losing their parameters.
After this lesson, you should be able to count parameters before running code, trace a linear layer numerically, and distinguish parameter registration from graph construction. Implement nn-module first, then nn-linear; later exercises supply these building blocks so you can focus on language modeling.
The two structures in a model
The module structure persists across training steps. It contains parameter leaves and child modules. The computation graph is rebuilt on each forward pass and contains temporary activations, losses, and references to those leaves.
Calling model(x) delegates to model.forward(x). It does not create parameters. Construct weights in __init__, then reuse the same objects in forward. Creating fresh weights inside forward discards learning on every call; wrapping a weight's .data in a new Value disconnects the registered parameter from the loss.
Register a nested structure
Initialize _parameters, _modules, and training with object.__setattr__ before enabling automatic registration. The custom __setattr__ inspects assigned values:
| Assignment | Registration |
|---|---|
self.bias = Value(...) |
One parameter |
self.layer = Linear(...) |
One child module |
self.weight = [[Value(...), ...], ...] |
Scalar parameters under names such as weight.0.1 |
self.layers = [layer_a, layer_b] |
Child modules under layers.0, layers.1 |
self.width = 4 |
Ordinary metadata |
Finally perform the actual assignment with object.__setattr__; otherwise Python's attribute and registry views disagree. The exercise assumes the structure is constructed once. In-place list mutation, removing attributes, and changing their kinds after construction are outside this small registry's contract.
An alias is still one parameter: if two attributes reference the same Value, parameters() must return it once by identity. Likewise, a child exposed through both a list and an attribute must not double the optimizer updates. Traverse with sets of visited parameter and module identities. named_parameters() uses the first path in traversal order (own parameters before children) as the canonical name for an alias. Equal numeric values in distinct objects remain distinct parameters.
Trace a linear layer
Linear(in_features=2, out_features=2) owns weight rows for output features:
W = [[2, -1], [0.5, 1]]
b = [1, -2]
x = [3, 2]
Compute each row's dot product with the entire input:
y[0] = 2*3 + (-1)*2 + 1 = 5
y[1] = 0.5*3 + 1*2 - 2 = 1.5
The result has length 2. More generally, weight has shape (out, in) and output has shape (out,). For loss L=y[0]+y[1], dL/dx=[2.5, 0]; each weight row receives gradient [3,2], and each bias receives 1. These values give you a stronger check than output shape alone.
The exercise initializes each weight uniformly in [-1/sqrt(in), 1/sqrt(in)] and biases at zero. This is a fan-in-scaled uniform choice. Xavier uniform, with gain 1, uses sqrt(6/(in+out)) as its bound, so these names are not interchangeable. See the initialization definitions.
Composition and parameter accounting
Consider Sequential(Linear(2,3), Tanh(), Linear(3,2)):
input (2,) -> affine (3,) -> tanh (3,) -> affine (2,)
The first layer owns 3*2+3=9 scalars. The second owns 2*3+2=8. The activation owns none. Total: 17 unique trainable scalars. Every call must return a list, including a linear layer with one output feature; changing a one-element vector to a scalar breaks composition.
Without an activation, two affine layers collapse algebraically to one affine layer. The activation introduces a nonlinear decision surface. Apply tanh, relu, or sigmoid to each Value; do not run math functions over .data in the model's forward pass.
Sequential stores execution order separately from the meaning of registration. Whether children are registered through a list or named attributes, apply each child exactly once in the requested order and expose every unique parameter once.
Training mode is a flag, not a gradient switch
train(mode) recursively sets child flags and returns self; eval() calls train(False). The linear and activation layers here behave identically in both modes because they have no dropout or running statistics. Evaluation mode does not disable autograd, clear gradients, or freeze parameters. Only the training loop performs updates.
Reconstruct and diagnose
Before coding, draw the module tree for the 17-parameter example and write five expected names, including layer_0.weight.0.0. Then inspect the implementation's parameter identities and names.
Checkpoint 1. A model returns 34 parameters for that architecture, but only 17 distinct identities. What failed?
Check your reasoning
a registry traversal visits aliased children twice. Gradients may be correct, yet an SGD loop applies each update twice.
Checkpoint 2. Forward outputs are correct but registered weights have zero gradients after backward. What should you inspect?
Check your reasoning
look for .data conversions or fresh Value(weight.data) objects in the forward calculation. Registration cannot repair a disconnected graph.
Once ownership, shapes, and gradients agree, the next lesson defines the token sequences those layers will learn from.
Practice sequence
Use the order below for a guided path, or choose an item to revisit. A pass records a successful attempt; try a fresh example later to check what you can reconstruct.
- Step 1
Neural Network Module Base Class
- Step 2
Linear, Sequential, and Activations
- Step 3
Neural Network Abstractions Checkpoint
Apply the module contracts to concrete traces and diagnose failures.