Coverage for mlprodict/onnxrt/ops_cpu/op_tree_ensemble_regressor.py: 94%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- encoding: utf-8 -*-
2# pylint: disable=E0203,E1101,C0111
3"""
4@file
5@brief Runtime operator.
6"""
7from collections import OrderedDict
8import numpy
9from onnx.defs import onnx_opset_version
10from ._op_helper import _get_typed_class_attribute
11from ._op import OpRunUnaryNum, RuntimeTypeError
12from ._new_ops import OperatorSchema
13from .op_tree_ensemble_regressor_ import ( # pylint: disable=E0611,E0401
14 RuntimeTreeEnsembleRegressorFloat, RuntimeTreeEnsembleRegressorDouble)
15from .op_tree_ensemble_regressor_p_ import ( # pylint: disable=E0611,E0401
16 RuntimeTreeEnsembleRegressorPFloat, RuntimeTreeEnsembleRegressorPDouble)
19class TreeEnsembleRegressorCommon(OpRunUnaryNum):
21 def __init__(self, dtype, onnx_node, desc=None,
22 expected_attributes=None, runtime_version=3, **options):
23 OpRunUnaryNum.__init__(
24 self, onnx_node, desc=desc,
25 expected_attributes=expected_attributes, **options)
26 self._init(dtype=dtype, version=runtime_version)
28 def _get_typed_attributes(self, k):
29 return _get_typed_class_attribute(self, k, self.__class__.atts)
31 def _find_custom_operator_schema(self, op_name):
32 """
33 Finds a custom operator defined by this runtime.
34 """
35 if op_name == "TreeEnsembleRegressorDouble":
36 return TreeEnsembleRegressorDoubleSchema()
37 raise RuntimeError( # pragma: no cover
38 "Unable to find a schema for operator '{}'.".format(op_name))
40 def _init(self, dtype, version):
41 atts = []
42 for k in self.__class__.atts:
43 v = self._get_typed_attributes(k)
44 if k.endswith('_as_tensor'):
45 if (v is not None and isinstance(v, numpy.ndarray) and
46 v.size > 0):
47 # replacements
48 atts[-1] = v
49 if dtype is None:
50 dtype = v.dtype
51 continue
52 atts.append(v)
54 if dtype is None:
55 dtype = numpy.float32
57 if dtype == numpy.float32:
58 if version == 0:
59 self.rt_ = RuntimeTreeEnsembleRegressorFloat()
60 elif version == 1:
61 self.rt_ = RuntimeTreeEnsembleRegressorPFloat(
62 60, 20, False, False)
63 elif version == 2:
64 self.rt_ = RuntimeTreeEnsembleRegressorPFloat(
65 60, 20, True, False)
66 elif version == 3:
67 self.rt_ = RuntimeTreeEnsembleRegressorPFloat(
68 60, 20, True, True)
69 else:
70 raise ValueError("Unknown version '{}'.".format(version))
71 elif dtype == numpy.float64:
72 if version == 0:
73 self.rt_ = RuntimeTreeEnsembleRegressorDouble()
74 elif version == 1:
75 self.rt_ = RuntimeTreeEnsembleRegressorPDouble(
76 60, 20, False, False)
77 elif version == 2:
78 self.rt_ = RuntimeTreeEnsembleRegressorPDouble(
79 60, 20, True, False)
80 elif version == 3:
81 self.rt_ = RuntimeTreeEnsembleRegressorPDouble(
82 60, 20, True, True)
83 else:
84 raise ValueError("Unknown version '{}'.".format(version))
85 else:
86 raise RuntimeTypeError( # pragma: no cover
87 "Unsupported dtype={}.".format(dtype))
88 self.rt_.init(*atts)
90 def _run(self, x): # pylint: disable=W0221
91 """
92 This is a C++ implementation coming from
93 :epkg:`onnxruntime`.
94 `tree_ensemble_classifier.cc
95 <https://github.com/microsoft/onnxruntime/blob/master/onnxruntime/core/providers/cpu/ml/tree_ensemble_classifier.cc>`_.
96 See class :class:`RuntimeTreeEnsembleRegressorFloat
97 <mlprodict.onnxrt.ops_cpu.op_tree_ensemble_regressor_.RuntimeTreeEnsembleRegressorFloat>` or
98 class :class:`RuntimeTreeEnsembleRegressorDouble
99 <mlprodict.onnxrt.ops_cpu.op_tree_ensemble_regressor_.RuntimeTreeEnsembleRegressorDouble>`.
100 """
101 if hasattr(x, 'todense'):
102 x = x.todense()
103 pred = self.rt_.compute(x)
104 if pred.shape[0] != x.shape[0]:
105 pred = pred.reshape(x.shape[0], pred.shape[0] // x.shape[0])
106 return (pred, )
109class TreeEnsembleRegressor_1(TreeEnsembleRegressorCommon):
111 atts = OrderedDict([
112 ('aggregate_function', b'SUM'),
113 ('base_values', numpy.empty(0, dtype=numpy.float32)),
114 ('base_values_as_tensor', []),
115 ('n_targets', 1),
116 ('nodes_falsenodeids', numpy.empty(0, dtype=numpy.int64)),
117 ('nodes_featureids', numpy.empty(0, dtype=numpy.int64)),
118 ('nodes_hitrates', numpy.empty(0, dtype=numpy.float32)),
119 ('nodes_missing_value_tracks_true', numpy.empty(0, dtype=numpy.int64)),
120 ('nodes_modes', []),
121 ('nodes_nodeids', numpy.empty(0, dtype=numpy.int64)),
122 ('nodes_treeids', numpy.empty(0, dtype=numpy.int64)),
123 ('nodes_truenodeids', numpy.empty(0, dtype=numpy.int64)),
124 ('nodes_values', numpy.empty(0, dtype=numpy.float32)),
125 ('post_transform', b'NONE'),
126 ('target_ids', numpy.empty(0, dtype=numpy.int64)),
127 ('target_nodeids', numpy.empty(0, dtype=numpy.int64)),
128 ('target_treeids', numpy.empty(0, dtype=numpy.int64)),
129 ('target_weights', numpy.empty(0, dtype=numpy.float32)),
130 ])
132 def __init__(self, onnx_node, desc=None, runtime_version=1, **options):
133 TreeEnsembleRegressorCommon.__init__(
134 self, numpy.float32, onnx_node, desc=desc,
135 expected_attributes=TreeEnsembleRegressor_1.atts,
136 runtime_version=runtime_version, **options)
139class TreeEnsembleRegressor_3(TreeEnsembleRegressorCommon):
141 atts = OrderedDict([
142 ('aggregate_function', b'SUM'),
143 ('base_values', numpy.empty(0, dtype=numpy.float32)),
144 ('base_values_as_tensor', []),
145 ('n_targets', 1),
146 ('nodes_falsenodeids', numpy.empty(0, dtype=numpy.int64)),
147 ('nodes_featureids', numpy.empty(0, dtype=numpy.int64)),
148 ('nodes_hitrates', numpy.empty(0, dtype=numpy.float32)),
149 ('nodes_hitrates_as_tensor', []),
150 ('nodes_missing_value_tracks_true', numpy.empty(0, dtype=numpy.int64)),
151 ('nodes_modes', []),
152 ('nodes_nodeids', numpy.empty(0, dtype=numpy.int64)),
153 ('nodes_treeids', numpy.empty(0, dtype=numpy.int64)),
154 ('nodes_truenodeids', numpy.empty(0, dtype=numpy.int64)),
155 ('nodes_values', numpy.empty(0, dtype=numpy.float32)),
156 ('nodes_values_as_tensor', []),
157 ('post_transform', b'NONE'),
158 ('target_ids', numpy.empty(0, dtype=numpy.int64)),
159 ('target_nodeids', numpy.empty(0, dtype=numpy.int64)),
160 ('target_treeids', numpy.empty(0, dtype=numpy.int64)),
161 ('target_weights', numpy.empty(0, dtype=numpy.float32)),
162 ('target_weights_as_tensor', []),
163 ])
165 def __init__(self, onnx_node, desc=None, runtime_version=1, **options):
166 TreeEnsembleRegressorCommon.__init__(
167 self, None, onnx_node, desc=desc,
168 expected_attributes=TreeEnsembleRegressor_3.atts,
169 runtime_version=runtime_version, **options)
172class TreeEnsembleRegressorDouble(TreeEnsembleRegressorCommon):
173 """
174 Runtime for the custom operator `TreeEnsembleRegressorDouble`.
175 .. exref::
176 :title: How to use TreeEnsembleRegressorDouble instead of TreeEnsembleRegressor
177 .. runpython::
178 :showcode:
179 import warnings
180 import numpy
181 from sklearn.datasets import make_regression
182 from sklearn.ensemble import (
183 RandomForestRegressor, GradientBoostingRegressor,
184 HistGradientBoostingRegressor)
185 from mlprodict.onnx_conv import to_onnx
186 from mlprodict.onnxrt import OnnxInference
187 with warnings.catch_warnings():
188 warnings.simplefilter("ignore")
189 models = [
190 RandomForestRegressor(n_estimators=10),
191 GradientBoostingRegressor(n_estimators=10),
192 HistGradientBoostingRegressor(max_iter=10),
193 ]
194 X, y = make_regression(1000, n_features=5, n_targets=1)
195 X = X.astype(numpy.float64)
196 conv = {}
197 for model in models:
198 model.fit(X[:500], y[:500])
199 onx64 = to_onnx(model, X, rewrite_ops=True, target_opset=15)
200 assert 'TreeEnsembleRegressorDouble' in str(onx64)
201 expected = model.predict(X)
202 oinf = OnnxInference(onx64)
203 got = oinf.run({'X': X})
204 diff = numpy.abs(got['variable'] - expected)
205 print("%s: max=%f mean=%f" % (
206 model.__class__.__name__, diff.max(), diff.mean()))
207 """
209 atts = OrderedDict([
210 ('aggregate_function', b'SUM'),
211 ('base_values', numpy.empty(0, dtype=numpy.float64)),
212 ('n_targets', 1),
213 ('nodes_falsenodeids', numpy.empty(0, dtype=numpy.int64)),
214 ('nodes_featureids', numpy.empty(0, dtype=numpy.int64)),
215 ('nodes_hitrates', numpy.empty(0, dtype=numpy.float64)),
216 ('nodes_missing_value_tracks_true', numpy.empty(0, dtype=numpy.int64)),
217 ('nodes_modes', []),
218 ('nodes_nodeids', numpy.empty(0, dtype=numpy.int64)),
219 ('nodes_treeids', numpy.empty(0, dtype=numpy.int64)),
220 ('nodes_truenodeids', numpy.empty(0, dtype=numpy.int64)),
221 ('nodes_values', numpy.empty(0, dtype=numpy.float64)),
222 ('post_transform', b'NONE'),
223 ('target_ids', numpy.empty(0, dtype=numpy.int64)),
224 ('target_nodeids', numpy.empty(0, dtype=numpy.int64)),
225 ('target_treeids', numpy.empty(0, dtype=numpy.int64)),
226 ('target_weights', numpy.empty(0, dtype=numpy.float64)),
227 ])
229 def __init__(self, onnx_node, desc=None, runtime_version=1, **options):
230 TreeEnsembleRegressorCommon.__init__(
231 self, numpy.float64, onnx_node, desc=desc,
232 expected_attributes=TreeEnsembleRegressorDouble.atts,
233 runtime_version=runtime_version, **options)
236class TreeEnsembleRegressorDoubleSchema(OperatorSchema):
237 """
238 Defines a schema for operators added in this package
239 such as @see cl TreeEnsembleRegressorDouble.
240 """
242 def __init__(self):
243 OperatorSchema.__init__(self, 'TreeEnsembleRegressorDouble')
244 self.attributes = TreeEnsembleRegressorDouble.atts
247if onnx_opset_version() >= 16:
248 TreeEnsembleRegressor = TreeEnsembleRegressor_3
249else:
250 TreeEnsembleRegressor = TreeEnsembleRegressor_1