From 57d5c7855dbec11d848c4ed59e6cc6d410e8f0dc Mon Sep 17 00:00:00 2001 From: Rory Mitchell Date: Fri, 5 Jun 2026 02:41:43 -0700 Subject: [PATCH] Refactor RF objectives by family --- cpp/CMakeLists.txt | 16 +- .../batched-levelalgo/builder.cuh | 4 +- ...opy-double.cu => classification-double.cu} | 4 +- ...tropy-float.cu => classification-float.cu} | 4 +- .../batched-levelalgo/kernels/gamma-double.cu | 23 - .../batched-levelalgo/kernels/gamma-float.cu | 23 - .../batched-levelalgo/kernels/gini-double.cu | 23 - .../batched-levelalgo/kernels/gini-float.cu | 23 - .../kernels/inverse_gaussian-double.cu | 23 - .../kernels/inverse_gaussian-float.cu | 23 - .../kernels/poisson-double.cu | 23 - .../kernels/poisson-float.cu | 23 - .../{mse-double.cu => regression-double.cu} | 4 +- .../{mse-float.cu => regression-float.cu} | 4 +- .../batched-levelalgo/objectives.cuh | 434 +++++------------- cpp/src/decisiontree/decisiontree.cuh | 118 ++--- cpp/tests/sg/rf_test.cu | 110 +++-- 17 files changed, 220 insertions(+), 662 deletions(-) rename cpp/src/decisiontree/batched-levelalgo/kernels/{entropy-double.cu => classification-double.cu} (76%) rename cpp/src/decisiontree/batched-levelalgo/kernels/{entropy-float.cu => classification-float.cu} (76%) delete mode 100644 cpp/src/decisiontree/batched-levelalgo/kernels/gamma-double.cu delete mode 100644 cpp/src/decisiontree/batched-levelalgo/kernels/gamma-float.cu delete mode 100644 cpp/src/decisiontree/batched-levelalgo/kernels/gini-double.cu delete mode 100644 cpp/src/decisiontree/batched-levelalgo/kernels/gini-float.cu delete mode 100644 cpp/src/decisiontree/batched-levelalgo/kernels/inverse_gaussian-double.cu delete mode 100644 cpp/src/decisiontree/batched-levelalgo/kernels/inverse_gaussian-float.cu delete mode 100644 cpp/src/decisiontree/batched-levelalgo/kernels/poisson-double.cu delete mode 100644 cpp/src/decisiontree/batched-levelalgo/kernels/poisson-float.cu rename cpp/src/decisiontree/batched-levelalgo/kernels/{mse-double.cu => regression-double.cu} (77%) rename cpp/src/decisiontree/batched-levelalgo/kernels/{mse-float.cu => regression-float.cu} (77%) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index bf3c949ffb..c198263725 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -352,18 +352,10 @@ if(BUILD_CUML_CPP_LIBRARY) if(all_algo OR decisiontree_algo) target_sources( cuml_objs - PRIVATE src/decisiontree/batched-levelalgo/kernels/entropy-double.cu - src/decisiontree/batched-levelalgo/kernels/entropy-float.cu - src/decisiontree/batched-levelalgo/kernels/gamma-double.cu - src/decisiontree/batched-levelalgo/kernels/gamma-float.cu - src/decisiontree/batched-levelalgo/kernels/gini-double.cu - src/decisiontree/batched-levelalgo/kernels/gini-float.cu - src/decisiontree/batched-levelalgo/kernels/inverse_gaussian-double.cu - src/decisiontree/batched-levelalgo/kernels/inverse_gaussian-float.cu - src/decisiontree/batched-levelalgo/kernels/mse-double.cu - src/decisiontree/batched-levelalgo/kernels/mse-float.cu - src/decisiontree/batched-levelalgo/kernels/poisson-double.cu - src/decisiontree/batched-levelalgo/kernels/poisson-float.cu + PRIVATE src/decisiontree/batched-levelalgo/kernels/classification-double.cu + src/decisiontree/batched-levelalgo/kernels/classification-float.cu + src/decisiontree/batched-levelalgo/kernels/regression-double.cu + src/decisiontree/batched-levelalgo/kernels/regression-float.cu src/decisiontree/decisiontree.cu ) endif() diff --git a/cpp/src/decisiontree/batched-levelalgo/builder.cuh b/cpp/src/decisiontree/batched-levelalgo/builder.cuh index e9a7996b65..c04c1ee78c 100644 --- a/cpp/src/decisiontree/batched-levelalgo/builder.cuh +++ b/cpp/src/decisiontree/batched-levelalgo/builder.cuh @@ -519,7 +519,7 @@ struct Builder { int len_histograms = n_bins * n_classes * n_blocks_dimy * n_large_nodes; RAFT_CUDA_TRY(cudaMemsetAsync(histograms, 0, sizeof(BinT) * len_histograms, builder_stream)); // create the objective function object - ObjectiveT objective(dataset.num_outputs, params.min_samples_leaf); + ObjectiveT objective(dataset.num_outputs, params.min_samples_leaf, params.split_criterion); // call the computeSplitKernel raft::common::nvtx::range kernel_scope("computeSplitKernel @builder.cuh [batched-levelalgo]"); launchComputeSplitKernel(histograms, @@ -556,7 +556,7 @@ struct Builder { rmm::device_uvector d_instance_ranges(max_batch_size, builder_stream); rmm::device_uvector d_leaves(max_batch_size * dataset.num_outputs, builder_stream); - ObjectiveT objective(dataset.num_outputs, params.min_samples_leaf); + ObjectiveT objective(dataset.num_outputs, params.min_samples_leaf, params.split_criterion); for (std::size_t batch_begin = 0; batch_begin < tree->sparsetree.size(); batch_begin += max_batch_size) { std::size_t batch_end = min(batch_begin + max_batch_size, tree->sparsetree.size()); diff --git a/cpp/src/decisiontree/batched-levelalgo/kernels/entropy-double.cu b/cpp/src/decisiontree/batched-levelalgo/kernels/classification-double.cu similarity index 76% rename from cpp/src/decisiontree/batched-levelalgo/kernels/entropy-double.cu rename to cpp/src/decisiontree/batched-levelalgo/kernels/classification-double.cu index 137c3084ee..ed72a703e9 100644 --- a/cpp/src/decisiontree/batched-levelalgo/kernels/entropy-double.cu +++ b/cpp/src/decisiontree/batched-levelalgo/kernels/classification-double.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2019-2024, NVIDIA CORPORATION. + * SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION. * SPDX-License-Identifier: Apache-2.0 */ @@ -13,7 +13,7 @@ namespace DT { using _DataT = double; using _LabelT = int; using _IdxT = int; -using _ObjectiveT = EntropyObjectiveFunction<_DataT, _LabelT, _IdxT>; +using _ObjectiveT = ClassificationObjectiveFunction<_DataT, _LabelT, _IdxT>; using _BinT = CountBin; using _DatasetT = Dataset<_DataT, _LabelT, _IdxT>; using _NodeT = SparseTreeNode<_DataT, _LabelT, _IdxT>; diff --git a/cpp/src/decisiontree/batched-levelalgo/kernels/entropy-float.cu b/cpp/src/decisiontree/batched-levelalgo/kernels/classification-float.cu similarity index 76% rename from cpp/src/decisiontree/batched-levelalgo/kernels/entropy-float.cu rename to cpp/src/decisiontree/batched-levelalgo/kernels/classification-float.cu index 12b8ec9ab5..a2257f2fd2 100644 --- a/cpp/src/decisiontree/batched-levelalgo/kernels/entropy-float.cu +++ b/cpp/src/decisiontree/batched-levelalgo/kernels/classification-float.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2019-2024, NVIDIA CORPORATION. + * SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION. * SPDX-License-Identifier: Apache-2.0 */ @@ -13,7 +13,7 @@ namespace DT { using _DataT = float; using _LabelT = int; using _IdxT = int; -using _ObjectiveT = EntropyObjectiveFunction<_DataT, _LabelT, _IdxT>; +using _ObjectiveT = ClassificationObjectiveFunction<_DataT, _LabelT, _IdxT>; using _BinT = CountBin; using _DatasetT = Dataset<_DataT, _LabelT, _IdxT>; using _NodeT = SparseTreeNode<_DataT, _LabelT, _IdxT>; diff --git a/cpp/src/decisiontree/batched-levelalgo/kernels/gamma-double.cu b/cpp/src/decisiontree/batched-levelalgo/kernels/gamma-double.cu deleted file mode 100644 index 71339a6289..0000000000 --- a/cpp/src/decisiontree/batched-levelalgo/kernels/gamma-double.cu +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2019-2024, NVIDIA CORPORATION. - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "../bins.cuh" -#include "../objectives.cuh" - -#include - -namespace ML { -namespace DT { -using _DataT = double; -using _LabelT = double; -using _IdxT = int; -using _ObjectiveT = GammaObjectiveFunction<_DataT, _LabelT, _IdxT>; -using _BinT = AggregateBin; -using _DatasetT = Dataset<_DataT, _LabelT, _IdxT>; -using _NodeT = SparseTreeNode<_DataT, _LabelT, _IdxT>; -} // namespace DT -} // namespace ML - -#include "builder_kernels_impl.cuh" diff --git a/cpp/src/decisiontree/batched-levelalgo/kernels/gamma-float.cu b/cpp/src/decisiontree/batched-levelalgo/kernels/gamma-float.cu deleted file mode 100644 index 2d01710dbe..0000000000 --- a/cpp/src/decisiontree/batched-levelalgo/kernels/gamma-float.cu +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2019-2024, NVIDIA CORPORATION. - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "../bins.cuh" -#include "../objectives.cuh" - -#include - -namespace ML { -namespace DT { -using _DataT = float; -using _LabelT = float; -using _IdxT = int; -using _ObjectiveT = GammaObjectiveFunction<_DataT, _LabelT, _IdxT>; -using _BinT = AggregateBin; -using _DatasetT = Dataset<_DataT, _LabelT, _IdxT>; -using _NodeT = SparseTreeNode<_DataT, _LabelT, _IdxT>; -} // namespace DT -} // namespace ML - -#include "builder_kernels_impl.cuh" diff --git a/cpp/src/decisiontree/batched-levelalgo/kernels/gini-double.cu b/cpp/src/decisiontree/batched-levelalgo/kernels/gini-double.cu deleted file mode 100644 index 39d180dc2c..0000000000 --- a/cpp/src/decisiontree/batched-levelalgo/kernels/gini-double.cu +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2019-2024, NVIDIA CORPORATION. - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "../bins.cuh" -#include "../objectives.cuh" - -#include - -namespace ML { -namespace DT { -using _DataT = double; -using _LabelT = int; -using _IdxT = int; -using _ObjectiveT = GiniObjectiveFunction<_DataT, _LabelT, _IdxT>; -using _BinT = CountBin; -using _DatasetT = Dataset<_DataT, _LabelT, _IdxT>; -using _NodeT = SparseTreeNode<_DataT, _LabelT, _IdxT>; -} // namespace DT -} // namespace ML - -#include "builder_kernels_impl.cuh" diff --git a/cpp/src/decisiontree/batched-levelalgo/kernels/gini-float.cu b/cpp/src/decisiontree/batched-levelalgo/kernels/gini-float.cu deleted file mode 100644 index 90d98e682b..0000000000 --- a/cpp/src/decisiontree/batched-levelalgo/kernels/gini-float.cu +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2019-2024, NVIDIA CORPORATION. - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "../bins.cuh" -#include "../objectives.cuh" - -#include - -namespace ML { -namespace DT { -using _DataT = float; -using _LabelT = int; -using _IdxT = int; -using _ObjectiveT = GiniObjectiveFunction<_DataT, _LabelT, _IdxT>; -using _BinT = CountBin; -using _DatasetT = Dataset<_DataT, _LabelT, _IdxT>; -using _NodeT = SparseTreeNode<_DataT, _LabelT, _IdxT>; -} // namespace DT -} // namespace ML - -#include "builder_kernels_impl.cuh" diff --git a/cpp/src/decisiontree/batched-levelalgo/kernels/inverse_gaussian-double.cu b/cpp/src/decisiontree/batched-levelalgo/kernels/inverse_gaussian-double.cu deleted file mode 100644 index 9f2269b97b..0000000000 --- a/cpp/src/decisiontree/batched-levelalgo/kernels/inverse_gaussian-double.cu +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2019-2024, NVIDIA CORPORATION. - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "../bins.cuh" -#include "../objectives.cuh" - -#include - -namespace ML { -namespace DT { -using _DataT = double; -using _LabelT = double; -using _IdxT = int; -using _ObjectiveT = InverseGaussianObjectiveFunction<_DataT, _LabelT, _IdxT>; -using _BinT = AggregateBin; -using _DatasetT = Dataset<_DataT, _LabelT, _IdxT>; -using _NodeT = SparseTreeNode<_DataT, _LabelT, _IdxT>; -} // namespace DT -} // namespace ML - -#include "builder_kernels_impl.cuh" diff --git a/cpp/src/decisiontree/batched-levelalgo/kernels/inverse_gaussian-float.cu b/cpp/src/decisiontree/batched-levelalgo/kernels/inverse_gaussian-float.cu deleted file mode 100644 index 9177ead81a..0000000000 --- a/cpp/src/decisiontree/batched-levelalgo/kernels/inverse_gaussian-float.cu +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2019-2024, NVIDIA CORPORATION. - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "../bins.cuh" -#include "../objectives.cuh" - -#include - -namespace ML { -namespace DT { -using _DataT = float; -using _LabelT = float; -using _IdxT = int; -using _ObjectiveT = InverseGaussianObjectiveFunction<_DataT, _LabelT, _IdxT>; -using _BinT = AggregateBin; -using _DatasetT = Dataset<_DataT, _LabelT, _IdxT>; -using _NodeT = SparseTreeNode<_DataT, _LabelT, _IdxT>; -} // namespace DT -} // namespace ML - -#include "builder_kernels_impl.cuh" diff --git a/cpp/src/decisiontree/batched-levelalgo/kernels/poisson-double.cu b/cpp/src/decisiontree/batched-levelalgo/kernels/poisson-double.cu deleted file mode 100644 index 61e98ac054..0000000000 --- a/cpp/src/decisiontree/batched-levelalgo/kernels/poisson-double.cu +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2019-2024, NVIDIA CORPORATION. - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "../bins.cuh" -#include "../objectives.cuh" - -#include - -namespace ML { -namespace DT { -using _DataT = double; -using _LabelT = double; -using _IdxT = int; -using _ObjectiveT = PoissonObjectiveFunction<_DataT, _LabelT, _IdxT>; -using _BinT = AggregateBin; -using _DatasetT = Dataset<_DataT, _LabelT, _IdxT>; -using _NodeT = SparseTreeNode<_DataT, _LabelT, _IdxT>; -} // namespace DT -} // namespace ML - -#include "builder_kernels_impl.cuh" diff --git a/cpp/src/decisiontree/batched-levelalgo/kernels/poisson-float.cu b/cpp/src/decisiontree/batched-levelalgo/kernels/poisson-float.cu deleted file mode 100644 index 061845915f..0000000000 --- a/cpp/src/decisiontree/batched-levelalgo/kernels/poisson-float.cu +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright (c) 2019-2024, NVIDIA CORPORATION. - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "../bins.cuh" -#include "../objectives.cuh" - -#include - -namespace ML { -namespace DT { -using _DataT = float; -using _LabelT = float; -using _IdxT = int; -using _ObjectiveT = PoissonObjectiveFunction<_DataT, _LabelT, _IdxT>; -using _BinT = AggregateBin; -using _DatasetT = Dataset<_DataT, _LabelT, _IdxT>; -using _NodeT = SparseTreeNode<_DataT, _LabelT, _IdxT>; -} // namespace DT -} // namespace ML - -#include "builder_kernels_impl.cuh" diff --git a/cpp/src/decisiontree/batched-levelalgo/kernels/mse-double.cu b/cpp/src/decisiontree/batched-levelalgo/kernels/regression-double.cu similarity index 77% rename from cpp/src/decisiontree/batched-levelalgo/kernels/mse-double.cu rename to cpp/src/decisiontree/batched-levelalgo/kernels/regression-double.cu index 465cfc072f..44bbeab917 100644 --- a/cpp/src/decisiontree/batched-levelalgo/kernels/mse-double.cu +++ b/cpp/src/decisiontree/batched-levelalgo/kernels/regression-double.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2019-2024, NVIDIA CORPORATION. + * SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION. * SPDX-License-Identifier: Apache-2.0 */ @@ -13,7 +13,7 @@ namespace DT { using _DataT = double; using _LabelT = double; using _IdxT = int; -using _ObjectiveT = MSEObjectiveFunction<_DataT, _LabelT, _IdxT>; +using _ObjectiveT = RegressionObjectiveFunction<_DataT, _LabelT, _IdxT>; using _BinT = AggregateBin; using _DatasetT = Dataset<_DataT, _LabelT, _IdxT>; using _NodeT = SparseTreeNode<_DataT, _LabelT, _IdxT>; diff --git a/cpp/src/decisiontree/batched-levelalgo/kernels/mse-float.cu b/cpp/src/decisiontree/batched-levelalgo/kernels/regression-float.cu similarity index 77% rename from cpp/src/decisiontree/batched-levelalgo/kernels/mse-float.cu rename to cpp/src/decisiontree/batched-levelalgo/kernels/regression-float.cu index 0a0b70dfab..0008a39bca 100644 --- a/cpp/src/decisiontree/batched-levelalgo/kernels/mse-float.cu +++ b/cpp/src/decisiontree/batched-levelalgo/kernels/regression-float.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2019-2024, NVIDIA CORPORATION. + * SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION. * SPDX-License-Identifier: Apache-2.0 */ @@ -13,7 +13,7 @@ namespace DT { using _DataT = float; using _LabelT = float; using _IdxT = int; -using _ObjectiveT = MSEObjectiveFunction<_DataT, _LabelT, _IdxT>; +using _ObjectiveT = RegressionObjectiveFunction<_DataT, _LabelT, _IdxT>; using _BinT = AggregateBin; using _DatasetT = Dataset<_DataT, _LabelT, _IdxT>; using _NodeT = SparseTreeNode<_DataT, _LabelT, _IdxT>; diff --git a/cpp/src/decisiontree/batched-levelalgo/objectives.cuh b/cpp/src/decisiontree/batched-levelalgo/objectives.cuh index 773ab56955..3dbb1360b2 100644 --- a/cpp/src/decisiontree/batched-levelalgo/objectives.cuh +++ b/cpp/src/decisiontree/batched-levelalgo/objectives.cuh @@ -5,10 +5,11 @@ #pragma once +#include "bins.cuh" #include "dataset.h" #include "split.cuh" -#include +#include #include @@ -16,41 +17,35 @@ namespace ML { namespace DT { template -class GiniObjectiveFunction { +class ClassificationObjectiveFunction { public: using DataT = DataT_; using LabelT = LabelT_; using IdxT = IdxT_; + using BinT = CountBin; private: IdxT nclasses; IdxT min_samples_leaf; + CRITERION criterion; - public: - using BinT = CountBin; - GiniObjectiveFunction(IdxT nclasses, IdxT min_samples_leaf) - : nclasses(nclasses), min_samples_leaf(min_samples_leaf) + DI IdxT CountLeft(BinT const* hist, IdxT i, IdxT n_bins) const { + IdxT nLeft = 0; + for (IdxT j = 0; j < nclasses; ++j) { + nLeft += hist[n_bins * j + i].x; + } + return nLeft; } - DI IdxT NumClasses() const { return nclasses; } - - /** - * @brief compute the gini impurity reduction for each split - */ - HDI DataT GainPerSplit(BinT* hist, IdxT i, IdxT n_bins, IdxT len, IdxT nLeft) + HDI DataT GiniGain(BinT const* hist, IdxT i, IdxT n_bins, IdxT len, IdxT nLeft, IdxT nRight) const { - IdxT nRight = len - nLeft; constexpr DataT One = DataT(1.0); auto invLen = One / len; auto invLeft = One / nLeft; auto invRight = One / nRight; auto gain = DataT(0.0); - // if there aren't enough samples in this split, don't bother! - if (nLeft < min_samples_leaf || nRight < min_samples_leaf) - return -std::numeric_limits::max(); - for (IdxT j = 0; j < nclasses; ++j) { double val_i = 0.0; auto lval_i = hist[n_bins * j + i].x; @@ -71,190 +66,88 @@ class GiniObjectiveFunction { return gain; } - DI Split Gain(BinT* shist, DataT* squantiles, IdxT col, IdxT len, IdxT n_bins) + HDI DataT + EntropyGain(BinT const* hist, IdxT i, IdxT n_bins, IdxT len, IdxT nLeft, IdxT nRight) const { - Split sp; - for (IdxT i = threadIdx.x; i < n_bins; i += blockDim.x) { - IdxT nLeft = 0; - for (IdxT j = 0; j < nclasses; ++j) { - nLeft += shist[n_bins * j + i].x; - } - sp.update({squantiles[i], col, GainPerSplit(shist, i, n_bins, len, nLeft), nLeft}); - } - return sp; - } - static DI void SetLeafVector(BinT const* shist, int nclasses, DataT* out) - { - // Output probability - double total = 0.0; - for (int i = 0; i < nclasses; i++) { - total += shist[i].x; - } - for (int i = 0; i < nclasses; i++) { - out[i] = DataT(shist[i].x) / total; - } - } -}; - -template -class EntropyObjectiveFunction { - public: - using DataT = DataT_; - using LabelT = LabelT_; - using IdxT = IdxT_; - - private: - IdxT nclasses; - IdxT min_samples_leaf; - - public: - using BinT = CountBin; - EntropyObjectiveFunction(IdxT nclasses, IdxT min_samples_leaf) - : nclasses(nclasses), min_samples_leaf(min_samples_leaf) - { - } - DI IdxT NumClasses() const { return nclasses; } - - /** - * @brief compute the Entropy (or information gain) for each split - */ - HDI DataT GainPerSplit(BinT const* hist, IdxT i, IdxT n_bins, IdxT len, IdxT nLeft) - { - IdxT nRight{len - nLeft}; auto gain{DataT(0.0)}; - // if there aren't enough samples in this split, don't bother! - if (nLeft < min_samples_leaf || nRight < min_samples_leaf) { - return -std::numeric_limits::max(); - } else { - auto invLeft{DataT(1.0) / nLeft}; - auto invRight{DataT(1.0) / nRight}; - auto invLen{DataT(1.0) / len}; - for (IdxT c = 0; c < nclasses; ++c) { - double val_i = 0.0; - auto lval_i = hist[n_bins * c + i].x; - if (lval_i != 0) { - auto lval = DataT(lval_i); - gain += raft::log(lval * invLeft) / raft::log(DataT(2)) * lval * invLen; - } - - val_i += lval_i; - auto total_sum = hist[n_bins * c + n_bins - 1].x; - auto rval_i = total_sum - lval_i; - if (rval_i != 0) { - auto rval = DataT(rval_i); - gain += raft::log(rval * invRight) / raft::log(DataT(2)) * rval * invLen; - } - - val_i += rval_i; - if (val_i != 0) { - auto val = DataT(val_i) * invLen; - gain -= val * raft::log(val) / raft::log(DataT(2)); - } + auto invLeft{DataT(1.0) / nLeft}; + auto invRight{DataT(1.0) / nRight}; + auto invLen{DataT(1.0) / len}; + for (IdxT c = 0; c < nclasses; ++c) { + double val_i = 0.0; + auto lval_i = hist[n_bins * c + i].x; + if (lval_i != 0) { + auto lval = DataT(lval_i); + gain += raft::log(lval * invLeft) / raft::log(DataT(2)) * lval * invLen; } - return gain; - } - } + val_i += lval_i; + auto total_sum = hist[n_bins * c + n_bins - 1].x; + auto rval_i = total_sum - lval_i; + if (rval_i != 0) { + auto rval = DataT(rval_i); + gain += raft::log(rval * invRight) / raft::log(DataT(2)) * rval * invLen; + } - DI Split Gain(BinT* scdf_labels, DataT* squantiles, IdxT col, IdxT len, IdxT n_bins) - { - Split sp; - for (IdxT i = threadIdx.x; i < n_bins; i += blockDim.x) { - IdxT nLeft = 0; - for (IdxT j = 0; j < nclasses; ++j) { - nLeft += scdf_labels[n_bins * j + i].x; + val_i += rval_i; + if (val_i != 0) { + auto val = DataT(val_i) * invLen; + gain -= val * raft::log(val) / raft::log(DataT(2)); } - sp.update({squantiles[i], col, GainPerSplit(scdf_labels, i, n_bins, len, nLeft), nLeft}); } - return sp; - } - static DI void SetLeafVector(BinT const* shist, int nclasses, DataT* out) - { - // Output probability - double total = 0.0; - for (int i = 0; i < nclasses; i++) { - total += shist[i].x; - } - for (int i = 0; i < nclasses; i++) { - out[i] = DataT(shist[i].x) / total; - } - } -}; -template -class MSEObjectiveFunction { - public: - using DataT = DataT_; - using LabelT = LabelT_; - using IdxT = IdxT_; - using BinT = AggregateBin; - - private: - IdxT min_samples_leaf; + return gain; + } public: - HDI MSEObjectiveFunction(IdxT nclasses, IdxT min_samples_leaf) - : min_samples_leaf(min_samples_leaf) + HDI DataT + GainPerSplit(BinT const* hist, IdxT i, IdxT n_bins, IdxT len, IdxT nLeft, IdxT nRight) const { + switch (criterion) { + case CRITERION::GINI: return GiniGain(hist, i, n_bins, len, nLeft, nRight); + case CRITERION::ENTROPY: return EntropyGain(hist, i, n_bins, len, nLeft, nRight); + default: return -std::numeric_limits::max(); + } } - /** - * @brief compute the Mean squared error impurity reduction (or purity gain) for each split - * - * @note This method is used to speed up the search for the best split - * by calculating the gain using a proxy mean squared error reduction. - * It is a proxy quantity such that the split that maximizes this value - * also maximizes the impurity improvement. It neglects all constant terms - * of the impurity decrease for a given split. - * The Gain is the difference in the proxy impurities of the parent and the - * weighted sum of impurities of its children - * and is mathematically equivalent to the respective differences of - * mean-squared errors. - */ - HDI DataT GainPerSplit(BinT const* hist, IdxT i, IdxT n_bins, IdxT len, IdxT nLeft) const + HDI ClassificationObjectiveFunction(IdxT nclasses, IdxT min_samples_leaf, CRITERION criterion) + : nclasses(nclasses), min_samples_leaf(min_samples_leaf), criterion(criterion) { - auto gain{DataT(0)}; - IdxT nRight{len - nLeft}; - auto invLen = DataT(1.0) / len; - // if there aren't enough samples in this split, don't bother! - if (nLeft < min_samples_leaf || nRight < min_samples_leaf) { - return -std::numeric_limits::max(); - } else { - auto label_sum = hist[n_bins - 1].label_sum; - DataT parent_obj = -label_sum * label_sum * invLen; - DataT left_obj = -(hist[i].label_sum * hist[i].label_sum) / nLeft; - DataT right_label_sum = hist[i].label_sum - label_sum; - DataT right_obj = -(right_label_sum * right_label_sum) / nRight; - gain = parent_obj - (left_obj + right_obj); - gain *= DataT(0.5) * invLen; - - return gain; - } } + DI IdxT NumClasses() const { return nclasses; } + DI Split Gain( BinT const* shist, DataT const* squantiles, IdxT col, IdxT len, IdxT n_bins) const { Split sp; for (IdxT i = threadIdx.x; i < n_bins; i += blockDim.x) { - auto nLeft = shist[i].count; - sp.update({squantiles[i], col, GainPerSplit(shist, i, n_bins, len, nLeft), nLeft}); + auto nLeft = CountLeft(shist, i, n_bins); + auto nRight = len - nLeft; + auto gain = -std::numeric_limits::max(); + if (nLeft >= min_samples_leaf && nRight >= min_samples_leaf) { + gain = GainPerSplit(shist, i, n_bins, len, nLeft, nRight); + } + sp.update({squantiles[i], col, gain, nLeft}); } return sp; } - DI IdxT NumClasses() const { return 1; } - static DI void SetLeafVector(BinT const* shist, int nclasses, DataT* out) { + // Output probability + double total = 0.0; for (int i = 0; i < nclasses; i++) { - out[i] = shist[i].label_sum / shist[i].count; + total += shist[i].x; + } + for (int i = 0; i < nclasses; i++) { + out[i] = DataT(shist[i].x) / total; } } }; template -class PoissonObjectiveFunction { +class RegressionObjectiveFunction { public: using DataT = DataT_; using LabelT = LabelT_; @@ -263,38 +156,27 @@ class PoissonObjectiveFunction { private: IdxT min_samples_leaf; - - public: + CRITERION criterion; static constexpr auto eps_ = 10 * std::numeric_limits::epsilon(); - HDI PoissonObjectiveFunction(IdxT nclasses, IdxT min_samples_leaf) - : min_samples_leaf(min_samples_leaf) + HDI DataT MSEGain(BinT const* hist, IdxT i, IdxT n_bins, IdxT len, IdxT nLeft, IdxT nRight) const { + auto invLen = DataT(1.0) / len; + auto label_sum = hist[n_bins - 1].label_sum; + DataT parent_obj = -label_sum * label_sum * invLen; + DataT left_obj = -(hist[i].label_sum * hist[i].label_sum) / nLeft; + DataT right_label_sum = hist[i].label_sum - label_sum; + DataT right_obj = -(right_label_sum * right_label_sum) / nRight; + DataT gain = parent_obj - (left_obj + right_obj); + gain *= DataT(0.5) * invLen; + + return gain; } - /** - * @brief compute the poisson impurity reduction (or purity gain) for each split - * - * @note This method is used to speed up the search for the best split - * by calculating the gain using a proxy poisson half deviance reduction. - * It is a proxy quantity such that the split that maximizes this value - * also maximizes the impurity improvement. It neglects all constant terms - * of the impurity decrease for a given split. - * The Gain is the difference in the proxy impurities of the parent and the - * weighted sum of impurities of its children - * and is mathematically equivalent to the respective differences of - * poisson half deviances. - */ - HDI DataT GainPerSplit(BinT const* hist, IdxT i, IdxT n_bins, IdxT len, IdxT nLeft) const + HDI DataT + PoissonGain(BinT const* hist, IdxT i, IdxT n_bins, IdxT len, IdxT nLeft, IdxT nRight) const { - // get the lens' - IdxT nRight = len - nLeft; - auto invLen = DataT(1) / len; - - // if there aren't enough samples in this split, don't bother! - if (nLeft < min_samples_leaf || nRight < min_samples_leaf) - return -std::numeric_limits::max(); - + auto invLen = DataT(1) / len; auto label_sum = hist[n_bins - 1].label_sum; auto left_label_sum = (hist[i].label_sum); auto right_label_sum = (hist[n_bins - 1].label_sum - hist[i].label_sum); @@ -303,7 +185,6 @@ class PoissonObjectiveFunction { if (label_sum < eps_ || left_label_sum < eps_ || right_label_sum < eps_) return -std::numeric_limits::max(); - // compute the gain to be DataT parent_obj = -label_sum * raft::log(label_sum * invLen); DataT left_obj = -left_label_sum * raft::log(left_label_sum / nLeft); DataT right_obj = -right_label_sum * raft::log(right_label_sum / nRight); @@ -313,76 +194,18 @@ class PoissonObjectiveFunction { return gain; } - DI Split Gain( - BinT const* shist, DataT const* squantiles, IdxT col, IdxT len, IdxT n_bins) const + HDI DataT + GammaGain(BinT const* hist, IdxT i, IdxT n_bins, IdxT len, IdxT nLeft, IdxT nRight) const { - Split sp; - for (IdxT i = threadIdx.x; i < n_bins; i += blockDim.x) { - auto nLeft = shist[i].count; - sp.update({squantiles[i], col, GainPerSplit(shist, i, n_bins, len, nLeft), nLeft}); - } - return sp; - } - - DI IdxT NumClasses() const { return 1; } - - static DI void SetLeafVector(BinT const* shist, int nclasses, DataT* out) - { - for (int i = 0; i < nclasses; i++) { - out[i] = shist[i].label_sum / shist[i].count; - } - } -}; - -template -class GammaObjectiveFunction { - public: - using DataT = DataT_; - using LabelT = LabelT_; - using IdxT = IdxT_; - using BinT = AggregateBin; - static constexpr auto eps_ = 10 * std::numeric_limits::epsilon(); - - private: - IdxT min_samples_leaf; - - public: - HDI GammaObjectiveFunction(IdxT nclasses, IdxT min_samples_leaf) - : min_samples_leaf{min_samples_leaf} - { - } - - /** - * @brief compute the gamma impurity reduction (or purity gain) for each split - * - * @note This method is used to speed up the search for the best split - * by calculating the gain using a proxy gamma half deviance reduction. - * It is a proxy quantity such that the split that maximizes this value - * also maximizes the impurity improvement. It neglects all constant terms - * of the impurity decrease for a given split. - * The Gain is the difference in the proxy impurities of the parent and the - * weighted sum of impurities of its children - * and is mathematically equivalent to the respective differences of - * gamma half deviances. - */ - HDI DataT GainPerSplit(BinT const* hist, IdxT i, IdxT n_bins, IdxT len, IdxT nLeft) const - { - IdxT nRight = len - nLeft; - auto invLen = DataT(1) / len; - - // if there aren't enough samples in this split, don't bother! - if (nLeft < min_samples_leaf || nRight < min_samples_leaf) - return -std::numeric_limits::max(); - - DataT label_sum = hist[n_bins - 1].label_sum; - DataT left_label_sum = (hist[i].label_sum); - DataT right_label_sum = (hist[n_bins - 1].label_sum - hist[i].label_sum); + auto invLen = DataT(1) / len; + auto label_sum = hist[n_bins - 1].label_sum; + auto left_label_sum = (hist[i].label_sum); + auto right_label_sum = (hist[n_bins - 1].label_sum - hist[i].label_sum); // label sum cannot be non-positive if (label_sum < eps_ || left_label_sum < eps_ || right_label_sum < eps_) return -std::numeric_limits::max(); - // compute the gain to be DataT parent_obj = len * raft::log(label_sum * invLen); DataT left_obj = nLeft * raft::log(left_label_sum / nLeft); DataT right_obj = nRight * raft::log(right_label_sum / nRight); @@ -392,66 +215,9 @@ class GammaObjectiveFunction { return gain; } - DI Split Gain( - BinT const* shist, DataT const* squantiles, IdxT col, IdxT len, IdxT n_bins) const - { - Split sp; - for (IdxT i = threadIdx.x; i < n_bins; i += blockDim.x) { - auto nLeft = shist[i].count; - sp.update({squantiles[i], col, GainPerSplit(shist, i, n_bins, len, nLeft), nLeft}); - } - return sp; - } - DI IdxT NumClasses() const { return 1; } - - static DI void SetLeafVector(BinT const* shist, int nclasses, DataT* out) + HDI DataT InverseGaussianGain( + BinT const* hist, IdxT i, IdxT n_bins, IdxT len, IdxT nLeft, IdxT nRight) const { - for (int i = 0; i < nclasses; i++) { - out[i] = shist[i].label_sum / shist[i].count; - } - } -}; - -template -class InverseGaussianObjectiveFunction { - public: - using DataT = DataT_; - using LabelT = LabelT_; - using IdxT = IdxT_; - using BinT = AggregateBin; - static constexpr auto eps_ = 10 * std::numeric_limits::epsilon(); - - private: - IdxT min_samples_leaf; - - public: - HDI InverseGaussianObjectiveFunction(IdxT nclasses, IdxT min_samples_leaf) - : min_samples_leaf{min_samples_leaf} - { - } - - /** - * @brief compute the inverse gaussian impurity reduction (or purity gain) for each split - * - * @note This method is used to speed up the search for the best split - * by calculating the gain using a proxy inverse gaussian half deviance reduction. - * It is a proxy quantity such that the split that maximizes this value - * also maximizes the impurity improvement. It neglects all constant terms - * of the impurity decrease for a given split. - * The Gain is the difference in the proxy impurities of the parent and the - * weighted sum of impurities of its children - * and is mathematically equivalent to the respective differences of - * inverse gaussian deviances. - */ - HDI DataT GainPerSplit(BinT const* hist, IdxT i, IdxT n_bins, IdxT len, IdxT nLeft) const - { - // get the lens' - IdxT nRight = len - nLeft; - - // if there aren't enough samples in this split, don't bother! - if (nLeft < min_samples_leaf || nRight < min_samples_leaf) - return -std::numeric_limits::max(); - auto label_sum = hist[n_bins - 1].label_sum; auto left_label_sum = (hist[i].label_sum); auto right_label_sum = (hist[n_bins - 1].label_sum - hist[i].label_sum); @@ -460,7 +226,6 @@ class InverseGaussianObjectiveFunction { if (label_sum < eps_ || left_label_sum < eps_ || right_label_sum < eps_) return -std::numeric_limits::max(); - // compute the gain to be DataT parent_obj = -DataT(len) * DataT(len) / label_sum; DataT left_obj = -DataT(nLeft) * DataT(nLeft) / left_label_sum; DataT right_obj = -DataT(nRight) * DataT(nRight) / right_label_sum; @@ -470,17 +235,42 @@ class InverseGaussianObjectiveFunction { return gain; } + public: + HDI DataT + GainPerSplit(BinT const* hist, IdxT i, IdxT n_bins, IdxT len, IdxT nLeft, IdxT nRight) const + { + switch (criterion) { + case CRITERION::MSE: return MSEGain(hist, i, n_bins, len, nLeft, nRight); + case CRITERION::POISSON: return PoissonGain(hist, i, n_bins, len, nLeft, nRight); + case CRITERION::GAMMA: return GammaGain(hist, i, n_bins, len, nLeft, nRight); + case CRITERION::INVERSE_GAUSSIAN: + return InverseGaussianGain(hist, i, n_bins, len, nLeft, nRight); + default: return -std::numeric_limits::max(); + } + } + + HDI RegressionObjectiveFunction(IdxT, IdxT min_samples_leaf, CRITERION criterion) + : min_samples_leaf(min_samples_leaf), criterion(criterion) + { + } + + DI IdxT NumClasses() const { return 1; } + DI Split Gain( BinT const* shist, DataT const* squantiles, IdxT col, IdxT len, IdxT n_bins) const { Split sp; for (IdxT i = threadIdx.x; i < n_bins; i += blockDim.x) { - auto nLeft = shist[i].count; - sp.update({squantiles[i], col, GainPerSplit(shist, i, n_bins, len, nLeft), nLeft}); + auto nLeft = shist[i].count; + auto nRight = len - nLeft; + auto gain = -std::numeric_limits::max(); + if (nLeft >= min_samples_leaf && nRight >= min_samples_leaf) { + gain = GainPerSplit(shist, i, n_bins, len, nLeft, nRight); + } + sp.update({squantiles[i], col, gain, nLeft}); } return sp; } - DI IdxT NumClasses() const { return 1; } static DI void SetLeafVector(BinT const* shist, int nclasses, DataT* out) { diff --git a/cpp/src/decisiontree/decisiontree.cuh b/cpp/src/decisiontree/decisiontree.cuh index 796f0109d5..e521eb62f1 100644 --- a/cpp/src/decisiontree/decisiontree.cuh +++ b/cpp/src/decisiontree/decisiontree.cuh @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2019-2025, NVIDIA CORPORATION. + * SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION. * SPDX-License-Identifier: Apache-2.0 */ @@ -252,93 +252,39 @@ class DecisionTree { params.split_criterion = default_criterion; } using IdxT = int; - // Dispatch objective - if (not std::is_same::value and params.split_criterion == CRITERION::GINI) { - return Builder>(handle, - s, - treeid, - seed, - params, - data, - labels, - nrows, - ncols, - row_ids, - unique_labels, - quantiles) - .train(); - } else if (not std::is_same::value and - params.split_criterion == CRITERION::ENTROPY) { - return Builder>(handle, - s, - treeid, - seed, - params, - data, - labels, - nrows, - ncols, - row_ids, - unique_labels, - quantiles) - .train(); - } else if (std::is_same::value and params.split_criterion == CRITERION::MSE) { - return Builder>(handle, - s, - treeid, - seed, - params, - data, - labels, - nrows, - ncols, - row_ids, - unique_labels, - quantiles) - .train(); - } else if (std::is_same::value and - params.split_criterion == CRITERION::POISSON) { - return Builder>(handle, - s, - treeid, - seed, - params, - data, - labels, - nrows, - ncols, - row_ids, - unique_labels, - quantiles) - .train(); - } else if (std::is_same::value and params.split_criterion == CRITERION::GAMMA) { - return Builder>(handle, - s, - treeid, - seed, - params, - data, - labels, - nrows, - ncols, - row_ids, - unique_labels, - quantiles) + // Dispatch objective family. The objective object switches on the criterion at runtime. + if (not std::is_same::value and (params.split_criterion == CRITERION::GINI || + params.split_criterion == CRITERION::ENTROPY)) { + return Builder>(handle, + s, + treeid, + seed, + params, + data, + labels, + nrows, + ncols, + row_ids, + unique_labels, + quantiles) .train(); } else if (std::is_same::value and - params.split_criterion == CRITERION::INVERSE_GAUSSIAN) { - return Builder>(handle, - s, - treeid, - seed, - params, - data, - labels, - nrows, - ncols, - row_ids, - unique_labels, - quantiles) + (params.split_criterion == CRITERION::MSE || + params.split_criterion == CRITERION::POISSON || + params.split_criterion == CRITERION::GAMMA || + params.split_criterion == CRITERION::INVERSE_GAUSSIAN)) { + return Builder>(handle, + s, + treeid, + seed, + params, + data, + labels, + nrows, + ncols, + row_ids, + unique_labels, + quantiles) .train(); } else { ASSERT(false, "Unknown split criterion."); diff --git a/cpp/tests/sg/rf_test.cu b/cpp/tests/sg/rf_test.cu index adc2692ae1..955d14585d 100644 --- a/cpp/tests/sg/rf_test.cu +++ b/cpp/tests/sg/rf_test.cu @@ -1089,13 +1089,22 @@ struct ObjectiveTestParameters { double tolerance; }; -template +template +struct ObjectiveTestConfig { + using ObjectiveT = ObjectiveT_; + static constexpr CRITERION splitCriteria = Criterion_; +}; + +template class ObjectiveTest : public ::testing::TestWithParam { + using ObjectiveT = typename ObjectiveConfig::ObjectiveT; typedef typename ObjectiveT::DataT DataT; typedef typename ObjectiveT::LabelT LabelT; typedef typename ObjectiveT::IdxT IdxT; typedef typename ObjectiveT::BinT BinT; + static constexpr auto eps_ = 10 * std::numeric_limits::epsilon(); + ObjectiveTestParameters params; public: @@ -1220,9 +1229,8 @@ class ObjectiveTest : public ::testing::TestWithParam { (n_right / n) * right_ighd); // gain in long form without proxy // edge cases - if (n_left < params.min_samples_leaf or n_right < params.min_samples_leaf or - label_sum < ObjectiveT::eps_ or label_sum_right < ObjectiveT::eps_ or - label_sum_left < ObjectiveT::eps_) + if (n_left < params.min_samples_leaf or n_right < params.min_samples_leaf or label_sum < eps_ or + label_sum_right < eps_ or label_sum_left < eps_) return -std::numeric_limits::max(); else return gain; @@ -1260,9 +1268,8 @@ class ObjectiveTest : public ::testing::TestWithParam { (n_right / n) * right_ghd); // gain in long form without proxy // edge cases - if (n_left < params.min_samples_leaf or n_right < params.min_samples_leaf or - label_sum < ObjectiveT::eps_ or label_sum_right < ObjectiveT::eps_ or - label_sum_left < ObjectiveT::eps_) + if (n_left < params.min_samples_leaf or n_right < params.min_samples_leaf or label_sum < eps_ or + label_sum_right < eps_ or label_sum_left < eps_) return -std::numeric_limits::max(); else return gain; @@ -1298,9 +1305,8 @@ class ObjectiveTest : public ::testing::TestWithParam { (n_right / n) * right_phd); // gain in long form without proxy // edge cases - if (n_left < params.min_samples_leaf or n_right < params.min_samples_leaf or - label_sum < ObjectiveT::eps_ or label_sum_right < ObjectiveT::eps_ or - label_sum_left < ObjectiveT::eps_) + if (n_left < params.min_samples_leaf or n_right < params.min_samples_leaf or label_sum < eps_ or + label_sum_right < eps_ or label_sum_left < eps_) return -std::numeric_limits::max(); else return gain; @@ -1383,30 +1389,17 @@ class ObjectiveTest : public ::testing::TestWithParam { auto GroundTruthGain(std::vector const& data, std::size_t const split_bin_index) { - if constexpr (std::is_same>:: - value) // mean squared error - { + if constexpr (ObjectiveConfig::splitCriteria == CRITERION::MSE) { return MSEGroundTruthGain(data, split_bin_index); - } else if constexpr (std::is_same>:: - value) // poisson - { + } else if constexpr (ObjectiveConfig::splitCriteria == CRITERION::POISSON) { return PoissonGroundTruthGain(data, split_bin_index); - } else if constexpr (std::is_same>::value) // gamma - { + } else if constexpr (ObjectiveConfig::splitCriteria == CRITERION::GAMMA) { return GammaGroundTruthGain(data, split_bin_index); - } else if constexpr (std::is_same>:: - value) // inverse gaussian - { + } else if constexpr (ObjectiveConfig::splitCriteria == CRITERION::INVERSE_GAUSSIAN) { return InverseGaussianGroundTruthGain(data, split_bin_index); - } else if constexpr (std::is_same>:: - value) // entropy - { + } else if constexpr (ObjectiveConfig::splitCriteria == CRITERION::ENTROPY) { return EntropyGroundTruthGain(data, split_bin_index); - } else if constexpr (std::is_same>::value) // gini - { + } else if constexpr (ObjectiveConfig::splitCriteria == CRITERION::GINI) { return GiniGroundTruthGain(data, split_bin_index); } return DataT(0.0); @@ -1429,20 +1422,19 @@ class ObjectiveTest : public ::testing::TestWithParam { void SetUp() override { - srand(params.seed); params = ::testing::TestWithParam::GetParam(); - ObjectiveT objective(params.n_classes, params.min_samples_leaf); + srand(params.seed); + ObjectiveT objective(params.n_classes, params.min_samples_leaf, ObjectiveConfig::splitCriteria); auto data = GenRandomData(); auto [cdf_hist, pdf_hist] = GenHist(data); auto split_bin_index = RandUnder(params.max_n_bins); auto ground_truth_gain = GroundTruthGain(data, split_bin_index); + auto len = NumLeftOfBin(cdf_hist, params.max_n_bins - 1); + auto nLeft = NumLeftOfBin(cdf_hist, split_bin_index); - auto hypothesis_gain = objective.GainPerSplit(&cdf_hist[0], - split_bin_index, - params.max_n_bins, - NumLeftOfBin(cdf_hist, params.max_n_bins - 1), - NumLeftOfBin(cdf_hist, split_bin_index)); + auto hypothesis_gain = objective.GainPerSplit( + &cdf_hist[0], split_bin_index, params.max_n_bins, len, nLeft, len - nLeft); // The gain may actually be NaN. If so, a comparison between the result and // ground truth would yield false, even if they are both (correctly) NaNs. @@ -1495,49 +1487,63 @@ const std::vector gini_objective_test_parameters = { }; // mse objective test -typedef ObjectiveTest> MSEObjectiveTestD; +typedef ObjectiveTest< + ObjectiveTestConfig, CRITERION::MSE>> + MSEObjectiveTestD; TEST_P(MSEObjectiveTestD, MSEObjectiveTest) {} INSTANTIATE_TEST_CASE_P(RfTests, MSEObjectiveTestD, ::testing::ValuesIn(mse_objective_test_parameters)); -typedef ObjectiveTest> MSEObjectiveTestF; +typedef ObjectiveTest< + ObjectiveTestConfig, CRITERION::MSE>> + MSEObjectiveTestF; TEST_P(MSEObjectiveTestF, MSEObjectiveTest) {} INSTANTIATE_TEST_CASE_P(RfTests, MSEObjectiveTestF, ::testing::ValuesIn(mse_objective_test_parameters)); // poisson objective test -typedef ObjectiveTest> PoissonObjectiveTestD; +typedef ObjectiveTest< + ObjectiveTestConfig, CRITERION::POISSON>> + PoissonObjectiveTestD; TEST_P(PoissonObjectiveTestD, poissonObjectiveTest) {} INSTANTIATE_TEST_CASE_P(RfTests, PoissonObjectiveTestD, ::testing::ValuesIn(poisson_objective_test_parameters)); -typedef ObjectiveTest> PoissonObjectiveTestF; +typedef ObjectiveTest< + ObjectiveTestConfig, CRITERION::POISSON>> + PoissonObjectiveTestF; TEST_P(PoissonObjectiveTestF, poissonObjectiveTest) {} INSTANTIATE_TEST_CASE_P(RfTests, PoissonObjectiveTestF, ::testing::ValuesIn(poisson_objective_test_parameters)); // gamma objective test -typedef ObjectiveTest> GammaObjectiveTestD; +typedef ObjectiveTest< + ObjectiveTestConfig, CRITERION::GAMMA>> + GammaObjectiveTestD; TEST_P(GammaObjectiveTestD, GammaObjectiveTest) {} INSTANTIATE_TEST_CASE_P(RfTests, GammaObjectiveTestD, ::testing::ValuesIn(gamma_objective_test_parameters)); -typedef ObjectiveTest> GammaObjectiveTestF; +typedef ObjectiveTest< + ObjectiveTestConfig, CRITERION::GAMMA>> + GammaObjectiveTestF; TEST_P(GammaObjectiveTestF, GammaObjectiveTest) {} INSTANTIATE_TEST_CASE_P(RfTests, GammaObjectiveTestF, ::testing::ValuesIn(gamma_objective_test_parameters)); // InvGauss objective test -typedef ObjectiveTest> +typedef ObjectiveTest, + CRITERION::INVERSE_GAUSSIAN>> InverseGaussianObjectiveTestD; TEST_P(InverseGaussianObjectiveTestD, InverseGaussianObjectiveTest) {} INSTANTIATE_TEST_CASE_P(RfTests, InverseGaussianObjectiveTestD, ::testing::ValuesIn(invgauss_objective_test_parameters)); -typedef ObjectiveTest> +typedef ObjectiveTest< + ObjectiveTestConfig, CRITERION::INVERSE_GAUSSIAN>> InverseGaussianObjectiveTestF; TEST_P(InverseGaussianObjectiveTestF, InverseGaussianObjectiveTest) {} INSTANTIATE_TEST_CASE_P(RfTests, @@ -1545,24 +1551,32 @@ INSTANTIATE_TEST_CASE_P(RfTests, ::testing::ValuesIn(invgauss_objective_test_parameters)); // entropy objective test -typedef ObjectiveTest> EntropyObjectiveTestD; +typedef ObjectiveTest< + ObjectiveTestConfig, CRITERION::ENTROPY>> + EntropyObjectiveTestD; TEST_P(EntropyObjectiveTestD, entropyObjectiveTest) {} INSTANTIATE_TEST_CASE_P(RfTests, EntropyObjectiveTestD, ::testing::ValuesIn(entropy_objective_test_parameters)); -typedef ObjectiveTest> EntropyObjectiveTestF; +typedef ObjectiveTest< + ObjectiveTestConfig, CRITERION::ENTROPY>> + EntropyObjectiveTestF; TEST_P(EntropyObjectiveTestF, entropyObjectiveTest) {} INSTANTIATE_TEST_CASE_P(RfTests, EntropyObjectiveTestF, ::testing::ValuesIn(entropy_objective_test_parameters)); // gini objective test -typedef ObjectiveTest> GiniObjectiveTestD; +typedef ObjectiveTest< + ObjectiveTestConfig, CRITERION::GINI>> + GiniObjectiveTestD; TEST_P(GiniObjectiveTestD, giniObjectiveTest) {} INSTANTIATE_TEST_CASE_P(RfTests, GiniObjectiveTestD, ::testing::ValuesIn(gini_objective_test_parameters)); -typedef ObjectiveTest> GiniObjectiveTestF; +typedef ObjectiveTest< + ObjectiveTestConfig, CRITERION::GINI>> + GiniObjectiveTestF; TEST_P(GiniObjectiveTestF, giniObjectiveTest) {} INSTANTIATE_TEST_CASE_P(RfTests, GiniObjectiveTestF,