mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-07-12 17:57:38 +00:00
Improves implementation of tree ensemble regressor and classifier (4 to 5 times faster) (#2692)
* Improves implementation of tree ensemble regressor (4 to 5 times faster) * Use ORT_THROW
This commit is contained in:
parent
e9d5ed270f
commit
d99554bea1
8 changed files with 1264 additions and 707 deletions
|
|
@ -305,57 +305,78 @@ static inline void ComputeSoftmaxZero(std::vector<float>& values) {
|
|||
}
|
||||
|
||||
template <typename T>
|
||||
void write_scores(std::vector<T>& scores, POST_EVAL_TRANSFORM post_transform, int64_t write_index, Tensor* Z,
|
||||
int add_second_class) {
|
||||
static void write_scores(std::vector<T>& 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<T>(ComputeProbit(static_cast<float>(*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<T>(ComputeLogistic(static_cast<float>(*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<T>(ComputeProbit(static_cast<float>(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<T>(ComputeLogistic(static_cast<float>(scores[0]))));
|
||||
scores[0] = static_cast<T>(ComputeLogistic(static_cast<float>(-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 <typename T>
|
||||
static void write_scores(std::vector<T>& scores, POST_EVAL_TRANSFORM post_transform, int64_t write_index, Tensor* Z,
|
||||
int add_second_class) {
|
||||
T* out_p = Z->template MutableData<T>() + 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).
|
||||
|
|
|
|||
492
onnxruntime/core/providers/cpu/ml/tree_ensemble_aggregator.h
Normal file
492
onnxruntime/core/providers/cpu/ml/tree_ensemble_aggregator.h
Normal file
|
|
@ -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 <math.h>
|
||||
|
||||
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 <typename T>
|
||||
struct SparseValue {
|
||||
int64_t i;
|
||||
T value;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct ScoreValue {
|
||||
unsigned char has_score;
|
||||
T score;
|
||||
};
|
||||
|
||||
enum MissingTrack {
|
||||
NONE,
|
||||
TRUE,
|
||||
FALSE
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct TreeNodeElement {
|
||||
TreeNodeElementId id;
|
||||
int feature_id;
|
||||
T value;
|
||||
T hitrates;
|
||||
NODE_MODE mode;
|
||||
TreeNodeElement<T>* truenode;
|
||||
TreeNodeElement<T>* falsenode;
|
||||
MissingTrack missing_tracks;
|
||||
std::vector<SparseValue<T>> weights;
|
||||
|
||||
bool is_not_leaf;
|
||||
bool is_missing_track_true;
|
||||
};
|
||||
|
||||
template <typename ITYPE, typename OTYPE>
|
||||
class TreeAggregator {
|
||||
protected:
|
||||
size_t n_trees_;
|
||||
int64_t n_targets_or_classes_;
|
||||
POST_EVAL_TRANSFORM post_transform_;
|
||||
const std::vector<OTYPE>& 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<OTYPE>& 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<size_t>(n_targets_or_classes_);
|
||||
}
|
||||
|
||||
// 1 output
|
||||
|
||||
void ProcessTreeNodePrediction1(ScoreValue<OTYPE>& /*prediction*/, const TreeNodeElement<OTYPE>& /*root*/) const {}
|
||||
|
||||
void MergePrediction1(ScoreValue<OTYPE>& /*prediction*/, ScoreValue<OTYPE>& /*prediction2*/) const {}
|
||||
|
||||
void FinalizeScores1(OTYPE* Z, ScoreValue<OTYPE>& prediction, int64_t* /*Y*/) const {
|
||||
prediction.score = prediction.has_score ? (prediction.score + origin_) : origin_;
|
||||
*Z = this->post_transform_ == POST_EVAL_TRANSFORM::PROBIT ? static_cast<OTYPE>(ComputeProbit(static_cast<float>(prediction.score))) : prediction.score;
|
||||
}
|
||||
|
||||
// N outputs
|
||||
|
||||
void ProcessTreeNodePrediction(std::vector<ScoreValue<OTYPE>>& /*predictions*/, const TreeNodeElement<OTYPE>& /*root*/) const {}
|
||||
|
||||
void MergePrediction(std::vector<ScoreValue<OTYPE>>& /*predictions*/, const std::vector<ScoreValue<OTYPE>>& /*predictions2*/) const {}
|
||||
|
||||
void FinalizeScores(std::vector<ScoreValue<OTYPE>>& predictions, OTYPE* Z, int add_second_class, int64_t*) const {
|
||||
ORT_ENFORCE(predictions.size() == (size_t)n_targets_or_classes_);
|
||||
OTYPE val;
|
||||
std::vector<OTYPE> 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 <typename ITYPE, typename OTYPE>
|
||||
class TreeAggregatorSum : public TreeAggregator<ITYPE, OTYPE> {
|
||||
public:
|
||||
TreeAggregatorSum(size_t n_trees,
|
||||
const int64_t& n_targets_or_classes,
|
||||
POST_EVAL_TRANSFORM post_transform,
|
||||
const std::vector<OTYPE>& base_values) : TreeAggregator<ITYPE, OTYPE>(n_trees, n_targets_or_classes,
|
||||
post_transform, base_values) {}
|
||||
|
||||
// 1 output
|
||||
|
||||
void ProcessTreeNodePrediction1(ScoreValue<OTYPE>& prediction, const TreeNodeElement<OTYPE>& root) const {
|
||||
prediction.score += root.weights[0].value;
|
||||
}
|
||||
|
||||
void MergePrediction1(ScoreValue<OTYPE>& prediction, const ScoreValue<OTYPE>& prediction2) const {
|
||||
prediction.score += prediction2.score;
|
||||
}
|
||||
|
||||
void FinalizeScores1(OTYPE* Z, ScoreValue<OTYPE>& prediction, int64_t* /*Y*/) const {
|
||||
prediction.score += this->origin_;
|
||||
*Z = this->post_transform_ == POST_EVAL_TRANSFORM::PROBIT ? static_cast<OTYPE>(ComputeProbit(static_cast<float>(prediction.score))) : prediction.score;
|
||||
}
|
||||
|
||||
// N outputs
|
||||
|
||||
void ProcessTreeNodePrediction(std::vector<ScoreValue<OTYPE>>& predictions, const TreeNodeElement<OTYPE>& 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<ScoreValue<OTYPE>>& predictions, const std::vector<ScoreValue<OTYPE>>& 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<ScoreValue<OTYPE>>& predictions, OTYPE* Z, int add_second_class, int64_t*) const {
|
||||
std::vector<OTYPE> 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 <typename ITYPE, typename OTYPE>
|
||||
class TreeAggregatorAverage : public TreeAggregatorSum<ITYPE, OTYPE> {
|
||||
public:
|
||||
TreeAggregatorAverage(size_t n_trees,
|
||||
const int64_t& n_targets_or_classes,
|
||||
POST_EVAL_TRANSFORM post_transform,
|
||||
const std::vector<OTYPE>& base_values) : TreeAggregatorSum<ITYPE, OTYPE>(n_trees, n_targets_or_classes,
|
||||
post_transform, base_values) {}
|
||||
|
||||
void FinalizeScores1(OTYPE* Z, ScoreValue<OTYPE>& prediction, int64_t* /*Y*/) const {
|
||||
prediction.score /= this->n_trees_;
|
||||
prediction.score += this->origin_;
|
||||
*Z = this->post_transform_ == POST_EVAL_TRANSFORM::PROBIT ? static_cast<OTYPE>(ComputeProbit(static_cast<float>(prediction.score))) : prediction.score;
|
||||
}
|
||||
|
||||
void FinalizeScores(std::vector<ScoreValue<OTYPE>>& predictions, OTYPE* Z, int add_second_class, int64_t*) const {
|
||||
std::vector<OTYPE> 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 <typename ITYPE, typename OTYPE>
|
||||
class TreeAggregatorMin : public TreeAggregator<ITYPE, OTYPE> {
|
||||
public:
|
||||
TreeAggregatorMin(size_t n_trees,
|
||||
const int64_t& n_targets_or_classes,
|
||||
POST_EVAL_TRANSFORM post_transform,
|
||||
const std::vector<OTYPE>& base_values) : TreeAggregator<ITYPE, OTYPE>(n_trees, n_targets_or_classes,
|
||||
post_transform, base_values) {}
|
||||
|
||||
// 1 output
|
||||
|
||||
void ProcessTreeNodePrediction1(ScoreValue<OTYPE>& prediction, const TreeNodeElement<OTYPE>& 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<OTYPE>& prediction, const ScoreValue<OTYPE>& 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<ScoreValue<OTYPE>>& predictions, const TreeNodeElement<OTYPE>& 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<ScoreValue<OTYPE>>& predictions, const std::vector<ScoreValue<OTYPE>>& 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 <typename ITYPE, typename OTYPE>
|
||||
class TreeAggregatorMax : public TreeAggregator<ITYPE, OTYPE> {
|
||||
public:
|
||||
TreeAggregatorMax<ITYPE, OTYPE>(size_t n_trees,
|
||||
const int64_t& n_targets_or_classes,
|
||||
POST_EVAL_TRANSFORM post_transform,
|
||||
const std::vector<OTYPE>& base_values) : TreeAggregator<ITYPE, OTYPE>(n_trees, n_targets_or_classes,
|
||||
post_transform, base_values) {}
|
||||
|
||||
// 1 output
|
||||
|
||||
void ProcessTreeNodePrediction1(ScoreValue<OTYPE>& prediction, const TreeNodeElement<OTYPE>& 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<OTYPE>& prediction, const ScoreValue<OTYPE>& 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<ScoreValue<OTYPE>>& predictions, const TreeNodeElement<OTYPE>& 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<ScoreValue<OTYPE>>& predictions, const std::vector<ScoreValue<OTYPE>>& 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 <typename ITYPE, typename OTYPE>
|
||||
class TreeAggregatorClassifier : public TreeAggregatorSum<ITYPE, OTYPE> {
|
||||
private:
|
||||
const std::vector<int64_t>& 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<OTYPE>& base_values,
|
||||
const std::vector<int64_t>& class_labels,
|
||||
bool binary_case,
|
||||
bool weights_are_all_positive,
|
||||
int64_t positive_label = 1,
|
||||
int64_t negative_label = 0) : TreeAggregatorSum<ITYPE, OTYPE>(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<ScoreValue<OTYPE>>& 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<ScoreValue<OTYPE>>& 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<OTYPE>& prediction, int64_t* Y) const {
|
||||
std::vector<OTYPE> 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<ScoreValue<OTYPE>>& 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<OTYPE> preds;
|
||||
if (this->n_targets_or_classes_ > 2) {
|
||||
// add base values
|
||||
for (int64_t k = 0, end = static_cast<int64_t>(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
|
||||
|
|
@ -138,161 +138,28 @@ ADD_IN_TYPE_TREE_ENSEMBLE_CLASSIFIER_OP(int32_t);
|
|||
template <typename T>
|
||||
TreeEnsembleClassifier<T>::TreeEnsembleClassifier(const OpKernelInfo& info)
|
||||
: OpKernel(info),
|
||||
nodes_treeids_(info.GetAttrsOrDefault<int64_t>("nodes_treeids")),
|
||||
nodes_nodeids_(info.GetAttrsOrDefault<int64_t>("nodes_nodeids")),
|
||||
nodes_featureids_(info.GetAttrsOrDefault<int64_t>("nodes_featureids")),
|
||||
nodes_values_(info.GetAttrsOrDefault<float>("nodes_values")),
|
||||
nodes_hitrates_(info.GetAttrsOrDefault<float>("nodes_hitrates")),
|
||||
nodes_modes_names_(info.GetAttrsOrDefault<std::string>("nodes_modes")),
|
||||
nodes_truenodeids_(info.GetAttrsOrDefault<int64_t>("nodes_truenodeids")),
|
||||
nodes_falsenodeids_(info.GetAttrsOrDefault<int64_t>("nodes_falsenodeids")),
|
||||
missing_tracks_true_(info.GetAttrsOrDefault<int64_t>("nodes_missing_value_tracks_true")),
|
||||
class_nodeids_(info.GetAttrsOrDefault<int64_t>("class_nodeids")),
|
||||
class_treeids_(info.GetAttrsOrDefault<int64_t>("class_treeids")),
|
||||
class_ids_(info.GetAttrsOrDefault<int64_t>("class_ids")),
|
||||
class_weights_(info.GetAttrsOrDefault<float>("class_weights")),
|
||||
base_values_(info.GetAttrsOrDefault<float>("base_values")),
|
||||
classlabels_strings_(info.GetAttrsOrDefault<std::string>("classlabels_strings")),
|
||||
classlabels_int64s_(info.GetAttrsOrDefault<int64_t>("classlabels_int64s")),
|
||||
post_transform_(MakeTransform(info.GetAttrOrDefault<std::string>("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 <typename T>
|
||||
void TreeEnsembleClassifier<T>::Initialize() {
|
||||
int64_t current_tree_id = 1234567891L;
|
||||
std::vector<int64_t> tree_offsets;
|
||||
weights_are_all_positive_ = true;
|
||||
|
||||
for (int64_t i = 0, size_node_treeids = static_cast<int64_t>(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<int64_t>(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<int64_t, int64_t, int64_t, float>;
|
||||
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<int64_t>(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<int64_t, int64_t> parents; // holds count of all who point to you
|
||||
std::unordered_map<int64_t, int64_t> indices;
|
||||
// add all the nodes to a map, and the ones that have parents are not roots_
|
||||
std::unordered_map<int64_t, int64_t>::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<int64_t>(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<size_t>(class_count_) ||
|
||||
base_values_.size() == weights_classes_.size());
|
||||
}
|
||||
tree_ensemble_(
|
||||
100,
|
||||
50,
|
||||
info.GetAttrOrDefault<std::string>("aggregate_function", "SUM"),
|
||||
info.GetAttrsOrDefault<float>("base_values"),
|
||||
info.GetAttrsOrDefault<int64_t>("nodes_falsenodeids"),
|
||||
info.GetAttrsOrDefault<int64_t>("nodes_featureids"),
|
||||
info.GetAttrsOrDefault<float>("nodes_hitrates"),
|
||||
info.GetAttrsOrDefault<int64_t>("nodes_missing_value_tracks_true"),
|
||||
info.GetAttrsOrDefault<std::string>("nodes_modes"),
|
||||
info.GetAttrsOrDefault<int64_t>("nodes_nodeids"),
|
||||
info.GetAttrsOrDefault<int64_t>("nodes_treeids"),
|
||||
info.GetAttrsOrDefault<int64_t>("nodes_truenodeids"),
|
||||
info.GetAttrsOrDefault<float>("nodes_values"),
|
||||
info.GetAttrOrDefault<std::string>("post_transform", "NONE"),
|
||||
info.GetAttrsOrDefault<int64_t>("class_ids"),
|
||||
info.GetAttrsOrDefault<int64_t>("class_nodeids"),
|
||||
info.GetAttrsOrDefault<int64_t>("class_treeids"),
|
||||
info.GetAttrsOrDefault<float>("class_weights"),
|
||||
info.GetAttrsOrDefault<std::string>("classlabels_strings"),
|
||||
info.GetAttrsOrDefault<int64_t>("classlabels_int64s")) {
|
||||
} // namespace ml
|
||||
|
||||
template <typename T>
|
||||
common::Status TreeEnsembleClassifier<T>::Compute(OpKernelContext* context) const {
|
||||
|
|
@ -303,212 +170,13 @@ common::Status TreeEnsembleClassifier<T>::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<T>();
|
||||
|
||||
// for each class
|
||||
std::vector<float> scores;
|
||||
scores.reserve(class_count_);
|
||||
for (int64_t i = 0; i < N; ++i) {
|
||||
scores.clear();
|
||||
int64_t current_weight_0 = i * stride;
|
||||
std::map<int64_t, float> classes;
|
||||
// fill in base values, this might be empty but that is ok
|
||||
for (int64_t k = 0, end = static_cast<int64_t>(base_values_.size()); k < end; ++k) {
|
||||
auto p1 = std::make_pair<int64_t&, const float&>(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<std::string>()[i] = classlabels_strings_[maxclass];
|
||||
} else {
|
||||
Y->template MutableData<int64_t>()[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<std::string>();
|
||||
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<int64_t>();
|
||||
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<size_t>(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 <typename T>
|
||||
common::Status TreeEnsembleClassifier<T>::ProcessTreeNode(std::map<int64_t, float>& classes,
|
||||
int64_t treeindex,
|
||||
const T* x_data,
|
||||
int64_t feature_base) const {
|
||||
// walk down tree to the leaf
|
||||
auto mode = static_cast<NODE_MODE>(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<float>(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<std::underlying_type<NODE_MODE>::type>(mode);
|
||||
return Status(ONNXRUNTIME, INVALID_ARGUMENT, err_msg.str());
|
||||
}
|
||||
}
|
||||
ORT_ENFORCE(treeindex >= 0);
|
||||
treeindex = treeindex + root;
|
||||
mode = static_cast<NODE_MODE>(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<int64_t, float>::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<int64_t>(leafnodedata_.size())) {
|
||||
break;
|
||||
}
|
||||
treeid = std::get<0>(leafnodedata_[index]);
|
||||
nodeid = std::get<1>(leafnodedata_[index]);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
} // namespace ml
|
||||
} // namespace onnxruntime
|
||||
|
|
|
|||
|
|
@ -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<int64_t, float>& classes,
|
||||
int64_t treeindex,
|
||||
const T* x_data,
|
||||
int64_t feature_base) const;
|
||||
|
||||
std::vector<int64_t> nodes_treeids_;
|
||||
std::vector<int64_t> nodes_nodeids_;
|
||||
std::vector<int64_t> nodes_featureids_;
|
||||
std::vector<float> nodes_values_;
|
||||
std::vector<float> nodes_hitrates_;
|
||||
std::vector<std::string> nodes_modes_names_;
|
||||
std::vector<NODE_MODE> nodes_modes_;
|
||||
std::vector<int64_t> nodes_truenodeids_;
|
||||
std::vector<int64_t> nodes_falsenodeids_;
|
||||
std::vector<int64_t> missing_tracks_true_; // no bool type
|
||||
|
||||
std::vector<int64_t> class_nodeids_;
|
||||
std::vector<int64_t> class_treeids_;
|
||||
std::vector<int64_t> class_ids_;
|
||||
std::vector<float> class_weights_;
|
||||
int64_t class_count_;
|
||||
std::set<int64_t> weights_classes_;
|
||||
|
||||
std::vector<float> base_values_;
|
||||
std::vector<std::string> classlabels_strings_;
|
||||
std::vector<int64_t> classlabels_int64s_;
|
||||
bool using_strings_;
|
||||
|
||||
std::vector<std::tuple<int64_t, int64_t, int64_t, float>> leafnodedata_;
|
||||
std::unordered_map<int64_t, int64_t> leafdata_map_;
|
||||
std::vector<int64_t> roots_;
|
||||
const int64_t kOffset_ = 4000000000L;
|
||||
const int64_t kMaxTreeDepth_ = 1000;
|
||||
POST_EVAL_TRANSFORM post_transform_;
|
||||
bool weights_are_all_positive_;
|
||||
detail::TreeEnsembleCommonClassifier<T, float> tree_ensemble_;
|
||||
};
|
||||
} // namespace ml
|
||||
} // namespace onnxruntime
|
||||
|
|
|
|||
603
onnxruntime/core/providers/cpu/ml/tree_ensemble_common.h
Normal file
603
onnxruntime/core/providers/cpu/ml/tree_ensemble_common.h
Normal file
|
|
@ -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 <typename ITYPE, typename OTYPE>
|
||||
class TreeEnsembleCommon {
|
||||
public:
|
||||
int64_t n_targets_or_classes_;
|
||||
|
||||
protected:
|
||||
std::vector<OTYPE> base_values_;
|
||||
POST_EVAL_TRANSFORM post_transform_;
|
||||
AGGREGATE_FUNCTION aggregate_function_;
|
||||
int64_t n_nodes_;
|
||||
std::vector<TreeNodeElement<OTYPE>> nodes_;
|
||||
std::vector<TreeNodeElement<OTYPE>*> 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<OTYPE>& base_values,
|
||||
int64_t n_targets_or_classes,
|
||||
const std::vector<int64_t>& nodes_falsenodeids,
|
||||
const std::vector<int64_t>& nodes_featureids,
|
||||
const std::vector<OTYPE>& nodes_hitrates,
|
||||
const std::vector<int64_t>& nodes_missing_value_tracks_true,
|
||||
const std::vector<std::string>& nodes_modes,
|
||||
const std::vector<int64_t>& nodes_nodeids,
|
||||
const std::vector<int64_t>& nodes_treeids,
|
||||
const std::vector<int64_t>& nodes_truenodeids,
|
||||
const std::vector<OTYPE>& nodes_values,
|
||||
const std::string& post_transform,
|
||||
const std::vector<int64_t>& target_class_ids,
|
||||
const std::vector<int64_t>& target_class_nodeids,
|
||||
const std::vector<int64_t>& target_class_treeids,
|
||||
const std::vector<OTYPE>& target_class_weights);
|
||||
|
||||
void compute(const Tensor* X, Tensor* Z, Tensor* label) const;
|
||||
|
||||
protected:
|
||||
TreeNodeElement<OTYPE>* ProcessTreeNodeLeave(
|
||||
TreeNodeElement<OTYPE>* root, const ITYPE* x_data) const;
|
||||
|
||||
template <typename AGG>
|
||||
void compute_agg(const Tensor* X, Tensor* Z, Tensor* label, const AGG& agg) const;
|
||||
};
|
||||
|
||||
template <typename ITYPE, typename OTYPE>
|
||||
TreeEnsembleCommon<ITYPE, OTYPE>::TreeEnsembleCommon(int parallel_tree, int parallel_N,
|
||||
const std::string& aggregate_function,
|
||||
const std::vector<OTYPE>& base_values,
|
||||
int64_t n_targets_or_classes,
|
||||
const std::vector<int64_t>& nodes_falsenodeids,
|
||||
const std::vector<int64_t>& nodes_featureids,
|
||||
const std::vector<OTYPE>& nodes_hitrates,
|
||||
const std::vector<int64_t>& nodes_missing_value_tracks_true,
|
||||
const std::vector<std::string>& nodes_modes,
|
||||
const std::vector<int64_t>& nodes_nodeids,
|
||||
const std::vector<int64_t>& nodes_treeids,
|
||||
const std::vector<int64_t>& nodes_truenodeids,
|
||||
const std::vector<OTYPE>& nodes_values,
|
||||
const std::string& post_transform,
|
||||
const std::vector<int64_t>& target_class_ids,
|
||||
const std::vector<int64_t>& target_class_nodeids,
|
||||
const std::vector<int64_t>& target_class_treeids,
|
||||
const std::vector<OTYPE>& 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<NODE_MODE> 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<int>(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<TreeNodeElementId, TreeNodeElement<OTYPE>*> idi;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < nodes_treeids.size(); ++i) {
|
||||
TreeNodeElement<OTYPE>& node = nodes_[i];
|
||||
node.id.tree_id = static_cast<int>(nodes_treeids[i]);
|
||||
node.id.node_id = static_cast<int>(nodes_nodeids[i]);
|
||||
node.feature_id = static_cast<int>(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<size_t>(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<TreeNodeElementId, TreeNodeElement<OTYPE>*>(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<int>(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<int>(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<size_t>(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<OTYPE> w;
|
||||
for (i = 0; i < target_class_nodeids.size(); i++) {
|
||||
ind.tree_id = static_cast<int>(target_class_treeids[i]);
|
||||
ind.node_id = static_cast<int>(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 <typename ITYPE, typename OTYPE>
|
||||
void TreeEnsembleCommon<ITYPE, OTYPE>::compute(const Tensor* X, Tensor* Z, Tensor* label) const {
|
||||
switch (aggregate_function_) {
|
||||
case AGGREGATE_FUNCTION::AVERAGE:
|
||||
compute_agg(
|
||||
X, Z, label,
|
||||
TreeAggregatorAverage<ITYPE, OTYPE>(
|
||||
roots_.size(), n_targets_or_classes_,
|
||||
post_transform_, base_values_));
|
||||
return;
|
||||
case AGGREGATE_FUNCTION::SUM:
|
||||
compute_agg(
|
||||
X, Z, label,
|
||||
TreeAggregatorSum<ITYPE, OTYPE>(
|
||||
roots_.size(), n_targets_or_classes_,
|
||||
post_transform_, base_values_));
|
||||
return;
|
||||
case AGGREGATE_FUNCTION::MIN:
|
||||
compute_agg(
|
||||
X, Z, label,
|
||||
TreeAggregatorMin<ITYPE, OTYPE>(
|
||||
roots_.size(), n_targets_or_classes_,
|
||||
post_transform_, base_values_));
|
||||
return;
|
||||
case AGGREGATE_FUNCTION::MAX:
|
||||
compute_agg(
|
||||
X, Z, label,
|
||||
TreeAggregatorMax<ITYPE, OTYPE>(
|
||||
roots_.size(), n_targets_or_classes_,
|
||||
post_transform_, base_values_));
|
||||
return;
|
||||
default:
|
||||
ORT_THROW("Unknown aggregation function in TreeEnsemble.");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename ITYPE, typename OTYPE>
|
||||
template <typename AGG>
|
||||
void TreeEnsembleCommon<ITYPE, OTYPE>::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<ITYPE>();
|
||||
OTYPE* z_data = Z->template MutableData<OTYPE>();
|
||||
int64_t* label_data = label == NULL ? NULL : label->template MutableData<int64_t>();
|
||||
|
||||
if (n_targets_or_classes_ == 1) {
|
||||
if (N == 1) {
|
||||
ScoreValue<OTYPE> 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<ScoreValue<OTYPE>> 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<OTYPE> score;
|
||||
size_t j;
|
||||
|
||||
for (int64_t i = 0; i < N; ++i) {
|
||||
score = {0, 0};
|
||||
for (j = 0; j < static_cast<size_t>(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<OTYPE> score = {0, 0};
|
||||
for (size_t j = 0; j < static_cast<size_t>(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<ScoreValue<OTYPE>> 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<ScoreValue<OTYPE>> 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<ScoreValue<OTYPE>> scores(n_targets_or_classes_);
|
||||
size_t j;
|
||||
|
||||
for (int64_t i = 0; i < N; ++i) {
|
||||
std::fill(scores.begin(), scores.end(), ScoreValue<OTYPE>({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<ScoreValue<OTYPE>> 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<OTYPE>({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 <typename ITYPE, typename OTYPE>
|
||||
TreeNodeElement<OTYPE>*
|
||||
TreeEnsembleCommon<ITYPE, OTYPE>::ProcessTreeNodeLeave(
|
||||
TreeNodeElement<OTYPE>* 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 <typename ITYPE, typename OTYPE>
|
||||
class TreeEnsembleCommonClassifier : TreeEnsembleCommon<ITYPE, OTYPE> {
|
||||
private:
|
||||
bool weights_are_all_positive_;
|
||||
bool binary_case_;
|
||||
std::vector<std::string> classlabels_strings_;
|
||||
std::vector<int64_t> classlabels_int64s_;
|
||||
std::vector<int64_t> class_labels_;
|
||||
|
||||
public:
|
||||
TreeEnsembleCommonClassifier(int parallel_tree,
|
||||
int parallel_N,
|
||||
const std::string& aggregate_function,
|
||||
const std::vector<OTYPE>& base_values,
|
||||
const std::vector<int64_t>& nodes_falsenodeids,
|
||||
const std::vector<int64_t>& nodes_featureids,
|
||||
const std::vector<OTYPE>& nodes_hitrates,
|
||||
const std::vector<int64_t>& nodes_missing_value_tracks_true,
|
||||
const std::vector<std::string>& nodes_modes,
|
||||
const std::vector<int64_t>& nodes_nodeids,
|
||||
const std::vector<int64_t>& nodes_treeids,
|
||||
const std::vector<int64_t>& nodes_truenodeids,
|
||||
const std::vector<OTYPE>& nodes_values,
|
||||
const std::string& post_transform,
|
||||
const std::vector<int64_t>& class_ids,
|
||||
const std::vector<int64_t>& class_nodeids,
|
||||
const std::vector<int64_t>& class_treeids,
|
||||
const std::vector<OTYPE>& class_weights,
|
||||
const std::vector<std::string>& classlabels_strings,
|
||||
const std::vector<int64_t>& 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 <typename ITYPE, typename OTYPE>
|
||||
TreeEnsembleCommonClassifier<ITYPE, OTYPE>::TreeEnsembleCommonClassifier(
|
||||
int parallel_tree,
|
||||
int parallel_N,
|
||||
const std::string& aggregate_function,
|
||||
const std::vector<OTYPE>& base_values,
|
||||
const std::vector<int64_t>& nodes_falsenodeids,
|
||||
const std::vector<int64_t>& nodes_featureids,
|
||||
const std::vector<OTYPE>& nodes_hitrates,
|
||||
const std::vector<int64_t>& nodes_missing_value_tracks_true,
|
||||
const std::vector<std::string>& nodes_modes,
|
||||
const std::vector<int64_t>& nodes_nodeids,
|
||||
const std::vector<int64_t>& nodes_treeids,
|
||||
const std::vector<int64_t>& nodes_truenodeids,
|
||||
const std::vector<OTYPE>& nodes_values,
|
||||
const std::string& post_transform,
|
||||
const std::vector<int64_t>& class_ids,
|
||||
const std::vector<int64_t>& class_nodeids,
|
||||
const std::vector<int64_t>& class_treeids,
|
||||
const std::vector<OTYPE>& class_weights,
|
||||
const std::vector<std::string>& classlabels_strings,
|
||||
const std::vector<int64_t>& classlabels_int64s) : TreeEnsembleCommon<ITYPE, OTYPE>(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<int64_t> 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 <typename ITYPE, typename OTYPE>
|
||||
void TreeEnsembleCommonClassifier<ITYPE, OTYPE>::compute(const Tensor* X, Tensor* Z, Tensor* label) const {
|
||||
if (classlabels_strings_.size() == 0) {
|
||||
this->compute_agg(
|
||||
X, Z, label,
|
||||
TreeAggregatorClassifier<ITYPE, OTYPE>(
|
||||
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<IAllocator> allocator = std::make_shared<CPUAllocator>();
|
||||
Tensor label_int64(DataTypeImpl::GetType<int64_t>(), TensorShape({N}), allocator);
|
||||
this->compute_agg(
|
||||
X, Z, &label_int64,
|
||||
TreeAggregatorClassifier<ITYPE, OTYPE>(
|
||||
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<int64_t>();
|
||||
std::string* labels = label->template MutableData<std::string>();
|
||||
for (size_t i = 0; i < (size_t)N; ++i)
|
||||
labels[i] = classlabels_strings_[plabel[i]];
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace ml
|
||||
} // namespace onnxruntime
|
||||
|
|
@ -10,221 +10,40 @@ ONNX_CPU_OPERATOR_TYPED_ML_KERNEL(
|
|||
TreeEnsembleRegressor,
|
||||
1,
|
||||
float,
|
||||
KernelDefBuilder().TypeConstraint("T", DataTypeImpl::GetTensorType<float>()).MayInplace(0, 0), \
|
||||
KernelDefBuilder().TypeConstraint("T", DataTypeImpl::GetTensorType<float>()).MayInplace(0, 0),
|
||||
TreeEnsembleRegressor<float>);
|
||||
|
||||
ONNX_CPU_OPERATOR_TYPED_ML_KERNEL(
|
||||
TreeEnsembleRegressor,
|
||||
1,
|
||||
double,
|
||||
KernelDefBuilder().TypeConstraint("T", DataTypeImpl::GetTensorType<double>()).MayInplace(0, 0), \
|
||||
KernelDefBuilder().TypeConstraint("T", DataTypeImpl::GetTensorType<double>()).MayInplace(0, 0),
|
||||
TreeEnsembleRegressor<double>);
|
||||
|
||||
template <typename T>
|
||||
TreeEnsembleRegressor<T>::TreeEnsembleRegressor(const OpKernelInfo& info)
|
||||
: OpKernel(info),
|
||||
nodes_treeids_(info.GetAttrsOrDefault<int64_t>("nodes_treeids")),
|
||||
nodes_nodeids_(info.GetAttrsOrDefault<int64_t>("nodes_nodeids")),
|
||||
nodes_featureids_(info.GetAttrsOrDefault<int64_t>("nodes_featureids")),
|
||||
nodes_values_(info.GetAttrsOrDefault<float>("nodes_values")),
|
||||
nodes_hitrates_(info.GetAttrsOrDefault<float>("nodes_hitrates")),
|
||||
nodes_truenodeids_(info.GetAttrsOrDefault<int64_t>("nodes_truenodeids")),
|
||||
nodes_falsenodeids_(info.GetAttrsOrDefault<int64_t>("nodes_falsenodeids")),
|
||||
missing_tracks_true_(info.GetAttrsOrDefault<int64_t>("nodes_missing_value_tracks_true")),
|
||||
target_nodeids_(info.GetAttrsOrDefault<int64_t>("target_nodeids")),
|
||||
target_treeids_(info.GetAttrsOrDefault<int64_t>("target_treeids")),
|
||||
target_ids_(info.GetAttrsOrDefault<int64_t>("target_ids")),
|
||||
target_weights_(info.GetAttrsOrDefault<float>("target_weights")),
|
||||
base_values_(info.GetAttrsOrDefault<float>("base_values")),
|
||||
transform_(::onnxruntime::ml::MakeTransform(info.GetAttrOrDefault<std::string>("post_transform", "NONE"))),
|
||||
aggregate_function_(::onnxruntime::ml::MakeAggregateFunction(info.GetAttrOrDefault<std::string>("aggregate_function", "SUM"))) {
|
||||
ORT_ENFORCE(info.GetAttr<int64_t>("n_targets", &n_targets_).IsOK());
|
||||
|
||||
//update nodeids to start at 0
|
||||
ORT_ENFORCE(!nodes_treeids_.empty());
|
||||
int64_t current_tree_id = 1234567891L;
|
||||
std::vector<int64_t> 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<std::string> modes = info.GetAttrsOrDefault<std::string>("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<int64_t, int64_t, int64_t, float>;
|
||||
//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<int64_t>(i);
|
||||
}
|
||||
}
|
||||
//treenode ids, some are roots, and roots have no parents
|
||||
std::unordered_map<int64_t, size_t> parents; //holds count of all who point to you
|
||||
std::unordered_map<int64_t, size_t> indices;
|
||||
//add all the nodes to a map, and the ones that have parents are not roots
|
||||
std::unordered_map<int64_t, size_t>::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<size_t>(n_targets_));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
common::Status TreeEnsembleRegressor<T>::ProcessTreeNode(std::unordered_map < int64_t, std::tuple<float, float, float>>& 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<float>(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<float, float, float> 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<std::string>("aggregate_function", "SUM"),
|
||||
info.GetAttrsOrDefault<float>("base_values"),
|
||||
info.GetAttrOrDefault<int64_t>("n_targets", 0),
|
||||
info.GetAttrsOrDefault<int64_t>("nodes_falsenodeids"),
|
||||
info.GetAttrsOrDefault<int64_t>("nodes_featureids"),
|
||||
info.GetAttrsOrDefault<float>("nodes_hitrates"),
|
||||
info.GetAttrsOrDefault<int64_t>("nodes_missing_value_tracks_true"),
|
||||
info.GetAttrsOrDefault<std::string>("nodes_modes"),
|
||||
info.GetAttrsOrDefault<int64_t>("nodes_nodeids"),
|
||||
info.GetAttrsOrDefault<int64_t>("nodes_treeids"),
|
||||
info.GetAttrsOrDefault<int64_t>("nodes_truenodeids"),
|
||||
info.GetAttrsOrDefault<float>("nodes_values"),
|
||||
info.GetAttrOrDefault<std::string>("post_transform", "NONE"),
|
||||
info.GetAttrsOrDefault<int64_t>("target_ids"),
|
||||
info.GetAttrsOrDefault<int64_t>("target_nodeids"),
|
||||
info.GetAttrsOrDefault<int64_t>("target_treeids"),
|
||||
info.GetAttrsOrDefault<float>("target_weights")) {
|
||||
} // namespace ml
|
||||
|
||||
template <typename T>
|
||||
common::Status TreeEnsembleRegressor<T>::Compute(OpKernelContext* context) const {
|
||||
|
|
@ -234,47 +53,13 @@ common::Status TreeEnsembleRegressor<T>::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<T>();
|
||||
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<int64_t, std::tuple<float, float, float>> 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<float> 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
|
||||
|
|
|
|||
|
|
@ -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<float, float, float>>& classes, int64_t treeindex, const T* Xdata, int64_t feature_base) const;
|
||||
|
||||
std::vector<int64_t> nodes_treeids_;
|
||||
std::vector<int64_t> nodes_nodeids_;
|
||||
std::vector<int64_t> nodes_featureids_;
|
||||
std::vector<float> nodes_values_;
|
||||
std::vector<float> nodes_hitrates_;
|
||||
std::vector<NODE_MODE> nodes_modes_;
|
||||
std::vector<int64_t> nodes_truenodeids_;
|
||||
std::vector<int64_t> nodes_falsenodeids_;
|
||||
std::vector<int64_t> missing_tracks_true_;
|
||||
|
||||
std::vector<int64_t> target_nodeids_;
|
||||
std::vector<int64_t> target_treeids_;
|
||||
std::vector<int64_t> target_ids_;
|
||||
std::vector<float> target_weights_;
|
||||
|
||||
std::vector<float> base_values_;
|
||||
int64_t n_targets_;
|
||||
::onnxruntime::ml::POST_EVAL_TRANSFORM transform_;
|
||||
::onnxruntime::ml::AGGREGATE_FUNCTION aggregate_function_;
|
||||
std::vector<std::tuple<int64_t, int64_t, int64_t, float>> leafnode_data_;
|
||||
std::unordered_map<int64_t, size_t> leafdata_map_;
|
||||
std::vector<int64_t> roots_;
|
||||
int64_t offset_;
|
||||
int64_t max_tree_depth_;
|
||||
const int64_t four_billion_ = 4000000000L;
|
||||
detail::TreeEnsembleCommon<T, float> tree_ensemble_;
|
||||
};
|
||||
} // namespace ml
|
||||
} // namespace onnxruntime
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@ namespace onnxruntime {
|
|||
namespace test {
|
||||
|
||||
template <typename T>
|
||||
void GenTreeAndRunTest(const std::vector<T>& X, const std::vector<float>& base_values, const std::vector<float>& results, const std::string& aggFunction)
|
||||
{
|
||||
void GenTreeAndRunTest(const std::vector<T>& X, const std::vector<float>& base_values, const std::vector<float>& 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<T>& X, const std::vector<float>& 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<T>("X", {8, 3}, X);
|
||||
test.AddOutput<float>("Y", {8, 2}, results);
|
||||
if (one_obs) {
|
||||
auto X1 = X;
|
||||
auto results1 = results;
|
||||
X1.resize(3);
|
||||
results1.resize(2);
|
||||
test.AddInput<T>("X", {1, 3}, X1);
|
||||
test.AddOutput<float>("Y", {1, 2}, results1);
|
||||
} else {
|
||||
test.AddInput<T>("X", {8, 3}, X);
|
||||
test.AddOutput<float>("Y", {8, 2}, results);
|
||||
}
|
||||
test.Run();
|
||||
}
|
||||
} // namespace test
|
||||
|
||||
TEST(MLOpTest, TreeRegressorMultiTargetAverage) {
|
||||
std::vector<float> 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<float> 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<float> base_values{0.f, 0.f};
|
||||
GenTreeAndRunTest<float>(X, base_values, results, "AVERAGE");
|
||||
GenTreeAndRunTest<float>(X, base_values, results, "AVERAGE", false);
|
||||
GenTreeAndRunTest<float>(X, base_values, results, "AVERAGE", true);
|
||||
}
|
||||
|
||||
TEST(MLOpTest, TreeRegressorMultiTargetMin) {
|
||||
std::vector<float> 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<float> 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<float> base_values{5.f, 5.f};
|
||||
GenTreeAndRunTest<float>(X, base_values, results, "MIN");
|
||||
GenTreeAndRunTest<float>(X, base_values, results, "MIN", false);
|
||||
GenTreeAndRunTest<float>(X, base_values, results, "MIN", true);
|
||||
}
|
||||
|
||||
TEST(MLOpTest, TreeRegressorMultiTargetMax) {
|
||||
std::vector<float> 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<float> 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<float> base_values{0.f, 0.f};
|
||||
GenTreeAndRunTest<float>(X, base_values, results, "MAX");
|
||||
GenTreeAndRunTest<float>(X, base_values, results, "MAX", false);
|
||||
GenTreeAndRunTest<float>(X, base_values, results, "MAX", true);
|
||||
}
|
||||
|
||||
TEST(MLOpTest, TreeRegressorMultiTargetMaxDouble) {
|
||||
std::vector<double> 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<float> 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<float> base_values{0.f, 0.f};
|
||||
GenTreeAndRunTest<double>(X, base_values, results, "MAX");
|
||||
GenTreeAndRunTest<double>(X, base_values, results, "MAX", false);
|
||||
GenTreeAndRunTest<double>(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<int64_t> lefts = {1, 0, 0, 1, 0, 0, 1, 0 ,0};
|
||||
std::vector<int64_t> rights = {2,0,0,2,0,0,2,0,0};
|
||||
std::vector<int64_t> treeids = {0,0,0,1,1,1,2,2,2};
|
||||
std::vector<int64_t> nodeids = {0,1,2,0,1,2,0,1,2};
|
||||
std::vector<int64_t> featureids = {0,0,0,0,0,0,1,0,0};
|
||||
std::vector<float> thresholds = {1,0,0,0.5,0,0,0.5,0,0 };
|
||||
std::vector<int64_t> lefts = {1, 0, 0, 1, 0, 0, 1, 0, 0};
|
||||
std::vector<int64_t> rights = {2, 0, 0, 2, 0, 0, 2, 0, 0};
|
||||
std::vector<int64_t> treeids = {0, 0, 0, 1, 1, 1, 2, 2, 2};
|
||||
std::vector<int64_t> nodeids = {0, 1, 2, 0, 1, 2, 0, 1, 2};
|
||||
std::vector<int64_t> featureids = {0, 0, 0, 0, 0, 0, 1, 0, 0};
|
||||
std::vector<float> thresholds = {1, 0, 0, 0.5, 0, 0, 0.5, 0, 0};
|
||||
std::vector<std::string> modes = {"BRANCH_LEQ", "LEAF", "LEAF", "BRANCH_LEQ", "LEAF", "LEAF", "BRANCH_LEQ", "LEAF", "LEAF"};
|
||||
|
||||
std::vector<int64_t> target_treeids = {0,0,1,1,2,2};
|
||||
std::vector<int64_t> target_nodeids = {1,2,1,2,1,2};
|
||||
std::vector<int64_t> target_classids = {0,0,0,0,0,0};
|
||||
std::vector<int64_t> target_treeids = {0, 0, 1, 1, 2, 2};
|
||||
std::vector<int64_t> target_nodeids = {1, 2, 1, 2, 1, 2};
|
||||
std::vector<int64_t> target_classids = {0, 0, 0, 0, 0, 0};
|
||||
std::vector<float> target_weights = {33.33333f, 16.66666f, 33.33333f, -3.33333f, 16.66666f, -3.333333f};
|
||||
std::vector<int64_t> classes = {0, 1};
|
||||
|
||||
std::vector<float> 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<float> X = {0,1,1,1,2,0};
|
||||
std::vector<float> results = {63.33333333f, 26.66666667f, 30.0f};
|
||||
std::vector<float> 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<float>("X", {3, 2}, X);
|
||||
test.AddOutput<float>("Y", {3, 1}, results);
|
||||
if (one_obs) {
|
||||
auto X1 = X;
|
||||
auto results1 = results;
|
||||
X1.resize(2);
|
||||
results1.resize(1);
|
||||
test.AddInput<float>("X", {1, 2}, X1);
|
||||
test.AddOutput<float>("Y", {1, 1}, results1);
|
||||
} else {
|
||||
test.AddInput<float>("X", {3, 2}, X);
|
||||
test.AddOutput<float>("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
|
||||
|
|
|
|||
Loading…
Reference in a new issue