See the NN-Revival series page for methodology, scope, and other entries. Note #1 (Weightless Neural Networks on Modern Compute) frames the hypothesis this note tests.
Abstract
Note #1 found that vanilla WiSARD degrades past its capacity knee — at N=10k accuracy falls (0.877 → 0.847) — and framed the fix as sparse, specialized addressing. CMAC (Albus, 1975) is that fix in its original form: an input activates a small fixed number C of memory cells via overlapping quantization tilings; the output is their sum; learning is a local delta rule on only those C cells, O(C) per example, no backprop. We test it faithfully in its native domain — low-dimensional function approximation and open-loop control — with a labeled, out-of-domain MNIST comparability arm, under the same protocol as #1: three seeds, natural budgets, predictions pre-registered and frozen before runs.
The headline holds within the range we tested: across C from 4 to 256, adding capacity never degraded CMAC's quality — it improved and then plateaued (0.0379 → 0.0113 RMSE), where WiSARD's N-scaling actively reverses at N=10k. We did not find a saturation cliff on CMAC's capacity axis; we do not claim none exists beyond what we swept. CMAC's one sharp failure — over-fine tiles for the data budget — is undersampling, not interference: it recovers fully with more data (0.123 → 0.0036 as n grows 4k → 32k), a failure mode WiSARD's intrinsic cliff does not share. The costs are honest: sample efficiency is local, so a small MLP wins the ultra-low-data regime and CMAC only crosses ahead once the domain is covered (n ≥ 1000 on both native tasks); and the memory footprint is large (~130× the MLP's parameters at the collision floor). Online, CMAC tracks a shifted target cheaply and without replay (0.190 vs its own frozen 0.754). This is a toy-scale study; on 784-d MNIST, out of CMAC's native domain, a plain MLP wins (0.972 vs 0.950). Not a SOTA claim.
Why
Note #1 left an explicit hypothesis on the table: WiSARD saturates because every input touches the same fixed bank of RAMs at scale, and a sparse/specialized addressing scheme — routing each input to a small, input-dependent subset of cells — might avoid it. CMAC is the cleanest 1975-vintage instance of exactly that idea, and it was built for a concrete job: low-dimensional function approximation and robot control. So #2 is not a fresh survey; it is the direct empirical follow-through on #1's predicted fix, tested in the domain CMAC was actually designed for.
We do not claim a new architecture. CMAC is prior art. The tiling/hashing/local-delta machinery is Albus's. Our contribution is the honest, controlled measurement: does the sparse-addressing story hold, and at what cost?
Scope
This series re-examines architectures in their original form under modern controls. The faithful anchor must be in the architecture's native domain. CMAC's native domain is low-dimensional continuous function approximation and control — so the faithful arms are a 2-D nonlinear surface and an open-loop 2-link inverse-kinematics map. MNIST (784-d binary) is not native to CMAC; it appears only as a labeled, secondary comparability arm using a hashing adaptation, carried so the series has a common yardstick against #1's WiSARD and MLP. Rigor and defensibility concentrate on the native anchors; the MNIST arm is explicitly out-of-domain and is not where CMAC is meant to win.
Setup
Faithful CMAC (native arms). C
overlapping quantization tilings on the continuous input; tiling
t offset by (t/C) · tile_width on every
dimension. Each input falls in exactly one tile per tiling
→ C active cells. Tile coordinates are hashed
into a bounded table for memory only. Output = sum of the C
active weight vectors; update:
w_a += (η/C) · (target − output) on those C
cells. Natural native budget: C=32, tile_width=0.05,
table=8192, η=0.35. No bit-hashing is used on the native tasks.
MNIST arm (out-of-domain, labeled). A hashing-CMAC: C fixed random bit-subsets of the 784-d input, each FNV-hashed to a bounded table → C active cells → sum → local delta. This is a sparse-code-by-construction adaptation, not faithful tiling; it carries no native-domain claim.
Baselines. A small MLP (native: one hidden layer
h=64, lr=0.05, plain Rust, no framework). MNIST
comparability additionally carries wisard and
mlp from #1, run here under the same protocol; the
MNIST wisard uses #1's bleach-threshold search (a
no-bleach variant was archived and is not cited —
it collapses full-data WiSARD to 0.671, which is a broken
configuration, not a result).
Protocol. Seeds {0, 1, 2}; mean ± range with
range = (max − min)/2. Native runs use early-stop on validation
RMSE (patience 10, max 80 epochs) and report
best-val, not final epoch. Predictions written in
SPEC.md before the runs and never edited afterward
— outcomes append only.
Faithfulness gate. Before any native task ran, a geometric probe confirmed local generalization emerges from the tiling (below). This is the check that the native arm is faithful CMAC and not random hashing wearing tiling's name.
Results
All figures below are generated from frozen artifacts:
results/stage2_full_frozen/,
results/stage3_mnist_frozen/,
results/stage4_full_frozen/,
results/stage4_n4_control_frozen/.
Faithfulness gate — local generalization (pre-hash tile overlap)
Two inputs at Euclidean distance d; mean shared active tiles out of C=16, measured on tile coordinates (pre-hash) so hash collisions cannot fake sharing.
| distance | dist / tile_w | mean shared / 16 |
|---|---|---|
| 0.000 | 0.0 | 16.00 |
| 0.010 | 0.1 | 13.92 |
| 0.025 | 0.25 | 11.08 |
| 0.050 | 0.5 | 7.23 |
| 0.090 | 0.9 | 1.86 |
| 0.100 | 1.0 | 0.83 |
| ≥0.150 | ≥1.5 | 0.00 |
Monotone falloff, hitting zero by ~1.5 tile widths. Values sit just below the 1-D stagger line C · max(0, 1 − d/w) because in 2-D a tile is shared only when both axes stay in-tile — the correct direction for the discrepancy. Local generalization is real and tile-width-scaled. Gate: PASS.
Q1 — native sample-efficiency (held-out RMSE, 3 seeds)
| task | arm | n=50 | n=200 | n=1000 | n=4000 | params | active |
|---|---|---|---|---|---|---|---|
| fn_approx | cmac | 0.4502±0.0036 | 0.3442±0.0121 | 0.1262±0.0032 | 0.0175±0.0047 | 262,144 | 32 |
| fn_approx | mlp | 0.4876±0.0043 | 0.3778±0.0718 | 0.1550±0.0058 | 0.1465±0.0004 | 257 | — |
| ik | cmac | 1.5635±0.0112 | 1.1420±0.0173 | 0.3978±0.0360 | 0.1697±0.0222 | 524,288 | 32 |
| ik | mlp | 0.8079±0.0396 | 0.5901±0.0439 | 0.4981±0.0849 | 0.3705±0.0343 | 322 | — |
The crossover is real and is not smoothed. On IK, the MLP wins the low-data regime outright (n=50, 200) and CMAC crosses ahead only at n ≥ 1000. On fn_approx the mean shows CMAC ahead throughout, but at n=200 the per-seed picture is mixed — the MLP wins seeds 0 and 1, and its large range (±0.0718) reflects a single-seed failure that flatters CMAC's mean. The honest, defensible crossover for both native tasks is n ≥ 1000. Once the domain is covered, CMAC pulls far ahead: fn_approx reaches RMSE 0.0175 on a signal of scale ≈0.4, and IK reaches 0.170 rad (≈9.7°). Active cells are exactly C=32 on every row.
Q2 — capacity (held-out RMSE, native fn_approx, n=4000)
| C | tile_w | test RMSE | active | params |
|---|---|---|---|---|
| 8 | 0.100 | 0.02197±0.00025 | 8 | 131,072 |
| 16 | 0.100 | 0.01472±0.00027 | 16 | 262,144 |
| 32 | 0.100 | 0.01213±0.00024 | 32 | 524,288 |
| 32 | 0.050 | 0.01701±0.00331 | 32 | 524,288 |
| 64 | 0.050 | 0.01700±0.00328 | 64 | 1,048,576 |
| 64 | 0.025 | 0.12317±0.00139 | 64 | 1,048,576 |
| 128 | 0.025 | 0.12305±0.00118 | 128 | 2,097,152 |
#1 WiSARD N-scaling, on the same "add
capacity → watch quality" axis (accuracy, cited from
ramnet-study):
| N | acc |
|---|---|
| 100 | 0.8477±0.0064 |
| 500 | 0.8713±0.0013 |
| 1000 | 0.8769±0.0002 |
| 5000 | 0.8759±0.0039 |
| 10000 | 0.8474±0.0051 |
Along the C axis at fixed width, adding capacity never degrades CMAC — it improves and then plateaus. WiSARD, on its capacity axis, degrades past the knee: N=10k falls back to N=100 accuracy. But note the two rows where CMAC does cliff — width 0.025 jumps to 0.123. That is not a capacity-interference cliff; the control below shows it is coverage.
Q2 control — the fine-tile cliff is coverage, not saturation
(A) Fine tiles recover with data (C=64, tile_w=0.025):
| n_train | test RMSE |
|---|---|
| 500 | 0.38283±0.00104 |
| 1000 | 0.31557±0.00656 |
| 2000 | 0.21439±0.00842 |
| 4000 | 0.11783±0.01302 |
| 8000 | 0.04031±0.00354 |
| 16000 | 0.01350±0.00200 |
| 32000 | 0.00356±0.00038 |
(B) Adding tilings never hurts (fixed tile_w=0.10, n=4000):
| C | test RMSE |
|---|---|
| 4 | 0.03786±0.00124 |
| 8 | 0.02234±0.00065 |
| 16 | 0.01488±0.00033 |
| 32 | 0.01237±0.00035 |
| 64 | 0.01166±0.00040 |
| 128 | 0.01135±0.00033 |
| 256 | 0.01132±0.00035 |
The fine-tile cliff at width 0.025 fully recovers as data grows (0.118 at n=4k → 0.0036 at n=32k) — it is the same local-generalization coverage limit as Q1's low-n regime: finer tiles cover less domain per tile, so they need more samples to fill. Cure it with data and it vanishes. WiSARD's N=10k degradation does not cure with data — it is intrinsic interference. This is the study's central result: across the capacity range we swept, sparse tiled addressing shows no interference-saturation. Adding capacity never cost quality; the only sharp failure was orthogonal (undersampling) and data-curable.
Q3 — hash collisions (native fn_approx, C=32, n=4000)
| table_size | test RMSE | params |
|---|---|---|
| 64 | 0.39272±0.00417 | 2,048 |
| 128 | 0.35990±0.00419 | 4,096 |
| 256 | 0.24533±0.00244 | 8,192 |
| 512 | 0.18649±0.00217 | 16,384 |
| 1024 | 0.01310±0.00151 | 32,768 |
| 2048 | 0.01310±0.00151 | 65,536 |
| 4096 | 0.01310±0.00151 | 131,072 |
| 8192 | 0.01310±0.00151 | 262,144 |
| 16384 | 0.01310±0.00151 | 524,288 |
Collisions bite hard below 1024 bins/tiling and vanish at/above it — flat through 16k. The collision floor sits at ~33k parameters (1024 × C). The natural-budget table used in Q1 (262k params) was ~8× larger than needed. So CMAC's real memory penalty on this task is ~130× the MLP's 257 floats, not the ~1000× the naive budget implied — still a large footprint, but the honest number.
Q4 — online adaptation (native, RMSE across a mid-stream target shift)
| arm | pre | mid (post-shift) | post-online | post-frozen |
|---|---|---|---|---|
| cmac | 0.0149±0.0010 | 0.3835±0.0124 | 0.1904±0.0191 | 0.7540±0.0116 |
| mlp | 0.1577±0.0028 | 0.3192±0.0475 | 0.3021±0.0203 | 0.7450±0.0184 |
The target is shifted mid-stream; both learners then adapt online. CMAC recovers to 0.190 via cheap local updates (O(C), no replay) versus its own frozen-weights control at 0.754. The MLP also adapts (0.302 vs its frozen 0.745) — this is not "only CMAC can" — but CMAC adapts further and more cheaply. Online tracking with local writes is a genuine CMAC edge, not a unique capability.
MNIST comparability (out-of-domain, labeled)
| arm | k=1 | k=5 | k=10 | k=50 | k=100 | full | params |
|---|---|---|---|---|---|---|---|
| hash_cmac | 0.3552±0.0121 | 0.5872±0.0089 | 0.7002±0.0183 | 0.8370±0.0068 | 0.8731±0.0024 | 0.9496±0.0012 | 5,242,880 |
| wisard | 0.3979±0.0134 | 0.6614±0.0219 | 0.7706±0.0277 | 0.8629±0.0070 | 0.8696±0.0036 | 0.8749±0.0017 | 0 |
| mlp | 0.3741±0.0328 | 0.6630±0.0030 | 0.7517±0.0249 | 0.8526±0.0091 | 0.8739±0.0025 | 0.9718±0.0013 | 50,890 |
Off native turf, the MLP wins full-data (0.972), hash-CMAC is second (0.950), and bleached WiSARD trails at full data (0.875) — but WiSARD leads the few-shot regime (k=5, k=10). Hash-CMAC's footprint here is large (5.2M) and it is a sparse random code, not a tiling.
Scorecard (pre-registered → outcome; predictions never edited)
Native anchors (the faithful priority):
| # | Prediction | Verdict |
|---|---|---|
| N1 | Approximates native tasks well; competitive with a small MLP | SUPPORTED — beats MLP at n ≥ 1000 on both tasks; low absolute error (fn 0.0175; IK 0.170 rad at n=4000). |
| N2 | Sample-efficient (low error from few samples) | PARTIAL MISS — contradicted below coverage: MLP wins ultra-low-n (IK n=50/200; fn n=200 per-seed mixed). Real finding: generalization is local, so CMAC crosses ahead only once the domain is covered (n ≥ 1000). |
| N3 | Online adaptation tracks a changing function via cheap local updates | SUPPORTED — recovers to 0.190 post-shift (vs frozen 0.754), O(C), no replay. Caveat: the MLP also adapts (0.302), just less and dearer. |
| N4 | Capacity scaling shows a softer plateau than WiSARD | SUPPORTED — adding C never degrades (0.0379 → 0.0113, monotone), where WiSARD's N=10k reverses. The one sharp CMAC failure (over-fine tiles) is coverage, not interference: it fully recovers with data (0.123 → 0.0036). |
| N5 | Active cells per example stays O(C) regardless of capacity | SUPPORTED (exact) — active ≡ C on every configuration. |
MNIST comparability (secondary, out-of-domain):
| # | Prediction | Verdict |
|---|---|---|
| M1 | CMAC < MLP on MNIST (out of native domain) | TRUE — 0.9496 < 0.9718. |
| M2 | Local generalization weakened on 784-d → behaves like a random sparse code | CONSISTENT, NOT INDEPENDENTLY TESTED — the MNIST arm is a hashing sparse-code by construction (no tiling), so it cannot lose a local generalization it never had. Consistent with M2, but not evidence for it. |
| M3 | vs WiSARD: sparse-tiled vs n-tuple addressing — direction open | RESOLVED (direction): low-data → WiSARD (k=5: 0.661 vs 0.587; k=10: 0.771 vs 0.700); full-data → MLP > hash-CMAC > WiSARD (0.972 > 0.950 > 0.875). |
Predictions that came out a PARTIAL MISS (N2) or not independently testable (M2) are left in the scorecard as written. No retcon.
What it's good for
CMAC earns its keep exactly where 1975 aimed it:
- Low-dimensional function approximation and control, where the input space can actually be covered — then local generalization gives very low error at O(C) update cost.
- Online / nonstationary settings: track a drifting target with cheap local writes, no backprop, no replay buffer.
- Capacity you can add safely: unlike WiSARD, more tilings never hurt quality — you tune C and width for coverage, not to dodge a saturation cliff.
It is not for high-dimensional perception. On 784-d MNIST it needs a hashing adaptation, loses its smooth-interpolation story, carries a huge table, and still loses to a small MLP. Curse of dimensionality is the boundary of its native domain, and it shows.
Limitations
- Toy scale, small baselines. The MLP baselines are one-hidden-layer nets; "beats the MLP" means at matched small scale, not versus a modern deep net.
- Native tasks are synthetic. A fixed 2-D surface and an open-loop 2-link IK map — chosen to be genuinely native, not to be hard. Open-loop only; no closed-loop dynamics.
- Memory footprint is large. Even at the collision floor (~33k params) CMAC is ~130× the native MLP's parameter count. Its efficiency claim is compute per example (O(C), ~1 µs/update), not memory.
- Sample efficiency is local, not global. The headline N2 caveat: CMAC needs the domain covered; it is not a few-shot learner in low dimensions the way the pre-registration guessed.
- MNIST arm is out-of-domain by construction. Hashing-CMAC there is a labeled comparability stretch, not a faithful measurement.
Series note
Note #2 closes the loop #1 opened: the sparse/specialized addressing #1 predicted as the fix for WiSARD's N-scaling saturation does remove the interference cliff, at the cost of a large table and a locality constraint on sample efficiency. Banked for a later hybrid-CMAC rethink is prediction P7 — that a static+plastic CMAC will show the same readout-domination #1's hybrid did, the plastic channel over-saturating the shared sum unless explicitly balanced. The shared harness across #1 and #2 will be extracted now that two entries exist to reveal what is actually common (not before). Next in the backlog: Cascade-Correlation (#3) — a different mechanism (grow-on-demand structure) against the same adaptive-capacity thesis.
Reproducibility
- Code: github.com/Vulkgryph/cmac-study — package
cmac-studyunder NN-Revival. - Spec:
SPEC.md— predictions frozen 2026-08-12 before the runs; outcomes append-only. - Frozen results (do not overwrite):
results/stage2_full_frozen/— Q1 native sample-efficiency + local-gen probe, 3 seedsresults/stage3_mnist_frozen/— MNIST comparability (bleached WiSARD), 3 seedsresults/stage4_full_frozen/— Q2 capacity / Q3 collisions / Q4 onlineresults/stage4_n4_control_frozen/— the coverage-vs-saturation control
SHA-256 of frozen JSON (verify with the command below):
| File | SHA-256 |
|---|---|
stage2_full_frozen/summary.json | d41c756fbe19b22706c639b8e6f0a28ac9ade27be424e100d877a5bbde6b3603 |
stage2_full_frozen/local_gen_probe.json | 8ca8b474bbad7ad034a86ff56503d4f7868cb20ee889cbcc19292cc6b434d698 |
stage3_mnist_frozen/summary.json | 1c818cbb49c635b9d3451c7d078f9bf889b57251970d0c93a8aa26646f2a51c8 |
stage4_full_frozen/summary.json | ab7fe382f4ff0ac72f4dc6624d1dd2419268d6ac3cc1bfad5866a8da4904acd1 |
stage4_n4_control_frozen/summary.json | a211d0ef4a2d3beffbc200e6d7ca179506d1215115c25e83daa59991a8bfe2a0 |
shasum -a 256 \
cmac-study/results/stage2_full_frozen/summary.json \
cmac-study/results/stage2_full_frozen/local_gen_probe.json \
cmac-study/results/stage3_mnist_frozen/summary.json \
cmac-study/results/stage4_full_frozen/summary.json \
cmac-study/results/stage4_n4_control_frozen/summary.json
Tables: every figure above is generated from the
frozen JSON by cmac-study/gen_tables.py →
results/GENERATED_TABLES.md. No result numbers are
hand-typed.