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.json

Usage

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

  1. 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.
  2. Three method math. adagrad accumulates sum(g^2) from step zero — never forgets, so its effective LR can only shrink. rmsprop replaces the sum with an exponentially weighted moving average (decay = 0.9) so old gradients fade out. adam adds 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.
  3. Walks the gradient prefix each render. The component derives the running accumulator at render time from gradients.slice(0, currentStep + 1) — so the same params and the same currentStep always produce the same bars, safe across SSR / hydration boundaries.
  4. Six-parameter built-in demo. When params is 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.
  5. Method picker is a radio group. The three method buttons live inside a role="radiogroup" and carry aria-checked, so screen readers announce the active method and arrow keys move between the options.
  6. Reduced-motion fallback. With prefers-reduced-motion: reduce the bars snap to height with no spring; autoplay is suppressed.

Props

PropTypeDefaultDescription
paramsreadonly AdaptiveLRParam[]built-in demoPer-parameter gradient histories. Omit for the six-parameter showcase.
baseLRnumber0.01The unscaled α. Bars track α / (sqrt(accumulator) + ε).
method'adagrad' | 'rmsprop' | 'adam'Controlled method. Pair with onMethodChange.
defaultMethod'adagrad' | 'rmsprop' | 'adam''adam'Uncontrolled initial method.
onMethodChange(method) => voidFires when the method picker changes.
currentStepnumberControlled step cursor. Pair with onCurrentStepChange.
defaultCurrentStepnumber0Uncontrolled initial step.
onCurrentStepChange(step) => voidFires on autoplay tick and on manual scrub.
playingbooleanfalseWhen true, advances currentStep every playSpeed ms.
playSpeednumber400Milliseconds between autoplay ticks.
transitionTransitionSPRINGS.smoothSpring for the bar-height animation.
classNamestringMerged 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 a role="radio" with aria-checked, so the toggle state is screen-reader native.
  • The scrubber is a native <input type="range"> with an explicit aria-label so 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: reduce snaps 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 for method and currentStep and a pluggable playSpeed autoplay.