What it is
A nine-section interactive primer where the models are real. Train a logistic-regression classifier with a button and watch the decision field resolve; drag a flexibility slider from 1 to 12 and watch the same fit go from too simple to memorising noise (train 0.042, test 1.730 at degree 12); train a tabular Q-learning agent on a 6×6 grid until the arrows become a policy. 84 KB, one file, no libraries, no network — every number on the page is computed in the tab that displays it.
Version history
Straight from the project’s own card data — what each version added.
84,013 bytes · 3 live demos · 22/22 QA checks · graded at 390 / 1440
Files on the site
What broke, and what I’d change
Not written yet. This is the part only I can write — what actually went wrong, and what I’d do differently. It lives in builds/reflections/ml-loop.md (see _TEMPLATE.md); as soon as that file has words in it, this section fills in on the next build.
Everything else on this page — the spec, the version history, the file counts — is generated from the project’s own build notes and files, so nothing here is guesswork.
Build spec
The project’s own build notes, rendered. Open the raw .md ↗
Show the spec
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:
- The idea — guess → error → adjust, drawn as a cycle with a marker moving
- Train one (live) — logistic regression with two inputs and three
- Inside the machine — the trained classifier has exactly three numbers;
- Overfitting (live) — polynomial least squares with a flexibility slider,
- Reward (live) — tabular Q-learning on a 6×6 grid, arrow policy drawn from
- Three ways — supervised / unsupervised / reinforcement learning.
- The limits — why "the model decided" is never an explanation.
- Quiz — 7 questions with explanations on wrong answers, scored.
- Glossary — 15 expandable terms.
around it.
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).
what a weight and a bias mean.
1→12, and both errors shown. Degree 12 gives train 0.042 / test 1.730.
the Q-table after N episodes.
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
- Curve fit: least squares by modified Gram-Schmidt QR on a Vandermonde
- Agent: tabular Q-learning, ε-greedy exploration decaying over episodes,
points, loss = binary cross-entropy, boundary drawn from the weights.
basis (10–13 parameters on 14 points), train/test RMSE computed on separate sets, 90 unseen points for the test error.
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
- The overfitting demo did not overfit. Degree 9 on 18 points left eight
- The normal equations were numerically useless at high degree. Raw
- The rotated y-axis label was struck through by the plot border. Canvas
- A section number overlapped its heading — "03 / INSIDE THE MACHINE" is
- RL sparkline bars covered their own label, because the tallest bar was
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.
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.
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.
191 px of monospace in a 118 px column, so it ran into the h2. Shortened to "03 / INSIDE".
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.