# The Loop — an ML primer where the model actually trains in your browser

**Category:** Software & apps · **Status:** Done · **Built:** 2026-11-02

## The ask

> "make a website for people to learn machine learning and ai of repeats and machine learns over time"

Two ideas had to be the *mechanism*, not the topic: repetition, and a model that
visibly learns over time. A page of prose about gradient descent would have
failed the brief even if every sentence were correct.

## What it is

A single-file interactive primer, ~84 KB, no libraries, no network calls. Nine
sections, three of which are live training demos the reader can drive:

1. **The idea** — guess → error → adjust, drawn as a cycle with a marker moving
   around it.
2. **Train one (live)** — logistic regression with two inputs and three
   parameters, written from scratch. 200 points, real gradient descent, a
   decision field that resolves as the boundary rotates. Three datasets:
   separable, overlapping, and non-linear (which stalls at ~70% accuracy — the
   honest reason neural networks exist).
3. **Inside the machine** — the trained classifier has exactly three numbers;
   what a weight and a bias mean.
4. **Overfitting (live)** — polynomial least squares with a flexibility slider,
   1→12, and both errors shown. Degree 12 gives train 0.042 / test 1.730.
5. **Reward (live)** — tabular Q-learning on a 6×6 grid, arrow policy drawn from
   the Q-table after N episodes.
6. **Three ways** — supervised / unsupervised / reinforcement learning.
7. **The limits** — why "the model decided" is never an explanation.
8. **Quiz** — 7 questions with explanations on wrong answers, scored.
9. **Glossary** — 15 expandable terms.

## How it was built

`src/*.html` + `src/*.js` concatenated by `build.sh` into one `index.html`, with
`node --check` on the extracted script as a build gate. Canvas 2D for all three
live visuals; no WebGL, no framework.

The three demos are real algorithms, not animations:

- **Classifier**: logistic regression, per-epoch gradient step over all 200
  points, loss = binary cross-entropy, boundary drawn from the weights.
- **Curve fit**: least squares by modified Gram-Schmidt QR on a Vandermonde
  basis (10–13 parameters on 14 points), train/test RMSE computed on separate
  sets, 90 unseen points for the test error.
- **Agent**: tabular Q-learning, ε-greedy exploration decaying over episodes,
  reward −1 per step, +10 at the goal, Q-table rendered as per-cell arrows.

## Verified

`qa/test.py` drives the real page in headless Chromium: 22 checks, 0 failures —
console clean, no horizontal overflow at 390 and 1440, training actually
converges (accuracy ≥ 95% on the separable set), the overfit slider produces an
overfit verdict at degree 12, the quiz reaches its final question and scores,
the glossary opens, tap targets ≥ 44 px.

## What was wrong, and how it was found

1. **The overfitting demo did not overfit.** Degree 9 on 18 points left *eight*
   spare degrees of freedom, so train error never fell below 0.14 and test error
   barely moved — the section told a story the chart refused to show. Fixed by
   shrinking the training set to 14 points and extending the slider to degree
   12: train 0.042, test 1.730, a 41× gap. The demo now argues its own point.
2. **The normal equations were numerically useless at high degree.** Raw
   monomials on 18 points give a Hilbert-like system; Gaussian elimination
   returned garbage coefficients that *looked* plausible. Replaced with
   modified Gram-Schmidt QR, which fits clean data to 0.000 and noisy data to
   the noise floor.
3. **The rotated y-axis label was struck through by the plot border.** Canvas
   `textBaseline` defaults to alphabetic, so rotating −90° pushed glyphs into
   the frame line. Setting `textBaseline = 'bottom'` inside the rotated block
   moved the text outside the border.
4. **A section number overlapped its heading** — "03 / INSIDE THE MACHINE" is
   191 px of monospace in a 118 px column, so it ran into the `h2`. Shortened to
   "03 / INSIDE".
5. **RL sparkline bars covered their own label**, because the tallest bar was
   scaled to the full canvas height instead of leaving a 30 px gutter.

## Honest limits

The classifier is linear by construction: the non-linear dataset is *supposed*
to fail, which is the lesson, not a bug. The Q-learning demo is tabular and
small on purpose — no function approximation, no neural network. The fit demo
uses test error to pick the "best" degree, which is circular in a way the page
does not hide. Progress is per-tab: the quiz score is not saved between visits,
and there are no accounts, which is the trade for shipping it as static bytes.
