Two Step Optimizer

A six-click walkthrough of two complete gradient-descent training iterations on the parabola L(w) = (w − wOpt)². Each iteration is split into three sub-steps — Forward, Backward, Update — so the learner feels the cycle as three distinct moves rather than one monolithic step.

Forward   →  compute L(w)
Backward  →  compute dL/dw = 2(w − wOpt)
Update    →  w ← w − lr × dL/dw

The fixed learning rate keeps the focus on the cycle. The key visual is that the gradient arrow visibly shrinks between the two iterations — closer to the minimum means a gentler slope, which means a smaller arrow and a smaller step.

Two-step gradient descent walkthrough on L of w equals w minus 3 all squared.
Phase 0 of 6. Weight: 0, Loss: 9.00. idle phase.

Two training steps. Each step is a cycle: forward pass, backward pass, update. Click Forward to begin. The minimum is at w = 3.

Customize
Parabola
0.0
3.0
Walker
0.10

Installation

npx shadcn@latest add https://craftbits.dev/r/two-step-optimizer.json

Usage

import { TwoStepOptimizer } from "@craft-bits/viz/two-step-optimizer";
 
<TwoStepOptimizer />

Re-key the parabola — descend toward w = 5:

<TwoStepOptimizer startWeight={1} optimalWeight={5} />

Use a more aggressive learning rate so the two-step preview lands closer to the minimum:

<TwoStepOptimizer learningRate={0.2} />

Subscribe to phase changes to drive an external trace:

<TwoStepOptimizer
  onPhaseChange={(phase, iteration) => {
    if (iteration) {
      /* iteration.wNew, iteration.lossNew available here */
    }
  }}
/>

Understanding the component

  1. The plot. A wide SVG renders the w ∈ [−1, 7], L ∈ [0, 16] window with a faint grid and a dashed target marker at (wOpt, 0). The parabola is sampled at 121 points and stroked once on first render.
  2. The pipeline. Three labels — FORWARD, BACKWARD, UPDATE — sit above the plot with between them. The active sub-step is bold and full opacity; completed sub-steps fade; the iteration counter lives on the right.
  3. Forward phase. The current weight and loss appear in monospaced labels next to the probe.
  4. Backward phase. A gradient arrow grows from the probe pointing toward the minimum. On iteration 2 the arrow is visibly shorter — a small banner reads was −6.0 → now −4.8 to make the comparison legible.
  5. Update phase. The probe springs along the parabola from w to w − lr × dL/dw. A faint dashed polyline records the trail of visited weights.
  6. Idle pulse and completion ring. Before the first click and after the last, a soft breathing pulse and a celebratory ring play around the dot. Both disable under prefers-reduced-motion: reduce.
  7. Reduced motion. Under the reduced-motion preference, all springs collapse to instant attribute writes.

Props

PropTypeDefaultDescription
optimalWeightnumber3The minimum of L(w) = (w − optimalWeight)².
learningRatenumber0.1Fixed learning rate applied at each update.
startWeightnumber0Weight at the start of iteration 1.
transitionTransitionSPRINGS.smoothOverride the dot's update spring.
onPhaseChange(phase, iteration) => voidFires after each phase advance. Receives the iteration record on update phases (3 and 6); null otherwise.
onReset() => voidFires when the user clicks Reset.
classNamestringMerged onto the root via cn().

Accessibility

  • The plot SVG is role="img" with an aria-label summarising the current phase, weight, loss, and learning rate.
  • The advance button is the single primary control; Reset replaces it once the cycle completes.
  • A live region below the button announces the current phase, weight, and loss after each step.
  • The narration paragraph also has aria-live="polite" and reads as plain prose — the canonical explanation for each phase.
  • Colour is never the only signal — the active pipeline step is encoded in bold weight and an arrow position in addition to colour.
  • Motion respects prefers-reduced-motion: reduce — the dot and arrow snap instantly, and the idle and completion pulses disable.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/math/TwoStepOptimizer.tsx). The source depended on lesson-chrome primitives (SvgLabel, ChallengeBtn), on per-track palette tokens, and on hand-tuned spring names from the lesson's motion lib. The viz extract drops the lesson chrome, generalises the parabola via startWeight and optimalWeight props, generalises the learning rate via learningRate, remaps every colour to the var(--cb-*) semantic palette, and re-keys the dot animation to SPRINGS.smooth and the gradient arrow to SPRINGS.snap.