Build log · Software & apps

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

Software & appsdone1 versionssingle filegradient descent84 KBno libraries3 live demos

reflection not written yet · every fact below is generated from this project’s own spec and files

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.

V1

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:

  1. The idea — guess → error → adjust, drawn as a cycle with a marker moving
  2. around it.

  3. Train one (live) — logistic regression with two inputs and three
  4. 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).

  5. Inside the machine — the trained classifier has exactly three numbers;
  6. what a weight and a bias mean.

  7. Overfitting (live) — polynomial least squares with a flexibility slider,
  8. 1→12, and both errors shown. Degree 12 gives train 0.042 / test 1.730.

  9. Reward (live) — tabular Q-learning on a 6×6 grid, arrow policy drawn from
  10. the Q-table after N episodes.

  11. Three ways — supervised / unsupervised / reinforcement learning.
  12. The limits — why "the model decided" is never an explanation.
  13. Quiz — 7 questions with explanations on wrong answers, scored.
  14. 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
  2. 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.

  3. The normal equations were numerically useless at high degree. Raw
  4. 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.

  5. The rotated y-axis label was struck through by the plot border. Canvas
  6. 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.

  7. A section number overlapped its heading — "03 / INSIDE THE MACHINE" is
  8. 191 px of monospace in a 118 px column, so it ran into the h2. Shortened to "03 / INSIDE".

  9. RL sparkline bars covered their own label, because the tallest bar was
  10. 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.