diff --git a/onnxruntime/core/providers/cpu/ml/tree_ensemble_classifier.cc b/onnxruntime/core/providers/cpu/ml/tree_ensemble_classifier.cc index 13a62400e7..4c7ac2b327 100644 --- a/onnxruntime/core/providers/cpu/ml/tree_ensemble_classifier.cc +++ b/onnxruntime/core/providers/cpu/ml/tree_ensemble_classifier.cc @@ -293,58 +293,6 @@ void TreeEnsembleClassifier::Initialize() { base_values_.size() == weights_classes_.size()); } -void get_max_weight(const std::map& classes, int64_t& maxclass, float& maxweight) { - maxclass = -1; - maxweight = 0.f; - for (auto& classe : classes) { - if (maxclass == -1 || classe.second > maxweight) { - maxclass = classe.first; - maxweight = classe.second; - } - } -} - -void get_weight_class_positive(std::map& classes, float& pos_weight) { - auto it_classes = classes.find(1); - pos_weight = it_classes == classes.end() - ? (classes.size() > 0 ? classes[0] : 0.f) // only 1 class - : it_classes->second; -} - -template -void _set_score_binary(int64_t i, LabelType* y_data, int& write_additional_scores, - bool weights_are_all_positive_, - std::map& classes, - const std::vector& classes_labels_, - const std::set& weights_classes_, - LabelType positive_label, LabelType negative_label) { - float pos_weight; - get_weight_class_positive(classes, pos_weight); - if (classes_labels_.size() == 2 && weights_classes_.size() == 1) { - if (weights_are_all_positive_) { - if (pos_weight > 0.5) { - y_data[i] = classes_labels_[1]; // positive label - write_additional_scores = 0; - } else { - y_data[i] = classes_labels_[0]; // negative label - write_additional_scores = 1; - } - } else { - if (pos_weight > 0) { - y_data[i] = classes_labels_[1]; // positive label - write_additional_scores = 2; - } else { - y_data[i] = classes_labels_[0]; // negative label - write_additional_scores = 3; - } - } - } else if (pos_weight > 0) { - y_data[i] = positive_label; // positive label - } else { - y_data[i] = negative_label; // negative label - } -} - template common::Status TreeEnsembleClassifier::Compute(OpKernelContext* context) const { const Tensor& X = *context->Input(0); @@ -358,41 +306,37 @@ common::Status TreeEnsembleClassifier::Compute(OpKernelContext* context) cons int64_t N = x_dims.size() == 1 ? 1 : x_dims[0]; Tensor* Y = context->Output(0, TensorShape({N})); auto* Z = context->Output(1, TensorShape({N, class_count_})); + + int64_t zindex = 0; const T* x_data = X.template Data(); - common::Status status; -#ifdef USE_OPENMP -#pragma omp parallel for -#endif + // for each class + std::vector scores; + scores.reserve(class_count_); for (int64_t i = 0; i < N; ++i) { - int64_t zindex = i * class_count_; - std::vector scores; + scores.clear(); int64_t current_weight_0 = i * stride; std::map classes; + // fill in base values, this might be empty but that is ok + for (int64_t k = 0, end = static_cast(base_values_.size()); k < end; ++k) { + auto p1 = std::make_pair(k, base_values_[k]); + classes.insert(p1); + } // walk each tree from its root for (size_t j = 0, end = roots_.size(); j < end; ++j) { - auto process_status = ProcessTreeNode(classes, roots_[j], x_data, current_weight_0); - if (!process_status.IsOK()) { - status = process_status; - } + ORT_RETURN_IF_ERROR(ProcessTreeNode(classes, roots_[j], x_data, current_weight_0)); } float maxweight = 0.f; int64_t maxclass = -1; // write top class int write_additional_scores = -1; if (class_count_ > 2) { - // add base values - std::map::iterator it_classes; - for (int64_t k = 0, end = static_cast(base_values_.size()); k < end; ++k) { - it_classes = classes.find(k); - if (it_classes == classes.end()) { - auto p1 = std::make_pair(k, base_values_[k]); - classes.insert(p1); - } else { - it_classes->second += base_values_[k]; + for (auto& classe : classes) { + if (maxclass == -1 || classe.second > maxweight) { + maxclass = classe.first; + maxweight = classe.second; } } - get_max_weight(classes, maxclass, maxweight); if (using_strings_) { Y->template MutableData()[i] = classlabels_strings_[maxclass]; } else { @@ -400,32 +344,68 @@ common::Status TreeEnsembleClassifier::Compute(OpKernelContext* context) cons } } else // binary case { - if (base_values_.size() == 2) { - // add base values - std::map::iterator it_classes; - it_classes = classes.find(1); - if (it_classes == classes.end()) { - // base_value_[0] is not used. It assumes base_value[0] == base_value[1] in this case. - // The specification does not forbid it but does not say what the output should be in that case. - std::map::iterator it_classes0 = classes.find(0); - classes[1] = base_values_[1] + it_classes0->second; - it_classes0->second = -classes[1]; - } else { - // binary as multiclass - it_classes->second += base_values_[1]; - classes[0] += base_values_[0]; - } - } + maxweight = !classes.empty() ? classes[0] : 0.f; // only 1 class if (using_strings_) { - _set_score_binary(i, Y->template MutableData(), - write_additional_scores, weights_are_all_positive_, - classes, classlabels_strings_, - weights_classes_, "1", "0"); + auto* y_data = Y->template MutableData(); + if (classlabels_strings_.size() == 2 && + weights_are_all_positive_ && + maxweight > 0.5 && + weights_classes_.size() == 1) { + y_data[i] = classlabels_strings_[1]; // positive label + write_additional_scores = 0; + } else if (classlabels_strings_.size() == 2 && + weights_are_all_positive_ && + maxweight <= 0.5 && + weights_classes_.size() == 1) { + y_data[i] = classlabels_strings_[0]; // negative label + write_additional_scores = 1; + } else if (classlabels_strings_.size() == 2 && + maxweight > 0 && + !weights_are_all_positive_ && weights_classes_.size() == 1) { + y_data[i] = classlabels_strings_[1]; // pos label + write_additional_scores = 2; + } else if (classlabels_strings_.size() == 2 && + maxweight <= 0 && + !weights_are_all_positive_ && + weights_classes_.size() == 1) { + y_data[i] = classlabels_strings_[0]; // neg label + write_additional_scores = 3; + } else if (maxweight > 0) { + y_data[i] = "1"; // positive label + } else { + y_data[i] = "0"; // negative label + } } else { - _set_score_binary(i, Y->template MutableData(), - write_additional_scores, weights_are_all_positive_, - classes, classlabels_int64s_, - weights_classes_, 1, 0); + auto* y_data = Y->template MutableData(); + if (classlabels_int64s_.size() == 2 && + weights_are_all_positive_ && + maxweight > 0.5 && + weights_classes_.size() == 1) { + y_data[i] = classlabels_int64s_[1]; // positive label + write_additional_scores = 0; + } else if (classlabels_int64s_.size() == 2 && + weights_are_all_positive_ && + maxweight <= 0.5 && + weights_classes_.size() == 1) { + y_data[i] = classlabels_int64s_[0]; // negative label + write_additional_scores = 1; + } else if (classlabels_int64s_.size() == 2 && + maxweight > 0 && + !weights_are_all_positive_ && + weights_classes_.size() == 1) { + y_data[i] = classlabels_int64s_[1]; // pos label + write_additional_scores = 2; + } else if (classlabels_int64s_.size() == 2 && + maxweight <= 0 && + !weights_are_all_positive_ && + weights_classes_.size() == 1) { + y_data[i] = classlabels_int64s_[0]; // neg label + write_additional_scores = 3; + } else if (maxweight > 0) { + y_data[i] = 1; // positive label + } else { + y_data[i] = 0; // negative label + } } } // write float values, might not have all the classes in the output yet @@ -445,9 +425,10 @@ common::Status TreeEnsembleClassifier::Compute(OpKernelContext* context) cons } } write_scores(scores, post_transform_, zindex, Z, write_additional_scores); - } // namespace ml - return status; -} // namespace ml + zindex += scores.size(); + } // for every batch + return Status::OK(); +} template common::Status TreeEnsembleClassifier::ProcessTreeNode(std::map& classes, diff --git a/onnxruntime/test/providers/cpu/ml/tree_ensembler_classifier_test.cc b/onnxruntime/test/providers/cpu/ml/tree_ensembler_classifier_test.cc index f1b7616943..36318226ef 100644 --- a/onnxruntime/test/providers/cpu/ml/tree_ensembler_classifier_test.cc +++ b/onnxruntime/test/providers/cpu/ml/tree_ensembler_classifier_test.cc @@ -151,120 +151,5 @@ TEST(MLOpTest, TreeEnsembleClassifierBinary) { test.Run(); } -TEST(MLOpTest, TreeEnsembleClassifierBinaryBaseValue) { - OpTester test("TreeEnsembleClassifier", 1, onnxruntime::kMLDomain); - - // The example was generated by the following python script: - // model = GradientBoostingClassifier(n_estimators = 1, max_depth = 2) - // X, y = make_classification(10, n_features = 4, random_state = 42) - // X = X[:, :2] - // model.fit(X, y) - // model.init_.class_prior_ = np.array([0.231, 0.231]) - - std::vector base_values = {-1.202673316001892f, -1.202673316001892f}; - std::vector class_ids = {0, 0, 0}; - std::vector class_nodeids = {2, 3, 4}; - std::vector class_treeids = {0, 0, 0}; - std::vector class_weights = {-0.2f, -0.06f, 0.2f}; - std::vector classlabels_int64s = {0, 1}; - std::vector nodes_falsenodeids = {4, 3, 0, 0, 0}; - std::vector nodes_featureids = {0, 0, 0, 0, 0}; - std::vector nodes_hitrates = {1, 1, 1, 1, 1}; - std::vector nodes_missing_value_tracks_true = {0, 0, 0, 0, 0}; - std::vector nodes_modes = {"BRANCH_LEQ", "BRANCH_LEQ", "LEAF", "LEAF", "LEAF"}; - std::vector nodes_nodeids = {0, 1, 2, 3, 4}; - std::vector nodes_treeids = {0, 0, 0, 0, 0}; - std::vector nodes_truenodeids = {1, 2, 0, 0, 0}; - std::vector nodes_values = {0.21111594140529633f, -0.8440752029418945f, 0, 0, 0}; - std::string post_transform = "LOGISTIC"; - - std::vector X = {-0.92533575f, -1.14021544f, -0.46171143f, -0.58723065f, 1.44044386f, 1.77736657f}; - std::vector results = {0, 0, 0}; - std::vector probs = {}; - std::vector log_probs = {}; - std::vector scores{0.802607834f, 0.197392166f, 0.779485941f, 0.220514059f, 0.731583834f, 0.268416166f}; - - //define the context of the operator call - const int N = 3; - test.AddAttribute("base_values", base_values); - test.AddAttribute("class_ids", class_ids); - test.AddAttribute("class_nodeids", class_nodeids); - test.AddAttribute("class_treeids", class_treeids); - test.AddAttribute("class_weights", class_weights); - test.AddAttribute("classlabels_int64s", classlabels_int64s); - test.AddAttribute("nodes_falsenodeids", nodes_falsenodeids); - test.AddAttribute("nodes_featureids", nodes_featureids); - test.AddAttribute("nodes_hitrates", nodes_hitrates); - test.AddAttribute("nodes_modes", nodes_modes); - test.AddAttribute("nodes_nodeids", nodes_nodeids); - test.AddAttribute("nodes_treeids", nodes_treeids); - test.AddAttribute("nodes_truenodeids", nodes_truenodeids); - test.AddAttribute("nodes_values", nodes_values); - test.AddAttribute("post_transform", post_transform); - - test.AddInput("X", {N, 2}, X); - test.AddOutput("Y", {N}, results); - test.AddOutput("Z", {N, 2}, scores); - - test.Run(); -} - -TEST(MLOpTest, TreeEnsembleClassifierBinaryBaseValueNull) { - OpTester test("TreeEnsembleClassifier", 1, onnxruntime::kMLDomain); - - // The example was generated by the following python script: - // model = GradientBoostingClassifier(n_estimators = 1, max_depth = 2) - // X, y = make_classification(10, n_features = 4, random_state = 42) - // X = X[:, :2] - // model.fit(X, y) - - std::vector base_values = {0, 0}; - std::vector class_ids = {0, 0, 0}; - std::vector class_nodeids = {2, 3, 4}; - std::vector class_treeids = {0, 0, 0}; - std::vector class_weights = {-0.2f, -0.0666f, 0.2f}; - std::vector classlabels_int64s = {0, 1}; - std::vector nodes_falsenodeids = {4, 3, 0, 0, 0}; - std::vector nodes_featureids = {0, 0, 0, 0, 0}; - std::vector nodes_hitrates = {1, 1, 1, 1, 1}; - std::vector nodes_missing_value_tracks_true = {0, 0, 0, 0, 0}; - std::vector nodes_modes = {"BRANCH_LEQ", "BRANCH_LEQ", "LEAF", "LEAF", "LEAF"}; - std::vector nodes_nodeids = {0, 1, 2, 3, 4}; - std::vector nodes_treeids = {0, 0, 0, 0, 0}; - std::vector nodes_truenodeids = {1, 2, 0, 0, 0}; - std::vector nodes_values = {0.24055418372154236f, -0.8440752029418945f, 0, 0, 0}; - std::string post_transform = "LOGISTIC"; - - std::vector X = {-0.92533575f, -1.14021544f, -0.46171143f, -0.58723065f, 1.44044386f, 1.77736657f}; - std::vector results = {0, 0, 1}; - std::vector probs = {}; - std::vector log_probs = {}; - std::vector scores{0.549834f, 0.450166f, 0.5166605f, 0.4833395f, 0.450166f, 0.549834f}; - - //define the context of the operator call - const int N = 3; - test.AddAttribute("base_values", base_values); - test.AddAttribute("class_ids", class_ids); - test.AddAttribute("class_nodeids", class_nodeids); - test.AddAttribute("class_treeids", class_treeids); - test.AddAttribute("class_weights", class_weights); - test.AddAttribute("classlabels_int64s", classlabels_int64s); - test.AddAttribute("nodes_falsenodeids", nodes_falsenodeids); - test.AddAttribute("nodes_featureids", nodes_featureids); - test.AddAttribute("nodes_hitrates", nodes_hitrates); - test.AddAttribute("nodes_modes", nodes_modes); - test.AddAttribute("nodes_nodeids", nodes_nodeids); - test.AddAttribute("nodes_treeids", nodes_treeids); - test.AddAttribute("nodes_truenodeids", nodes_truenodeids); - test.AddAttribute("nodes_values", nodes_values); - test.AddAttribute("post_transform", post_transform); - - test.AddInput("X", {N, 2}, X); - test.AddOutput("Y", {N}, results); - test.AddOutput("Z", {N, 2}, scores); - - test.Run(); -} - } // namespace test } // namespace onnxruntime