GAP Collapse Viz
A teaching primitive for Global Average Pooling. Render an H × W × C feature map as one mini heatmap per channel; as progress advances from 0 to 1, every cell shrinks toward its grid centre, the per-cell labels fade, and a single bold scalar — the channel mean — emerges in the middle of each grid. The result is the GAP output: C numbers, zero parameters, any spatial size collapsed to 1 × 1. Use it to teach CNN classifier heads, why GAP replaces fully-connected layers in modern architectures, or how spatial information is intentionally discarded in favour of channel-wise summaries.
Global Average Pooling · 6×6×4 → 1×1×4Global average pooling. 6 by 6 by 4 feature map collapsing to a 1 by 1 by 4 vector. Progress 0 percent.
Customize
Playback
1800
Scrub (autoplay off)
0.00
Installation
npx shadcn@latest add https://craftbits.dev/r/gap-collapse-viz.jsonUsage
import { GAPCollapseViz } from "@craft-bits/core";
<GAPCollapseViz defaultPlaying />;Drive the collapse from outside — e.g. synced to a scroll step:
const [progress, setProgress] = useState(0);
<GAPCollapseViz
progress={progress}
onProgressChange={setProgress}
/>;Provide your own feature map (shape [H][W][C]):
const featureMap: number[][][] = /* H × W × C numbers */ [];
<GAPCollapseViz inputMap={featureMap} defaultPlaying playSpeed={2200} />;Understanding the component
- Three nested arrays.
inputMapis shaped[H][W][C]— height, then width, then channels. Every channel is a separateH × Wheatmap; the component renders one grid per channel, all stacked side by side, so the collapse happens in parallel. - Progress is the animation phase.
progress = 0shows the full spatial feature map;progress = 1shows each grid collapsed to a single bold scalar. The shrink uses an ease-in curve (t²) so the collapse feels accelerating, like a real reduction. - GAP is the channel mean. For each channel the component precomputes
mean(map[:, :, c])once. As cells shrink and slide toward their channel's centre, that scalar fades in — visually the cell soup becomes the scalar. - Per-channel magnitudes for the heatmap. Cell opacity is normalised by each channel's own
max |value|, so a near-empty channel reads as faint and a peaky channel reads as bright. The teaching point — that a strong-feature channel ends up with a high scalar and a weak-feature channel with a low one — survives without a global colour scale. - Default 6×6×4 fixture. When
inputMapis omitted, a deterministic feature map is synthesised with four channels that peak differently (left edge, centre blob, diagonal stripe, low background noise). Each channel's GAP average lands at a clearly different magnitude, so the collapsed output reads as four distinct numbers. - Controlled or uncontrolled progress. Pass
progress+onProgressChangeto drive from outside (e.g. synced to a scroll scrubber), or leave it uncontrolled and pair withplaying/defaultPlayingfor autoplay. requestAnimationFrameautoplay. Whenplaying, the component advancesprogresssmoothly via a RAF loop scaled toplaySpeedms per sweep, holds briefly at1for the collapsed reveal, then snaps back to0for the next loop.- Reduced-motion fallback.
prefers-reduced-motion: reducedisables autoplay automatically and replaces theSPRINGS.smoothmotion with aduration: 0snap.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
inputMap | readonly number[][][] | synthesised 6×6×4 | Feature map shaped [H][W][C]. |
progress | number | — | Controlled animation phase in [0, 1]. |
defaultProgress | number | 0 | Uncontrolled initial progress. |
onProgressChange | (progress: number) => void | — | Fires on every autoplay tick or scrub. |
playing | boolean | — | Controlled play state. Pair with onPlayingChange. |
defaultPlaying | boolean | false | Uncontrolled initial play state. |
onPlayingChange | (playing: boolean) => void | — | Fires when play / pause flips. |
playSpeed | number | 1800 | Milliseconds for one 0 → 1 sweep. |
transition | Transition | SPRINGS.smooth | Spring used for per-channel label transitions. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The figure is
role="figure"with anaria-labelledbyheading announcing the input shape (H × W × C → 1 × 1 × C) and anaria-live="polite"summary that announces the current progress and — once collapsed — the per-channel averages. - The
<svg>carriesrole="img"with a<title>describing the current progress percentage, so non-sighted users get a textual snapshot even without the surrounding heading. - Color is never the only signal — channel labels (
ch 1,ch 2, …) and the bold collapsed scalars are textual; the output band names the fact-sheet (C numbers · zero params). prefers-reduced-motion: reducedisables autoplay and replaces theSPRINGS.smoothmotion with an instant snap; users can still scrub through the collapse via the controlledprogressprop.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/nn/GAPCollapseViz.tsx). Stripped the lesson-specific phase-narration state machine, the bespoke 4×4×3 fixture with hand-coded channel names (Edge / Blob / Background), theChallengeBtncontrols, and the slider chrome — generalised to a pure GAP primitive that accepts an arbitraryH × W × Cfeature map and exposes controlled / uncontrolledprogress+playingso consumers can drive the collapse from scroll, a parent stepper, or a debug panel. Channel labels are now genericch N, opacity is per-channel normalised, and autoplay uses a RAF sweep instead of asetTimeoutladder.