Skip to content

Releases: bbstats/chimeraboost

0.21.0

Choose a tag to compare

@bbstats bbstats released this 22 Jul 19:57

Changed

  • pandas is no longer a dependency. The categorical machinery (factorize, predict-time code mapping, gdiff group means) is now numpy/numba only, bit-identical to the pandas implementation (the gdiff group sums replicate pandas' Kahan-compensated groupby exactly). DataFrames still work as input — they are consumed through their own to_numpy/columns attributes. Installs pull four packages instead of five.
  • Bagged predict no longer redoes the categorical transform per member. Members factorize each categorical (and combo) column once per call through a shared cache and remap only the unique values through their own fit-time maps; the input conversion and validation also run once instead of once per member. Predictions are bit-identical. 50k rows, n_ensembles=8, 4 string categoricals: binary predict 1.21 s → 0.40 s, multiclass 1.78 s → 0.74 s, regression 1.35 s → 0.49 s; single-model predict is unchanged at big batches and ~9× faster at 1-row calls (0.19 ms vs 1.7 ms).

Fixed

  • Bagged members no longer keep their fit-time thread cap at predict. A parallel n_ensembles fit divides the thread budget across workers, but members retained that sliver permanently — an 8-member bag walked each forest on 1–2 threads. Members now get the parent's thread_count back after fitting. Bit-identical; bagged predict ~3–5× faster on an 8-core box (numeric 0.80 s → 0.24 s at 50k rows).

Full details in CHANGELOG.md.

0.20.0 — cross features for categorical columns

Choose a tag to compare

@bbstats bbstats released this 21 Jul 02:44

Group-centered categorical crosses. The cross-feature race now also auditions columns of the form value minus the average of that value within this row's category (salary minus the department's average salary), so "above this row's own group's baseline" becomes one split instead of a per-category staircase. Target-free (no leakage machinery), weight-aware, kept per dataset only when validation loss agrees. On the real high-cardinality suite: +0.25% mean, employee_salaries +2.5%; the headline Grinsztajn suite is bit-identical by construction; the independent OpenML check was flat.

Faster selection fits. Audition fits no longer compute a training-set loss nobody reads: predictions bit-identical, a 50k-row binary fit ~15% faster, Grinsztajn suite fit time ~3% lower (single model now 4.8× LightGBM, was 5.1×).

Full validation record: benchmarks/CATCROSS_PLAN.md (PR #24). TabArena-Lite re-read post-release: Elo 1278, rank 31/68 (was 1264, within CI).

0.19.0

Choose a tag to compare

@bbstats bbstats released this 20 Jul 23:33
919a80a

Added

  • With verbose=True, fit prints a notice when early_stopping=True silently holds out validation_fraction of the training rows as the validation set, so the effective training size is visible. Default verbose=False unchanged.

Changed

  • Classifier leaf_estimation_iterations default is now None (auto) instead of a concrete 3; it resolves to 3 and fits bit-identically. An A/B across the decision suites confirmed 3 is the right effective value (helps small/categorical binary, inert where linear leaves take over or for multiclass) — None just stops the API from advertising a refinement count that is dead in those regimes.

Fixed

  • sample_weight is now honored everywhere, not just in the gradient step: the ordered-target categorical encoder, the quantile bin borders, and the early-stopping validation metric on the auto-split (or bagged out-of-bag) holdout. Previously a weight-0 row still shaped encodings and bin edges and scored the early-stopping metric at full weight — up to an order-of-magnitude model degradation in an adversarial repro. sample_weight=None and uniform weights stay bitwise-identical to before. eval_set now accepts an optional third (X, y, sample_weight) element.
  • The inert-knob warnings for leaf_estimation_iterations now fire for any explicitly-set value > 1 that will be ignored on the path about to run; the auto default stays quiet.

v0.18.1

Choose a tag to compare

@bbstats bbstats released this 20 Jul 11:44

Maintenance release: audit-driven robustness fixes plus an early-stopping correctness fix. No default or accuracy changes on the benchmark suites — all fits stay benchmark-identical.

Fixed

  • Early stopping truncates to the best iteration when n_estimators runs out before patience fires. Previously every tree built past the best validation round was kept, contradicting the documented best_iteration_ contract (~5% validation RMSE cost on budget-exhausted fits in an adversarial repro). Patience stops, early-stopping-off, and callback stops are unchanged.
  • Thread hygiene: thread_count now applies to predict as well as fit, and the process-global numba thread setting is restored afterwards.
  • Pickles no longer carry the packed-forest predict cache (~2x smaller payloads); it rebuilds lazily on first predict. Loaded models predict bit-identically.
  • Loud failures replace silent misbehavior for unseen eval_set labels, class-stranding early-stopping splits, fit(X, y, w) positional misuse, and inert ordered_boosting/leaf_estimation_iterations settings.
  • cat_features accepts a numpy integer array; classifier depth=None resolves to the default 6; bagged members retain the parent's feature names.

Full details in the CHANGELOG.

0.18.0

Choose a tag to compare

@bbstats bbstats released this 18 Jul 19:00

Quantized-gradient histograms are now the default (quantize_gradients=True): the split search runs on ~15-bit quantized gradient/hessian pairs packed one-integer-per-sample into integer histograms (the LightGBM-4 quantized-training idea, adapted). Summed single-model fit time fell 26% on the Grinsztajn suite and 20% on the high-cardinality suite (bagged Ens8 arm likewise -19%) at benchmark-flat accuracy; leaf values are always computed from the unquantized float gradients. Deterministic per random_state; quantize_gradients=False restores exact float64 histograms. Also: tree growth now runs one fused kernel launch per level (bit-identical; biggest on small fits). Full changelog in CHANGELOG.md.

0.17.0 — multiclass cross features

Choose a tag to compare

@bbstats bbstats released this 18 Jul 01:19

Cross features now cover multiclass classification: the same validation-selected difference/product columns regression and binary get (top numeric pairs of the base fit, raced audition at selection_rounds, judged on softmax validation log loss). Auto-on under the same gates (>= 2000 rows, >= 2 numeric features); explicit cross_features=True no longer raises on multiclass. Regression and binary paths are bit-identical to 0.16.1.

Full experiment record: benchmarks/M1_PLAN.md.

0.16.1

Choose a tag to compare

@bbstats bbstats released this 17 Jul 20:14

Faster fits at identical output: internal model selection no longer recomputes preprocessing. The variant auditions, the cross-feature candidate, and the winner refit inside one fit previously each reran the full preprocessing pipeline (target encoding, quantile borders, binning); they now share it. Models are bit-identical to 0.16.0 (verified by numerical-identity goldens and exact prediction ties on all 73 benchmark datasets). Biggest on categorical-heavy data (17-32% faster single-model fits measured there); bagged ensembles save it in every member. See CHANGELOG.md.

0.16.0

Choose a tag to compare

@bbstats bbstats released this 17 Jul 18:42

Bagging program — the opt-in n_ensembles mode is now a tuned, first-class accuracy mode.

Changed

  • Members train on 80% row subsamples without replacement (new max_samples, default 0.8; 1.0 restores the classic bootstrap). A bootstrap gives a member only ~63% unique rows at full compute; 80% without replacement is more effective data and less work. Grinsztajn 54W-5L +0.94% with a 23-0 Brier sweep at 0.87x fit; high-card Brier 8-0 at 0.73x.
  • Tuned bagged-member defaults: inside a bag, learning_rate None → 0.15 and colsample None → 0.85 (single-model resolution unchanged). Announced at fit; recorded in member_params_; explicit values always win. Recommended size n_ensembles=8 (stronger than 5 at similar cost; 2 remains anti-recommended).
  • Members fit in parallel by default (ensemble_n_jobs 1 → -1): workers split the thread budget, so a bagged fit uses the same cores a single fit would. Models identical to sequential (verified on 73 datasets); 1.2–2x faster wall-clock.

Fixed

  • ensemble_n_jobs=-1 previously gave every worker the full thread budget (oversubscribing); the budget is now divided across workers.

The single-model default is unchanged.

0.15.0 — faster fits: raced selection auditions

Choose a tag to compare

@bbstats bbstats released this 16 Jul 21:01

Highlights

  • selection_rounds (new, default 100): ~1.5x faster fits at the same accuracy. The internal selection fits (constant/linear leaf variants and the pre-cross-features base fit) now run as capped auditions judged on their best validation loss within a shared budget; only the winner continues to full early stopping. An audition that early-stops before the cap is reused as the finished model — that path is bit-identical to the previous behavior. selection_rounds=None restores the old run-everything-to-full-early-stopping selection.
  • Grinsztajn benchmark suite: fit time 351→235 s with accuracy columns flat; headline slowdown vs the fastest competitor improves 7.9x → 6.0x at unchanged blended strength (see images/pareto.png).
  • TabArena-Lite (report-only holdout): Elo 1256 (+54/−57), statistically unchanged, on a fresh 51-task run.
  • Measured trade-off, documented in the CHANGELOG: on a minority of regression datasets the 100-round audition can pick the leaf variant a full run would have rejected (typically 0.5–1.5% there).

Full details in CHANGELOG.md.

0.14.2 — predict 1.35–1.63× faster, bit-identical

Choose a tag to compare

@bbstats bbstats released this 14 Jul 00:46
0d0fa1d

Cuts the 0.14.2 release. The diff here is just the version bump and CHANGELOG
date stamp -the changes being released are already on main (merge a5810fa).

What's in this release

Predict is 1.35–1.63× faster end-to-end, with predictions unchanged to the
last bit. Two changes:

  1. Row-major fused predict kernels (_predict_forest_rm,
    _predict_forest_linear_rm) — predict consumes the binner's row-major
    output directly, so each sample's bins sit in one or two cache lines for
    the whole forest walk and the per-predict feature-major transpose copy is
    gone. Fit-side kernels keep the feature-major layout (histograms want it);
    SHAP keeps the feature-major kernels for its background walk.
  2. No more whole-matrix copy for all-numeric input
    FeaturePreprocessor extracted the numeric block as
    X[:, num_features_] even when every column is numeric, a full
    fancy-index copy on each transform (~18% of end-to-end predict on large
    batches). Plain asarray now covers that case; mixed/categorical inputs
    keep the gather.

Numbers (fit 200k / predict 2M×30 / 200 trees / 12 threads, min-of-5)

config 0.14.1 0.14.2 speedup
classifier, linear leaves (default) 2.140 s 1.588 s 1.35×
classifier, constant leaves 1.244 s 0.764 s 1.63×
regressor 1.236 s 0.757 s 1.63×

Against the field on the same benchmark (benchmarks/scaling_predict.py):
default binary predict is now 1.26 Mrows/s — 1.30× LightGBM, 3.0×
sklearn-HGB, 0.50× XGBoost; constant-leaf paths reach ~2.6 Mrows/s, on par
with XGBoost. CatBoost's SIMD-fused C++ inference remains ~10× faster.

Why "bit-identical" is safe to claim

  • Exact-equality kernel test (test_rowmajor_predict_kernels_match_feature_major_exactly):
    row-major vs feature-major kernels compared with np.array_equal across
    depth-0 trees, constant and linear leaf blocks, NaN center bins, n ∈ {0, 1, many}.
  • Regenerated golden baseline changed only machine-relative timing ratios —
    every accuracy metric field is byte-identical.
  • Full suite: 378 passed. warmup() compiles the new kernels automatically
    (same numba argument types), so the 0.14.1 fresh-worker JIT fix carries over.

After merging

  • git tag v0.14.2 && git push origin v0.14.2
  • build + upload to PyPI

🤖 Generated with Claude Code