Skip to content

RF: add class_weight to RandomForestClassifier - #8188

Closed
switch527 wants to merge 2 commits into
NVIDIA:mainfrom
switch527:enh-rf-classifier-class-weight
Closed

RF: add class_weight to RandomForestClassifier#8188
switch527 wants to merge 2 commits into
NVIDIA:mainfrom
switch527:enh-rf-classifier-class-weight

Conversation

@switch527

Copy link
Copy Markdown
Contributor

Closes #8093, Refs #8146

Adds class_weight to RandomForestClassifier. Supports None, a dict, 'balanced', and 'balanced_subsample'. The first three reuse the existing helper from LR/SVC. 'balanced_subsample' re-weights per tree from each bootstrap sample (matches sklearn). With bootstrap=False it silently collapses to 'balanced', also matching sklearn.

cuml exposes class_weight_ as a fitted attribute (mirroring SVC); sklearn's RFC doesn't. Under weighted training, feature_importances_ is well-formed but not byte-equivalent to sklearn's; cuml weights the impurity term, sklearn weights node counts. Documented in the docstring.

Overhead on a 500k × 30 × 10-class fixture: balanced is essentially free, balanced_subsample is +2.5%. The unweighted path stays byte-identical.

Adjacent fix: max_samples now round-trips to None when bootstrap=False so the new round-trip tests on balanced_subsample succeed (sklearn rejects the combination).

Tests cover each class_weight shape, refit, predict_proba, sklearn round-trip parametrized over class_weight × bootstrap × oob_score, plus stress coverage. Two upstream tests stay xfailed (importance-formula divergence; reliance on a cuml-unsupported parameter); both rationales name a revisit trigger.

PR-4 (#8143) closes the ETC half of #8146 by inheritance.

@copy-pr-bot

copy-pr-bot Bot commented Jun 1, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added Cython / Python Cython or Python issue CMake CUDA/C++ labels Jun 1, 2026
@switch527
switch527 marked this pull request as ready for review June 1, 2026 04:14
@switch527
switch527 requested review from a team as code owners June 1, 2026 04:14
@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 798e76c0-f9f1-47af-8a04-366223f0b173

📥 Commits

Reviewing files that changed from the base of the PR and between 9d0c03d and c45e96e.

📒 Files selected for processing (30)
  • cpp/CMakeLists.txt
  • cpp/bench/CMakeLists.txt
  • cpp/bench/sg/rf_regressor.cu
  • cpp/include/cuml/ensemble/randomforest.hpp
  • cpp/src/decisiontree/batched-levelalgo/bins.cuh
  • cpp/src/decisiontree/batched-levelalgo/builder.cuh
  • cpp/src/decisiontree/batched-levelalgo/dataset.h
  • cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels.cuh
  • cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels_impl.cuh
  • cpp/src/decisiontree/batched-levelalgo/objectives.cuh
  • cpp/src/decisiontree/decisiontree.cuh
  • cpp/src/randomforest/per_tree_weights.cu
  • cpp/src/randomforest/per_tree_weights.cuh
  • cpp/src/randomforest/randomforest.cu
  • cpp/src/randomforest/randomforest.cuh
  • cpp/tests/sg/rf_test.cu
  • docs/source/cuml-accel/limitations.rst
  • python/cuml/cuml/accel/_overrides/sklearn/ensemble.py
  • python/cuml/cuml/dask/ensemble/randomforestclassifier.py
  • python/cuml/cuml/dask/ensemble/randomforestregressor.py
  • python/cuml/cuml/ensemble/randomforest_common.pyx
  • python/cuml/cuml/ensemble/randomforestclassifier.py
  • python/cuml/cuml/ensemble/randomforestregressor.py
  • python/cuml/cuml_accel_tests/integration/test_rf_classifier.py
  • python/cuml/cuml_accel_tests/integration/test_rf_regressor.py
  • python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
  • python/cuml/tests/dask/test_dask_random_forest.py
  • python/cuml/tests/test_random_forest.py
  • python/cuml/tests/test_sklearn_compatibility.py
  • python/cuml/tests/test_sklearn_import_export.py
✅ Files skipped from review due to trivial changes (1)
  • docs/source/cuml-accel/limitations.rst
🚧 Files skipped from review as they are similar to previous changes (26)
  • cpp/bench/CMakeLists.txt
  • cpp/src/decisiontree/batched-levelalgo/dataset.h
  • python/cuml/cuml/ensemble/randomforestregressor.py
  • python/cuml/cuml/dask/ensemble/randomforestclassifier.py
  • cpp/CMakeLists.txt
  • cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels.cuh
  • cpp/src/decisiontree/batched-levelalgo/bins.cuh
  • python/cuml/tests/dask/test_dask_random_forest.py
  • cpp/include/cuml/ensemble/randomforest.hpp
  • cpp/src/decisiontree/decisiontree.cuh
  • cpp/src/randomforest/randomforest.cuh
  • cpp/src/randomforest/per_tree_weights.cu
  • python/cuml/cuml/dask/ensemble/randomforestregressor.py
  • python/cuml/cuml_accel_tests/integration/test_rf_regressor.py
  • cpp/tests/sg/rf_test.cu
  • cpp/src/randomforest/per_tree_weights.cuh
  • cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels_impl.cuh
  • python/cuml/tests/test_sklearn_import_export.py
  • python/cuml/cuml/accel/_overrides/sklearn/ensemble.py
  • cpp/src/decisiontree/batched-levelalgo/builder.cuh
  • cpp/src/randomforest/randomforest.cu
  • python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
  • python/cuml/cuml/ensemble/randomforestclassifier.py
  • cpp/bench/sg/rf_regressor.cu
  • python/cuml/cuml_accel_tests/integration/test_rf_classifier.py
  • cpp/src/decisiontree/batched-levelalgo/objectives.cuh

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • GPU RandomForest: support for sample_weight in training/score and new class_weight with balanced / balanced_subsample modes, including per-tree balanced-weight handling.
  • Behavior Changes
    • Distributed (Dask) RandomForest: passing sample_weight now raises NotImplementedError (use single-GPU estimator).
  • Documentation
    • Refined GPU fallback rules for class_weight/sample_weight.
  • Tests
    • Extensive unit/integration tests for sample_weight and class_weight behavior.

Walkthrough

This PR implements sample_weight and class_weight support for RandomForest by threading weighted training through decision-tree kernels, objectives, and estimator APIs. Per-sample weights are accumulated in CUDA histograms, gain computations incorporate weighted totals, and class_weight='balanced_subsample' triggers per-tree balanced-weight recomputation.

Changes

RandomForest weighted training

Layer / File(s) Summary
Public API Contracts
cpp/include/cuml/ensemble/randomforest.hpp
ClassWeightMode enum added; classification fit overloads and fit_treelite template accept sample_weight, class_weight_mode, class_weight_array; regression fit overloads and fit_treelite accept sample_weight.
Bin types and Dataset for weights
cpp/src/decisiontree/batched-levelalgo/bins.cuh, cpp/src/decisiontree/batched-levelalgo/dataset.h
CountBin and AggregateBin::IncrementHistogram accept double weight; Dataset adds optional sample_weight pointer.
Builder companion histograms and workspace
cpp/src/decisiontree/batched-levelalgo/builder.cuh
Builder accepts sample_weight, adds type-gated companion histogram pointers (unweighted_histograms/weighted_count_histograms); workspace sizing/assignment and shared-memory accounting updated.
Kernel interfaces and wiring
cpp/src/decisiontree/batched-levelalgo/kernels/*
launchComputeSplitKernel declarations and launches updated to pass companion histogram pointers; explicit instantiations extended.
Weighted CUDA kernels
cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels_impl.cuh
leafKernel and computeSplitKernel read per-sample weights, maintain type-dependent companion arrays (int for classifier, double for regressor), perform cross-block accumulation, and compute PDF→CDF and objective gains using companion arrays.
Weighted objective functions
cpp/src/decisiontree/batched-levelalgo/objectives.cuh
Gini, Entropy, MSE, Poisson, Gamma, InverseGaussian updated to accept weighted totals and weighted CDFs; gain formulas use weight-based scaling; SetLeafVector handles zero/negative total-weight cases.
DecisionTree fit plumbing
cpp/src/decisiontree/decisiontree.cuh
DecisionTree::fit extended to accept sample_weight and forward it into Builder for all supported criteria.
Per-tree balanced-weight computation
cpp/src/randomforest/per_tree_weights.cu, cpp/src/randomforest/per_tree_weights.cuh
New computePerTreeBalancedWeights template: shared-memory bincount kernel, classes-present reduction, per-row weight fill kernel, host orchestration with async device allocations, and explicit instantiations.
RandomForest C++ wiring
cpp/src/randomforest/randomforest.cu, cpp/src/randomforest/randomforest.cuh
RandomForest::fit accepts sample_weight, class_weight_mode, class_weight_array; validates contract; optionally builds per-tree balanced weights and forwards tree_sample_weight into DecisionTree::fit; classifier/regressor fit entry points and fit_treelite updated; CUML_EXPORT instantiations extended.
Cython bridge
python/cuml/cuml/ensemble/randomforest_common.pyx
_fit_forest extended to accept sample_weight, class_weight_mode, class_weight_array; maps Python/CuPy None to null pointers and forwards into fit_treelite; conditional max_samples forwarding when bootstrap enabled.
RandomForestClassifier Python API
python/cuml/cuml/ensemble/randomforestclassifier.py
Adds class_weight parameter and docs; fit accepts sample_weight, resolves class_weight/class_weight_array/class_weight_mode, computes class_weight_, forwards into _fit_forest; score accepts sample_weight.
RandomForestRegressor Python API
python/cuml/cuml/ensemble/randomforestregressor.py
fit and score accept optional sample_weight and forward to _fit_forest and r2_score.
GPU accelerator overrides
python/cuml/cuml/accel/_overrides/sklearn/ensemble.py
_check_inputs no longer rejects sample_weight; _gpu_fit and _gpu_score forward sample_weight to GPU implementations.
Dask distributed estimators
python/cuml/cuml/dask/ensemble/randomforest*.py
Distributed fit signatures accept sample_weight but raise NotImplementedError when non-None; docstrings updated.
Docs and xfails
docs/source/cuml-accel/limitations.rst, python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
Refined CPU-fallback rules for class_weight; removed sample_weight fallback bullets; xfail entries added/adjusted for sample_weight equivalence checks.
C++ unit tests
cpp/tests/sg/rf_test.cu
Added sample_weight identity tests, weighted gain ground-truths, SetLeafVector validation for zero-weight leaves, and ClassWeightMode contract/behavior tests.
Integration tests
python/cuml/cuml_accel_tests/integration/test_rf_*.py
Tests added verifying sample_weight and class_weight GPU paths with sklearn metric comparisons.
Dask tests
python/cuml/tests/dask/test_dask_random_forest.py
Tests for distributed sample_weight NotImplementedError and acceptance of sample_weight=None.
Python unit tests
python/cuml/tests/test_random_forest.py
Large additions covering classifier/regressor sample_weight behavior, class_weight handling, sklearn parity, and feature_importances_ under weighting.
Roundtrip import/export tests
python/cuml/tests/test_sklearn_import_export.py
Parametrized class_weight and bootstrap, effective_oob gating, and exclusion of class_weight_ from roundtrip equality.
Benchmarks and build updates
cpp/CMakeLists.txt, cpp/bench/CMakeLists.txt, cpp/bench/sg/rf_regressor.cu
Added per_tree_weights.cu to build; enabled rf_regressor bench with pointer passing and scaled-down benchmark config.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

Possibly related PRs

  • rapidsai/cuml#8132: Parallel work on batched-levelalgo weighted training (bins, histogram accumulation, weight-aware kernels).
  • rapidsai/cuml#8023: Earlier PR that modified GPU override input validation for sample_weight; related to the change in _overrides/sklearn/ensemble.py.

Suggested labels

improvement, sklearn-api-compat, non-breaking

Suggested reviewers

  • csadorf
  • viclafargue
  • jinsolp
  • dantegd
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@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: 1

Caution

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

⚠️ Outside diff range comments (1)
cpp/include/cuml/ensemble/randomforest.hpp (1)

150-162: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Missing Doxygen documentation for new public API parameters.

The new sample_weight, class_weight_mode, and class_weight_array parameters lack documentation. Public API functions should have Doxygen comments describing parameter semantics, valid values, and ownership expectations.

📝 Suggested documentation additions
+/**
+ * `@brief` Train a random forest classifier.
+ * ...existing params...
+ * `@param`[in] sample_weight Optional per-sample weights (device pointer, length n_rows).
+ *                          Pass nullptr for uniform weights.
+ * `@param`[in] class_weight_mode Class weight mode: 0=NONE, 1=BALANCED_SUBSAMPLE.
+ * `@param`[in] class_weight_array Optional per-class weight array (device pointer, length n_unique_labels).
+ *                               Required when class_weight_mode is NONE and custom class weights are desired.
+ */
 void fit(const raft::handle_t& user_handle,
          RandomForestClassifierF* forest,

As per coding guidelines: "Provide Doxygen documentation for all public functions."

Also applies to: 163-175

🤖 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 `@cpp/include/cuml/ensemble/randomforest.hpp` around lines 150 - 162, Add
Doxygen comments to the public fit overload(s) for RandomForestClassifierF (and
the corresponding double/other overload) documenting the new parameters:
describe sample_weight (per-sample importance, length n_rows, nullable and
ownership/ownership: caller-owned), class_weight_mode (enumerate valid values
and behavior for 0/1/... meaning e.g., none/auto/custom), and class_weight_array
(when used, length n_unique_labels, ordering/mapping to label indices, nullable
and ownership). Also update the brief function description to mention weighted
training support and include `@param` tags for sample_weight, class_weight_mode,
and class_weight_array consistent with existing Doxygen style used for this
header.
🧹 Nitpick comments (3)
cpp/bench/sg/rf_regressor.cu (1)

2-2: 💤 Low value

Update copyright year for consistency.

The classifier benchmark was updated to 2026 (cpp/bench/sg/rf_classifier.cu:2), but this file remains at 2024. Consider updating for consistency.

📅 Proposed fix
-  * SPDX-FileCopyrightText: Copyright (c) 2019-2024, NVIDIA CORPORATION.
+  * SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION.
🤖 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 `@cpp/bench/sg/rf_regressor.cu` at line 2, Update the copyright header in
rf_regressor.cu to match the other benchmark file by changing the year range
from "2019-2024" to "2019-2026"; locate the top-of-file SPDX/ copyright comment
in rf_regressor.cu and edit the year span so it is consistent with
rf_classifier.cu.
cpp/include/cuml/ensemble/randomforest.hpp (1)

29-32: 💤 Low value

Enum comment references internal implementation file.

The comment mentions per_tree_weights.cuh which is an implementation detail not visible to API consumers. Consider documenting the formula or behavior directly here, or referencing user-facing documentation instead.

🤖 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 `@cpp/include/cuml/ensemble/randomforest.hpp` around lines 29 - 32, The comment
on the ClassWeightMode enum currently references an internal file
per_tree_weights.cuh; replace that implementation-detail reference with a
user-facing description: for ClassWeightMode::NONE state that sample_weight is
passed through unchanged, and for ClassWeightMode::BALANCED_SUBSAMPLE explain
concisely that class weights are recomputed per tree from each bootstrap sample
as the reciprocal of class frequencies (i.e., weight = total_samples /
(n_classes * class_count_in_bootstrap)) and note parity with scikit-learn
behavior or link to external user documentation instead of the .cuh file; update
the enum comment accordingly.
cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels_impl.cuh (1)

263-294: 💤 Low value

Dead code in else branch.

The else branch at lines 280-282 is unreachable because the static_assert at line 265 guarantees kIsClassifier || kIsRegressor. This dead code adds confusion without providing any fallback benefit.

♻️ Suggested cleanup
   if constexpr (kIsClassifier) {
     shared_unweighted = alignPointer<int>(shared_histogram + shared_histogram_len);
     shared_quantiles  = alignPointer<DataT>(shared_unweighted + n_bins);
   } else if constexpr (kIsRegressor) {
     shared_weighted_count = alignPointer<double>(shared_histogram + shared_histogram_len);
     shared_quantiles      = alignPointer<DataT>(shared_weighted_count + n_bins);
-  } else {
-    shared_quantiles = alignPointer<DataT>(shared_histogram + shared_histogram_len);
   }
🤖 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 `@cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels_impl.cuh`
around lines 263 - 294, The else branch assigning shared_quantiles is dead due
to the static_assert(kIsClassifier || kIsRegressor) in computeSplitKernel;
remove the unreachable else block (the branch that does shared_quantiles =
alignPointer<DataT>(shared_histogram + shared_histogram_len)) and keep the
existing assignments inside the kIsClassifier and kIsRegressor if constexpr
branches (or, alternatively, move a single shared_quantiles assignment after
those two constexpr branches if you prefer one place of initialization).
🤖 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 `@cpp/tests/sg/rf_test.cu`:
- Around line 704-711: The current assertions only check sizes/counters
(leaf_counter, depth_counter, sparsetree.size(), vector_leaf.size()) but not the
actual sparsetree/node contents; update the test to iterate each tree in
forest_null/forest_ones and assert deep equality of sparsetree elements (compare
corresponding node fields such as split_feature, split_threshold, left/right
indices, and any other node payload) and also compare vector_leaf
element-by-element to ensure byte-identical node structure; apply the same
deep-comparison approach to the other analogous test blocks (the similar checks
around the other occurrences of forest_null/forest_ones).

---

Outside diff comments:
In `@cpp/include/cuml/ensemble/randomforest.hpp`:
- Around line 150-162: Add Doxygen comments to the public fit overload(s) for
RandomForestClassifierF (and the corresponding double/other overload)
documenting the new parameters: describe sample_weight (per-sample importance,
length n_rows, nullable and ownership/ownership: caller-owned),
class_weight_mode (enumerate valid values and behavior for 0/1/... meaning e.g.,
none/auto/custom), and class_weight_array (when used, length n_unique_labels,
ordering/mapping to label indices, nullable and ownership). Also update the
brief function description to mention weighted training support and include
`@param` tags for sample_weight, class_weight_mode, and class_weight_array
consistent with existing Doxygen style used for this header.

---

Nitpick comments:
In `@cpp/bench/sg/rf_regressor.cu`:
- Line 2: Update the copyright header in rf_regressor.cu to match the other
benchmark file by changing the year range from "2019-2024" to "2019-2026";
locate the top-of-file SPDX/ copyright comment in rf_regressor.cu and edit the
year span so it is consistent with rf_classifier.cu.

In `@cpp/include/cuml/ensemble/randomforest.hpp`:
- Around line 29-32: The comment on the ClassWeightMode enum currently
references an internal file per_tree_weights.cuh; replace that
implementation-detail reference with a user-facing description: for
ClassWeightMode::NONE state that sample_weight is passed through unchanged, and
for ClassWeightMode::BALANCED_SUBSAMPLE explain concisely that class weights are
recomputed per tree from each bootstrap sample as the reciprocal of class
frequencies (i.e., weight = total_samples / (n_classes *
class_count_in_bootstrap)) and note parity with scikit-learn behavior or link to
external user documentation instead of the .cuh file; update the enum comment
accordingly.

In `@cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels_impl.cuh`:
- Around line 263-294: The else branch assigning shared_quantiles is dead due to
the static_assert(kIsClassifier || kIsRegressor) in computeSplitKernel; remove
the unreachable else block (the branch that does shared_quantiles =
alignPointer<DataT>(shared_histogram + shared_histogram_len)) and keep the
existing assignments inside the kIsClassifier and kIsRegressor if constexpr
branches (or, alternatively, move a single shared_quantiles assignment after
those two constexpr branches if you prefer one place of initialization).
🪄 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: 55041c41-f738-4e8a-88f2-6d12ccb093bb

📥 Commits

Reviewing files that changed from the base of the PR and between 4d01e52 and 2c3911a.

📒 Files selected for processing (32)
  • cpp/CMakeLists.txt
  • cpp/bench/CMakeLists.txt
  • cpp/bench/sg/rf_classifier.cu
  • cpp/bench/sg/rf_regressor.cu
  • cpp/include/cuml/ensemble/randomforest.hpp
  • cpp/src/decisiontree/batched-levelalgo/bins.cuh
  • cpp/src/decisiontree/batched-levelalgo/builder.cuh
  • cpp/src/decisiontree/batched-levelalgo/dataset.h
  • cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels.cuh
  • cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels_impl.cuh
  • cpp/src/decisiontree/batched-levelalgo/objectives.cuh
  • cpp/src/decisiontree/decisiontree.cuh
  • cpp/src/randomforest/per_tree_weights.cu
  • cpp/src/randomforest/per_tree_weights.cuh
  • cpp/src/randomforest/randomforest.cu
  • cpp/src/randomforest/randomforest.cuh
  • cpp/tests/sg/rf_test.cu
  • docs/source/cuml-accel/limitations.rst
  • python/cuml/cuml/accel/_overrides/sklearn/ensemble.py
  • python/cuml/cuml/dask/ensemble/randomforestclassifier.py
  • python/cuml/cuml/dask/ensemble/randomforestregressor.py
  • python/cuml/cuml/ensemble/randomforest_common.pyx
  • python/cuml/cuml/ensemble/randomforestclassifier.py
  • python/cuml/cuml/ensemble/randomforestregressor.py
  • python/cuml/cuml_accel_tests/integration/test_rf_classifier.py
  • python/cuml/cuml_accel_tests/integration/test_rf_regressor.py
  • python/cuml/cuml_accel_tests/test_onnx.py
  • python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
  • python/cuml/tests/dask/test_dask_random_forest.py
  • python/cuml/tests/test_random_forest.py
  • python/cuml/tests/test_sklearn_compatibility.py
  • python/cuml/tests/test_sklearn_import_export.py
💤 Files with no reviewable changes (1)
  • python/cuml/cuml_accel_tests/test_onnx.py

Comment thread cpp/tests/sg/rf_test.cu
@switch527
switch527 force-pushed the enh-rf-classifier-class-weight branch 2 times, most recently from 4350fee to 9d0c03d Compare June 2, 2026 03:51
switch527 added 2 commits June 2, 2026 09:07
Towards NVIDIA#8093. Extends sample_weight to RandomForestRegressor.fit,
building on the classifier work in NVIDIA#8132. Single-GPU only; distributed
(Dask) raises NotImplementedError and points users at the single-GPU
class (tracking NVIDIA#8186).

Regressor mirrors the classifier companion-buffer pattern with a
double* weighted_count_histograms alongside the existing AggregateBin
histogram. The companion holds sum(weight) per bin and feeds Gain for
the weighted denominator and SetLeafVector for the leaf mean.
AggregateBin.count stays unweighted so min_samples_leaf and Split::nLeft
still operate on integer sample counts. The four regressor objectives
substitute n with sum(weight) following sklearn 1.7.2 _criterion.pyx
proxy_impurity_improvement (MSE :1089-1118; Poisson :1597-1642); Gamma
and InverseGaussian apply the same substitution to cuml's existing
formulas. All six SetLeafVector paths NaN-guard the all-zero-weight
leaf.

Tests add per-objective weighted ground-truth gtests with hand-derived
expected values, NaN-guard gtests for both SetLeafVector paths, and
Python tests mirrored across RFC and RFR. Accel proxy forwards
sample_weight to GPU. test_sklearn_compatibility.py and xfail-list.yaml
xfail check_sample_weight_equivalence for both estimators (quantile
binning != row duplication).

Perf: RFR<double> MSE +11.1% on the rf_regressor bench vs the pre-PR
baseline (extra per-sample atomicAdd into the companion buffer + extra
pdf_to_cdf<double> scan); RFR<float> flat; classifier within noise.
libcuml.so +36 KB. Adjacent fix: re-enabled cpp/bench/sg/rf_regressor.cu
(disabled in a 2024 FIXME); pre-existing compile errors fixed and
workload scaled down so it finishes in minutes.
Closes NVIDIA#8093, Refs NVIDIA#8146

Adds `class_weight` to `RandomForestClassifier`. Supports `None`, a dict, `'balanced'`, and `'balanced_subsample'`. The first three reuse the existing helper from LR/SVC. `'balanced_subsample'` re-weights per tree from each bootstrap sample (matches sklearn). With `bootstrap=False` it silently collapses to `'balanced'`, also matching sklearn.

cuml exposes `class_weight_` as a fitted attribute (mirroring SVC); sklearn's RFC doesn't. Under weighted training, `feature_importances_` is well-formed but not byte-equivalent to sklearn's; cuml weights the impurity term, sklearn weights node counts. Documented in the `Notes` section.

Overhead on a 500k x 30 x 10-class fixture: `balanced` is essentially free, `balanced_subsample` is +2.5%. The unweighted path stays byte-identical.

Adjacent fix: `max_samples` now round-trips to `None` when `bootstrap=False` so the new round-trip tests on `balanced_subsample` succeed (sklearn rejects the combination).

Tests cover each `class_weight` shape, refit, `predict_proba`, sklearn round-trip parametrized over `class_weight x bootstrap x oob_score`, plus stress coverage. Two upstream tests stay xfailed (importance-formula divergence; reliance on a cuml-unsupported parameter); both rationales name a revisit trigger.

PR-4 (NVIDIA#8143) closes the ETC half of NVIDIA#8146 by inheritance.
@switch527

switch527 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

@divyegala, it requires the previous PR in this series to function, but I think your team wanted to think through the implementation more so I'm going to go ahead and close it out

@switch527 switch527 closed this Jun 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEA] Add class_weight + sample_weight to RandomForest estimators

3 participants