Running Stats Viz

A teaching visualisation for the EMA inside every normalisation layer. As batch means arrive, BatchNorm (and LayerNorm at inference) maintains a running mean and running variance via exponential moving averages:

running_mean = momentum * running_mean + (1 - momentum) * batch_mean
running_var  = momentum * running_var  + (1 - momentum) * (batch - running_mean)^2

The component plots the incoming batch means as a faint stepped trace, the running mean as a solid accent curve riding it, and the running variance as a ±stddev band around the running mean. The momentum knob makes the responsiveness ↔ stability trade-off visible: high momentum smooths heavily and lags reality; low momentum chases every batch.

Running statistics visualisation.Step 0. Running mean 5.48, stddev 0.
Running statsstep 00/50 μ=5.48 ±0
m=0.90 · σ=0
Customize
EMA
0.90
140ms
Display

Installation

npx shadcn@latest add https://craftbits.dev/r/running-stats-viz.json

Usage

import { RunningStatsViz } from "@craft-bits/core";
 
<RunningStatsViz samples={batchMeans} momentum={0.9} defaultPlaying />

Drive the cursor externally — pin a specific step with no autoplay:

<RunningStatsViz
  samples={batchMeans}
  momentum={0.95}
  currentStep={28}
  playing={false}
/>

Hide the variance band for a leaner figure:

<RunningStatsViz samples={batchMeans} showVarianceBand={false} />

Anatomy

  1. Three layers, one chart. The per-batch mean is drawn as a dashed stepped curve in cb-fg-subtle — the raw signal. The running mean rides on top in solid cb-accent — the EMA. A cb-warning-tinted band hugs the running mean at ± sqrt(running_variance) — the second-moment EMA. Together they show the smoothing chain BatchNorm / LayerNorm performs at training time and freezes at inference.
  2. Single-pass precompute. Every render walks samples once and emits two parallel curves of length samples.length + 1. Index 0 is the zero-batch initial state (running mean seeded from the first sample, variance seeded to 0); subsequent indices apply the standard EMA recurrence. The same samples always yield the same curves — safe across SSR and hydration and external scrubber timelines.
  3. momentum is the responsiveness knob. Near 1, the EMA listens to the long-run mean and the curve barely budges with each batch — a stable, lagged estimate. Near 0, the EMA chases every batch and the curve becomes nearly identical to the dashed batch-mean trace. The same recurrence underlies BatchNorm's momentum (PyTorch default 0.1 → here 0.9) and Adam's β1 and β2 moment estimates.
  4. Variance band shows uncertainty, not just spread. As batches accumulate, the variance EMA settles around the per-batch noise floor — the band stops shrinking once it captures the actual sampling variation. Under distribution drift the band widens for a few steps before re-anchoring; that is the visual cue for why track_running_stats=False matters in transfer learning.
  5. Autoplay sweeps the cursor. When playing, an interval advances currentStep by 1 every playSpeed ms and wraps at the end of the stream. The cursor is a vertical dashed line plus an accent dot pinned to the running-mean curve. Both spring with SPRINGS.snap; the curves themselves spring with SPRINGS.smooth.
  6. Controlled or uncontrolled everywhere. currentStep and playing each have controlled and uncontrolled forms (the Radix pattern). Pass real batch means through samples and the same component works as a scrubbable post-mortem of a training run rather than a synthetic toy.
  7. Reduced motion. prefers-reduced-motion: reduce collapses every spring to an instant swap and disables autoplay; manual scrubbing still works.

Props

PropTypeDefaultDescription
samplesreadonly number[]The stream of batch means. Non-finite entries are dropped.
momentumnumber0.9EMA decay rate in [0, 1). Clamped to 0.999 so the EMA always moves.
showVarianceBandbooleantrueShade the ±stddev band in cb-warning around the running mean.
showBatchMeanbooleantrueDraw the per-batch stepped trace under the running mean.
currentStepnumberControlled cursor step. Pair with onCurrentStepChange.
defaultCurrentStepnumber0Uncontrolled initial cursor.
onCurrentStepChange(step) => voidFires on autoplay tick and scrub.
playingbooleanControlled play state. Pair with onPlayingChange.
defaultPlayingbooleantrueUncontrolled initial play state.
onPlayingChange(playing) => voidFires when play / pause flips.
playSpeednumber140Milliseconds per autoplay tick.
transitionTransitionSPRINGS.snapSpring for the cursor. Curves always use SPRINGS.smooth.
classNamestringMerged onto the root via cn().

Accessibility

  • The figure is role="figure" with an aria-label describing the momentum, current step, running mean, and running stddev.
  • An aria-live="polite" summary announces the step, running mean, stddev, and current batch whenever the cursor moves.
  • The play / pause button uses aria-pressed; its label flips between "Play running stats playback" and "Pause running stats playback".
  • The running-mean curve is solid, the batch trace is dashed and stepped, and the variance band is a filled polygon — three distinct shape signals, never colour alone.
  • The scrubber is a native range input with an explicit aria-label; arrow keys nudge the cursor by one step and screen readers narrate the value.
  • prefers-reduced-motion: reduce collapses every spring to an instant swap and disables autoplay; manual scrubbing still works.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/nn/RunningStatsViz.tsx). The source was a single-batch-at-a-time number-line interaction with a ChallengeBtn "Next Batch" loop, a four-phase narration machine (observe / converging / tracking / insight), an imperative motion/animate cx loop over runningDotRef, an SVG glow filter, ghost-dot trails, a true-mean dashed reference, and a phase-coloured narration footer. Re-architected into a curves-over-steps line chart: precomputed running mean (EMA) plus running variance (EMA of squared deviations), cb-warning-tinted ±stddev band, stepped batch-mean trace in cb-fg-subtle, and the standard motion.path plus SPRINGS.smooth curve transition pattern. Replaced the --color-accent-* and --color-warn-* raw vars with cb-* tokens, the inline animate() loop with declarative motion.path / motion.circle springs, and the per-batch ChallengeBtn with the standard autoplay plus scrubber transport. Surfaced samples, momentum, currentStep, and playing as controlled / uncontrolled (Radix pattern) props so consumers can pipe in real batch means and own the playback state.