Dropout Ensemble Viz
Dropout taught as ensemble learning. A small input → hidden → output network is drawn at the top; the learner taps Sample mask to apply a fresh Bernoulli mask over the hidden layer, killing a random subset of neurons (and the connections that flow through them). Each sampled mask is preserved as a mini-network thumbnail in a strip underneath — a "voter" in the ensemble.
The phase machine derives the narration from the strip length: observe (no masks), sample (1–3), diverse (4–5), insight (6+). At the insight phase a callout reveals the punchline: at inference time, every neuron is active and the prediction is the average of every subnetwork that was ever trained. Dropout gives you ensemble learning for free.
The full network uses all 6 hidden neurons. During training, dropout creates a different subnetwork each step. Sample a mask to see one.
Installation
npx shadcn@latest add https://craftbits.dev/r/dropout-ensemble-viz.jsonUsage
import { DropoutEnsembleViz } from "@craft-bits/viz/dropout-ensemble-viz";
<DropoutEnsembleViz />Tune the dropout rate and hidden-layer width:
<DropoutEnsembleViz numHidden={8} dropoutRate={0.3} />Pin the sampler to a deterministic seed (useful in tests, SSR, and recorded walkthroughs):
<DropoutEnsembleViz seed={1729} />Drive the mask history from outside (controlled mode):
const [masks, setMasks] = useState<ReadonlyArray<readonly boolean[]>>([]);
<DropoutEnsembleViz masks={masks} onMasksChange={setMasks} />Understanding the component
- Three-layer network. The top half renders an
input → hidden → outputnetwork withnumInput,numHidden, andnumOutputneurons. Hidden neurons are the only ones dropout acts on; the input and output layers are intact every frame. - Bernoulli mask per sample. Tapping Sample mask generates a fresh mask of length
numHidden: each entry is independently kept with probability1 − dropoutRate. WhennumHidden ≥ 4, the sampler guarantees at least two alive and at least two dead neurons so the teaching picture stays legible. - Imperative neuron animation. The hidden-neuron radius pulse, fill, stroke, and dropped-neuron error flash are all driven imperatively via motion's
animate()against refs — the SVG never re-renders per frame as masks come and go. - Mask strip as voter accumulator. Each sampled mask is preserved as a mini-network thumbnail in a strip underneath. Once the strip exceeds
maxVisibleMasks, the oldest masks scroll off but stay in the controlled history; an ellipsis indicates the overflow. - Phase machine. The narration phase derives from the mask count:
observe(0),sample(1–3),diverse(4–5),insight(6+). The phase drives the accent colour of the narration paragraph and gates the "Inference = average of all N voters" callout. - Deterministic sampling. When a
seedis provided, masks are sampled viamulberry32(seed XOR history.length). This keeps SSR and client renders in lockstep and makes recorded walkthroughs reproducible. Omittingseedfalls back toMath.random()for genuine variety. - Reduced motion. Under
prefers-reduced-motion: reduce, every animation collapses to an instant attribute set — radius pulses, error flashes, and connection-opacity easing all snap.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
numInput | number | 4 | Input-layer width. Clamped to ≥ 1. |
numHidden | number | 6 | Hidden-layer width (the layer dropout acts on). Clamped to ≥ 1. |
numOutput | number | 2 | Output-layer width. Clamped to ≥ 1. |
dropoutRate | number | 0.5 | Per-neuron Bernoulli drop probability in [0, 1]. |
maxVisibleMasks | number | 6 | Maximum thumbnails shown in the strip; older masks scroll off. |
seed | number | — | When set, masks are sampled deterministically via mulberry32. |
masks | ReadonlyArray<readonly boolean[]> | — | Controlled mask history. Pair with onMasksChange. |
defaultMasks | ReadonlyArray<readonly boolean[]> | [] | Uncontrolled initial mask history. |
onMasksChange | (masks) => void | — | Fires after each sample / reset. |
transition | Transition | SPRINGS.snap | Override the hidden-neuron radius-pulse spring. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The SVG is
role="img"with anaria-labelsummarising the mask count and the current subnetwork's active-neuron count. - A live region below the SVG narrates each phase transition as plain prose so screen-reader users learn the same lesson sighted users do.
- The narration paragraph is also
aria-live="polite"and reads as plain prose; it is the canonical explanation for each phase. - Colour is never the only signal — dropped neurons display a bold
×glyph, the hidden-layer label flips to the warning tone, and the active-neuron counter reads as text. - The Sample mask and Reset buttons have visible labels, are reachable in tab order, and show a focus ring via
:focus-visible. - Motion respects
prefers-reduced-motion: reduce— every neuron-radius pulse, error flash, and connection-opacity ease collapses to an instant attribute set.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/nn/DropoutEnsembleViz.tsx). The source was a tightly bundled lesson component — it consumedSvgLabelfrom the SVG primitives andChallengeBtnfrom the lesson chrome, hardcoded the 4 → 6 → 2 architecture andp = 0.5, depended on per-track lesson palette tokens (--color-accent-500,--color-warn-400,--color-fail-500,--color-success-500,--color-ink-*), and embeddedMath.random()directly into the sampler. The viz extract drops the lesson chrome (raw<text>plus token-styled buttons), parameterisesnumInput/numHidden/numOutput/dropoutRate/maxVisibleMasks, remaps the palette tovar(--cb-*)semantic tokens so consumer themes repaint freely, surfaces the mask history as a controlled-or-uncontrolled Radix-style prop (masks/defaultMasks/onMasksChange), and adds a deterministicseedfor SSR / hydration parity and reproducible walkthroughs. Inline{ type: "spring", stiffness, damping }tunings and theSTAGGER.tightconstant from the source were swapped forSPRINGS.snapandSTAGGERfrom@craft-bits/core/motion.