Skip to content

v5.5.1 — Categorical Domain Extension

Latest

Choose a tag to compare

@charles-monce charles-monce released this 03 Jun 16:12

Snake's domain extension gains a third family — and the routing rule that makes it fire on the data that broke v5.5.0.

The problem that demanded it

Point Snake at a number-theory regression — predict Ω(n), the count of prime factors of n with multiplicity, from cheap surface features (digit stats, small-modulus residues n mod 2, 3, 5, …) — and v5.5.0 domain extension hurt: the expansion delta went negative. The signal is real (Erdős–Kac) but it lives almost entirely in divisibilityn mod 2 == 0 already guarantees a factor of 2. A residue's meaning is membership, not magnitude, so routing it through GAUSSIAN's position KPIs (z / density / cdf) was pure noise that crowded the raw signal.

What's new

CATEGORICAL family (pure Python, zero dependencies):

  • Cardinality is the router. A column (numeric or text) with 2 ≤ n_unique ≤ 100 is categorical, not continuous. It one-hots each recurrent value into a col==v 0/1 column — an exact-match bit Snake reads with a single literal, no stochastic threshold to miss.
  • Flood-filter parity with TF-IDF. A value must recur (count ≥ min_df, the same floor TOKENSET uses) to be a candidate — one-hotting singletons would memorize row IDs. Flood every recurrent value, then Shannon MI keeps the global top-10 across all sources.
  • Names surface verbatim in the auditmod2==0, status==refurb.

GAUSSIAN normality gate. GAUSSIAN now only fires on a high-card numeric whose empirical CDF agrees with the fitted normal to ≥ 0.95 (a KS-style score; N(0,1)→0.985, U(0,1)→0.940). Non-normal columns get no position KPIs instead of injecting noise.

Global per-family MI cut. The MI gate pools all candidates of a family across every source and keeps the global top-10, instead of top-K per source — fixing a multi-source dilution regression.

MI binning fix. Low-cardinality numerics (incl. one-hot 0/1 columns) now bin by exact value when they recur. The old quantile cut collapsed a 2-value column into one bin and reported MI=0 — silently starving every low-card numeric.

The result

On the prime-factor benchmark (benchmark_categorical.py, 4000 train / 1000 test, 15 layers, seed 42, identical features to RF/GB):

model MAE
baseline (predict mean) −0.003
Snake expand=off 0.345 1.113
Snake expand="auto" 0.388 1.074
RandomForest 0.488 1.015
GradientBoosting 0.530 0.967

Expansion delta flips −0.006 → +0.043 R²: CATEGORICAL turns the family that hurt into one that helps, on the exact problem that exposed the gap. The lesson generalizes past math: expansion helps when the family matches the column's structure (membership) rather than its statistics (position).

Compatibility first — the bar held seamlessly

  • v5.4.8 models load intact (expansions=[], perfect-fit preserved — verified against genuine v5.4.8-trained JSON)
  • v5.5.1 to_json() round-trips to an identical object (0 prediction mismatches over 250 rows, expansions identical)
  • 10 top-level to_json keys unchanged; expand=False byte-exact v5.4.8
  • 346 tests (+10 CATEGORICAL), zero regressions