diff --git a/onnxruntime/core/providers/cpu/ml/ml_common.h b/onnxruntime/core/providers/cpu/ml/ml_common.h index 3db2976a87..6bf43e1a34 100644 --- a/onnxruntime/core/providers/cpu/ml/ml_common.h +++ b/onnxruntime/core/providers/cpu/ml/ml_common.h @@ -305,57 +305,78 @@ static inline void ComputeSoftmaxZero(std::vector& values) { } template -void write_scores(std::vector& scores, POST_EVAL_TRANSFORM post_transform, int64_t write_index, Tensor* Z, - int add_second_class) { +static void write_scores(std::vector& scores, POST_EVAL_TRANSFORM post_transform, + T* Z, int add_second_class) { if (scores.size() >= 2) { switch (post_transform) { case POST_EVAL_TRANSFORM::PROBIT: - for (float& score : scores) - score = ComputeProbit(score); + for (auto it = scores.cbegin(); it != scores.cend(); ++it, ++Z) + *Z = static_cast(ComputeProbit(static_cast(*it))); break; case POST_EVAL_TRANSFORM::LOGISTIC: - for (float& score : scores) - score = ComputeLogistic(score); + for (auto it = scores.cbegin(); it != scores.cend(); ++it, ++Z) + *Z = static_cast(ComputeLogistic(static_cast(*it))); break; case POST_EVAL_TRANSFORM::SOFTMAX: ComputeSoftmax(scores); + memcpy(Z, scores.data(), scores.size() * sizeof(T)); break; case POST_EVAL_TRANSFORM::SOFTMAX_ZERO: ComputeSoftmaxZero(scores); + memcpy(Z, scores.data(), scores.size() * sizeof(T)); break; default: case POST_EVAL_TRANSFORM::NONE: + memcpy(Z, scores.data(), scores.size() * sizeof(T)); break; } } else if (scores.size() == 1) { //binary case if (post_transform == POST_EVAL_TRANSFORM::PROBIT) { - scores[0] = ComputeProbit(scores[0]); + scores[0] = static_cast(ComputeProbit(static_cast(scores[0]))); + *Z = scores[0]; } else { switch (add_second_class) { - case 0: - case 1: + case 0: //0=all positive weights, winning class is positive scores.push_back(scores[0]); - scores[0] = 1.f - scores[0]; + scores[0] = 1.f - scores[0]; //put opposite score in positive slot + *Z = scores[0]; + *(Z + 1) = scores[1]; break; - case 2: //2 = mixed weights, winning class is positive - case 3: //3 = mixed weights, winning class is negative + case 1: //1 = all positive weights, winning class is negative + scores.push_back(scores[0]); + scores[0] = 1.f - scores[0]; //put opposite score in positive slot + *Z = scores[0]; + *(Z + 1) = scores[1]; + break; + case 2: + case 3: //2 = mixed weights, winning class is positive if (post_transform == POST_EVAL_TRANSFORM::LOGISTIC) { - scores.push_back(ComputeLogistic(scores[0])); //ml_logit(scores[k]); - scores[0] = ComputeLogistic(-scores[0]); + scores.push_back(static_cast(ComputeLogistic(static_cast(scores[0])))); + scores[0] = static_cast(ComputeLogistic(static_cast(-scores[0]))); } else { scores.push_back(scores[0]); scores[0] = -scores[0]; } + *Z = scores[0]; + *(Z + 1) = scores[1]; + break; + default: + *Z = scores[0]; break; } } } +} + +template +static void write_scores(std::vector& scores, POST_EVAL_TRANSFORM post_transform, int64_t write_index, Tensor* Z, + int add_second_class) { T* out_p = Z->template MutableData() + write_index; size_t len; if (!IAllocator::CalcMemSizeForArray(scores.size(), sizeof(T), &len)) { ORT_THROW("length overflow"); } - memcpy(out_p, scores.data(), len); + write_scores(scores, post_transform, out_p, add_second_class); } // TODO: Starting with just the pieces needed for LinearRegressor from write_scores (see above). diff --git a/onnxruntime/core/providers/cpu/ml/tree_ensemble_aggregator.h b/onnxruntime/core/providers/cpu/ml/tree_ensemble_aggregator.h new file mode 100644 index 0000000000..6a32cc6b2d --- /dev/null +++ b/onnxruntime/core/providers/cpu/ml/tree_ensemble_aggregator.h @@ -0,0 +1,492 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once +#include "core/common/common.h" +#include "core/framework/op_kernel.h" +#include "ml_common.h" +#include + +namespace onnxruntime { +namespace ml { +namespace detail { + +struct TreeNodeElementId { + int tree_id; + int node_id; + bool operator==(const TreeNodeElementId& xyz) const { + return (tree_id == xyz.tree_id) && (node_id == xyz.node_id); + } + bool operator<(const TreeNodeElementId& xyz) const { + return ((tree_id < xyz.tree_id) || (tree_id == xyz.tree_id && node_id < xyz.node_id)); + } +}; + +template +struct SparseValue { + int64_t i; + T value; +}; + +template +struct ScoreValue { + unsigned char has_score; + T score; +}; + +enum MissingTrack { + NONE, + TRUE, + FALSE +}; + +template +struct TreeNodeElement { + TreeNodeElementId id; + int feature_id; + T value; + T hitrates; + NODE_MODE mode; + TreeNodeElement* truenode; + TreeNodeElement* falsenode; + MissingTrack missing_tracks; + std::vector> weights; + + bool is_not_leaf; + bool is_missing_track_true; +}; + +template +class TreeAggregator { + protected: + size_t n_trees_; + int64_t n_targets_or_classes_; + POST_EVAL_TRANSFORM post_transform_; + const std::vector& base_values_; + OTYPE origin_; + bool use_base_values_; + + public: + TreeAggregator(size_t n_trees, + const int64_t& n_targets_or_classes, + POST_EVAL_TRANSFORM post_transform, + const std::vector& base_values) : n_trees_(n_trees), n_targets_or_classes_(n_targets_or_classes), post_transform_(post_transform), base_values_(base_values) { + origin_ = base_values_.size() == 1 ? base_values_[0] : 0.f; + use_base_values_ = base_values_.size() == static_cast(n_targets_or_classes_); + } + + // 1 output + + void ProcessTreeNodePrediction1(ScoreValue& /*prediction*/, const TreeNodeElement& /*root*/) const {} + + void MergePrediction1(ScoreValue& /*prediction*/, ScoreValue& /*prediction2*/) const {} + + void FinalizeScores1(OTYPE* Z, ScoreValue& prediction, int64_t* /*Y*/) const { + prediction.score = prediction.has_score ? (prediction.score + origin_) : origin_; + *Z = this->post_transform_ == POST_EVAL_TRANSFORM::PROBIT ? static_cast(ComputeProbit(static_cast(prediction.score))) : prediction.score; + } + + // N outputs + + void ProcessTreeNodePrediction(std::vector>& /*predictions*/, const TreeNodeElement& /*root*/) const {} + + void MergePrediction(std::vector>& /*predictions*/, const std::vector>& /*predictions2*/) const {} + + void FinalizeScores(std::vector>& predictions, OTYPE* Z, int add_second_class, int64_t*) const { + ORT_ENFORCE(predictions.size() == (size_t)n_targets_or_classes_); + OTYPE val; + std::vector scores(predictions.size(), 0); + auto it = predictions.begin(); + for (int64_t jt = 0; jt < n_targets_or_classes_; ++jt, ++it) { + val = use_base_values_ ? base_values_[jt] : 0.f; + val += it->has_score ? it->score : 0; + scores[jt] = val; + } + write_scores(scores, post_transform_, Z, add_second_class); + } +}; + +///////////// +// regression +///////////// + +template +class TreeAggregatorSum : public TreeAggregator { + public: + TreeAggregatorSum(size_t n_trees, + const int64_t& n_targets_or_classes, + POST_EVAL_TRANSFORM post_transform, + const std::vector& base_values) : TreeAggregator(n_trees, n_targets_or_classes, + post_transform, base_values) {} + + // 1 output + + void ProcessTreeNodePrediction1(ScoreValue& prediction, const TreeNodeElement& root) const { + prediction.score += root.weights[0].value; + } + + void MergePrediction1(ScoreValue& prediction, const ScoreValue& prediction2) const { + prediction.score += prediction2.score; + } + + void FinalizeScores1(OTYPE* Z, ScoreValue& prediction, int64_t* /*Y*/) const { + prediction.score += this->origin_; + *Z = this->post_transform_ == POST_EVAL_TRANSFORM::PROBIT ? static_cast(ComputeProbit(static_cast(prediction.score))) : prediction.score; + } + + // N outputs + + void ProcessTreeNodePrediction(std::vector>& predictions, const TreeNodeElement& root) const { + for (auto it = root.weights.cbegin(); it != root.weights.cend(); ++it) { + ORT_ENFORCE(it->i < (int64_t)predictions.size()); + predictions[it->i].score += it->value; + predictions[it->i].has_score = 1; + } + } + + void MergePrediction(std::vector>& predictions, const std::vector>& predictions2) const { + ORT_ENFORCE(predictions.size() == predictions2.size()); + for (size_t i = 0; i < predictions.size(); ++i) { + if (predictions2[i].has_score) { + predictions[i].score += predictions2[i].score; + predictions[i].has_score = 1; + } + } + } + + void FinalizeScores(std::vector>& predictions, OTYPE* Z, int add_second_class, int64_t*) const { + std::vector scores(predictions.size()); + auto its = scores.begin(); + auto it = predictions.begin(); + if (this->use_base_values_) { + auto it2 = this->base_values_.cbegin(); + for (; it != predictions.end(); ++it, ++it2, ++its) + *its = it->score + *it2; + } else { + for (; it != predictions.end(); ++it, ++its) + *its = it->score; + } + write_scores(scores, this->post_transform_, Z, add_second_class); + } +}; + +template +class TreeAggregatorAverage : public TreeAggregatorSum { + public: + TreeAggregatorAverage(size_t n_trees, + const int64_t& n_targets_or_classes, + POST_EVAL_TRANSFORM post_transform, + const std::vector& base_values) : TreeAggregatorSum(n_trees, n_targets_or_classes, + post_transform, base_values) {} + + void FinalizeScores1(OTYPE* Z, ScoreValue& prediction, int64_t* /*Y*/) const { + prediction.score /= this->n_trees_; + prediction.score += this->origin_; + *Z = this->post_transform_ == POST_EVAL_TRANSFORM::PROBIT ? static_cast(ComputeProbit(static_cast(prediction.score))) : prediction.score; + } + + void FinalizeScores(std::vector>& predictions, OTYPE* Z, int add_second_class, int64_t*) const { + std::vector scores(predictions.size(), 0); + auto its = scores.begin(); + if (this->use_base_values_) { + ORT_ENFORCE(this->base_values_.size() == predictions.size()); + auto it = predictions.begin(); + auto it2 = this->base_values_.cbegin(); + for (; it != predictions.end(); ++it, ++it2, ++its) + *its = it->score / this->n_trees_ + *it2; + } else { + auto it = predictions.begin(); + for (; it != predictions.end(); ++it, ++its) + *its = it->score / this->n_trees_; + } + write_scores(scores, this->post_transform_, Z, add_second_class); + } +}; + +template +class TreeAggregatorMin : public TreeAggregator { + public: + TreeAggregatorMin(size_t n_trees, + const int64_t& n_targets_or_classes, + POST_EVAL_TRANSFORM post_transform, + const std::vector& base_values) : TreeAggregator(n_trees, n_targets_or_classes, + post_transform, base_values) {} + + // 1 output + + void ProcessTreeNodePrediction1(ScoreValue& prediction, const TreeNodeElement& root) const { + prediction.score = (!(prediction.has_score) || root.weights[0].value < prediction.score) + ? root.weights[0].value + : prediction.score; + prediction.has_score = 1; + } + + void MergePrediction1(ScoreValue& prediction, const ScoreValue& prediction2) const { + if (prediction2.has_score) { + prediction.score = prediction.has_score && (prediction.score < prediction2.score) + ? prediction.score + : prediction2.score; + prediction.has_score = 1; + } + } + + // N outputs + + void ProcessTreeNodePrediction(std::vector>& predictions, const TreeNodeElement& root) const { + for (auto it = root.weights.begin(); it != root.weights.end(); ++it) { + predictions[it->i].score = (!predictions[it->i].has_score || it->value < predictions[it->i].score) + ? it->value + : predictions[it->i].score; + predictions[it->i].has_score = 1; + } + } + + void MergePrediction(std::vector>& predictions, const std::vector>& predictions2) const { + ORT_ENFORCE(predictions.size() == predictions2.size()); + for (size_t i = 0; i < predictions.size(); ++i) { + if (predictions2[i].has_score) { + predictions[i].score = predictions[i].has_score && (predictions[i].score < predictions2[i].score) + ? predictions[i].score + : predictions2[i].score; + predictions[i].has_score = 1; + } + } + } +}; + +template +class TreeAggregatorMax : public TreeAggregator { + public: + TreeAggregatorMax(size_t n_trees, + const int64_t& n_targets_or_classes, + POST_EVAL_TRANSFORM post_transform, + const std::vector& base_values) : TreeAggregator(n_trees, n_targets_or_classes, + post_transform, base_values) {} + + // 1 output + + void ProcessTreeNodePrediction1(ScoreValue& prediction, const TreeNodeElement& root) const { + prediction.score = (!(prediction.has_score) || root.weights[0].value > prediction.score) + ? root.weights[0].value + : prediction.score; + prediction.has_score = 1; + } + + void MergePrediction1(ScoreValue& prediction, const ScoreValue& prediction2) const { + if (prediction2.has_score) { + prediction.score = prediction.has_score && (prediction.score > prediction2.score) + ? prediction.score + : prediction2.score; + prediction.has_score = 1; + } + } + + // N outputs + + void ProcessTreeNodePrediction(std::vector>& predictions, const TreeNodeElement& root) const { + for (auto it = root.weights.begin(); it != root.weights.end(); ++it) { + predictions[it->i].score = (!predictions[it->i].has_score || it->value > predictions[it->i].score) + ? it->value + : predictions[it->i].score; + predictions[it->i].has_score = 1; + } + } + + void MergePrediction(std::vector>& predictions, const std::vector>& predictions2) const { + ORT_ENFORCE(predictions.size() == predictions2.size()); + for (size_t i = 0; i < predictions.size(); ++i) { + if (predictions2[i].has_score) { + predictions[i].score = predictions[i].has_score && (predictions[i].score > predictions2[i].score) + ? predictions[i].score + : predictions2[i].score; + predictions[i].has_score = 1; + } + } + } +}; + +///////////////// +// classification +///////////////// + +template +class TreeAggregatorClassifier : public TreeAggregatorSum { + private: + const std::vector& class_labels_; + bool binary_case_; + bool weights_are_all_positive_; + int64_t positive_label_; + int64_t negative_label_; + + public: + TreeAggregatorClassifier(size_t n_trees, + const int64_t& n_targets_or_classes, + POST_EVAL_TRANSFORM post_transform, + const std::vector& base_values, + const std::vector& class_labels, + bool binary_case, + bool weights_are_all_positive, + int64_t positive_label = 1, + int64_t negative_label = 0) : TreeAggregatorSum(n_trees, n_targets_or_classes, + post_transform, base_values), + class_labels_(class_labels), + binary_case_(binary_case), + weights_are_all_positive_(weights_are_all_positive), + positive_label_(positive_label), + negative_label_(negative_label) {} + + void get_max_weight(const std::vector>& classes, int64_t& maxclass, OTYPE& maxweight) const { + maxclass = -1; + maxweight = 0; + for (auto it = classes.cbegin(); it != classes.cend(); ++it) { + if (it->has_score && (maxclass == -1 || it->score > maxweight)) { + maxclass = (int64_t)(it - classes.cbegin()); + maxweight = it->score; + } + } + } + + int64_t _set_score_binary(int& write_additional_scores, const std::vector>& classes) const { + ORT_ENFORCE(classes.size() == 2 || classes.size() == 1); + return classes.size() == 2 + ? _set_score_binary(write_additional_scores, classes[0].score, classes[0].has_score, classes[1].score, classes[1].has_score) + : _set_score_binary(write_additional_scores, classes[0].score, classes[0].has_score, 0, 0); + } + + int64_t _set_score_binary(int& write_additional_scores, OTYPE score0, unsigned char has_score0, OTYPE score1, unsigned char has_score1) const { + OTYPE pos_weight = has_score1 ? score1 : (has_score0 ? score0 : 0); // only 1 class + if (binary_case_) { + if (weights_are_all_positive_) { + if (pos_weight > 0.5) { + write_additional_scores = 0; + return class_labels_[1]; // positive label + } else { + write_additional_scores = 1; + return class_labels_[0]; // negative label + } + } else { + if (pos_weight > 0) { + write_additional_scores = 2; + return class_labels_[1]; // positive label + } else { + write_additional_scores = 3; + return class_labels_[0]; // negative label + } + } + } + return (pos_weight > 0) + ? positive_label_ // positive label + : negative_label_; // negative label + } + + // 1 output + + void FinalizeScores1(OTYPE* Z, ScoreValue& prediction, int64_t* Y) const { + std::vector scores(2); + unsigned char has_scores[2] = {1, 0}; + + int write_additional_scores = -1; + if (this->base_values_.size() == 2) { + // add base_values + scores[1] = this->base_values_[1] + prediction.score; + scores[0] = -scores[1]; + //has_score = true; + has_scores[1] = 1; + } else if (this->base_values_.size() == 1) { + // ONNX is vague about two classes and only one base_values. + scores[0] = prediction.score + this->base_values_[0]; + //if (!has_scores[1]) + //scores.pop_back(); + scores[0] = prediction.score; + } else if (this->base_values_.size() == 0) { + //if (!has_score) + // scores.pop_back(); + scores[0] = prediction.score; + } + + *Y = _set_score_binary(write_additional_scores, scores[0], has_scores[0], scores[1], has_scores[1]); + write_scores(scores, this->post_transform_, Z, write_additional_scores); + } + + // N outputs + + void FinalizeScores(std::vector>& predictions, OTYPE* Z, int /*add_second_class*/, int64_t* Y = 0) const { + OTYPE maxweight = 0; + int64_t maxclass = -1; + + int write_additional_scores = -1; + std::vector preds; + if (this->n_targets_or_classes_ > 2) { + // add base values + for (int64_t k = 0, end = static_cast(this->base_values_.size()); k < end; ++k) { + if (!predictions[k].has_score) { + predictions[k].has_score = 1; + predictions[k].score = this->base_values_[k]; + } else { + predictions[k].score += this->base_values_[k]; + } + } + get_max_weight(predictions, maxclass, maxweight); + *Y = class_labels_[maxclass]; + preds.resize(predictions.size()); + auto it2 = predictions.cbegin(); + for (auto it = preds.begin(); it != preds.end(); ++it, ++it2) + *it = it2->has_score ? it2->score : 0; + } else { // binary case + ORT_ENFORCE(predictions.size() == 2); + if (this->base_values_.size() == 2) { + // add base values + if (predictions[1].has_score) { + // 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. + predictions[1].score = this->base_values_[1] + predictions[0].score; + predictions[0].score = -predictions[1].score; + predictions[1].has_score = 1; + } else { + // binary as multiclass + predictions[1].score += this->base_values_[1]; + predictions[0].score += this->base_values_[0]; + } + preds.resize(2); + preds[0] = predictions[0].score; + preds[1] = predictions[1].score; + } else if (this->base_values_.size() == 1) { + // ONNX is vague about two classes and only one base_values. + predictions[0].score += this->base_values_[0]; + if (!predictions[1].has_score) { + preds.resize(1); + preds[0] = predictions[0].score; + } else { + preds.resize(2); + preds[0] = predictions[0].score; + preds[1] = predictions[1].score; + } + } else if (this->base_values_.size() == 0) { + if (!predictions[1].has_score) { + preds.resize(1); + preds[0] = predictions[0].score; + } else { + preds.resize(2); + preds[0] = predictions[0].score; + preds[1] = predictions[1].score; + } + } + if (preds.size() == 0) { + ORT_ENFORCE(predictions.size() == 2); + preds.resize(2); + preds[0] = predictions[0].score; + preds[1] = predictions[1].score; + } + + *Y = _set_score_binary(write_additional_scores, predictions); + } + + write_scores(preds, this->post_transform_, Z, write_additional_scores); + } +}; + +} // namespace detail +} // namespace ml +} // namespace onnxruntime diff --git a/onnxruntime/core/providers/cpu/ml/tree_ensemble_classifier.cc b/onnxruntime/core/providers/cpu/ml/tree_ensemble_classifier.cc index 0a55a7fe3f..1c418e7302 100644 --- a/onnxruntime/core/providers/cpu/ml/tree_ensemble_classifier.cc +++ b/onnxruntime/core/providers/cpu/ml/tree_ensemble_classifier.cc @@ -138,161 +138,28 @@ ADD_IN_TYPE_TREE_ENSEMBLE_CLASSIFIER_OP(int32_t); template TreeEnsembleClassifier::TreeEnsembleClassifier(const OpKernelInfo& info) : OpKernel(info), - nodes_treeids_(info.GetAttrsOrDefault("nodes_treeids")), - nodes_nodeids_(info.GetAttrsOrDefault("nodes_nodeids")), - nodes_featureids_(info.GetAttrsOrDefault("nodes_featureids")), - nodes_values_(info.GetAttrsOrDefault("nodes_values")), - nodes_hitrates_(info.GetAttrsOrDefault("nodes_hitrates")), - nodes_modes_names_(info.GetAttrsOrDefault("nodes_modes")), - nodes_truenodeids_(info.GetAttrsOrDefault("nodes_truenodeids")), - nodes_falsenodeids_(info.GetAttrsOrDefault("nodes_falsenodeids")), - missing_tracks_true_(info.GetAttrsOrDefault("nodes_missing_value_tracks_true")), - class_nodeids_(info.GetAttrsOrDefault("class_nodeids")), - class_treeids_(info.GetAttrsOrDefault("class_treeids")), - class_ids_(info.GetAttrsOrDefault("class_ids")), - class_weights_(info.GetAttrsOrDefault("class_weights")), - base_values_(info.GetAttrsOrDefault("base_values")), - classlabels_strings_(info.GetAttrsOrDefault("classlabels_strings")), - classlabels_int64s_(info.GetAttrsOrDefault("classlabels_int64s")), - post_transform_(MakeTransform(info.GetAttrOrDefault("post_transform", "NONE"))) { - ORT_ENFORCE(!nodes_treeids_.empty()); - ORT_ENFORCE(class_nodeids_.size() == class_ids_.size()); - ORT_ENFORCE(class_nodeids_.size() == class_weights_.size()); - ORT_ENFORCE(nodes_nodeids_.size() == nodes_featureids_.size()); - ORT_ENFORCE(nodes_nodeids_.size() == nodes_modes_names_.size()); - ORT_ENFORCE(nodes_nodeids_.size() == nodes_values_.size()); - ORT_ENFORCE(nodes_nodeids_.size() == nodes_truenodeids_.size()); - ORT_ENFORCE(nodes_nodeids_.size() == nodes_falsenodeids_.size()); - ORT_ENFORCE((nodes_nodeids_.size() == nodes_hitrates_.size()) || (nodes_hitrates_.empty())); - - ORT_ENFORCE(classlabels_strings_.empty() ^ classlabels_int64s_.empty(), - "Must provide classlabels_strings or classlabels_int64s but not both."); - - // in the absence of bool type supported by GetAttrs this ensure that we don't have any negative - // values so that we can check for the truth condition without worrying about negative values. - ORT_ENFORCE(std::all_of( - std::begin(missing_tracks_true_), - std::end(missing_tracks_true_), [](int64_t elem) { return elem >= 0; })); - - Initialize(); -} - -template -void TreeEnsembleClassifier::Initialize() { - int64_t current_tree_id = 1234567891L; - std::vector tree_offsets; - weights_are_all_positive_ = true; - - for (int64_t i = 0, size_node_treeids = static_cast(nodes_treeids_.size()); - i < size_node_treeids; - ++i) { - if (nodes_treeids_[i] != current_tree_id) { - tree_offsets.push_back(nodes_nodeids_[i]); - current_tree_id = nodes_treeids_[i]; - } - int64_t offset = tree_offsets[tree_offsets.size() - 1]; - nodes_nodeids_[i] = nodes_nodeids_[i] - offset; - if (nodes_falsenodeids_[i] >= 0) { - nodes_falsenodeids_[i] = nodes_falsenodeids_[i] - offset; - } - if (nodes_truenodeids_[i] >= 0) { - nodes_truenodeids_[i] = nodes_truenodeids_[i] - offset; - } - } - for (int64_t i = 0, size_class_nodeids = static_cast(class_nodeids_.size()); - i < size_class_nodeids; - ++i) { - int64_t offset = tree_offsets[class_treeids_[i]]; - class_nodeids_[i] = class_nodeids_[i] - offset; - if (class_weights_[i] < 0) { - weights_are_all_positive_ = false; - } - } - - nodes_modes_.reserve(nodes_modes_names_.size()); - for (size_t i = 0, end = nodes_modes_names_.size(); i < end; ++i) { - nodes_modes_.push_back(MakeTreeNodeMode(nodes_modes_names_[i])); - } - - // leafnode data, these are the votes that leaves do - using LeafNodeData = std::tuple; - for (size_t i = 0, end = class_nodeids_.size(); i < end; ++i) { - leafnodedata_.push_back(std::make_tuple(class_treeids_[i], class_nodeids_[i], class_ids_[i], class_weights_[i])); - weights_classes_.insert(class_ids_[i]); - } - std::sort(std::begin(leafnodedata_), std::end(leafnodedata_), [](LeafNodeData const& t1, LeafNodeData const& t2) { - if (std::get<0>(t1) != std::get<0>(t2)) - return std::get<0>(t1) < std::get<0>(t2); - - return std::get<1>(t1) < std::get<1>(t2); - }); - // make an index so we can find the leafnode data quickly when evaluating - int64_t field0 = -1; - int64_t field1 = -1; - for (size_t i = 0, end = leafnodedata_.size(); i < end; ++i) { - int64_t id0 = std::get<0>(leafnodedata_[i]); - int64_t id1 = std::get<1>(leafnodedata_[i]); - if (id0 != field0 || id1 != field1) { - int64_t id = id0 * kOffset_ + id1; - auto position = static_cast(i); - auto p3 = std::make_pair(id, position); - leafdata_map_.insert(p3); - field0 = id; - field1 = position; - } - } - - // treenode ids, some are roots_, and roots_ have no parents - std::unordered_map parents; // holds count of all who point to you - std::unordered_map indices; - // add all the nodes to a map, and the ones that have parents are not roots_ - std::unordered_map::iterator it; - for (size_t i = 0, end = nodes_treeids_.size(); i < end; ++i) { - // make an index to look up later - int64_t id = nodes_treeids_[i] * kOffset_ + nodes_nodeids_[i]; - auto position = static_cast(i); - auto p3 = std::make_pair(id, position); - indices.insert(p3); - it = parents.find(id); - if (it == parents.end()) { - // start counter at 0 - auto b = (int64_t)0L; - auto p1 = std::make_pair(id, b); - parents.insert(p1); - } - } - // all true nodes arent roots_ - for (size_t i = 0, end = nodes_truenodeids_.size(); i < end; ++i) { - if (nodes_modes_[i] == NODE_MODE::LEAF) continue; - // they must be in the same tree - int64_t id = nodes_treeids_[i] * kOffset_ + nodes_truenodeids_[i]; - it = parents.find(id); - ORT_ENFORCE(it != parents.end()); - it->second++; - } - // all false nodes arent roots_ - for (size_t i = 0, end = nodes_falsenodeids_.size(); i < end; ++i) { - if (nodes_modes_[i] == NODE_MODE::LEAF) continue; - // they must be in the same tree - int64_t id = nodes_treeids_[i] * kOffset_ + nodes_falsenodeids_[i]; - it = parents.find(id); - ORT_ENFORCE(it != parents.end()); - it->second++; - } - // find all the nodes that dont have other nodes pointing at them - for (auto& parent : parents) { - if (parent.second == 0) { - int64_t id = parent.first; - it = indices.find(id); - roots_.push_back(it->second); - } - } - class_count_ = !classlabels_strings_.empty() ? classlabels_strings_.size() : classlabels_int64s_.size(); - using_strings_ = !classlabels_strings_.empty(); - ORT_ENFORCE(base_values_.empty() || - base_values_.size() == static_cast(class_count_) || - base_values_.size() == weights_classes_.size()); -} + tree_ensemble_( + 100, + 50, + info.GetAttrOrDefault("aggregate_function", "SUM"), + info.GetAttrsOrDefault("base_values"), + info.GetAttrsOrDefault("nodes_falsenodeids"), + info.GetAttrsOrDefault("nodes_featureids"), + info.GetAttrsOrDefault("nodes_hitrates"), + info.GetAttrsOrDefault("nodes_missing_value_tracks_true"), + info.GetAttrsOrDefault("nodes_modes"), + info.GetAttrsOrDefault("nodes_nodeids"), + info.GetAttrsOrDefault("nodes_treeids"), + info.GetAttrsOrDefault("nodes_truenodeids"), + info.GetAttrsOrDefault("nodes_values"), + info.GetAttrOrDefault("post_transform", "NONE"), + info.GetAttrsOrDefault("class_ids"), + info.GetAttrsOrDefault("class_nodeids"), + info.GetAttrsOrDefault("class_treeids"), + info.GetAttrsOrDefault("class_weights"), + info.GetAttrsOrDefault("classlabels_strings"), + info.GetAttrsOrDefault("classlabels_int64s")) { +} // namespace ml template common::Status TreeEnsembleClassifier::Compute(OpKernelContext* context) const { @@ -303,212 +170,13 @@ common::Status TreeEnsembleClassifier::Compute(OpKernelContext* context) cons return Status(ONNXRUNTIME, INVALID_ARGUMENT, "X dims is empty."); } - int64_t stride = x_dims.size() == 1 ? x_dims[0] : x_dims[1]; // TODO(task 495): how does this work in the case of 3D tensors? 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_})); + Tensor* Z = context->Output(1, TensorShape({N, tree_ensemble_.get_class_count()})); - int64_t zindex = 0; - const T* x_data = X.template Data(); - - // for each class - std::vector scores; - scores.reserve(class_count_); - for (int64_t i = 0; i < N; ++i) { - 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) { - 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) { - for (auto& classe : classes) { - if (maxclass == -1 || classe.second > maxweight) { - maxclass = classe.first; - maxweight = classe.second; - } - } - if (using_strings_) { - Y->template MutableData()[i] = classlabels_strings_[maxclass]; - } else { - Y->template MutableData()[i] = classlabels_int64s_[maxclass]; - } - } else // binary case - { - maxweight = !classes.empty() ? classes[0] : 0.f; // only 1 class - if (using_strings_) { - 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 { - 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 - // for example a 10 class case where we only found 2 classes in the leaves - if (weights_classes_.size() == static_cast(class_count_)) { - for (int64_t k = 0; k < class_count_; ++k) { - auto it_classes = classes.find(k); - if (it_classes != classes.end()) { - scores.push_back(it_classes->second); - } else { - scores.push_back(0.f); - } - } - } else { - for (auto& classe : classes) { - scores.push_back(classe.second); - } - } - write_scores(scores, post_transform_, zindex, Z, write_additional_scores); - zindex += scores.size(); - } // for every batch + tree_ensemble_.compute(&X, Z, Y); return Status::OK(); } -template -common::Status TreeEnsembleClassifier::ProcessTreeNode(std::map& classes, - int64_t treeindex, - const T* x_data, - int64_t feature_base) const { - // walk down tree to the leaf - auto mode = static_cast(nodes_modes_[treeindex]); - int64_t loopcount = 0; - int64_t root = treeindex; - while (mode != NODE_MODE::LEAF) { - T val = x_data[feature_base + nodes_featureids_[treeindex]]; - bool tracktrue = true; - if (missing_tracks_true_.size() != nodes_truenodeids_.size()) { - tracktrue = false; - } else { - tracktrue = missing_tracks_true_[treeindex] && std::isnan(static_cast(val)); - } - float threshold = nodes_values_[treeindex]; - switch (mode) { - case NODE_MODE::BRANCH_LEQ: - treeindex = val <= threshold || tracktrue ? nodes_truenodeids_[treeindex] : nodes_falsenodeids_[treeindex]; - break; - case NODE_MODE::BRANCH_LT: - treeindex = val < threshold || tracktrue ? nodes_truenodeids_[treeindex] : nodes_falsenodeids_[treeindex]; - break; - case NODE_MODE::BRANCH_GTE: - treeindex = val >= threshold || tracktrue ? nodes_truenodeids_[treeindex] : nodes_falsenodeids_[treeindex]; - break; - case NODE_MODE::BRANCH_GT: - treeindex = val > threshold || tracktrue ? nodes_truenodeids_[treeindex] : nodes_falsenodeids_[treeindex]; - break; - case NODE_MODE::BRANCH_EQ: - treeindex = val == threshold || tracktrue ? nodes_truenodeids_[treeindex] : nodes_falsenodeids_[treeindex]; - break; - case NODE_MODE::BRANCH_NEQ: - treeindex = val != threshold || tracktrue ? nodes_truenodeids_[treeindex] : nodes_falsenodeids_[treeindex]; - break; - default: { - std::ostringstream err_msg; - err_msg << "Invalid mode of value: " << static_cast::type>(mode); - return Status(ONNXRUNTIME, INVALID_ARGUMENT, err_msg.str()); - } - } - ORT_ENFORCE(treeindex >= 0); - treeindex = treeindex + root; - mode = static_cast(nodes_modes_[treeindex]); - loopcount++; - if (loopcount > kMaxTreeDepth_) break; - } - // should be at leaf - int64_t id = nodes_treeids_[treeindex] * kOffset_ + nodes_nodeids_[treeindex]; - auto it_lp = leafdata_map_.find(id); - if (it_lp == leafdata_map_.end()) { // if not found, simply return - return Status::OK(); - } - int64_t index = it_lp->second; - int64_t treeid = std::get<0>(leafnodedata_[index]); - int64_t nodeid = std::get<1>(leafnodedata_[index]); - while (treeid == nodes_treeids_[treeindex] && nodeid == nodes_nodeids_[treeindex]) { - int64_t classid = std::get<2>(leafnodedata_[index]); - float weight = std::get<3>(leafnodedata_[index]); - std::map::iterator it_classes; - it_classes = classes.find(classid); - if (it_classes != classes.end()) { - it_classes->second += weight; - } else { - auto p1 = std::make_pair(classid, weight); - classes.insert(p1); - } - ++index; - // some tree node will be last - if (index >= static_cast(leafnodedata_.size())) { - break; - } - treeid = std::get<0>(leafnodedata_[index]); - nodeid = std::get<1>(leafnodedata_[index]); - } - return Status::OK(); -} } // namespace ml } // namespace onnxruntime diff --git a/onnxruntime/core/providers/cpu/ml/tree_ensemble_classifier.h b/onnxruntime/core/providers/cpu/ml/tree_ensemble_classifier.h index 34f3b2d2f6..ced1d9d9eb 100644 --- a/onnxruntime/core/providers/cpu/ml/tree_ensemble_classifier.h +++ b/onnxruntime/core/providers/cpu/ml/tree_ensemble_classifier.h @@ -2,9 +2,7 @@ // Licensed under the MIT License. #pragma once -#include "core/common/common.h" -#include "core/framework/op_kernel.h" -#include "ml_common.h" +#include "tree_ensemble_common.h" namespace onnxruntime { namespace ml { @@ -15,42 +13,7 @@ class TreeEnsembleClassifier final : public OpKernel { common::Status Compute(OpKernelContext* context) const override; private: - void Initialize(); - common::Status ProcessTreeNode(std::map& classes, - int64_t treeindex, - const T* x_data, - int64_t feature_base) const; - - std::vector nodes_treeids_; - std::vector nodes_nodeids_; - std::vector nodes_featureids_; - std::vector nodes_values_; - std::vector nodes_hitrates_; - std::vector nodes_modes_names_; - std::vector nodes_modes_; - std::vector nodes_truenodeids_; - std::vector nodes_falsenodeids_; - std::vector missing_tracks_true_; // no bool type - - std::vector class_nodeids_; - std::vector class_treeids_; - std::vector class_ids_; - std::vector class_weights_; - int64_t class_count_; - std::set weights_classes_; - - std::vector base_values_; - std::vector classlabels_strings_; - std::vector classlabels_int64s_; - bool using_strings_; - - std::vector> leafnodedata_; - std::unordered_map leafdata_map_; - std::vector roots_; - const int64_t kOffset_ = 4000000000L; - const int64_t kMaxTreeDepth_ = 1000; - POST_EVAL_TRANSFORM post_transform_; - bool weights_are_all_positive_; + detail::TreeEnsembleCommonClassifier tree_ensemble_; }; } // namespace ml } // namespace onnxruntime diff --git a/onnxruntime/core/providers/cpu/ml/tree_ensemble_common.h b/onnxruntime/core/providers/cpu/ml/tree_ensemble_common.h new file mode 100644 index 0000000000..0c4b2e417f --- /dev/null +++ b/onnxruntime/core/providers/cpu/ml/tree_ensemble_common.h @@ -0,0 +1,603 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once +#include "tree_ensemble_aggregator.h" + +namespace onnxruntime { +namespace ml { +namespace detail { + +template +class TreeEnsembleCommon { + public: + int64_t n_targets_or_classes_; + + protected: + std::vector base_values_; + POST_EVAL_TRANSFORM post_transform_; + AGGREGATE_FUNCTION aggregate_function_; + int64_t n_nodes_; + std::vector> nodes_; + std::vector*> roots_; + + int64_t max_tree_depth_; + int64_t n_trees_; + bool same_mode_; + bool has_missing_tracks_; + int parallel_tree_; // starts parallelizing the computing if n_tree >= parallel_tree_ and n_rows == 1 + int parallel_N_; // starts parallelizing the computing if n_rows >= parallel_N_ + + public: + TreeEnsembleCommon(int parallel_tree, + int parallel_N, + const std::string& aggregate_function, + const std::vector& base_values, + int64_t n_targets_or_classes, + const std::vector& nodes_falsenodeids, + const std::vector& nodes_featureids, + const std::vector& nodes_hitrates, + const std::vector& nodes_missing_value_tracks_true, + const std::vector& nodes_modes, + const std::vector& nodes_nodeids, + const std::vector& nodes_treeids, + const std::vector& nodes_truenodeids, + const std::vector& nodes_values, + const std::string& post_transform, + const std::vector& target_class_ids, + const std::vector& target_class_nodeids, + const std::vector& target_class_treeids, + const std::vector& target_class_weights); + + void compute(const Tensor* X, Tensor* Z, Tensor* label) const; + + protected: + TreeNodeElement* ProcessTreeNodeLeave( + TreeNodeElement* root, const ITYPE* x_data) const; + + template + void compute_agg(const Tensor* X, Tensor* Z, Tensor* label, const AGG& agg) const; +}; + +template +TreeEnsembleCommon::TreeEnsembleCommon(int parallel_tree, int parallel_N, + const std::string& aggregate_function, + const std::vector& base_values, + int64_t n_targets_or_classes, + const std::vector& nodes_falsenodeids, + const std::vector& nodes_featureids, + const std::vector& nodes_hitrates, + const std::vector& nodes_missing_value_tracks_true, + const std::vector& nodes_modes, + const std::vector& nodes_nodeids, + const std::vector& nodes_treeids, + const std::vector& nodes_truenodeids, + const std::vector& nodes_values, + const std::string& post_transform, + const std::vector& target_class_ids, + const std::vector& target_class_nodeids, + const std::vector& target_class_treeids, + const std::vector& target_class_weights) { + parallel_tree_ = parallel_tree; + parallel_N_ = parallel_N; + + ORT_ENFORCE(n_targets_or_classes > 0); + ORT_ENFORCE(nodes_falsenodeids.size() == nodes_featureids.size()); + ORT_ENFORCE(nodes_falsenodeids.size() == nodes_modes.size()); + ORT_ENFORCE(nodes_falsenodeids.size() == nodes_nodeids.size()); + ORT_ENFORCE(nodes_falsenodeids.size() == nodes_treeids.size()); + ORT_ENFORCE(nodes_falsenodeids.size() == nodes_truenodeids.size()); + ORT_ENFORCE(nodes_falsenodeids.size() == nodes_values.size()); + ORT_ENFORCE(target_class_ids.size() == target_class_nodeids.size()); + ORT_ENFORCE(target_class_ids.size() == target_class_treeids.size()); + ORT_ENFORCE(target_class_ids.size() == target_class_treeids.size()); + + aggregate_function_ = MakeAggregateFunction(aggregate_function); + post_transform_ = MakeTransform(post_transform); + base_values_ = base_values; + n_targets_or_classes_ = n_targets_or_classes; + max_tree_depth_ = 1000; + + // additional members + std::vector cmodes(nodes_modes.size()); + same_mode_ = true; + int fpos = -1; + for (size_t i = 0; i < nodes_modes.size(); ++i) { + cmodes[i] = MakeTreeNodeMode(nodes_modes[i]); + if (cmodes[i] == NODE_MODE::LEAF) + continue; + if (fpos == -1) { + fpos = static_cast(i); + continue; + } + if (cmodes[i] != cmodes[fpos]) + same_mode_ = false; + } + + // filling nodes + + n_nodes_ = nodes_treeids.size(); + nodes_.resize(n_nodes_); + roots_.clear(); + std::map*> idi; + size_t i; + + for (i = 0; i < nodes_treeids.size(); ++i) { + TreeNodeElement& node = nodes_[i]; + node.id.tree_id = static_cast(nodes_treeids[i]); + node.id.node_id = static_cast(nodes_nodeids[i]); + node.feature_id = static_cast(nodes_featureids[i]); + node.value = nodes_values[i]; + node.hitrates = i < nodes_hitrates.size() ? nodes_hitrates[i] : -1; + node.mode = cmodes[i]; + node.is_not_leaf = node.mode != NODE_MODE::LEAF; + node.truenode = NULL; // nodes_truenodeids[i]; + node.falsenode = NULL; // nodes_falsenodeids[i]; + node.missing_tracks = i < static_cast(nodes_missing_value_tracks_true.size()) + ? (nodes_missing_value_tracks_true[i] == 1 + ? MissingTrack::TRUE + : MissingTrack::FALSE) + : MissingTrack::NONE; + node.is_missing_track_true = node.missing_tracks == MissingTrack::TRUE; + if (idi.find(node.id) != idi.end()) { + ORT_THROW("Node ", node.id.node_id, " in tree ", node.id.tree_id, " is already there."); + } + idi.insert(std::pair*>(node.id, &node)); + } + + TreeNodeElementId coor; + for (auto it = nodes_.begin(); it != nodes_.end(); ++it, ++i) { + if (!it->is_not_leaf) + continue; + i = std::distance(nodes_.begin(), it); + coor.tree_id = it->id.tree_id; + coor.node_id = static_cast(nodes_truenodeids[i]); + + auto found = idi.find(coor); + if (found == idi.end()) { + ORT_THROW("Unable to find node ", coor.tree_id, "-", coor.node_id, " (truenode)."); + } + if (coor.node_id >= 0 && coor.node_id < n_nodes_) { + it->truenode = found->second; + if ((it->truenode->id.tree_id != it->id.tree_id) || + (it->truenode->id.node_id == it->id.node_id)) { + ORT_THROW("One falsenode is pointing either to itself, either to another tree."); + } + } else + it->truenode = NULL; + + coor.node_id = static_cast(nodes_falsenodeids[i]); + found = idi.find(coor); + if (found == idi.end()) { + ORT_THROW("Unable to find node ", coor.tree_id, "-", coor.node_id, " (falsenode)."); + } + if (coor.node_id >= 0 && coor.node_id < n_nodes_) { + it->falsenode = found->second; + if ((it->falsenode->id.tree_id != it->id.tree_id) || + (it->falsenode->id.node_id == it->id.node_id)) { + ORT_THROW("One falsenode is pointing either to itself, either to another tree."); + } + } else + it->falsenode = NULL; + } + + int64_t previous = -1; + for (i = 0; i < static_cast(n_nodes_); ++i) { + if ((previous == -1) || (previous != nodes_[i].id.tree_id)) + roots_.push_back(&(nodes_[i])); + previous = nodes_[i].id.tree_id; + } + + TreeNodeElementId ind; + SparseValue w; + for (i = 0; i < target_class_nodeids.size(); i++) { + ind.tree_id = static_cast(target_class_treeids[i]); + ind.node_id = static_cast(target_class_nodeids[i]); + if (idi.find(ind) == idi.end()) { + ORT_THROW("Unable to find node ", coor.tree_id, "-", coor.node_id, " (weights)."); + } + w.i = target_class_ids[i]; + w.value = target_class_weights[i]; + idi[ind]->weights.push_back(w); + } + + n_trees_ = roots_.size(); + has_missing_tracks_ = false; + for (auto itm = nodes_missing_value_tracks_true.begin(); + itm != nodes_missing_value_tracks_true.end(); ++itm) { + if (*itm) { + has_missing_tracks_ = true; + break; + } + } +} + +template +void TreeEnsembleCommon::compute(const Tensor* X, Tensor* Z, Tensor* label) const { + switch (aggregate_function_) { + case AGGREGATE_FUNCTION::AVERAGE: + compute_agg( + X, Z, label, + TreeAggregatorAverage( + roots_.size(), n_targets_or_classes_, + post_transform_, base_values_)); + return; + case AGGREGATE_FUNCTION::SUM: + compute_agg( + X, Z, label, + TreeAggregatorSum( + roots_.size(), n_targets_or_classes_, + post_transform_, base_values_)); + return; + case AGGREGATE_FUNCTION::MIN: + compute_agg( + X, Z, label, + TreeAggregatorMin( + roots_.size(), n_targets_or_classes_, + post_transform_, base_values_)); + return; + case AGGREGATE_FUNCTION::MAX: + compute_agg( + X, Z, label, + TreeAggregatorMax( + roots_.size(), n_targets_or_classes_, + post_transform_, base_values_)); + return; + default: + ORT_THROW("Unknown aggregation function in TreeEnsemble."); + } +} + +template +template +void TreeEnsembleCommon::compute_agg(const Tensor* X, Tensor* Z, Tensor* label, const AGG& agg) const { + int64_t stride = X->Shape().NumDimensions() == 1 ? X->Shape()[0] : X->Shape()[1]; + int64_t N = X->Shape().NumDimensions() == 1 ? 1 : X->Shape()[0]; + + const ITYPE* x_data = X->template Data(); + OTYPE* z_data = Z->template MutableData(); + int64_t* label_data = label == NULL ? NULL : label->template MutableData(); + + if (n_targets_or_classes_ == 1) { + if (N == 1) { + ScoreValue score = {0, 0}; + if (n_trees_ <= parallel_tree_) { + for (int64_t j = 0; j < n_trees_; ++j) + agg.ProcessTreeNodePrediction1(score, *ProcessTreeNodeLeave(roots_[j], x_data)); + } else { + std::vector> scores_t(n_trees_, {0, 0}); +#ifdef USE_OPENMP +#pragma omp parallel for +#endif + for (int64_t j = 0; j < n_trees_; ++j) { + agg.ProcessTreeNodePrediction1(scores_t[j], *ProcessTreeNodeLeave(roots_[j], x_data)); + } + for (auto it = scores_t.cbegin(); it != scores_t.cend(); ++it) + agg.MergePrediction1(score, *it); + } + + agg.FinalizeScores1(z_data, score, label_data); + } else { + if (N <= parallel_N_) { + ScoreValue score; + size_t j; + + for (int64_t i = 0; i < N; ++i) { + score = {0, 0}; + for (j = 0; j < static_cast(n_trees_); ++j) + agg.ProcessTreeNodePrediction1(score, *ProcessTreeNodeLeave(roots_[j], x_data + i * stride)); + agg.FinalizeScores1(z_data + i * n_targets_or_classes_, score, + label_data == NULL ? NULL : (label_data + i)); + } + } else { +#ifdef USE_OPENMP +#pragma omp parallel for +#endif + for (int64_t i = 0; i < N; ++i) { + ScoreValue score = {0, 0}; + for (size_t j = 0; j < static_cast(n_trees_); ++j) + agg.ProcessTreeNodePrediction1(score, *ProcessTreeNodeLeave(roots_[j], x_data + i * stride)); + agg.FinalizeScores1(z_data + i * n_targets_or_classes_, score, + label_data == NULL ? NULL : (label_data + i)); + } + } + } + } else { + if (N == 1) { + std::vector> scores(n_targets_or_classes_, {0, 0}); + + if (n_trees_ <= parallel_tree_) { + for (int64_t j = 0; j < n_trees_; ++j) + agg.ProcessTreeNodePrediction(scores, *ProcessTreeNodeLeave(roots_[j], x_data)); + agg.FinalizeScores(scores, z_data, -1, label_data); + } else { +#ifdef USE_OPENMP +#pragma omp parallel +#endif + { + std::vector> private_scores(n_targets_or_classes_, {0, 0}); +#ifdef USE_OPENMP +#pragma omp for +#endif + for (int64_t j = 0; j < n_trees_; ++j) { + agg.ProcessTreeNodePrediction(private_scores, *ProcessTreeNodeLeave(roots_[j], x_data)); + } + +#ifdef USE_OPENMP +#pragma omp critical +#endif + agg.MergePrediction(scores, private_scores); + } + + agg.FinalizeScores(scores, z_data, -1, label_data); + } + } else { + if (N <= parallel_N_) { + std::vector> scores(n_targets_or_classes_); + size_t j; + + for (int64_t i = 0; i < N; ++i) { + std::fill(scores.begin(), scores.end(), ScoreValue({0, 0})); + for (j = 0; j < roots_.size(); ++j) + agg.ProcessTreeNodePrediction(scores, *ProcessTreeNodeLeave(roots_[j], x_data + i * stride)); + agg.FinalizeScores(scores, + z_data + i * n_targets_or_classes_, -1, + label_data == NULL ? NULL : (label_data + i)); + ORT_ENFORCE((int64_t)scores.size() == n_targets_or_classes_); + } + } else { +#ifdef USE_OPENMP +#pragma omp parallel +#endif + { + std::vector> scores(n_targets_or_classes_); + size_t j; + +#ifdef USE_OPENMP +#pragma omp for +#endif + for (int64_t i = 0; i < N; ++i) { + std::fill(scores.begin(), scores.end(), ScoreValue({0, 0})); + for (j = 0; j < roots_.size(); ++j) + agg.ProcessTreeNodePrediction(scores, *ProcessTreeNodeLeave(roots_[j], x_data + i * stride)); + agg.FinalizeScores(scores, + z_data + i * n_targets_or_classes_, -1, + label_data == NULL ? NULL : (label_data + i)); + } + } + } + } + } +} + +#define TREE_FIND_VALUE(CMP) \ + if (has_missing_tracks_) { \ + while (root->is_not_leaf) { \ + val = x_data[root->feature_id]; \ + root = (val CMP root->value || \ + (root->is_missing_track_true && _isnan_(val))) \ + ? root->truenode \ + : root->falsenode; \ + } \ + } else { \ + while (root->is_not_leaf) { \ + val = x_data[root->feature_id]; \ + root = val CMP root->value ? root->truenode : root->falsenode; \ + } \ + } + +inline bool _isnan_(float x) { return std::isnan(x); } +inline bool _isnan_(double x) { return std::isnan(x); } +inline bool _isnan_(int64_t) { return false; } +inline bool _isnan_(int32_t) { return false; } + +template +TreeNodeElement* +TreeEnsembleCommon::ProcessTreeNodeLeave( + TreeNodeElement* root, const ITYPE* x_data) const { + ITYPE val; + if (same_mode_) { + switch (root->mode) { + case NODE_MODE::BRANCH_LEQ: + if (has_missing_tracks_) { + while (root->is_not_leaf) { + val = x_data[root->feature_id]; + root = (val <= root->value || + (root->is_missing_track_true && _isnan_(val))) + ? root->truenode + : root->falsenode; + } + } else { + while (root->is_not_leaf) { + val = x_data[root->feature_id]; + root = val <= root->value ? root->truenode : root->falsenode; + } + } + break; + case NODE_MODE::BRANCH_LT: + TREE_FIND_VALUE(<) + break; + case NODE_MODE::BRANCH_GTE: + TREE_FIND_VALUE(>=) + break; + case NODE_MODE::BRANCH_GT: + TREE_FIND_VALUE(>) + break; + case NODE_MODE::BRANCH_EQ: + TREE_FIND_VALUE(==) + break; + case NODE_MODE::BRANCH_NEQ: + TREE_FIND_VALUE(!=) + break; + case NODE_MODE::LEAF: + break; + } + } else { // Different rules to compare to node thresholds. + OTYPE threshold; + while (root->is_not_leaf) { + val = x_data[root->feature_id]; + threshold = root->value; + switch (root->mode) { + case NODE_MODE::BRANCH_LEQ: + root = val <= threshold || (root->is_missing_track_true && _isnan_(val)) + ? root->truenode + : root->falsenode; + break; + case NODE_MODE::BRANCH_LT: + root = val < threshold || (root->is_missing_track_true && _isnan_(val)) + ? root->truenode + : root->falsenode; + break; + case NODE_MODE::BRANCH_GTE: + root = val >= threshold || (root->is_missing_track_true && _isnan_(val)) + ? root->truenode + : root->falsenode; + break; + case NODE_MODE::BRANCH_GT: + root = val > threshold || (root->is_missing_track_true && _isnan_(val)) + ? root->truenode + : root->falsenode; + break; + case NODE_MODE::BRANCH_EQ: + root = val == threshold || (root->is_missing_track_true && _isnan_(val)) + ? root->truenode + : root->falsenode; + break; + case NODE_MODE::BRANCH_NEQ: + root = val != threshold || (root->is_missing_track_true && _isnan_(val)) + ? root->truenode + : root->falsenode; + break; + case NODE_MODE::LEAF: + break; + } + } + } + return root; +} + +template +class TreeEnsembleCommonClassifier : TreeEnsembleCommon { + private: + bool weights_are_all_positive_; + bool binary_case_; + std::vector classlabels_strings_; + std::vector classlabels_int64s_; + std::vector class_labels_; + + public: + TreeEnsembleCommonClassifier(int parallel_tree, + int parallel_N, + const std::string& aggregate_function, + const std::vector& base_values, + const std::vector& nodes_falsenodeids, + const std::vector& nodes_featureids, + const std::vector& nodes_hitrates, + const std::vector& nodes_missing_value_tracks_true, + const std::vector& nodes_modes, + const std::vector& nodes_nodeids, + const std::vector& nodes_treeids, + const std::vector& nodes_truenodeids, + const std::vector& nodes_values, + const std::string& post_transform, + const std::vector& class_ids, + const std::vector& class_nodeids, + const std::vector& class_treeids, + const std::vector& class_weights, + const std::vector& classlabels_strings, + const std::vector& classlabels_int64s); + + int64_t get_class_count() const { return this->n_targets_or_classes_; } + + void compute(const Tensor* X, Tensor* Z, Tensor* label) const; +}; + +template +TreeEnsembleCommonClassifier::TreeEnsembleCommonClassifier( + int parallel_tree, + int parallel_N, + const std::string& aggregate_function, + const std::vector& base_values, + const std::vector& nodes_falsenodeids, + const std::vector& nodes_featureids, + const std::vector& nodes_hitrates, + const std::vector& nodes_missing_value_tracks_true, + const std::vector& nodes_modes, + const std::vector& nodes_nodeids, + const std::vector& nodes_treeids, + const std::vector& nodes_truenodeids, + const std::vector& nodes_values, + const std::string& post_transform, + const std::vector& class_ids, + const std::vector& class_nodeids, + const std::vector& class_treeids, + const std::vector& class_weights, + const std::vector& classlabels_strings, + const std::vector& classlabels_int64s) : TreeEnsembleCommon(parallel_tree, + parallel_N, + aggregate_function, + base_values, + classlabels_strings.size() == 0 ? classlabels_int64s.size() : classlabels_strings.size(), + nodes_falsenodeids, + nodes_featureids, + nodes_hitrates, + nodes_missing_value_tracks_true, + nodes_modes, + nodes_nodeids, + nodes_treeids, + nodes_truenodeids, + nodes_values, + post_transform, + class_ids, + class_nodeids, + class_treeids, + class_weights) { + classlabels_strings_ = classlabels_strings; + classlabels_int64s_ = classlabels_int64s; + + std::set weights_classes; + weights_are_all_positive_ = true; + for (size_t i = 0, end = class_ids.size(); i < end; ++i) { + weights_classes.insert(class_ids[i]); + if (weights_are_all_positive_ && class_weights[i] < 0) + weights_are_all_positive_ = false; + } + binary_case_ = this->n_targets_or_classes_ == 2 && weights_classes.size() == 1; + if (classlabels_strings_.size() > 0) { + class_labels_.resize(classlabels_strings_.size()); + for (size_t i = 0; i < classlabels_strings_.size(); ++i) + class_labels_[i] = i; + } +} + +template +void TreeEnsembleCommonClassifier::compute(const Tensor* X, Tensor* Z, Tensor* label) const { + if (classlabels_strings_.size() == 0) { + this->compute_agg( + X, Z, label, + TreeAggregatorClassifier( + this->roots_.size(), this->n_targets_or_classes_, + this->post_transform_, this->base_values_, + classlabels_int64s_, binary_case_, + weights_are_all_positive_)); + } else { + int64_t N = X->Shape().NumDimensions() == 1 ? 1 : X->Shape()[0]; + std::shared_ptr allocator = std::make_shared(); + Tensor label_int64(DataTypeImpl::GetType(), TensorShape({N}), allocator); + this->compute_agg( + X, Z, &label_int64, + TreeAggregatorClassifier( + this->roots_.size(), this->n_targets_or_classes_, + this->post_transform_, this->base_values_, + class_labels_, binary_case_, + weights_are_all_positive_)); + const int64_t* plabel = label_int64.template Data(); + std::string* labels = label->template MutableData(); + for (size_t i = 0; i < (size_t)N; ++i) + labels[i] = classlabels_strings_[plabel[i]]; + } +} + +} // namespace detail +} // namespace ml +} // namespace onnxruntime diff --git a/onnxruntime/core/providers/cpu/ml/treeregressor.cc b/onnxruntime/core/providers/cpu/ml/treeregressor.cc index 15ca820065..55aba6f8c9 100644 --- a/onnxruntime/core/providers/cpu/ml/treeregressor.cc +++ b/onnxruntime/core/providers/cpu/ml/treeregressor.cc @@ -10,221 +10,40 @@ ONNX_CPU_OPERATOR_TYPED_ML_KERNEL( TreeEnsembleRegressor, 1, float, - KernelDefBuilder().TypeConstraint("T", DataTypeImpl::GetTensorType()).MayInplace(0, 0), \ + KernelDefBuilder().TypeConstraint("T", DataTypeImpl::GetTensorType()).MayInplace(0, 0), TreeEnsembleRegressor); ONNX_CPU_OPERATOR_TYPED_ML_KERNEL( TreeEnsembleRegressor, 1, double, - KernelDefBuilder().TypeConstraint("T", DataTypeImpl::GetTensorType()).MayInplace(0, 0), \ + KernelDefBuilder().TypeConstraint("T", DataTypeImpl::GetTensorType()).MayInplace(0, 0), TreeEnsembleRegressor); template TreeEnsembleRegressor::TreeEnsembleRegressor(const OpKernelInfo& info) : OpKernel(info), - nodes_treeids_(info.GetAttrsOrDefault("nodes_treeids")), - nodes_nodeids_(info.GetAttrsOrDefault("nodes_nodeids")), - nodes_featureids_(info.GetAttrsOrDefault("nodes_featureids")), - nodes_values_(info.GetAttrsOrDefault("nodes_values")), - nodes_hitrates_(info.GetAttrsOrDefault("nodes_hitrates")), - nodes_truenodeids_(info.GetAttrsOrDefault("nodes_truenodeids")), - nodes_falsenodeids_(info.GetAttrsOrDefault("nodes_falsenodeids")), - missing_tracks_true_(info.GetAttrsOrDefault("nodes_missing_value_tracks_true")), - target_nodeids_(info.GetAttrsOrDefault("target_nodeids")), - target_treeids_(info.GetAttrsOrDefault("target_treeids")), - target_ids_(info.GetAttrsOrDefault("target_ids")), - target_weights_(info.GetAttrsOrDefault("target_weights")), - base_values_(info.GetAttrsOrDefault("base_values")), - transform_(::onnxruntime::ml::MakeTransform(info.GetAttrOrDefault("post_transform", "NONE"))), - aggregate_function_(::onnxruntime::ml::MakeAggregateFunction(info.GetAttrOrDefault("aggregate_function", "SUM"))) { - ORT_ENFORCE(info.GetAttr("n_targets", &n_targets_).IsOK()); - - //update nodeids to start at 0 - ORT_ENFORCE(!nodes_treeids_.empty()); - int64_t current_tree_id = 1234567891L; - std::vector tree_offsets; - - for (size_t i = 0; i < nodes_treeids_.size(); i++) { - if (nodes_treeids_[i] != current_tree_id) { - tree_offsets.push_back(nodes_nodeids_[i]); - current_tree_id = nodes_treeids_[i]; - } - int64_t offset = tree_offsets[tree_offsets.size() - 1]; - nodes_nodeids_[i] = nodes_nodeids_[i] - offset; - if (nodes_falsenodeids_[i] >= 0) { - nodes_falsenodeids_[i] = nodes_falsenodeids_[i] - offset; - } - if (nodes_truenodeids_[i] >= 0) { - nodes_truenodeids_[i] = nodes_truenodeids_[i] - offset; - } - } - for (size_t i = 0; i < target_nodeids_.size(); i++) { - int64_t offset = tree_offsets[target_treeids_[i]]; - target_nodeids_[i] = target_nodeids_[i] - offset; - } - - std::vector modes = info.GetAttrsOrDefault("nodes_modes"); - - for (const auto& mode : modes) { - nodes_modes_.push_back(::onnxruntime::ml::MakeTreeNodeMode(mode)); - } - - size_t nodes_id_size = nodes_nodeids_.size(); - ORT_ENFORCE(target_nodeids_.size() == target_ids_.size()); - ORT_ENFORCE(target_nodeids_.size() == target_weights_.size()); - ORT_ENFORCE(nodes_id_size == nodes_featureids_.size()); - ORT_ENFORCE(nodes_id_size == nodes_values_.size()); - ORT_ENFORCE(nodes_id_size == nodes_modes_.size()); - ORT_ENFORCE(nodes_id_size == nodes_truenodeids_.size()); - ORT_ENFORCE(nodes_id_size == nodes_falsenodeids_.size()); - ORT_ENFORCE((nodes_id_size == nodes_hitrates_.size()) || (nodes_hitrates_.empty())); - - max_tree_depth_ = 1000; - offset_ = four_billion_; - using LeafNodeData = std::tuple; - //leafnode data, these are the votes that leaves do - for (size_t i = 0; i < target_nodeids_.size(); i++) { - leafnode_data_.push_back(std::make_tuple(target_treeids_[i], target_nodeids_[i], target_ids_[i], target_weights_[i])); - } - std::sort(begin(leafnode_data_), end(leafnode_data_), [](LeafNodeData const& t1, LeafNodeData const& t2) { - if (std::get<0>(t1) != std::get<0>(t2)) - return std::get<0>(t1) < std::get<0>(t2); - - return std::get<1>(t1) < std::get<1>(t2); - }); - //make an index so we can find the leafnode data quickly when evaluating - int64_t field0 = -1; - int64_t field1 = -1; - for (size_t i = 0; i < leafnode_data_.size(); i++) { - int64_t id0 = std::get<0>(leafnode_data_[i]); - int64_t id1 = std::get<1>(leafnode_data_[i]); - if (id0 != field0 || id1 != field1) { - int64_t id = id0 * four_billion_ + id1; - auto p3 = std::make_pair(id, i); // position is i - leafdata_map_.insert(p3); - field0 = id; - field1 = static_cast(i); - } - } - //treenode ids, some are roots, and roots have no parents - std::unordered_map parents; //holds count of all who point to you - std::unordered_map indices; - //add all the nodes to a map, and the ones that have parents are not roots - std::unordered_map::iterator it; - size_t start_counter = 0L; - for (size_t i = 0; i < nodes_treeids_.size(); i++) { - //make an index to look up later - int64_t id = nodes_treeids_[i] * four_billion_ + nodes_nodeids_[i]; - auto p3 = std::make_pair(id, i); // i is the position - indices.insert(p3); - it = parents.find(id); - if (it == parents.end()) { - //start counter at 0 - auto p1 = std::make_pair(id, start_counter); - parents.insert(p1); - } - } - //all true nodes aren't roots - for (size_t i = 0; i < nodes_truenodeids_.size(); i++) { - if (nodes_modes_[i] == ::onnxruntime::ml::NODE_MODE::LEAF) continue; - //they must be in the same tree - int64_t id = nodes_treeids_[i] * offset_ + nodes_truenodeids_[i]; - it = parents.find(id); - ORT_ENFORCE(it != parents.end()); - it->second++; - } - //all false nodes aren't roots - for (size_t i = 0; i < nodes_falsenodeids_.size(); i++) { - if (nodes_modes_[i] == ::onnxruntime::ml::NODE_MODE::LEAF) continue; - //they must be in the same tree - int64_t id = nodes_treeids_[i] * offset_ + nodes_falsenodeids_[i]; - it = parents.find(id); - ORT_ENFORCE(it != parents.end()); - it->second++; - } - //find all the nodes that dont have other nodes pointing at them - for (auto& parent : parents) { - if (parent.second == 0) { - int64_t id = parent.first; - it = indices.find(id); - ORT_ENFORCE(it != indices.end()); - roots_.push_back(it->second); - } - } - ORT_ENFORCE(base_values_.empty() || base_values_.size() == static_cast(n_targets_)); -} - -template -common::Status TreeEnsembleRegressor::ProcessTreeNode(std::unordered_map < int64_t, std::tuple>& classes, int64_t treeindex, const T* Xdata, int64_t feature_base) const { - //walk down tree to the leaf - auto mode = static_cast<::onnxruntime::ml::NODE_MODE>(nodes_modes_[treeindex]); - int64_t loopcount = 0; - int64_t root = treeindex; - while (mode != ::onnxruntime::ml::NODE_MODE::LEAF) { - T val = Xdata[feature_base + nodes_featureids_[treeindex]]; - bool tracktrue = true; - if (missing_tracks_true_.size() != nodes_truenodeids_.size()) { - tracktrue = false; - } else { - tracktrue = (missing_tracks_true_[treeindex] != 0) && std::isnan(static_cast(val)); - } - float threshold = nodes_values_[treeindex]; - if (mode == ::onnxruntime::ml::NODE_MODE::BRANCH_LEQ) { - treeindex = val <= threshold || tracktrue ? nodes_truenodeids_[treeindex] : nodes_falsenodeids_[treeindex]; - } else if (mode == ::onnxruntime::ml::NODE_MODE::BRANCH_LT) { - treeindex = val < threshold || tracktrue ? nodes_truenodeids_[treeindex] : nodes_falsenodeids_[treeindex]; - } else if (mode == ::onnxruntime::ml::NODE_MODE::BRANCH_GTE) { - treeindex = val >= threshold || tracktrue ? nodes_truenodeids_[treeindex] : nodes_falsenodeids_[treeindex]; - } else if (mode == ::onnxruntime::ml::NODE_MODE::BRANCH_GT) { - treeindex = val > threshold || tracktrue ? nodes_truenodeids_[treeindex] : nodes_falsenodeids_[treeindex]; - } else if (mode == ::onnxruntime::ml::NODE_MODE::BRANCH_EQ) { - treeindex = val == threshold || tracktrue ? nodes_truenodeids_[treeindex] : nodes_falsenodeids_[treeindex]; - } else if (mode == ::onnxruntime::ml::NODE_MODE::BRANCH_NEQ) { - treeindex = val != threshold || tracktrue ? nodes_truenodeids_[treeindex] : nodes_falsenodeids_[treeindex]; - } - - if (treeindex < 0) { - return common::Status(common::ONNXRUNTIME, common::RUNTIME_EXCEPTION, - "treeindex evaluated to a negative value, which should not happen."); - } - treeindex = treeindex + root; - mode = (::onnxruntime::ml::NODE_MODE)nodes_modes_[treeindex]; - loopcount++; - if (loopcount > max_tree_depth_) break; - } - //should be at leaf - int64_t id = nodes_treeids_[treeindex] * four_billion_ + nodes_nodeids_[treeindex]; - //auto it_lp = leafdata_map.find(id); - auto it_lp = leafdata_map_.find(id); - if (it_lp != leafdata_map_.end()) { - size_t index = it_lp->second; - int64_t treeid = std::get<0>(leafnode_data_[index]); - int64_t nodeid = std::get<1>(leafnode_data_[index]); - while (treeid == nodes_treeids_[treeindex] && nodeid == nodes_nodeids_[treeindex]) { - int64_t classid = std::get<2>(leafnode_data_[index]); - float weight = std::get<3>(leafnode_data_[index]); - auto it_classes = classes.find(classid); - if (it_classes != classes.end()) { - auto& tuple = it_classes->second; - std::get<0>(tuple) += weight; - if (weight < std::get<1>(tuple)) std::get<1>(tuple) = weight; - if (weight > std::get<2>(tuple)) std::get<2>(tuple) = weight; - } else { - std::tuple tuple = std::make_tuple(weight, weight, weight); - auto p1 = std::make_pair(classid, tuple); - classes.insert(p1); - } - index++; - if (index >= leafnode_data_.size()) { - break; - } - treeid = std::get<0>(leafnode_data_[index]); - nodeid = std::get<1>(leafnode_data_[index]); - } - } - return common::Status::OK(); -} + tree_ensemble_( + 100, + 50, + info.GetAttrOrDefault("aggregate_function", "SUM"), + info.GetAttrsOrDefault("base_values"), + info.GetAttrOrDefault("n_targets", 0), + info.GetAttrsOrDefault("nodes_falsenodeids"), + info.GetAttrsOrDefault("nodes_featureids"), + info.GetAttrsOrDefault("nodes_hitrates"), + info.GetAttrsOrDefault("nodes_missing_value_tracks_true"), + info.GetAttrsOrDefault("nodes_modes"), + info.GetAttrsOrDefault("nodes_nodeids"), + info.GetAttrsOrDefault("nodes_treeids"), + info.GetAttrsOrDefault("nodes_truenodeids"), + info.GetAttrsOrDefault("nodes_values"), + info.GetAttrOrDefault("post_transform", "NONE"), + info.GetAttrsOrDefault("target_ids"), + info.GetAttrsOrDefault("target_nodeids"), + info.GetAttrsOrDefault("target_treeids"), + info.GetAttrsOrDefault("target_weights")) { +} // namespace ml template common::Status TreeEnsembleRegressor::Compute(OpKernelContext* context) const { @@ -234,47 +53,13 @@ common::Status TreeEnsembleRegressor::Compute(OpKernelContext* context) const return Status(common::ONNXRUNTIME, common::INVALID_ARGUMENT, "Input shape needs to be at least a single dimension."); } - - int64_t stride = X->Shape().NumDimensions() == 1 ? X->Shape()[0] : X->Shape()[1]; int64_t N = X->Shape().NumDimensions() == 1 ? 1 : X->Shape()[0]; - Tensor* Y = context->Output(0, TensorShape({N, n_targets_})); + Tensor* Y = context->Output(0, TensorShape({N, tree_ensemble_.n_targets_or_classes_})); - int64_t write_index = 0; - const auto* x_data = X->template Data(); + tree_ensemble_.compute(X, Y, NULL); - for (int64_t i = 0; i < N; i++) //for each class - { - int64_t current_weight_0 = i * stride; - std::unordered_map> scores; // sum, min, max - //for each tree - for (size_t j = 0; j < roots_.size(); j++) { - //walk each tree from its root - ORT_RETURN_IF_ERROR(ProcessTreeNode(scores, roots_[j], x_data, current_weight_0)); - } - //find aggregate, could use a heap here if there are many classes - std::vector outputs; - for (int64_t j = 0; j < n_targets_; j++) { - //reweight scores based on number of voters - auto it_scores = scores.find(j); - float val = base_values_.size() == (size_t)n_targets_ ? base_values_[j] : 0.f; - if (it_scores != scores.end()) { - if (aggregate_function_ == ::onnxruntime::ml::AGGREGATE_FUNCTION::AVERAGE) { - val += std::get<0>(scores[j]) / roots_.size(); //first element of tuple is already a sum - } else if (aggregate_function_ == ::onnxruntime::ml::AGGREGATE_FUNCTION::SUM) { - val += std::get<0>(scores[j]); - } else if (aggregate_function_ == ::onnxruntime::ml::AGGREGATE_FUNCTION::MIN) { - val += std::get<1>(scores[j]); // second element of tuple is min - } else if (aggregate_function_ == ::onnxruntime::ml::AGGREGATE_FUNCTION::MAX) { - val += std::get<2>(scores[j]); // third element of tuple is max - } - } - outputs.push_back(val); - } - write_scores(outputs, transform_, write_index, Y, -1); - write_index += scores.size(); - } return Status::OK(); } -} // namespace ml +} // namespace onnxruntime } // namespace onnxruntime diff --git a/onnxruntime/core/providers/cpu/ml/treeregressor.h b/onnxruntime/core/providers/cpu/ml/treeregressor.h index 2af61fe393..043a27b5f5 100644 --- a/onnxruntime/core/providers/cpu/ml/treeregressor.h +++ b/onnxruntime/core/providers/cpu/ml/treeregressor.h @@ -2,9 +2,7 @@ // Licensed under the MIT License. #pragma once -#include "core/common/common.h" -#include "core/framework/op_kernel.h" -#include "ml_common.h" +#include "tree_ensemble_common.h" namespace onnxruntime { namespace ml { @@ -15,33 +13,7 @@ class TreeEnsembleRegressor final : public OpKernel { common::Status Compute(OpKernelContext* context) const override; private: - common::Status ProcessTreeNode(std::unordered_map < int64_t, std::tuple>& classes, int64_t treeindex, const T* Xdata, int64_t feature_base) const; - - std::vector nodes_treeids_; - std::vector nodes_nodeids_; - std::vector nodes_featureids_; - std::vector nodes_values_; - std::vector nodes_hitrates_; - std::vector nodes_modes_; - std::vector nodes_truenodeids_; - std::vector nodes_falsenodeids_; - std::vector missing_tracks_true_; - - std::vector target_nodeids_; - std::vector target_treeids_; - std::vector target_ids_; - std::vector target_weights_; - - std::vector base_values_; - int64_t n_targets_; - ::onnxruntime::ml::POST_EVAL_TRANSFORM transform_; - ::onnxruntime::ml::AGGREGATE_FUNCTION aggregate_function_; - std::vector> leafnode_data_; - std::unordered_map leafdata_map_; - std::vector roots_; - int64_t offset_; - int64_t max_tree_depth_; - const int64_t four_billion_ = 4000000000L; + detail::TreeEnsembleCommon tree_ensemble_; }; } // namespace ml } // namespace onnxruntime diff --git a/onnxruntime/test/providers/cpu/ml/treeregressor_test.cc b/onnxruntime/test/providers/cpu/ml/treeregressor_test.cc index 4d8782e121..87a3ecde9d 100644 --- a/onnxruntime/test/providers/cpu/ml/treeregressor_test.cc +++ b/onnxruntime/test/providers/cpu/ml/treeregressor_test.cc @@ -8,8 +8,7 @@ namespace onnxruntime { namespace test { template -void GenTreeAndRunTest(const std::vector& X, const std::vector& base_values, const std::vector& results, const std::string& aggFunction) -{ +void GenTreeAndRunTest(const std::vector& X, const std::vector& base_values, const std::vector& results, const std::string& aggFunction, bool one_obs = false) { OpTester test("TreeEnsembleRegressor", 1, onnxruntime::kMLDomain); //tree @@ -44,69 +43,94 @@ void GenTreeAndRunTest(const std::vector& X, const std::vector& base_v test.AddAttribute("n_targets", (int64_t)2); if (aggFunction == "AVERAGE") { - test.AddAttribute("aggregate_function", "AVERAGE"); + test.AddAttribute("aggregate_function", "AVERAGE"); } else if (aggFunction == "MIN") { test.AddAttribute("aggregate_function", "MIN"); } else if (aggFunction == "MAX") { test.AddAttribute("aggregate_function", "MAX"); - } // default function is SUM + } // default function is SUM //fill input data - test.AddInput("X", {8, 3}, X); - test.AddOutput("Y", {8, 2}, results); + if (one_obs) { + auto X1 = X; + auto results1 = results; + X1.resize(3); + results1.resize(2); + test.AddInput("X", {1, 3}, X1); + test.AddOutput("Y", {1, 2}, results1); + } else { + test.AddInput("X", {8, 3}, X); + test.AddOutput("Y", {8, 2}, results); + } test.Run(); -} +} // namespace test TEST(MLOpTest, TreeRegressorMultiTargetAverage) { std::vector X = {1.f, 0.0f, 0.4f, 3.0f, 44.0f, -3.f, 12.0f, 12.9f, -312.f, 23.0f, 11.3f, -222.f, 23.0f, 11.3f, -222.f, 23.0f, 3311.3f, -222.f, 23.0f, 11.3f, -222.f, 43.0f, 413.3f, -114.f}; std::vector results = {1.33333333f, 29.f, 3.f, 14.f, 2.f, 23.f, 2.f, 23.f, 2.f, 23.f, 2.66666667f, 17.f, 2.f, 23.f, 3.f, 14.f}; std::vector base_values{0.f, 0.f}; - GenTreeAndRunTest(X, base_values, results, "AVERAGE"); + GenTreeAndRunTest(X, base_values, results, "AVERAGE", false); + GenTreeAndRunTest(X, base_values, results, "AVERAGE", true); } TEST(MLOpTest, TreeRegressorMultiTargetMin) { std::vector X = {1.f, 0.0f, 0.4f, 3.0f, 44.0f, -3.f, 12.0f, 12.9f, -312.f, 23.0f, 11.3f, -222.f, 23.0f, 11.3f, -222.f, 23.0f, 3311.3f, -222.f, 23.0f, 11.3f, -222.f, 43.0f, 413.3f, -114.f}; std::vector results = {5.f, 28.f, 8.f, 19.f, 7.f, 28.f, 7.f, 28.f, 7.f, 28.f, 7.f, 19.f, 7.f, 28.f, 8.f, 19.f}; std::vector base_values{5.f, 5.f}; - GenTreeAndRunTest(X, base_values, results, "MIN"); + GenTreeAndRunTest(X, base_values, results, "MIN", false); + GenTreeAndRunTest(X, base_values, results, "MIN", true); } TEST(MLOpTest, TreeRegressorMultiTargetMax) { std::vector X = {1.f, 0.0f, 0.4f, 3.0f, 44.0f, -3.f, 12.0f, 12.9f, -312.f, 23.0f, 11.3f, -222.f, 23.0f, 11.3f, -222.f, 23.0f, 3311.3f, -222.f, 23.0f, 11.3f, -222.f, 43.0f, 413.3f, -114.f}; std::vector results = {2.f, 41.f, 3.f, 14.f, 2.f, 23.f, 2.f, 23.f, 2.f, 23.f, 3.f, 23.f, 2.f, 23.f, 3.f, 14.f}; std::vector base_values{0.f, 0.f}; - GenTreeAndRunTest(X, base_values, results, "MAX"); + GenTreeAndRunTest(X, base_values, results, "MAX", false); + GenTreeAndRunTest(X, base_values, results, "MAX", true); } TEST(MLOpTest, TreeRegressorMultiTargetMaxDouble) { std::vector X = {1.f, 0.0f, 0.4f, 3.0f, 44.0f, -3.f, 12.0f, 12.9f, -312.f, 23.0f, 11.3f, -222.f, 23.0f, 11.3f, -222.f, 23.0f, 3311.3f, -222.f, 23.0f, 11.3f, -222.f, 43.0f, 413.3f, -114.f}; std::vector results = {2.f, 41.f, 3.f, 14.f, 2.f, 23.f, 2.f, 23.f, 2.f, 23.f, 3.f, 23.f, 2.f, 23.f, 3.f, 14.f}; std::vector base_values{0.f, 0.f}; - GenTreeAndRunTest(X, base_values, results, "MAX"); + GenTreeAndRunTest(X, base_values, results, "MAX", false); + GenTreeAndRunTest(X, base_values, results, "MAX", true); } - -TEST(MLOpTest, TreeRegressorSingleTargetSum) { +void GenTreeAndRunTest1(const std::string& aggFunction, bool one_obs) { OpTester test("TreeEnsembleRegressor", 1, onnxruntime::kMLDomain); //tree - std::vector lefts = {1, 0, 0, 1, 0, 0, 1, 0 ,0}; - std::vector rights = {2,0,0,2,0,0,2,0,0}; - std::vector treeids = {0,0,0,1,1,1,2,2,2}; - std::vector nodeids = {0,1,2,0,1,2,0,1,2}; - std::vector featureids = {0,0,0,0,0,0,1,0,0}; - std::vector thresholds = {1,0,0,0.5,0,0,0.5,0,0 }; + std::vector lefts = {1, 0, 0, 1, 0, 0, 1, 0, 0}; + std::vector rights = {2, 0, 0, 2, 0, 0, 2, 0, 0}; + std::vector treeids = {0, 0, 0, 1, 1, 1, 2, 2, 2}; + std::vector nodeids = {0, 1, 2, 0, 1, 2, 0, 1, 2}; + std::vector featureids = {0, 0, 0, 0, 0, 0, 1, 0, 0}; + std::vector thresholds = {1, 0, 0, 0.5, 0, 0, 0.5, 0, 0}; std::vector modes = {"BRANCH_LEQ", "LEAF", "LEAF", "BRANCH_LEQ", "LEAF", "LEAF", "BRANCH_LEQ", "LEAF", "LEAF"}; - std::vector target_treeids = {0,0,1,1,2,2}; - std::vector target_nodeids = {1,2,1,2,1,2}; - std::vector target_classids = {0,0,0,0,0,0}; + std::vector target_treeids = {0, 0, 1, 1, 2, 2}; + std::vector target_nodeids = {1, 2, 1, 2, 1, 2}; + std::vector target_classids = {0, 0, 0, 0, 0, 0}; std::vector target_weights = {33.33333f, 16.66666f, 33.33333f, -3.33333f, 16.66666f, -3.333333f}; std::vector classes = {0, 1}; + std::vector results; + if (aggFunction == "AVERAGE") { + test.AddAttribute("aggregate_function", "AVERAGE"); + results = {63.33333333f / 3, 26.66666667f / 3, 30.0f / 3}; + } else if (aggFunction == "MIN") { + test.AddAttribute("aggregate_function", "MIN"); + results = {-3.33333f, -3.33333f, -3.33333f}; + } else if (aggFunction == "MAX") { + test.AddAttribute("aggregate_function", "MAX"); + results = {33.33333f, 33.33333f, 16.66666f}; + } else { // default function is SUM + results = {63.33333333f, 26.66666667f, 30.0f}; + } + //test data - std::vector X = {0,1,1,1,2,0}; - std::vector results = {63.33333333f, 26.66666667f, 30.0f}; + std::vector X = {0, 1, 1, 1, 2, 0}; //add attributes test.AddAttribute("nodes_truenodeids", lefts); @@ -125,10 +149,39 @@ TEST(MLOpTest, TreeRegressorSingleTargetSum) { // SUM aggregation by default -- no need to add explicitly //fill input data - test.AddInput("X", {3, 2}, X); - test.AddOutput("Y", {3, 1}, results); + if (one_obs) { + auto X1 = X; + auto results1 = results; + X1.resize(2); + results1.resize(1); + test.AddInput("X", {1, 2}, X1); + test.AddOutput("Y", {1, 1}, results1); + } else { + test.AddInput("X", {3, 2}, X); + test.AddOutput("Y", {3, 1}, results); + } test.Run(); } +TEST(MLOpTest, TreeRegressorSingleTargetSum) { + GenTreeAndRunTest1("SUM", false); + GenTreeAndRunTest1("SUM", true); +} + +TEST(MLOpTest, TreeRegressorSingleTargetAverage) { + GenTreeAndRunTest1("AVERAGE", false); + GenTreeAndRunTest1("AVERAGE", true); +} + +TEST(MLOpTest, TreeRegressorSingleTargetMin) { + GenTreeAndRunTest1("MIN", false); + GenTreeAndRunTest1("MIN", true); +} + +TEST(MLOpTest, TreeRegressorSingleTargetMax) { + GenTreeAndRunTest1("MAX", false); + GenTreeAndRunTest1("MAX", true); +} + } // namespace test } // namespace onnxruntime