← All examples

Machine Learning: Neural Networks & Deep Learning

Everything in this section is one idea repeated: a network of simple units, trained by backpropagation. Convolutional nets, LSTMs and transformers are not different algorithms so much as different inductive biases bolted onto that same core — assumptions about what structure the data has, built into the architecture. So the first example builds the core by hand, derives the backward pass, and proves it correct against finite differences before any framework appears.

The section opens with a result that sets up everything after it. On the tabular data that runs through the rest of the collection, the neural network loses to gradient boosting — comfortably clearing a linear baseline, tying the random forest, and never catching XGBoost. That is not a tuning failure, it is a well-replicated regularity: tabular columns have no spatial or temporal order for a network's biases to exploit, and permuting the features changes nothing. Deep learning earns its keep precisely where structure exists to be assumed — adjacency in images, ordering in sequences, long-range dependence in language — which is what each subsequent example supplies.

A second thread runs underneath: what a framework actually buys you. Matched on optimiser and initialisation, hand-written backpropagation and PyTorch's autograd agree to 0.001 AUC. The gap you see otherwise is settings, not mathematics — and it decomposes into pieces worth knowing the size of.

The training loop, and the words for its parts. Every model below is trained the same way, and the vocabulary is worth fixing once. A forward pass sends inputs through the network to a prediction, applying an activation function at each layer — almost always the ReLU, which simply zeroes negatives and passes positives through, and whose only real virtue is that its derivative is 0 or 1 and so neither shrinks nor explodes. A loss scores that prediction: cross-entropy for classification, which is the negative log-likelihood of the correct class and therefore the same objective a logistic regression maximises. Backpropagation is the chain rule applied backwards through the layers to get the gradient of that loss with respect to every weight, and an optimiser takes a step against it — SGD a fixed fraction of the gradient, Adam a per-parameter adaptive one — scaled by the learning rate. That step is taken not on the whole dataset but on a minibatch of a few dozen rows, and one sweep through all the batches is an epoch. Nothing in the section is more complicated than that loop; the architectures differ only in what sits between input and loss.

Initialisation is not a detail. The weights have to start somewhere, and where they start changes where they end up. Xavier and He initialisation both draw the starting weights with a variance set by the number of inputs to each layer, so that the signal neither dies out nor blows up as it passes through — He is the version tuned for ReLUs. This is usually filed as an implementation footnote, and the first example below shows why it should not be: the gap between the from-scratch network and PyTorch decomposes into +0.0098 from initialisation against +0.0071 from the optimiser. The starting point mattered slightly more than the algorithm. What a framework really supplies is not better gradients — hand-written backpropagation and autograd agree here to 0.0011 — but defaults someone else has already tuned.

What each architecture assumes. The three middle examples are not three unrelated designs but three assumptions about the data, each encoded in the wiring. A convolution assumes that a pattern means the same thing wherever it appears, so it slides one small filter across the whole image and reuses those weights everywhere — weight sharing — with pooling then shrinking the map so later layers see a coarser, wider view. A recurrent network assumes order matters and carries a state forward one step at a time, which creates its characteristic failure: gradients multiplied through many steps shrink toward nothing, the vanishing gradient problem, and long-range information is lost. The LSTM’s gates are learned switches that let information pass through unchanged, which is how it keeps a memory alive. Attention abandons recurrence entirely: every position reads every other in a single step, so nothing has to survive a long chain — at the cost of having no idea what order anything came in, which is why a positional encoding has to be added back by hand.

What the network does not know. A trained network returns one number and no sense of its own reliability. The Bayesian treatment puts a distribution over the weights instead of a point estimate, but the exact posterior is hopeless at this scale, so it is approximated by variational inference: propose a simple family of distributions and pick the member closest to the true posterior, where closeness is KL divergence. Because the KL cannot be computed directly either, what is actually maximised is the ELBO — the evidence lower bound, which is the fit to the data minus a penalty for straying from the prior, and which differs from the quantity of interest by exactly the KL gap. The cheap alternative is Monte Carlo dropout: leave dropout switched on at prediction time and treat the spread across repeated forward passes as uncertainty, which is approximate variational inference in disguise and costs one line of code.

What the depth bought, and where the gap actually came from

All values are committed notebook output. A and B combine the recurrent and transformer projects, which forecast the same 688 test days from the same information set; C comes from the MLP project.

A · Forecasting volatility — and what the parameters bought out-of-sample RMSE (log realized vol) LSTM 0.3465 4,513 params HAR-RV 0.3473 4 params transformer 0.3531 AR(1) 0.3916 2 params random walk 0.4206 0 params GARCH(1,1)-t 0.4568 0.32 0.36 0.40 0.44 DM = −0.35 — a statistical tie 4,513 parameters against 4, for the same accuracy 0.3764 debiased B · How it loses, not just that it loses slope of actual on predicted 1.0 = unbiased LSTM 0.954 HAR-RV 0.929 transformer 0.859 0.85 0.90 0.95 1.00 all three over-react; the transformer most C · A framework gap, decomposed out-of-sample AUC from-scratch 0.7495 PyTorch, same setup 0.7484 + PyTorch init 0.7582 + Adam 0.7653 0.748 0.756 0.764 agree to 0.0011 +0.0098 initialisation +0.0071 optimiser

A is the section’s most useful result and it is a negative one. The LSTM reaches 0.3465 and HAR-RV — a linear regression on yesterday, last week and last month — reaches 0.3473. A Diebold–Mariano test on the same test days gives DM = −0.35 against a critical value of 1.96, so they are statistically indistinguishable. The LSTM spent 4,513 parameters to tie four. The transformer, with attention and positional encodings, does not close the gap either: 0.3531, on the boundary of significance and seed-dependent. Three hand-designed features capture essentially everything a recurrent network or an attention mechanism can extract from this series. Worth noting too that GARCH’s apparent rout is mostly a level error — remove a constant and 0.4568 becomes 0.3764.

B asks the better question, which is not whether a model loses but how. Regressing what happened on what was predicted should give a slope of 1 for any optimal forecast, however much the forecast shrinks toward its mean. All three sit below it, so all three over-react slightly — but the transformer at 0.859 is furthest out, against the LSTM’s 0.954 and HAR’s 0.929. It does not merely lose on RMSE; it is the least well calibrated of the three, which matters more for anything sized off the forecast.

C is the reason to build one of these by hand at least once. A from-scratch MLP scores 0.7495 and PyTorch scores 0.7653, a gap of 0.0159 that invites the conclusion that the framework is simply better. Run PyTorch with the same optimiser and the same initialisation and it scores 0.7484 — hand-written backpropagation and autograd agree to 0.0011, which is the real check on the mathematics. The remaining gap is entirely settings: +0.0098 from PyTorch’s default initialisation and +0.0071 from Adam. What a framework supplies is not better gradients but defaults someone else has already tuned — and here the initialisation, usually filed as a footnote, mattered slightly more than the optimiser.

How the five examples relate

One training loop, three assumptions about data, and one question about what the network does not know. The three middle examples differ only in what sits between the input and the loss.

Parts 3 and 4 are best read against each other, and against the benchmark both are measured on. The LSTM and the transformer solve the same forecasting problem with opposite architectures and reach the same place — level with a linear model. The attention advantage is real but shows up elsewhere: on a synthetic recall task the transformer’s error is flat in sequence length while the LSTM’s grows by an order of magnitude.

MLP & Backpropagation — the Core Built by Hand

No autograd: the forward pass caches activations and backward implements the chain rule directly, producing the δ-recursion that every deep network runs on. Correctness is proved, not asserted — analytic gradients match finite differences to 10⁻⁸, and the check is run at the weight decay actually used for training, which exposed a trap worth keeping: difference an objective that omits the L2 penalty and the check reports a relative error of 0.7 on gradients that are perfectly correct. The PyTorch rebuild is then held to account rather than waved at: matched on optimiser and initialisation it agrees with the hand-written net to 0.0011 AUC, and the remaining 0.016 splits into +0.010 from initialisation and +0.007 from Adam — initialisation mattering at least as much as the optimiser. Closes on the honest tabular verdict, with seed variation admitted: the net ties the random forest and loses clearly to boosting.

View example →

Convolutional Networks — the Payoff of Structure

Where the section’s argument has to pay off, and it does — but not for the reason usually given. The convolution is built in two loops and matched to PyTorch at 10⁻⁷, then a CNN on Fashion-MNIST beats a dense MLP 0.890 to 0.875. The standard “fewer parameters” claim is tested and found weak: it is 12%, it depends on an arbitrary width, and only 2.3% of the CNN’s own weights are convolutional. Two extra fits make the real case. Halving the MLP shows it is not capacity-starved — 126k extra dense weights buy +0.010, where 4,800 convolutional weights buy +0.015, roughly 38× more per parameter. And stripping the CNN’s dense head drops it to 0.729, so the filters are not sufficient alone. Calibration is measured, not eyeballed: ECE 0.0085. Hardest class is Shirt at 0.54, confused with coat and pullover.

View example →

Recurrent Networks & LSTMs — Forecasting Volatility

Weight sharing across time rather than space, taken to a real problem: forecasting S&P realized volatility against the models a volatility desk actually uses. Setting the race up fairly was the hard part — the windowing let the network see one day less than its benchmarks, quietly making it solve a two-step-ahead problem worth about 0.02 RMSE, more than the gap being reported. Aligned properly, all five models forecast the same 688 days, and the top of the table is a tie: HAR-RV 0.3473 against the LSTM’s 0.3465, with a Diebold–Mariano statistic of −0.35. That tie is the point, given the price: 4 coefficients against 4,513 parameters for identical accuracy. GARCH trails, but 32% of its error is a level offset — realized volatility omits the overnight move — not worse dynamics.

View example →

Transformers & Self-Attention

Attention built from scratch in three matrix multiplies and a softmax, matched to PyTorch at 9×10⁻⁸. Its advantage over recurrence is measured rather than asserted, on a controlled recall task averaged over three seeds: the transformer’s error stays flat in sequence length while the LSTM’s grows, from apart at length 20 to 45× at length 120. Then the counterweight. Added to the volatility race — same 688 test days, benchmarks refitted rather than copied — it reaches 0.3531 against HAR-RV’s 0.3473, a gap sitting right on the Diebold–Mariano boundary and worse than the LSTM’s clean tie. A 22-day window of one autocorrelated series holds no long-range structure for attention to find that three HAR terms have not already spanned.

View example →

Bayesian Deep Learning — Calibrated Uncertainty

The capstone: MC-dropout and deep ensembles give a network an error bar, and the two are not interchangeable — on the 1-D extrapolation demo the ensemble separates unseen regions from seen ones by 15× where MC-dropout manages , because much of dropout’s “uncertainty” is just the dropout rate showing through. Intervals cover 92.2% at a nominal 90%, at a cost of about 0.03 RMSE in accuracy. The section then ends on a negative result worth more than a positive one: the textbook selective-prediction demonstration — accuracy climbing 0.82 → 0.90 as uncertain cases are deferred — is entirely the class mix. Lift over predicting the majority class falls to zero, and at 30% retention the model catches none of the defaults it exists to find.

View example →

Tabular Foundation Models — Learning Without Training

A transformer pretrained on millions of synthetic tables, so a forward pass approximates the posterior predictive distribution — the object the Bayesian notebooks reach by MCMC, here amortised into fixed weights. Dropped into the capstone volatility race untuned it lands fourth at 0.3311, indistinguishable from the leading ridge (DM p = 0.67). A fixed-test sample-size sweep then splits the usual claim in two: on 150 days of history its prior beats XGBoost by 0.0541 RMSE and the random forest by 0.0231, yet it loses to a ridge by 0.0210 and needs about 600 rows to draw level. A prior substitutes for data, not for knowing the truth is nearly linear. Its native 90% interval covers 81.6%, and split-conformal lifts that only to 85.0% — because the test errors are larger in the calmer period, and exchangeability is the one assumption the guarantee cannot do without.

View example →