Skip to content

Cleanup and apply new validation to cuml.naive_bayes - #8051

Merged
rapids-bot[bot] merged 6 commits into
NVIDIA:mainfrom
jcrist:validation-naive-bayes
May 6, 2026
Merged

Cleanup and apply new validation to cuml.naive_bayes#8051
rapids-bot[bot] merged 6 commits into
NVIDIA:mainfrom
jcrist:validation-naive-bayes

Conversation

@jcrist

@jcrist jcrist commented May 6, 2026

Copy link
Copy Markdown
Member

This is a general cleanup of cuml.naive_bayes, as well as applying the new validation utilities.

This:

  • Ports cuml.naive_bayes to use the new validation utilities, fixing many xfails.
  • Adds support for returning non-numeric classes from all classifiers, mirroring our support in our other classifiers.
  • Moves the classes_ attribute to be a numpy array (a breaking change), mirroring how it's handled on every other classifier.
  • Simplifies the control flow and definitions, greatly reducing the quantity of code in this module.
  • Raises a NotImplementedError if sample_weight is provided to BernouilliNB/CategoricalNB/MultinomialNB/ComplementNB. This argument was never supported, but would previously be silently ignored (leading to incorrect results!). Viewing this as a bug, I added an error so users wouldn't mistakenly think their weights were being applied.
  • Deprecates sample_weight to BernoulliNB/CategoricalNB/MultinomialNB/ComplementNB. Given the above, it doesn't make sense to keep around a parameter that we don't actually support (and silently ignoring can give wrong results). This deprecates it in 26.06, slated for full removal in 26.08. We warn if any value is passed via sample_weight (even supported things like None), and only error for unsupported things like arrays of weights.

To accomplish this, I also added support for specifying the classes to use for label encoding explicitly to check_y/check_inputs via the return_classes keyword. This may optionally take a numpy array (or list of numpy arrays) in the same format as those returned from check_y. If provided, these will be used for label encoding instead of deriving the classes from y itself. If any unknown classes are found in the data, a useful error will be raised. This is useful for classifiers that support partial_fit (like those in naive_bayes) where you want to consistently label encode y across multiple partial_fit calls.

Fixes #8000.
Fixes #6228.

@jcrist jcrist self-assigned this May 6, 2026
@jcrist
jcrist requested a review from a team as a code owner May 6, 2026 01:56
@jcrist
jcrist requested a review from csadorf May 6, 2026 01:56
@jcrist jcrist added improvement Improvement / enhancement to an existing function breaking Breaking change sklearn-api-compat Issues around cuml matching sklearn API conventions/standards labels May 6, 2026
@github-actions github-actions Bot added the Cython / Python Cython or Python issue label May 6, 2026
@coderabbitai

coderabbitai Bot commented May 6, 2026

Copy link
Copy Markdown

Caution

Review failed

Failed to post review comments

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Improved classification-target validation and encoding with explicit class-return options.
    • Predictions and probability outputs now preserve input indices across many classifiers.
  • Refactor

    • Major rework of Naive Bayes internals for more reliable counting, validation, and probability/prediction flows, including stronger categorical/discrete handling.
  • Tests

    • Expanded Naive Bayes tests and compatibility mappings.
    • Added tests for return_classes, multi-output labels, and sample_weight deprecation/unsupported behavior.

Walkthrough

Adds public check_classification_targets and extends check_y/check_inputs to support explicit class returns (single- and multi-output) and index propagation. Refactors Naive Bayes into a shared _BaseNB with unified counting, prediction/probability plumbing, and extensive categorical/sparse counting changes. Tests and sklearn-compat mappings updated.

Changes

Validation + Naive Bayes refactor

Layer / File(s) Summary
Validation: API / Data shape
python/cuml/cuml/internals/validation.py
Added public check_classification_targets(y) and exported it in __all__. Extended check_y() docstring/semantics: return_classes can be bool, np.ndarray, or list[np.ndarray] to support single- and multi-output class specification.
Validation: Encoding implementation
python/cuml/cuml/internals/validation.py
Introduced internal _encode(y, classes=None) used by check_y() to encode labels into integer codes and to return class arrays; handles numpy and cudf backends and validates discrete classification targets via check_classification_targets.
Validation: Integration / outputs
python/cuml/cuml/internals/validation.py
check_y() multi-column encoding path added; constructs final encoded cupy array with consistent dtype/order. check_inputs() updated to unpack/append classes and to propagate index when requested.
Label decoding: index propagation
python/cuml/cuml/common/classification.py
decode_labels(..., index=None) signature added; outputs (CumlArray/cudf Series/DataFrame) now attach the provided index when present.
Caller updates: index-aware decoding
python/cuml/cuml/*
randomforestclassifier.py, fil/fil.pyx, logistic_regression.py, mbsgd_classifier.py, multiclass/multiclass.py, kneighbors_classifier.pyx, linear_svc.py, svc.py
Multiple estimators refactored to capture an index from intermediate results, defer to_output conversions until after index extraction, and pass index into decode_labels/CumlArray returns to preserve input alignment.
Naive Bayes: Core design
python/cuml/cuml/naive_bayes/naive_bayes.py
Added _count_classes(y, n_classes, dtype) and new _BaseNB centralizing common NB logic and validation. Unifies predict() and predict_log_proba() to use internal contexts and decode_labels.
Naive Bayes: Variant logic / counting
python/cuml/cuml/naive_bayes/naive_bayes.py
GaussianNB _partial_fit signature expanded (classes, reset, convert_dtype) and online mean/variance updates refactored. Multinomial/Bernoulli/Complement/Categorical NB updated with static tags, _transform_X hooks, feature_all_ computation, and dense/sparse categorical counting plus joint log-likelihood paths.
Tests / Compatibility
python/cuml/tests/*
Added tests for explicit return_classes (single/multi-output, numpy/cudf). Updated base tests to include NB variants and index-preservation assertions. Added test_sample_weight_deprecated for discrete NB sample_weight behavior. Condensed/updated XFAIL mappings in sklearn compatibility tests for NB estimators.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • rapidsai/cuml#8024: Coordinated changes to the shared input/validation framework and check_inputs/check_y semantics.
  • rapidsai/cuml#7978: Modifies input-validation and target-encoding plumbing similar to this PR.
  • rapidsai/cuml#8029: Applies the new validation utilities (check_inputs/check_y) to SVM paths and touches the same label-encoding/index propagation flow.

Suggested reviewers

  • csadorf
  • dantegd
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Cleanup and apply new validation to cuml.naive_bayes' directly and clearly summarizes the main changes in the changeset, which involves refactoring naive_bayes module to use new validation utilities and reorganizing its structure.
Description check ✅ Passed The PR description comprehensively explains the objectives and changes, covering validation utilities, non-numeric class support, classes_ attribute changes, code simplification, sample_weight deprecation, and new return_classes functionality.
Linked Issues check ✅ Passed The PR addresses both linked issues: #8000 (update naive_bayes to new input validation) and #6228 (fix MultinomialNB predict/fit_predict failure) by porting to new validation utilities and fixing label decoding across multiple classifiers.
Out of Scope Changes check ✅ Passed All changes are within scope: naive_bayes refactoring, validation module updates, test additions, and supporting changes to decode_labels and other classifiers to propagate index information for proper label decoding.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
python/cuml/cuml/naive_bayes/naive_bayes.py (1)

1296-1315: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard _count_sparse() for all-zero sparse batches.

x_coo_data.max() raises on nnz == 0, so CategoricalNB.fit/partial_fit will crash on a valid sparse batch whose entries are all zero. For this estimator, zero is a legitimate category and should be handled the same way as the dense path.

Proposed fix
-        highest_feature = int(x_coo_data.max()) + 1
+        highest_feature = (
+            int(x_coo_data.max()) + 1 if x_coo_data.size else 1
+        )

As per coding guidelines, behavior for edge cases (empty arrays, single sample) must match scikit-learn.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cuml/cuml/naive_bayes/naive_bayes.py` around lines 1296 - 1315, The
code calls x_coo_data.max() unguarded which throws when nnz == 0; update
_count_sparse to handle all-zero sparse batches by checking x_coo_nnz (or
len(x_coo_data)) first and if it is 0 set highest_feature = 1 (so category 0 is
preserved and behavior matches the dense path), otherwise compute
highest_feature = int(x_coo_data.max()) + 1; reference the _count_sparse
function and the x_coo_data / x_coo_nnz / highest_feature symbols when applying
the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/cuml/cuml/naive_bayes/naive_bayes.py`:
- Around line 162-169: predict() currently ignores the index returned by
_check_predict(X, ...) and returns decode_labels(...) directly, dropping
DataFrame/cuDF indices; update predict (in naive_bayes.NaiveBayes.predict) to
preserve input labels the same way predict_proba()/predict_log_proba() do by
capturing the index from _check_predict, calling decode_labels to get the
predicted labels, and then wrapping those labels in a CumlArray (or the same
array wrapper used by predict_proba/predict_log_proba) with index=index and
output_type=output_type before returning; ensure you use the same
constructor/signature (e.g., CumlArray(..., index=index)) and keep
decode_labels, _check_predict, and _get_output_type references intact.
- Around line 621-627: Change the public signatures of the estimator methods to
keep sample_weight=None (e.g., def partial_fit(..., sample_weight=None) and def
fit(..., sample_weight=None)) so they remain scikit-learn compatible; if you
need to mark the parameter deprecated, handle deprecation inside the method
bodies of partial_fit and fit by checking for a private sentinel (or inspecting
sample_weight) and emitting a deprecation warning or mapping to the internal
sentinel, rather than changing the default in the signature of partial_fit, fit,
or any other estimator methods in _BaseDiscreteNB.

---

Outside diff comments:
In `@python/cuml/cuml/naive_bayes/naive_bayes.py`:
- Around line 1296-1315: The code calls x_coo_data.max() unguarded which throws
when nnz == 0; update _count_sparse to handle all-zero sparse batches by
checking x_coo_nnz (or len(x_coo_data)) first and if it is 0 set highest_feature
= 1 (so category 0 is preserved and behavior matches the dense path), otherwise
compute highest_feature = int(x_coo_data.max()) + 1; reference the _count_sparse
function and the x_coo_data / x_coo_nnz / highest_feature symbols when applying
the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: b9abd431-3314-4765-9e2b-73416c6c0597

📥 Commits

Reviewing files that changed from the base of the PR and between f39dde1 and b944d36.

📒 Files selected for processing (6)
  • python/cuml/cuml/internals/validation.py
  • python/cuml/cuml/naive_bayes/naive_bayes.py
  • python/cuml/tests/test_base.py
  • python/cuml/tests/test_naive_bayes.py
  • python/cuml/tests/test_sklearn_compatibility.py
  • python/cuml/tests/test_validation.py

Comment thread python/cuml/cuml/naive_bayes/naive_bayes.py Outdated
Comment thread python/cuml/cuml/naive_bayes/naive_bayes.py
jcrist added 6 commits May 6, 2026 11:47
This adds support to `check_y`/`check_inputs` for explicitly specifying
the classes used for label encoding via the `return_classes` keyword.
This may optionally take a numpy array (or list of numpy arrays) in the
same format as those returned from `check_y`. If provided, these will be
used for label encoding instead of deriving the classes from `y` itself.
If any unknown classes are found in the data, a useful error will be
raised.
This is a general cleanup of `cuml.naive_bayes`, motivated _mostly_ by
the new validation work. It:

- Ports `cuml.naive_bayes` to use the new validation utilities, fixing
  many xfails.
- Adds support for returning non-numeric classes from all classifiers,
  mirroring our support in our other classifiers.
- Moves the `classes_` attribute to be a numpy array (technically a
  breaking change), mirroring how it's handled on every other
  classifier.
- Simplifies the control flow and definitions, greatly reducing the LOC
  in this module.
This argument was previously unsupported and was just silently ignored
(leading to incorrect results). We now raise a `NotImplementedError`,
but even having the arg at all leads to some failing tests in the
sklearn test suite. Since we don't intend to spend time adding support
for `sample_weight`, better to just deprecate and rip it out instead.
@jcrist
jcrist force-pushed the validation-naive-bayes branch from bd02b4f to ccc9b6e Compare May 6, 2026 17:13
@jcrist

jcrist commented May 6, 2026

Copy link
Copy Markdown
Member Author

This is now stacked on top of #8056 That PR is now merged and this is ready for review

@csadorf

csadorf commented May 6, 2026

Copy link
Copy Markdown
Contributor

/merge

@rapids-bot
rapids-bot Bot merged commit 7800a15 into NVIDIA:main May 6, 2026
93 checks passed
@jcrist
jcrist deleted the validation-naive-bayes branch May 6, 2026 20:47
@coderabbitai coderabbitai Bot mentioned this pull request May 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Breaking change Cython / Python Cython or Python issue improvement Improvement / enhancement to an existing function sklearn-api-compat Issues around cuml matching sklearn API conventions/standards

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update cuml.naive_bayes to new input validation [BUG] cuml's Naive Bayes' MultinomialNB function can't predict or fit_predict

4 participants