Skip to content
This repository has been archived by the owner on Jan 13, 2024. It is now read-only.

Commit

Permalink
update C++ code
Browse files Browse the repository at this point in the history
  • Loading branch information
sdpython committed Dec 20, 2019
1 parent a3c7f1a commit 8c41df4
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 75 deletions.
35 changes: 34 additions & 1 deletion _unittests/ut_onnxrt/test_onnxrt_python_runtime_ml_tree.py
Expand Up @@ -145,6 +145,39 @@ def test_onnxrt_python_DecisionTreeRegressor(self):
clr = DecisionTreeRegressor()
clr.fit(X_train, y_train)

model_def = to_onnx(clr, X_train.astype(numpy.float32))
oinf = OnnxInference(model_def)
text = "\n".join(map(lambda x: str(x.ops_), oinf.sequence_))
self.assertIn("TreeEnsembleRegressor", text)

for i in range(0, 20):
y = oinf.run({'X': X_test.astype(numpy.float32)[i: i + 1]})
self.assertEqual(list(sorted(y)), ['variable'])
lexp = clr.predict(X_test[i: i + 1])
self.assertEqual(lexp.shape, y['variable'].shape)
self.assertEqualArray(lexp, y['variable'])

for i in range(0, 20):
y = oinf.run({'X': X_test.astype(numpy.float32)[i: i + 2]})
self.assertEqual(list(sorted(y)), ['variable'])
lexp = clr.predict(X_test[i: i + 2])
self.assertEqual(lexp.shape, y['variable'].shape)
self.assertEqualArray(lexp, y['variable'])

y = oinf.run({'X': X_test.astype(numpy.float32)})
self.assertEqual(list(sorted(y)), ['variable'])
lexp = clr.predict(X_test)
self.assertEqual(lexp.shape, y['variable'].shape)
self.assertEqualArray(lexp, y['variable'])

def test_onnxrt_python_DecisionTreeRegressor2(self):
iris = load_iris()
X, y = iris.data, iris.target
y = numpy.vstack([y, y]).T
X_train, X_test, y_train, _ = train_test_split(X, y, random_state=11)
clr = DecisionTreeRegressor()
clr.fit(X_train, y_train)

model_def = to_onnx(clr, X_train.astype(numpy.float32))
oinf = OnnxInference(model_def)
text = "\n".join(map(lambda x: str(x.ops_), oinf.sequence_))
Expand Down Expand Up @@ -313,5 +346,5 @@ def test_openmp_compilation(self):


if __name__ == "__main__":
TestOnnxrtPythonRuntimeMlTree().test_onnxrt_python_DecisionTreeClassifier_mlabel()
# TestOnnxrtPythonRuntimeMlTree().test_onnxrt_python_DecisionTreeRegressor()
unittest.main()
195 changes: 121 additions & 74 deletions mlprodict/onnxrt/ops_cpu/op_tree_ensemble_regressor_p_.cpp
Expand Up @@ -26,58 +26,60 @@ namespace py = pybind11;

#include "op_common_.hpp"

struct TreeNodeElementId {
int tree_id;
int node_id;
inline bool operator == (const TreeNodeElementId& xyz) const {
return (tree_id == xyz.tree_id) && (node_id == xyz.node_id);
}
inline bool operator < (const TreeNodeElementId& xyz) const {
return ((tree_id < xyz.tree_id) || (
tree_id == xyz.tree_id && node_id < xyz.node_id));
}
};

template<typename NTYPE>
struct SparseValue {
int64_t i;
NTYPE value;
};

enum MissingTrack {
NONE,
TRUE,
FALSE
};

template<typename NTYPE>
struct TreeNodeElement {
TreeNodeElementId id;
int feature_id;
NTYPE value;
NTYPE hitrates;
NODE_MODE mode;
TreeNodeElement *truenode;
TreeNodeElement *falsenode;
MissingTrack missing_tracks;
std::vector<SparseValue<NTYPE>> weights;

bool is_not_leave;
bool is_missing_track_true;
};


template<typename NTYPE>
class RuntimeTreeEnsembleRegressorP
{
public:

struct TreeNodeElementId {
int tree_id;
int node_id;
inline bool operator == (const TreeNodeElementId& xyz) const {
return (tree_id == xyz.tree_id) && (node_id == xyz.node_id);
}
inline bool operator < (const TreeNodeElementId& xyz) const {
return ((tree_id < xyz.tree_id) || (
tree_id == xyz.tree_id && node_id < xyz.node_id));
}
};

struct SparseValue {
int64_t i;
NTYPE value;
};

enum MissingTrack {
NONE,
TRUE,
FALSE
};

struct TreeNodeElement {
TreeNodeElementId id;
int feature_id;
NTYPE value;
NTYPE hitrates;
NODE_MODE mode;
TreeNodeElement *truenode;
TreeNodeElement *falsenode;
MissingTrack missing_tracks;
std::vector<SparseValue> weights;

bool is_not_leave;
bool is_missing_track_true;
};


// tree_ensemble_regressor.h
std::vector<NTYPE> base_values_;
int64_t n_targets_;
POST_EVAL_TRANSFORM post_transform_;
AGGREGATE_FUNCTION aggregate_function_;
int64_t nbnodes_;
TreeNodeElement* nodes_;
std::vector<TreeNodeElement*> roots_;
TreeNodeElement<NTYPE>* nodes_;
std::vector<TreeNodeElement<NTYPE>*> roots_;

int64_t max_tree_depth_;
int64_t nbtrees_;
Expand Down Expand Up @@ -112,9 +114,14 @@ class RuntimeTreeEnsembleRegressorP

py::array_t<NTYPE> compute(py::array_t<NTYPE> X) const;

void ProcessTreeNode(NTYPE* predictions, TreeNodeElement * root,
const NTYPE* x_data,
unsigned char* has_predictions) const;
TreeNodeElement<NTYPE> * ProcessTreeNodeLeave(
TreeNodeElement<NTYPE> * root, const NTYPE* x_data) const;
void ProcessTreeNodePrediction1(
NTYPE* predictions, TreeNodeElement<NTYPE> * leave,
unsigned char* has_predictions) const;
void ProcessTreeNodePrediction(
NTYPE* predictions, TreeNodeElement<NTYPE> * leave,
unsigned char* has_predictions) const;

std::string runtime_options();
std::vector<std::string> get_nodes_modes() const;
Expand Down Expand Up @@ -246,13 +253,13 @@ void RuntimeTreeEnsembleRegressorP<NTYPE>::init(
std::vector<TreeNodeElement<NTYPE>*> roots_;
*/
nbnodes_ = nodes_treeids_.size();
nodes_ = new TreeNodeElement[(int)nbnodes_];
nodes_ = new TreeNodeElement<NTYPE>[(int)nbnodes_];
roots_.clear();
std::map<TreeNodeElementId, TreeNodeElement*> idi;
std::map<TreeNodeElementId, TreeNodeElement<NTYPE>*> idi;
size_t i;

for (i = 0; i < nodes_treeids_.size(); ++i) {
TreeNodeElement * node = nodes_ + i;
TreeNodeElement<NTYPE> * node = nodes_ + i;
node->id.tree_id = (int)nodes_treeids_[i];
node->id.node_id = (int)nodes_nodeids_[i];
node->feature_id = (int)nodes_featureids_[i];
Expand All @@ -272,11 +279,11 @@ void RuntimeTreeEnsembleRegressorP<NTYPE>::init(
sprintf(buffer, "Node %d in tree %d is already there.", (int)node->id.node_id, (int)node->id.tree_id);
throw std::runtime_error(buffer);
}
idi.insert(std::pair<TreeNodeElementId, TreeNodeElement*>(node->id, node));
idi.insert(std::pair<TreeNodeElementId, TreeNodeElement<NTYPE>*>(node->id, node));
}

TreeNodeElementId coor;
TreeNodeElement * it;
TreeNodeElement<NTYPE> * it;
for(i = 0; i < (size_t)nbnodes_; ++i) {
it = nodes_ + i;
if (!it->is_not_leave)
Expand Down Expand Up @@ -333,7 +340,7 @@ void RuntimeTreeEnsembleRegressorP<NTYPE>::init(
}

TreeNodeElementId ind;
SparseValue w;
SparseValue<NTYPE> w;
for (i = 0; i < target_nodeids_.size(); i++) {
ind.tree_id = (int)target_treeids_[i];
ind.node_id = (int)target_nodeids_[i];
Expand Down Expand Up @@ -407,7 +414,10 @@ py::detail::unchecked_mutable_reference<double, 1> _mutable_unchecked1(py::array
scores = 0; \
has_scores = 0; \
for (j = 0; j < (size_t)nbtrees_; ++j) \
ProcessTreeNode(&scores, roots_[j], x_data + i * stride, &has_scores); \
ProcessTreeNodePrediction1( \
&scores, \
ProcessTreeNodeLeave(roots_[j], x_data + i * stride), \
&has_scores); \
val = has_scores \
? (aggregate_function_ == AGGREGATE_FUNCTION::AVERAGE \
? scores / roots_.size() \
Expand All @@ -422,8 +432,10 @@ py::detail::unchecked_mutable_reference<double, 1> _mutable_unchecked1(py::array
std::fill(outputs.begin(), outputs.end(), (NTYPE)0); \
std::fill(has_scores.begin(), has_scores.end(), 0); \
for (j = 0; j < roots_.size(); ++j) \
ProcessTreeNode(scores.data(), roots_[j], x_data + current_weight_0, \
has_scores.data()); \
ProcessTreeNodePrediction( \
scores.data(), \
ProcessTreeNodeLeave(roots_[j], x_data + current_weight_0), \
has_scores.data()); \
for (jt = 0; jt < n_targets_; ++jt) { \
val = base_values_.size() == (size_t)n_targets_ ? base_values_[jt] : 0.f; \
val = (has_scores[jt]) \
Expand Down Expand Up @@ -454,14 +466,20 @@ void RuntimeTreeEnsembleRegressorP<NTYPE>::compute_gil_free(

if (nbtrees_ <= omp_tree_) {
for (int64_t j = 0; j < nbtrees_; ++j)
ProcessTreeNode(&scores, roots_[j], x_data, &has_scores);
ProcessTreeNodePrediction1(
&scores,
ProcessTreeNodeLeave(roots_[j], x_data),
&has_scores);
}
else {
#ifdef USE_OPENMP
#pragma omp parallel for reduction(|: has_scores) reduction(+: scores)
#endif
for (int64_t j = 0; j < nbtrees_; ++j)
ProcessTreeNode(&scores, roots_[j], x_data, &has_scores);
ProcessTreeNodePrediction1(
&scores,
ProcessTreeNodeLeave(roots_[j], x_data),
&has_scores);
}

NTYPE val = has_scores
Expand Down Expand Up @@ -504,7 +522,10 @@ void RuntimeTreeEnsembleRegressorP<NTYPE>::compute_gil_free(
// #pragma omp parallel for reduction(vecdplus: scores) reduction(maxdplus: has_scores)
// #endif
for (j = 0; j < nbtrees_; ++j)
ProcessTreeNode(scores.data(), roots_[j], x_data, has_scores.data());
ProcessTreeNodePrediction(
scores.data(),
ProcessTreeNodeLeave(roots_[j], x_data),
has_scores.data());

std::vector<NTYPE> outputs(n_targets_);
NTYPE val;
Expand Down Expand Up @@ -549,47 +570,41 @@ void RuntimeTreeEnsembleRegressorP<NTYPE>::compute_gil_free(

#define TREE_FIND_VALUE(CMP) \
if (has_missing_tracks_) { \
while (root->is_not_leave && loopcount >= 0) { \
while (root->is_not_leave) { \
val = x_data[root->feature_id]; \
root = (val CMP root->value || \
(root->is_missing_track_true && _isnan_(val) )) \
? root->truenode : root->falsenode; \
--loopcount; \
} \
} \
else { \
while (root->is_not_leave && loopcount >= 0) { \
while (root->is_not_leave) { \
val = x_data[root->feature_id]; \
root = val CMP root->value ? root->truenode : root->falsenode; \
--loopcount; \
} \
}


template<typename NTYPE>
void RuntimeTreeEnsembleRegressorP<NTYPE>::ProcessTreeNode(
NTYPE* predictions, TreeNodeElement * root,
const NTYPE* x_data,
unsigned char* has_predictions) const {
TreeNodeElement<NTYPE> *
RuntimeTreeEnsembleRegressorP<NTYPE>::ProcessTreeNodeLeave(
TreeNodeElement<NTYPE> * root, const NTYPE* x_data) const {
NTYPE val;
if (same_mode_) {
int64_t loopcount = max_tree_depth_;
switch(root->mode) {
case NODE_MODE::BRANCH_LEQ:
if (has_missing_tracks_) {
while (root->is_not_leave && loopcount >= 0) {
while (root->is_not_leave) {
val = x_data[root->feature_id];
root = (val <= root->value ||
(root->is_missing_track_true && _isnan_(val) ))
? root->truenode : root->falsenode;
--loopcount;
}
}
else {
while (root->is_not_leave && loopcount >= 0) {
while (root->is_not_leave) {
val = x_data[root->feature_id];
root = val <= root->value ? root->truenode : root->falsenode;
--loopcount;
}
}
break;
Expand Down Expand Up @@ -618,9 +633,8 @@ void RuntimeTreeEnsembleRegressorP<NTYPE>::ProcessTreeNode(
}
}
else { // Different rules to compare to node thresholds.
int64_t loopcount = 0;
NTYPE threshold;
while ((root->is_not_leave) && (loopcount <= max_tree_depth_)) {
while (root->is_not_leave) {
val = x_data[root->feature_id];
threshold = root->value;
switch (root->mode) {
Expand Down Expand Up @@ -660,10 +674,15 @@ void RuntimeTreeEnsembleRegressorP<NTYPE>::ProcessTreeNode(
throw std::runtime_error(err_msg.str());
}
}
++loopcount;
}
}
return root;
}

template<typename NTYPE>
void RuntimeTreeEnsembleRegressorP<NTYPE>::ProcessTreeNodePrediction(
NTYPE* predictions, TreeNodeElement<NTYPE> * root,
unsigned char* has_predictions) const {
//should be at leaf
switch(aggregate_function_) {
case AGGREGATE_FUNCTION::AVERAGE:
Expand Down Expand Up @@ -691,6 +710,32 @@ void RuntimeTreeEnsembleRegressorP<NTYPE>::ProcessTreeNode(
}


template<typename NTYPE>
inline void RuntimeTreeEnsembleRegressorP<NTYPE>::ProcessTreeNodePrediction1(
NTYPE* predictions, TreeNodeElement<NTYPE> * root,
unsigned char* has_predictions) const {
switch(aggregate_function_) {
case AGGREGATE_FUNCTION::AVERAGE:
case AGGREGATE_FUNCTION::SUM:
*predictions = *has_predictions
? *predictions + root->weights[0].value
: root->weights[0].value;
*has_predictions = 1;
break;
case AGGREGATE_FUNCTION::MIN:
*predictions = (!(*has_predictions) || root->weights[0].value < *predictions)
? root->weights[0].value : *predictions;
*has_predictions = 1;
break;
case AGGREGATE_FUNCTION::MAX:
*predictions = (!(*has_predictions) || root->weights[0].value > *predictions)
? root->weights[0].value : *predictions;
*has_predictions = 1;
break;
}
}


template<typename NTYPE>
py::array_t<int> RuntimeTreeEnsembleRegressorP<NTYPE>::debug_threshold(
py::array_t<NTYPE> values) const {
Expand Down Expand Up @@ -739,8 +784,10 @@ py::array_t<NTYPE> RuntimeTreeEnsembleRegressorP<NTYPE>::compute_tree_outputs(py
for (size_t j = 0; j < roots_.size(); ++j, ++itb) {
std::vector<NTYPE> scores(n_targets_, (NTYPE)0);
std::vector<unsigned char> has_scores(n_targets_, 0);
ProcessTreeNode(scores.data(), roots_[j], x_data + current_weight_0,
has_scores.data());
ProcessTreeNodePrediction(
scores.data(),
ProcessTreeNodeLeave(roots_[j], x_data + current_weight_0),
has_scores.data());
*itb = scores[0];
}
}
Expand Down

0 comments on commit 8c41df4

Please sign in to comment.