Adaptive LR Viz
A grid of parameter cells where each bar height tracks the effective per-parameter learning rate the chosen adaptive optimizer would apply at the current step. AdaGrad accumulates squared gradients forever; RMSProp uses an exponentially weighted moving average; Adam adds first-moment momentum on top — switch methods and watch the bars redistribute as each parameter's gradient history tells a different story.
Adaptive learning-rate visualisation, method Adam.Method Adam, step 21 of 40, base learning rate 0.0100. w₀ effective learning rate 0.0071 (last gradient 1.40). w₁ effective learning rate 0.2000 (last gradient 0.05). w₂ effective learning rate 0.0167 (last gradient 0.60). w₃ effective learning rate 0.0152 (last gradient 0.23). w₄ effective learning rate 0.0187 (last gradient 1.00). w₅ effective learning rate 0.0262 (last gradient 0.60).
step 21 / 40
- w₀α* 0.0071
- w₁α* 0.2000
- w₂α* 0.0167
- w₃α* 0.0152
- w₄α* 0.0187
- w₅α* 0.0262
Customize
Method
adam
Step
20
Learning rate
0.010
Playback
Installation
npx shadcn@latest add https://craftbits.dev/r/adaptive-lr-viz.jsonUsage
import { AdaptiveLRViz } from "@craft-bits/core";
<AdaptiveLRViz />Drive the method and step from outside the component:
const [method, setMethod] = useState<"adagrad" | "rmsprop" | "adam">("adam");
const [step, setStep] = useState(0);
<AdaptiveLRViz
method={method}
onMethodChange={setMethod}
currentStep={step}
onCurrentStepChange={setStep}
/>Pass your own gradient histories:
<AdaptiveLRViz
baseLR={0.001}
params={[
{ id: "w0", label: "w_0", gradients: [1.4, 1.4, 1.4, 1.4, 1.4, 1.4] },
{ id: "w1", label: "w_1", gradients: [0.05, 0.05, 0.05, 0.05, 0.05, 0.05] },
{ id: "w2", label: "w_2", gradients: [0.6, -0.6, 0.6, -0.6, 0.6, -0.6] },
]}
/>Autoplay through the gradient history:
<AdaptiveLRViz playing playSpeed={300} />Understanding the component
- One bar per parameter, height = effective LR. Each cell renders the optimizer's per-parameter scaling factor
α / (sqrt(accumulator) + ε). The library normalises every bar against the maximum effective LR across visible parameters at the current step — so the tallest bar always reaches the top of its cell and the comparison is visual, not numeric. - Three method math.
adagradaccumulatessum(g^2)from step zero — never forgets, so its effective LR can only shrink.rmspropreplaces the sum with an exponentially weighted moving average (decay = 0.9) so old gradients fade out.adamadds a first-moment EMA (beta_1 = 0.9) plus bias correction on the second moment (beta_2 = 0.999) — switching to Adam reshuffles the cells whenever recent gradient magnitudes diverge from the long-run history. - Walks the gradient prefix each render. The component derives the running accumulator at render time from
gradients.slice(0, currentStep + 1)— so the sameparamsand the samecurrentStepalways produce the same bars, safe across SSR / hydration boundaries. - Six-parameter built-in demo. When
paramsis omitted the component renders a deterministic six-parameter scene — loud, quiet, oscillating, decaying, sparse, growing gradients — chosen so the divergence between methods is readable in two seconds. - Method picker is a radio group. The three method buttons live inside a
role="radiogroup"and carryaria-checked, so screen readers announce the active method and arrow keys move between the options. - Reduced-motion fallback. With
prefers-reduced-motion: reducethe bars snap to height with no spring; autoplay is suppressed.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
params | readonly AdaptiveLRParam[] | built-in demo | Per-parameter gradient histories. Omit for the six-parameter showcase. |
baseLR | number | 0.01 | The unscaled α. Bars track α / (sqrt(accumulator) + ε). |
method | 'adagrad' | 'rmsprop' | 'adam' | — | Controlled method. Pair with onMethodChange. |
defaultMethod | 'adagrad' | 'rmsprop' | 'adam' | 'adam' | Uncontrolled initial method. |
onMethodChange | (method) => void | — | Fires when the method picker changes. |
currentStep | number | — | Controlled step cursor. Pair with onCurrentStepChange. |
defaultCurrentStep | number | 0 | Uncontrolled initial step. |
onCurrentStepChange | (step) => void | — | Fires on autoplay tick and on manual scrub. |
playing | boolean | false | When true, advances currentStep every playSpeed ms. |
playSpeed | number | 400 | Milliseconds between autoplay ticks. |
transition | Transition | SPRINGS.smooth | Spring for the bar-height animation. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The figure is
role="figure"with a labelled title that names the active method. - An
aria-live="polite"summary above the grid announces the method, the current step, the base LR, and every parameter's effective LR and last gradient whenever the cursor moves. - The method picker is a
role="radiogroup"; each method is arole="radio"witharia-checked, so the toggle state is screen-reader native. - The scrubber is a native
<input type="range">with an explicitaria-labelso keyboard arrows nudge the step and screen readers narrate the value. - Color is never the only signal — every cell labels the parameter, displays the most recent gradient, and prints the effective LR textually next to its bar.
prefers-reduced-motion: reducesnaps bar heights and suppresses autoplay; no per-step motion is shown.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/viz/AdaptiveLRViz.tsx). Stripped the lesson-specific Widget chrome, predict-quiz mode, challenge mode, undo / redo history, bookmark strip, and the embedded contour plot tied to the lesson's elongated-valley scene. Generalised to a pure adaptive-LR primitive: per-parameter gradient histories in, normalized effective-LR bars out, with controlled / uncontrolled state pairs formethodandcurrentStepand a pluggableplaySpeedautoplay.