Skip to content

fix(naive-bayes): GaussianNB var_smoothing = var_smoothing*max_var (sklearn parity) — F-GAUSSIANNB-EPSILON-003 (PMAT-890) - #2181

Merged
noahgift merged 1 commit into
mainfrom
fix/pmat-890-gaussiannb-var-smoothing
Jun 22, 2026
Merged

fix(naive-bayes): GaussianNB var_smoothing = var_smoothing*max_var (sklearn parity) — F-GAUSSIANNB-EPSILON-003 (PMAT-890)#2181
noahgift merged 1 commit into
mainfrom
fix/pmat-890-gaussiannb-var-smoothing

Conversation

@noahgift

Copy link
Copy Markdown
Contributor

PMAT-890 — Pillar-1 GaussianNB var_smoothing parity (F-GAUSSIANNB-EPSILON-003)

LIVE wrong-answer bug vs scikit-learn (the provably-correct reference).

Defect

GaussianNB::fit (in crates/aprender-core/src/classification/linear_svm.rs) added a raw additive var_smoothing (default 1e-9) directly to each per-class feature variance:

*variance_val = sum_sq_diff / n_class_samples + self.var_smoothing;

scikit-learn instead defines the smoothing as a single scalar scaled by the largest feature variance:

epsilon = var_smoothing * X.var(axis=0).max()

added uniformly to every per-class feature variance. On a mixed-scale dataset (one feature var ~3.25e6, another ~1e-4) the raw additive constant makes the smoothed variance for the small feature ~33.5× too small, mis-scaling the Gaussian log-likelihood / Mahalanobis term — able to flip predict.

Fix

Compute max_feature_var = max_j Var(X[:,j]) over all training rows (biased/population variance, matching the existing convention), set epsilon = var_smoothing * max_feature_var, add that single epsilon to every per-class feature variance. var_smoothing stays configurable (default 1e-9).

RED → GREEN evidence

Falsifier test_gaussian_nb_var_smoothing_scaled_by_max_feature_var (no Python/sklearn required; closed-form sklearn reference):

  • RED (main, buggy): stored=1.000009943e-4 vs sklearn_expected=3.350000000e-3 (epsilon=3.25e-3), rel_err=0.97 → FAIL
  • GREEN (fixed): passes; predict_proba rows finite and sum to ~1

Full crate tests

cargo test -p aprender-core --lib13955 passed; 0 failed; 2 ignored (no sibling test encoded the old raw-1e-9 behavior). cargo fmt --all --check clean, cargo clippy -p aprender-core -- -D warnings clean.

Contract

F-GAUSSIANNB-EPSILON-003 proof_obligation + single-line cargo test falsifier added to contracts/naive-bayes-v1.yaml. pv lint contracts/PASS (0 errors).

🤖 Generated with Claude Code

…klearn parity) — F-GAUSSIANNB-EPSILON-003 (PMAT-890)

GaussianNB::fit added a raw additive `var_smoothing` (1e-9) directly to each
per-class feature variance. scikit-learn instead defines the smoothing as a
single scalar `epsilon = var_smoothing * X.var(axis=0).max()` — `var_smoothing`
SCALED by the largest feature variance (over all training rows, biased/population
variance), added uniformly to every per-class feature variance.

On a mixed-scale dataset (one feature var ~3.25e6, another ~1e-4) the raw
additive constant makes the smoothed variance for the small feature ~33.5x too
small, mis-scaling the Gaussian log-likelihood / Mahalanobis term and able to
flip predict. This is a LIVE wrong-answer bug vs the provably-correct sklearn
reference.

Fix: compute max_feature_var = max_j Var(X[:,j]) over all training rows, set
epsilon = var_smoothing * max_feature_var, and add that single epsilon to every
per-class feature variance. `var_smoothing` stays configurable (default 1e-9).

- RED: test_gaussian_nb_var_smoothing_scaled_by_max_feature_var fails on main —
  stored=1.000009943e-4 vs sklearn_expected=3.350000000e-3 (rel_err=0.97).
- GREEN: passes after fix; predict_proba rows finite + sum to ~1.
- Full crate: 13955 passed; 0 failed (no sibling test encoded the old behavior).
- Contract: F-GAUSSIANNB-EPSILON-003 proof_obligation + single-line falsifier
  added to contracts/naive-bayes-v1.yaml; `pv lint contracts/` PASS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@noahgift
noahgift enabled auto-merge June 22, 2026 07:22
@noahgift
noahgift added this pull request to the merge queue Jun 22, 2026
Merged via the queue into main with commit e91d118 Jun 22, 2026
11 checks passed
@noahgift
noahgift deleted the fix/pmat-890-gaussiannb-var-smoothing branch June 22, 2026 08:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant