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

78 statements  

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) 

17 

18 

19class TreeEnsembleRegressorCommon(OpRunUnaryNum): 

20 

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) 

27 

28 def _get_typed_attributes(self, k): 

29 return _get_typed_class_attribute(self, k, self.__class__.atts) 

30 

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)) 

39 

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) 

53 

54 if dtype is None: 

55 dtype = numpy.float32 

56 

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) 

89 

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, ) 

107 

108 

109class TreeEnsembleRegressor_1(TreeEnsembleRegressorCommon): 

110 

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 ]) 

131 

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) 

137 

138 

139class TreeEnsembleRegressor_3(TreeEnsembleRegressorCommon): 

140 

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 ]) 

164 

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) 

170 

171 

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 """ 

208 

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 ]) 

228 

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) 

234 

235 

236class TreeEnsembleRegressorDoubleSchema(OperatorSchema): 

237 """ 

238 Defines a schema for operators added in this package 

239 such as @see cl TreeEnsembleRegressorDouble. 

240 """ 

241 

242 def __init__(self): 

243 OperatorSchema.__init__(self, 'TreeEnsembleRegressorDouble') 

244 self.attributes = TreeEnsembleRegressorDouble.atts 

245 

246 

247if onnx_opset_version() >= 16: 

248 TreeEnsembleRegressor = TreeEnsembleRegressor_3 

249else: 

250 TreeEnsembleRegressor = TreeEnsembleRegressor_1