3 Concepts¶
This page is the cross-cutting foundations reference for the notebook collection: the training,
evaluation, and generalization machinery that every task composes, regardless of paradigm. Each
per-task deep-dive (under notebooks/) assumes this vocabulary and reports against these metrics;
this page collects them in one place so the deep-dives can focus on what is task-specific (the
architecture, the data pipeline, the loss head) rather than re-deriving softmax or ARI from
scratch each time. The three smallest, cleanest reference points are the Iris MLP
(notebooks/tabular_classification-iris-mlp-pytorch.md),
the MNIST FFNN sweep
(notebooks/image_classification-mnist-ffnn-pytorch.md),
and the TinyShakespeare transformer
(notebooks/text_generation-tinyshakespeare-transformer-pytorch.md).
3.1 Training & optimization¶
3.1.1 The training loop¶
Training a neural network is the iterative application of one rule: measure the disagreement between prediction and target with a loss, compute the gradient of the loss with respect to each parameter, and step the parameters a small amount against the gradient. One pass over the full training set is an epoch; one parameter update is a step; the set of samples used to compute a single gradient is a batch (or mini-batch when it is a subset of the training set). The Iris MLP trains 300 epochs in mini-batches of 32; the MNIST FFNN notebook runs 500 epochs of full-batch SGD (one step per epoch, the same regime as its NumPy sibling); the TinyShakespeare transformer runs 5 epochs of 43 mini-batches each. Both regimes are first-class.
The nnx library factors the loop into configuration objects and a single .train(...) call:
NNModelParams selects the net family, device, and loss; NNParams configures the topology
(hidden_dims, dropout_prob, input_dim, output_dim); NNTrainParams carries the budget,
the loaders, and the optimizer block (NNOptimParams). The returned NNRun is the training
history — a list of iteration data points (run.idps), each carrying train_edp and val_edp
(evaluation data points with error, accuracy, precision, recall, f1, loss, lr). The
best-checkpoint restoration uses NNCheckpoint.load(run=RUN_ID, type=Checkpoints.BEST) in a
fresh session.
3.1.2 Loss functions¶
The loss is the scalar the optimizer minimizes. Three losses cover every supervised task; a fourth (smooth-L1 reconstruction) covers the self-supervised case.
Cross-entropy (CE) is the multiclass classification loss. The classifier produces logits \(z = (z_0, \dots, z_{K-1})\); softmax maps them to a probability simplex, and CE measures the disagreement with the one-hot label \(y\):
where \(c\) is the correct-class index. The Iris and MNIST notebooks select this via
Losses.CROSS_ENTROPY; the framework pairs softmax with CE internally so neither notebook
instantiates either by hand. For autoregressive language modeling, the same CE applies along the
vocab dimension of every position. The TinyShakespeare transformer emits logits of shape
\((B, T, V)\); the custom lm_train_step flattens the batch and sequence axes into one "token"
axis (\((B \cdot T, V)\)) so torch.nn.functional.cross_entropy applies along the vocab dimension:
The train_step_fn= hook on model.train(...) is the seam that swaps in this LM-specific loss;
the same seam injects MSE for autoencoders, smooth-L1 for I-JEPA, and the contrastive DPO loss for
preference alignment.
Binary cross-entropy (BCE) drives binary tasks such as link prediction. The Karate
link-prediction notebook scores an edge as the dot product of endpoint
embeddings and applies binary_cross_entropy_with_logits on the raw scores against labels \(1\)
(positive edge) and \(0\) (sampled negative):
Using with_logits (rather than sigmoid then BCELoss) keeps the computation in the numerically
stable log-sum-exp regime.
Mean-squared error (MSE) is the regression loss. The Diabetes MLP produces a scalar \(\hat{y} \in \mathbb{R}\) and minimizes
via Losses.MEAN_SQUARED_ERROR paired with a one-unit output head. The same reconstruction
objective — F.mse_loss(net(X), X) — trains the autoencoders used for dimensionality reduction and
clustering, where the input is the target. MSE expects float targets shaped \((N, 1)\) to match
the network output; passing \((N,)\) broadcasts and silently produces a different reduction.
3.1.3 Gradient descent → SGD → Adam¶
Vanilla gradient descent updates each parameter \(\theta\) by stepping against the gradient,
\(\theta_{t+1} = \theta_t - \eta \, \nabla_\theta \mathcal{L}(\theta_t)\), where \(\eta\) is the
learning rate. Mini-batch SGD approximates the gradient over a batch rather than the full
dataset, which is what makes the per-step cost tractable. The from-scratch NumPy MNIST notebook
implements this update directly — every weight and every gradient is visible in NumPy; the
PyTorch-via-nnx sibling swaps the same loop onto Adam.
Adam (Optims.ADAM) is the optimizer the nnx notebooks actually use. It maintains per-
parameter first and second moment estimates of the gradient and steps using their bias-corrected
ratio:
Typical settings: \(\eta \in \{10^{-2}, 3 \times 10^{-4}, 5 \times 10^{-3}, 5 \times 10^{-4}\}\)
(task-tuned); \((\beta_1, \beta_2) = (0.9, 0.999)\) for feed-forward tasks and \((0.9, 0.95)\) for
the transformer (the smaller \(\beta_2\) is the GPT-family convention, which decays the
second-moment estimate faster and is more forgiving on early-step loss spikes); weight decay
\(5 \times 10^{-4}\) (Iris, Karate) or \(5 \times 10^{-5}\) (MNIST) or \(0\) (transformer). The
transformer also sets grad_clip_norm=1.0 to clip the global gradient norm — a stabilization that
tames the early-step spikes.
3.1.4 Learning-rate schedules¶
The MNIST FFNN notebook uses ReduceLROnPlateau as its default scheduler — it watches validation
error and multiplies the learning rate by factor = 0.95 after patience = 8 iterations of
insufficient improvement (delta below threshold = 0.001), enforces a cooldown = 2-iteration
pause between reductions, and floors at min_lr = 1e-7. The effect is per-run self-annealing:
each sweep candidate anneals independently as its own validation curve plateaus, so no global LR
schedule has to be tuned by hand. The LR-overlay plot the MNIST notebook produces
(VisUtils.multi_line_plot on idp.lr) makes this visible — the step-downs cluster across the top
runs at the same iteration range, the visible signature of the scheduler doing its job. The other
notebooks mostly pin a flat LR — appropriate for the short Tier-A budgets where the scheduler's
overhead would not pay back.
3.1.5 The nnx training-loop shape¶
The shape the notebooks converge on, repeatedly:
| Object | Role |
|---|---|
NNModelParams |
Net family, device, loss — the "what" |
NNParams |
Topology (hidden_dims, dropout_prob, dims) — the "how big" |
NNOptimParams |
Optimizer (name, max_lr, momentum, weight_decay, grad_clip_norm) — the "how fast" |
NNTrainParams |
n_epochs, loaders, seed — the "how long" |
NNModel.train(params=...) |
The single call that runs the loop |
NNRun |
Returned training history (idps, checkpoints) |
train_step_fn= (optional) |
Seam for swapping the default classification step for LM / AE / JEPA / DPO |
The default step assumes a classification head — forward through the net, compute CE against the
batch labels, backprop, step the optimizer, return an NNEvaluationDataPoint. The train_step_fn=
hook is the load-bearing customization seam: the transformer passes an lm_train_step that
flattens \((B, T, V)\) for cross-entropy; the autoencoder and clustering notebooks pass a step
computing F.mse_loss(net(X), X); the I-JEPA notebook passes the factory-built
jepa_train_step_factory(...). The framework still owns the scheduler, the checkpoint cadence, and
the per-iteration bookkeeping — only the loss computation is swapped.
3.2 Evaluation & metrics¶
Training loss going down proves the optimizer is working; held-out metrics prove the model is learning something that generalizes. The collection's evaluation surface splits along two axes: what kind of curve to plot, and which metric summarizes the held-out score for a given task family.
3.2.1 Curves and confusion matrices¶
The canonical training diagnostic is the per-iteration loss (or error) curve, overlaid for
training and validation. NNRun.idps is the data source; VisUtils.multi_line_plot is the
standard renderer (taking x, yss, and yss_legend). The MNIST sweep overlays the top five
candidates on one figure so the capacity-vs-marginal-gain curve is directly readable; the Iris
notebook overlays all three candidates so the linear-vs-MLP gap is visible at a glance.
For classification, the confusion matrix is the directly-interpretable artifact: each row is
the true class, each column the predicted class, and the diagonal-to-off-diagonal ratio answers
"which class pairs does this model confuse?" The Iris confusion matrices make the
versicolor/virginica overlap visible as the only meaningfully non-zero off-diagonal;
VisUtils.confusion_matrix is the renderer (normalize=False for raw counts, normalize=True
for row-normalized recalls).
3.2.2 Per-family metrics¶
Each task family has its own metric. The collection deliberately does not flatten everything to accuracy — the right metric for a regression is \(R^2\), for a clustering is ARI, for a link prediction is AUC, and reporting accuracy for any of them would be meaningless.
| Task family | Metric | Where it lands |
|---|---|---|
| Multiclass classification | Accuracy, macro precision/recall/f1 | Iris MLP, MNIST FFNN, MoE, PEFT |
| Regression | \(R^2\), RMSE, MAE, MSE | Diabetes MLP |
| Language modeling | Per-token cross-entropy, perplexity, bits-per-character | TinyShakespeare transformer |
| Link prediction | ROC-AUC, Average Precision | Karate GraphSAGE |
| Clustering / community detection | Adjusted Rand Index (ARI), Normalized Mutual Info (NMI) | Iris KMeans-vs-AE, Karate Louvain-vs-GNN |
| Self-supervised representation | Linear-probe accuracy | I-JEPA on Fashion-MNIST |
Multiclass classification. Accuracy is the fraction of correctly predicted labels. Macro- averaged precision, recall, and f1 compute each metric per class and then average equally — the interpretable choice under any class balance, and numerically identical to micro-averaging under uniform balance. The Iris notebook reports all four; the candidate ranking keys off f1-macro (then accuracy) because per-class behavior at the versicolor/virginica boundary is the variation that actually distinguishes candidates.
Regression. The coefficient of determination \(R^2\) is the fraction of target variance explained, relative to the constant-mean predictor:
\(R^2 = 1\) is a perfect predictor; \(0\) is "no better than predicting the mean"; \(< 0\) (possible on a held-out test split) flags "worse than the mean." RMSE \(= \sqrt{\mathcal{L}}\) returns the error to target units; MAE \(= \frac{1}{N}\sum_i |y_i - \hat{y}_i|\) is the robust-to-outliers sibling. The Diabetes notebook reports all four because each brackets a different failure mode — a negative \(R^2\) flags a broken model, an \(\mathrm{MAE} \ll \mathrm{RMSE}\) flags heavy-tailed residuals.
Language modeling. The directly-recorded LM loss is the per-batch cross-entropy (sum or mean over the \(B \times T\) token positions, depending on the step's reduction). To compare against published numbers, divide by \(B \times T\) for a per-token mean, then exponentiate for perplexity (\(\mathrm{ppl} = e^{\mathcal{L}_{\text{mean}}}\)) or divide by \(\ln 2\) for bits-per-character (\(\mathrm{bpc} = \mathcal{L}_{\text{mean}} / \ln 2\)). The TinyShakespeare notebook's 63 → 7.5 trajectory is the per-batch number — load-bearing as evidence the loop is correct, but not directly comparable to a published perplexity without the per-token normalization.
Link prediction. ROC-AUC and Average Precision score the ranking of held-out edges by their predicted score. AUC is the probability a random positive ranks above a random negative; AP is the precision-recall area. The Karate notebook's 0.735 val AUC (well above 0.5) is the load-bearing "the encoder learned real link signal" evidence; the 0.431 test AUC on 30 test edges is honest variance, not a defect — and AP (0.579) is the more stable metric on a small, class-imbalanced ranking.
Clustering / community detection. ARI and NMI score the agreement between the predicted clustering and true labels post hoc — the labels never enter training. ARI corrects the Rand Index for chance:
so \(1\) is perfect, \(0\) is random, negative is worse than chance. NMI normalizes mutual information to \([0, 1]\) regardless of cluster count: \(\mathrm{NMI}(U, V) = 2 I(U; V) / (H(U) + H(V))\). Both are symmetric in their arguments and invariant under permutations of cluster labels — essential properties for a clustering metric. The Iris KMeans-vs-AE notebook reports both (raw features ARI 0.716, AE latent ARI 0.835); the win on the learned latent confirms the reconstruction objective has implicitly grouped similar inputs.
Self-supervised representation. The standard SSL evaluation is linear-probe accuracy:
freeze the pretrained encoder, precompute its embeddings once, train a single Linear(d_model → C)
layer on those frozen features, report probe accuracy on the held-out label set. The I-JEPA
notebook's 74.43% probe accuracy (against a 10% random baseline and a ~93% supervised ceiling) is
the proof of life that unlabeled pretraining produced non-trivial features. Mean-pooling over
patch embeddings is the simplest probe input and a lower bound on representation quality — a more
expressive probe (attention pooling, fine-tuned head) would report higher accuracy from the same
frozen features.
3.2.3 Model selection¶
For sweep-shaped notebooks (MNIST's 18 candidates, Iris's 3, Diabetes's 2 MLPs), the selection metric is best-iteration validation error — the lowest validation error a run ever achieved, not its final-epoch value:
Ranking by best-iteration is robust to late-training overfitting oscillations; the selected checkpoint corresponds to the best-iteration moment. This is appropriate but worth flagging when comparing against papers that report final-epoch numbers — best-iteration is an optimistic estimate of generalization, and a stricter regime (k-fold cross-validation, explicit early stopping) tightens the variance estimate at the cost of compute.
3.3 Generalization & regularization¶
The training loss going to zero is not the goal — a model that fits the training set perfectly often generalizes poorly. The collection's generalization regime is a small set of disciplines: hold out data the model never sees, regularize the parameters, and seed everything so the result is reproducible.
3.3.1 Overfitting and the train/val/test split¶
Overfitting is the gap between train and validation performance — the model fitting noise in
the training set as if it were signal. The Diabetes notebook is the cleanest example: the deeper
[32, 16] MLP (625 params) underperforms the smaller [8] MLP (89 params) on the held-out test
split because it overfits the 308-sample train split before it extracts any non-linear signal that
generalizes. Capacity is not monotonically helpful — at small-data regimes, more parameters can
mean worse generalization.
The defense is the train/val/test split: the model trains on train, model selection uses val,
and the reported headline is measured once on test, which the model never saw during training or
selection. The collection's standard split is 70/15/15 (Iris, Diabetes); the MNIST notebooks use
the canonical 60k/1k/9k partition that NNDataset exposes by default. Stratification matters
for classification at small sample counts — at 150 Iris samples times 15% test, a default random
split can plausibly miss a whole class, so stratify= on train_test_split is load-bearing. It
is inapplicable for regression (continuous targets) and unnecessary for unsupervised
clustering (no split at all — all 150 Iris samples are seen by both KMeans and the AE).
3.3.2 Leakage footguns¶
The collection documents four leakage patterns that silently inflate metrics if missed:
- Scaler fit on train only. Applying
fit_transformto val or test leaks their feature ranges into training. The canonical anti-leakage pattern isscaler.fit_transform(X_train)thenscaler.transform(X_val)andscaler.transform(X_test). Both Iris (MinMaxScaler) and Diabetes (StandardScaler) notebooks follow this; both flag it in their pitfalls sections. - Link-split direction leakage. On an undirected graph, both directions of the same edge can
land in different splits unless
RandomLinkSplit(is_undirected=True)is set. Without it, the model has effectively seen each test edge's reverse during training, and test AUC silently inflates. The Karate link-prediction notebook pins this explicitly. - Message-passing vs supervised-edge confusion. In link prediction,
edge_index(the message-passing edges the encoder aggregates over) is always available;edge_label_index(the supervised BCE positives) is train-only. Aggregating overedge_label_indexat train time, or evaluating overedge_index, are both subtle leaks. The Karate notebook's pitfalls section flags this as the load-bearing distinction. - Autoregressive next-token leakage. The LM target is the input rolled by one
(
y = x.roll(-1)) — getting this wrong (e.g.,y = xory = x.roll(1)) silently lets the model see the token it is supposed to predict. The TinyShakespeare notebook uses the canonical left-shift; the same trick recurs in every autoregressive LM.
3.3.3 Dropout, weight decay, and early stopping¶
Dropout zeros each hidden unit with probability \(p\) (dropout_prob) per forward pass and
scales the survivors by \(1/(1-p)\) to keep the expected activation constant; at test time it is
disabled. The effect is implicit ensembling — each forward pass sees a thinned network. The Iris
MLP uses dropout_prob=0.1 (already at the upper end for a 50-parameter model on 105 samples;
heavier dropout starves the hidden layer); the MNIST sweep covers dropout_prob ∈ {0.25, 0.5};
the transformer, autoencoder, and link-prediction encoder set dropout_prob=0.0. The right value
is task-tuned, not a constant.
Weight decay (L2 regularization on the parameters, folded into the optimizer step) pulls weights toward zero proportional to a coefficient \(\lambda\), penalizing the large weights that are the signature of a model memorizing individual training examples. The collection uses \(\lambda = 5 \times 10^{-4}\) (Iris, Karate), \(5 \times 10^{-5}\) (MNIST), \(10^{-4}\) (Diabetes, I-JEPA pretrain), and \(0\) (transformer, autoencoder) — small models on small data benefit from more decay; the transformer's short-budget, low-LR regime does not need it.
Early stopping monitors a validation metric and halts training when it stops improving,
recovering the overfit-to-train gap without manual epoch tuning. nnx exposes EarlyStopping
(the Diabetes notebook flags that its default monitor="val_edp.error" is the classification
monitor; regression must pass monitor="val_edp.loss" explicitly), but most Tier-A notebooks use
a fixed n_epochs for budget predictability in CI and rely on best-iteration validation
ranking (§3.2.3) as the implicit early-stopping signal — the selected checkpoint is the one at
the best-iteration moment, not the final epoch.
3.3.4 Seeding and reproducibility¶
Reproducibility is the discipline that makes a recorded number mean something — without it, the
headline metric is a single draw from a wide distribution. The collection's convention is
nnx.set_seed(0), which pins Python's random, NumPy's RNG, and PyTorch's CPU + CUDA + cuDNN
RNGs in one call. For controlled-comparison sweeps (Iris, MNIST, Diabetes), the seed is
re-pinned inside the candidate loop so identical seeds produce identical weight inits — the
only difference between candidates is the swept axis (hidden_dims, dropout_prob). Without
the re-pin, the RNG state advances between candidates and the inits differ for reasons unrelated
to the swept axis, which defeats the controlled-comparison framing.
External RNGs — sklearn.train_test_split(random_state=...), KMeans(random_state=...),
RandomLinkSplit's seed, negative_sampling's draws — are pinned separately. The link-prediction
notebook is honest about a residual variance source it does not pin: it uses a single seed, so
the recorded 0.431 test AUC is one draw from a wide band, and the pitfalls section flags averaging
across seeds as the robust estimate. The same honesty recurs in the clustering notebook (single
seed, single split) and the Iris notebook (single 70/15/15 split, "on this split" rather than "on
Iris"). Reproducibility is the floor; honesty about the residual variance is the discipline.
Device determinism is the related caveat. Devices.get() (used by the MNIST notebook) returns
CUDA if available else CPU, which means the same notebook produces different wall-clock times (and
slightly different floating-point results, due to non-deterministic GPU kernels) across machines.
Reproducibility across machines requires pinning Devices.CPU explicitly — which the MNIST
notebook does not do, trading cross-machine bit-exactness for the speedup when a GPU is present.