Bias Variance Viz
Drag a single complexity slider. The left plot shows a family of polynomial fits drawn from independent noisy samples of a hidden target function — the spread between fits is the variance, the gap between their mean and the dashed target is the bias. The right plot precomputes bias², variance, and total error across the whole complexity grid so the U-shape of statistical learning is always visible; a guideline tracks the slider.
Bias-Variance Tradeoffdegree 07 · variance-dominated
bias² = 0.000variance = 0.004total = 0.014σ = 0.10
0.50 · deg 7
Customize
Model
0.50
Data
0.10
8
Installation
npx shadcn@latest add https://craftbits.dev/r/bias-variance-viz.jsonUsage
import { BiasVarianceViz } from "@craft-bits/core";
<BiasVarianceViz defaultComplexity={0.5} />Control complexity from outside:
const [c, setC] = useState(0.5);
<BiasVarianceViz
complexity={c}
onComplexityChange={setC}
noiseLevel={0.15}
nModels={12}
/>Swap the target function for a different learning problem:
<BiasVarianceViz
targetFn={(x) => Math.cos(2 * Math.PI * x) * 0.6 + 0.5 * x}
defaultComplexity={0.5}
/>Understanding the component
- Complexity drives polynomial degree.
complexityis a[0, 1]slider mapped to polynomial degreefloor(1 + c · 12)— so0is a straight line and1is a degree-13 polynomial that has more than enough flexibility to overfit the 14 training points. - Left plot — variance as spread.
nModelsindependent noisy datasets are sampled fromtargetFn(each with the samenoiseLevelGaussian observation noise), and a polynomial of the current degree is fit to each. The fits are drawn in accent at 30% opacity. High variance fans them out; low variance collapses them onto one curve. - Left plot — bias as gap. The mean of all fits is drawn as a solid accent line. Its distance from the dashed target function is the bias. Low-degree polynomials are too rigid to follow the target; high-degree polynomials follow it faithfully on average but fan wide.
- Right plot — the U-shaped error curve. Bias², variance, and total error are precomputed at 21 complexity values using a Monte Carlo estimate over a 32-point test grid. Bias² falls as degree grows; variance rises; the sum is the classic U — the sweet spot is the dip.
- Deterministic sampling. Random draws come from a seeded Mulberry32 PRNG. Plots are stable across renders and SSR.
- Spring transitions. Every curve animates between complexity values with
SPRINGS.smooth.prefers-reduced-motion: reducecollapses every animation toduration: 0.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
complexity | number | — | Controlled complexity in [0, 1]. |
defaultComplexity | number | 0.4 | Uncontrolled initial complexity. |
onComplexityChange | (complexity: number) => void | — | Fires on every slider drag. |
targetFn | (x: number) => number | Math.sin(x · π) | Ground-truth function sampled on [0, 1]. |
noiseLevel | number | 0.1 | Standard deviation of observation noise added to training samples. |
nModels | number | 8 | Number of polynomial fits drawn at the current complexity. |
transition | Transition | SPRINGS.smooth | Spring for the right-plot guideline. |
className | string | — | Merged onto the root <div> via cn(). |
Accessibility
- The whole visualization is
role="figure"with anaria-labelledbyheading. - The two SVG canvases share a
role="img"label; the textual status is in anaria-live="polite"region. - The complexity slider is the standard
LabeledSlider— native<input type="range">with full keyboard support (arrows, page-up/down, home/end). - Animation respects
prefers-reduced-motion: reduce— every spring collapses to an instant swap.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/nn/BiasVarianceViz.tsx). The original was a dartboard metaphor with three preset modes and per-mode narration; this rewrite replaces it with the canonical twin-plot tradeoff diagram — fit family on the left, U-shaped error curve on the right — and exposes a single continuouscomplexityslider as the only control.