From 7b34f3ae458e8e3b2f4ad1aa4d8769169bf3cc31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xavier=20dupr=C3=A9?= Date: Fri, 12 Nov 2021 17:27:30 +0100 Subject: [PATCH 1/6] Fix tf2onnx code --- .../ut_sklapi/test_onnx_speedup_regressor.py | 36 +++++--- _unittests/ut_tools/test_export_onnx.py | 85 ++++++++++++++++--- mlprodict/npy/onnx_variable.py | 10 ++- mlprodict/onnx_conv/helpers/lgbm_helper.py | 17 ++-- .../onnx_tools/exports/tf2onnx_helper.py | 18 +++- mlprodict/onnx_tools/onnx2py_helper.py | 4 +- mlprodict/onnx_tools/onnx_manipulations.py | 2 +- mlprodict/onnx_tools/onnx_tools.py | 6 +- mlprodict/onnxrt/onnx_inference_node.py | 2 +- mlprodict/onnxrt/ops_cpu/op_constant.py | 2 +- mlprodict/onnxrt/ops_cpu/op_conv.py | 6 +- mlprodict/onnxrt/ops_cpu/op_squeeze.py | 4 +- mlprodict/onnxrt/ops_onnxruntime/_op.py | 6 +- mlprodict/sklapi/onnx_speed_up.py | 6 +- mlprodict/testing/einsum/blas_lapack.py | 19 +++-- mlprodict/testing/einsum/einsum_fct.py | 22 ++--- mlprodict/testing/einsum/einsum_impl_ext.py | 37 ++++---- .../testing/test_utils/quantized_tensor.py | 14 +-- 18 files changed, 199 insertions(+), 97 deletions(-) diff --git a/_unittests/ut_sklapi/test_onnx_speedup_regressor.py b/_unittests/ut_sklapi/test_onnx_speedup_regressor.py index a8d922635..00d81b903 100644 --- a/_unittests/ut_sklapi/test_onnx_speedup_regressor.py +++ b/_unittests/ut_sklapi/test_onnx_speedup_regressor.py @@ -29,7 +29,7 @@ def setUp(self): def opset(self): return get_opset_number_from_onnx() - @ignore_warnings(ConvergenceWarning) + @ignore_warnings((ConvergenceWarning, DeprecationWarning)) def test_speedup_regressor32(self): data = load_iris() X, y = data.data, data.target @@ -38,7 +38,17 @@ def test_speedup_regressor32(self): spd.fit(X, y) spd.assert_almost_equal(X, decimal=5) - @ignore_warnings(ConvergenceWarning) + @ignore_warnings((ConvergenceWarning, DeprecationWarning)) + def test_speedup_regressor32_weights(self): + data = load_iris() + X, y = data.data, data.target + weights = (y.copy() + 1).astype(X.dtype) + spd = OnnxSpeedupRegressor( + LinearRegression(), target_opset=self.opset()) + spd.fit(X, y, weights) + spd.assert_almost_equal(X, decimal=5) + + @ignore_warnings((ConvergenceWarning, DeprecationWarning)) def test_speedup_regressor32_onnxruntime(self): data = load_iris() X, y = data.data, data.target @@ -48,7 +58,7 @@ def test_speedup_regressor32_onnxruntime(self): spd.fit(X, y) spd.assert_almost_equal(X, decimal=5) - @ignore_warnings(ConvergenceWarning) + @ignore_warnings((ConvergenceWarning, DeprecationWarning)) def test_speedup_regressor32_numpy(self): data = load_iris() X, y = data.data, data.target @@ -58,7 +68,7 @@ def test_speedup_regressor32_numpy(self): spd.fit(X, y) spd.assert_almost_equal(X, decimal=5) - @ignore_warnings(ConvergenceWarning) + @ignore_warnings((ConvergenceWarning, DeprecationWarning)) def test_speedup_regressor32_numba(self): data = load_iris() X, y = data.data, data.target @@ -70,7 +80,7 @@ def test_speedup_regressor32_numba(self): spd.assert_almost_equal(X, decimal=5) self.assertIn("CPUDispatch", str(spd.onnxrt_.func)) - @ignore_warnings(ConvergenceWarning) + @ignore_warnings((ConvergenceWarning, DeprecationWarning)) def test_speedup_regressor64(self): data = load_iris() X, y = data.data, data.target @@ -80,7 +90,7 @@ def test_speedup_regressor64(self): spd.fit(X, y) spd.assert_almost_equal(X) - @ignore_warnings(ConvergenceWarning) + @ignore_warnings((ConvergenceWarning, DeprecationWarning)) def test_speedup_regressor64_op_version(self): data = load_iris() X, y = data.data, data.target @@ -91,7 +101,7 @@ def test_speedup_regressor64_op_version(self): opset = spd.op_version self.assertGreater(self.opset(), opset['']) - @ignore_warnings(ConvergenceWarning) + @ignore_warnings((ConvergenceWarning, DeprecationWarning)) def test_speedup_regressor64_pickle(self): data = load_iris() X, y = data.data, data.target @@ -112,7 +122,7 @@ def test_speedup_regressor64_pickle(self): got = spd2.raw_predict(X) self.assertEqualArray(expected, got) - @ignore_warnings(ConvergenceWarning) + @ignore_warnings((ConvergenceWarning, DeprecationWarning)) def test_speedup_regressor64_numpy_pickle(self): data = load_iris() X, y = data.data, data.target @@ -133,7 +143,7 @@ def test_speedup_regressor64_numpy_pickle(self): got = spd2.raw_predict(X) self.assertEqualArray(expected, got) - @ignore_warnings(ConvergenceWarning) + @ignore_warnings((ConvergenceWarning, DeprecationWarning)) def test_speedup_regressor64_numba_pickle(self): data = load_iris() X, y = data.data, data.target @@ -154,7 +164,7 @@ def test_speedup_regressor64_numba_pickle(self): got = spd2.raw_predict(X) self.assertEqualArray(expected, got) - @ignore_warnings(ConvergenceWarning) + @ignore_warnings((ConvergenceWarning, DeprecationWarning)) def test_speedup_regressor64_onnx(self): data = load_iris() X, y = data.data, data.target @@ -168,7 +178,7 @@ def test_speedup_regressor64_onnx(self): got = oinf.run({'X': X})['variable'] self.assertEqualArray(expected, got) - @ignore_warnings(ConvergenceWarning) + @ignore_warnings((ConvergenceWarning, DeprecationWarning)) def test_speedup_regressor64_onnx_numpy(self): data = load_iris() X, y = data.data, data.target @@ -182,7 +192,7 @@ def test_speedup_regressor64_onnx_numpy(self): got = oinf.run({'X': X})['variable'] self.assertEqualArray(expected, got) - @ignore_warnings(ConvergenceWarning) + @ignore_warnings((ConvergenceWarning, DeprecationWarning)) def test_speedup_regressor64_onnx_numba(self): data = load_iris() X, y = data.data, data.target @@ -197,7 +207,7 @@ def test_speedup_regressor64_onnx_numba(self): got = oinf.run({'X': X})['variable'] self.assertEqualArray(expected, got) - @ignore_warnings(ConvergenceWarning) + @ignore_warnings((ConvergenceWarning, DeprecationWarning)) def test_speedup_regressor64_onnx_numba_python(self): data = load_iris() X, y = data.data, data.target diff --git a/_unittests/ut_tools/test_export_onnx.py b/_unittests/ut_tools/test_export_onnx.py index 5c6b1be00..b6ddaeeb7 100644 --- a/_unittests/ut_tools/test_export_onnx.py +++ b/_unittests/ut_tools/test_export_onnx.py @@ -5,6 +5,7 @@ import unittest import collections import inspect +import traceback from io import StringIO from contextlib import redirect_stdout, redirect_stderr import numpy @@ -500,6 +501,64 @@ def any_version(cls, opset, ctx, node, **kwargs): # pylint: disable=R0915 def version_13(cls, ctx, node, **kwargs): return cls.any_version(13, ctx, node, **kwargs) +class ConvertSliceOp: + supported_dtypes = [ + numpy.float32, + ] + + @classmethod + def version_1(cls, ctx, node, **kwargs): + # T output = Slice(T input, Index begin, Index size) + # T output = Slice(T input, Tind starts, Tind ends, Tind axes, Tind steps) + # "ends" are exclusive, "axes" and "steps" are optional, their default val are [0, ...] and 1 + input_tensor = node.input[0] + starts = node.input[1] + size = node.input[2] + # in tf, size can be -1 which means all elem are taken, so size can't be added starts directly. + # the way to make sure size are not less than 0: set "sizes"'s elem to be int_max if elem val is -1 + size_dtype = ctx.get_dtype(size) + size_np_dtype = utils.map_onnx_to_numpy_type(size_dtype) + if ctx.get_node_by_output(size).is_const() and ctx.get_node_by_output(starts).is_const(): + starts = ctx.get_node_by_output(starts).get_tensor_value() + sizes = ctx.get_node_by_output(size).get_tensor_value() + ends = [] + for start, size in zip(starts, sizes): + # get all elements + if size == -1: + dtype = ctx.get_dtype(node.input[1]) + utils.make_sure(dtype, "dtype of {} is None".format(node.input[1])) + utils.make_sure(dtype, "dtype of {} is None".format(node.input[1])) + ends.append(np.iinfo(dtype).max) + else: + ends.append(start + size) + + else: + neg_one_val = np.array([-1]).astype(size_np_dtype) + neg_one = ctx.make_const(utils.make_name("const"), neg_one_val).output[0] + + int_max_val = np.array([utils.get_max_value(size_np_dtype)]).astype(size_np_dtype) + int_max = ctx.make_const(utils.make_name("largest_int_val"), int_max_val).output[0] + + size_are_neg_one_flag = ctx.make_node("Equal", [neg_one, size]).output[0] + size_are_neg_one_flag = ctx.make_node("Cast", [size_are_neg_one_flag], attr={"to": size_dtype}).output[0] + value_to_add = ctx.make_node("Mul", [int_max, size_are_neg_one_flag]).output[0] + size_processed = ctx.make_node("Add", [size, value_to_add]).output[0] + ends = ctx.make_node("Add", [starts, size_processed]).output[0] + + ctx.remove_node(node.name) + inputs_map = {"data": input_tensor, "starts": starts, "ends": ends} + kwargs = {**inputs_map, "outputs": node.output} + _ = GraphBuilder(ctx).make_slice(kwargs, name=node.name) + + @classmethod + def version_10(cls, ctx, node, **kwargs): + cls.version_1(ctx, node, **kwargs) + + @classmethod + def version_11(cls, ctx, node, **kwargs): + cls.version_1(ctx, node, **kwargs) + + class TestExportOnnx(ExtTestCase): @@ -722,7 +781,9 @@ def verify_tf(self, content): 'print': print, 'sorted': sorted, 'collections': collections, 'inspect': inspect, 'helper': helper, "make_sure": make_sure, - 'ConvertFFT2DOp': ConvertFFT2DOp, "make_name": make_name, + 'ConvertFFT2DOp': ConvertFFT2DOp, + 'ConvertSliceOp': ConvertSliceOp, + "make_name": make_name, 'map_onnx_to_numpy_type': map_onnx_to_numpy_type, 'GraphBuilder': GraphBuilder} out = StringIO() @@ -736,19 +797,20 @@ def verify_tf(self, content): try: exec(obj, glo, loc) # pylint: disable=W0122 except Exception as e: + tb = traceback.format_exc() raise AssertionError( - "Unable to execute a script due to %r. " + "Unable to execute a script due to %r\n%s. " "\n--OUT--\n%s\n--ERR--\n%s\n--CODE--\n%s" - "" % (e, out.getvalue(), err.getvalue(), + "" % (e, tb, out.getvalue(), err.getvalue(), print_code(content))) from e return glo, loc def test_export2tf2onnx(self): this = os.path.dirname(__file__) folder = os.path.join(this, "data") - names = ["fft2d_any.onnx", "slice.onnx"] + names = [("fft2d_any.onnx", 'FFT2D'), ("slice.onnx", 'Slice')] for rt in ['python', 'onnxruntime1']: - for name in names: + for name, op_name in names: with self.subTest(name=name, rt=rt): oinf0 = OnnxInference( os.path.join(folder, name), runtime=rt) @@ -757,12 +819,15 @@ def test_export2tf2onnx(self): y = oinf0.run({'x': x}) new_onnx = export2tf2onnx( - os.path.join(folder, name), name="FFT2D") + os.path.join(folder, name), name=op_name) _, loc = self.verify_tf(new_onnx) model = loc['onnx_raw'] - self.assertIn('op_type: "FFT2D"', str(model)) + self.assertIn('op_type: "%s"' % op_name, str(model)) + self.assertNotEqual( + loc['onnx_raw'].SerializeToString(), + loc['onnx_model'].SerializeToString()) model = loc['onnx_model'] - self.assertNotIn('op_type: "FFT2D"', str(model)) + self.assertNotIn('op_type: "%s"' % op_name, str(model)) if rt == 'onnxruntime1': opts = SessionOptions() @@ -775,10 +840,10 @@ def test_export2tf2onnx(self): y1 = oinf.run({'x': x}) new_onnx = export2tf2onnx( - os.path.join(folder, name), name="FFT2D") + os.path.join(folder, name), name=op_name) _, loc = self.verify_tf(new_onnx) model = loc['onnx_model'] - self.assertNotIn('op_type: "FFT2D"', str(model)) + self.assertNotIn('op_type: "%s"' % op_name, str(model)) oinf = OnnxInference(model, runtime=rt) y2 = oinf.run({'x': x}) diff --git a/mlprodict/npy/onnx_variable.py b/mlprodict/npy/onnx_variable.py index bc24a1a4c..e2e10751a 100644 --- a/mlprodict/npy/onnx_variable.py +++ b/mlprodict/npy/onnx_variable.py @@ -74,8 +74,9 @@ def __init__(self, *inputs, op=None, select_output=None, if not isinstance(inp, numpy.ndarray): continue if inp.size > 0 and isinstance(inp.ravel()[0], (numpy.ndarray, OnnxVar)): - raise TypeError("Unexpected type for input %d: %r, %r." - "" % (i, type(inp), inp.ravel()[0])) + raise TypeError( # pragma: no cover + "Unexpected type for input %d: %r, %r." + "" % (i, type(inp), inp.ravel()[0])) self.dtype = self._guess_dtype(dtype) def _guess_dtype(self, dtype): @@ -177,8 +178,9 @@ def to_algebra(self, op_version=None): isinstance( inp.ravel()[0], # pylint: disable=E1101 (numpy.ndarray, OnnxVar))): - raise TypeError("Unexpected type for an input %r, %r." - "" % (type(inp), inp.ravel()[0])) # pylint: disable=E1101 + raise TypeError( # pragma: no cover + "Unexpected type for an input %r, %r." + "" % (type(inp), inp.ravel()[0])) # pylint: disable=E1101 new_inputs.append(inp) else: new_inputs.append( diff --git a/mlprodict/onnx_conv/helpers/lgbm_helper.py b/mlprodict/onnx_conv/helpers/lgbm_helper.py index 4449de818..5cd8deffc 100644 --- a/mlprodict/onnx_conv/helpers/lgbm_helper.py +++ b/mlprodict/onnx_conv/helpers/lgbm_helper.py @@ -91,7 +91,8 @@ def dump_booster_model(self, num_iteration=None, start_iteration=0, string_buffer = ctypes.create_string_buffer(buffer_len) ptr_string_buffer = ctypes.c_char_p(*[ctypes.addressof(string_buffer)]) if verbose >= 2: - print("[dump_booster_model] call CAPI: LGBM_BoosterDumpModel") + print( # pragma: no cover + "[dump_booster_model] call CAPI: LGBM_BoosterDumpModel") _safe_call(_LIB.LGBM_BoosterDumpModel( self.handle, ctypes.c_int(start_iteration), @@ -136,9 +137,9 @@ def __init__(self, *args, info=None, n_trees=None, verbose=0, self.verbose = verbose self.stored = 0 if verbose >= 2 and n_trees is not None: - from tqdm import tqdm - self.loop = tqdm(total=n_trees) - self.loop.set_description("dump_booster") + from tqdm import tqdm # pragma: no cover + self.loop = tqdm(total=n_trees) # pragma: no cover + self.loop.set_description("dump_booster") # pragma: no cover else: self.loop = None @@ -158,7 +159,7 @@ def hook(self, obj): if 'tree_info' in obj: self.info['decision_nodes'] = self.nodes if self.n_trees is not None and len(self.nodes) != self.n_trees: - raise RuntimeError( + raise RuntimeError( # pragma: no cover "Unexpected number of trees %d (expecting %d)." % ( len(self.nodes), self.n_trees)) self.nodes = [] @@ -179,7 +180,7 @@ def hook(self, obj): return obj if verbose >= 2: - print("[dump_booster_model] to_json") + print("[dump_booster_model] to_json") # pragma: no cover info = {} ret = json.loads(string_buffer.value.decode('utf-8'), cls=Hook, info=info, n_trees=self.num_trees(), verbose=verbose) @@ -187,7 +188,7 @@ def hook(self, obj): json.dumps(self.pandas_categorical, default=json_default_with_numpy)) if verbose >= 2: - print("[dump_booster_model] end.") + print("[dump_booster_model] end.") # pragma: no cover return ret, info @@ -253,7 +254,7 @@ def modify_tree_for_rule_in_set(gbm, use_float=False, verbose=0, count=0, # pyl dec_nodes = info['decision_nodes'] else: dec_nodes = None - if verbose >= 2: + if verbose >= 2: # pragma: no cover from tqdm import tqdm loop = tqdm(gbm['tree_info']) for i, tree in enumerate(loop): diff --git a/mlprodict/onnx_tools/exports/tf2onnx_helper.py b/mlprodict/onnx_tools/exports/tf2onnx_helper.py index 05e3f1069..a39ad5fec 100644 --- a/mlprodict/onnx_tools/exports/tf2onnx_helper.py +++ b/mlprodict/onnx_tools/exports/tf2onnx_helper.py @@ -113,6 +113,8 @@ def make_sure(cond, msg, *args): def map_onnx_to_numpy_type(onnx_dtype): "Converts ONNX type into numpy type." + if onnx_dtype is None: + return numpy.float32 return guess_dtype(onnx_dtype) @@ -162,10 +164,11 @@ class Tf2OnnxConvert: """ def __init__(self, onnx_model, _tf_op=None, verbose=None, - target_opset=None): + target_opset=None, max_iter=100): self._onnx_model = onnx_model self._tf_op = _tf_op or tf_op self.verbose = verbose + self.max_iter = max_iter if isinstance(target_opset, int): self.target_opsets = {'': target_opset} elif isinstance(target_opset, dict): @@ -317,13 +320,17 @@ def replace_all_inputs(self, old_name, new_name): :param new_name: new name :return: list of impacted nodes """ + if self.verbose: + print( # pragma: no cover + "[Tf2OnnxConvert.replace_all_inputs] replace %r by %r" % ( + old_name, new_name)) res = [] for node in self._names.values(): if not hasattr(node, 'input'): continue if old_name not in node.input: continue - new_inputs = [new_name if i.name == old_name else i.name + new_inputs = [new_name if i == old_name else i for i in node.input] node.input[:] = new_inputs[:] res.append(node) @@ -342,6 +349,9 @@ def replace_all_inputs(self, old_name, new_name): "[Tf2OnnxConvert.replace_all_inputs] add id node from %r to %r " "with node %r." % ( old_name, new_name, n.name)) # pylint: disable=E1101 + if self.verbose: + print( # pragma: no cover + "[Tf2OnnxConvert.replace_all_inputs] end") return res def remove_node(self, name): @@ -385,8 +395,10 @@ def run(self): done = {} modif = 1 - while modif > 0: + turn = 0 + while modif > 0 and turn < self.max_iter: modif = 0 + turn += 1 # The converter may alter the current list of nodes, we freeze it. current_values = list(self._names.values()) for node in current_values: diff --git a/mlprodict/onnx_tools/onnx2py_helper.py b/mlprodict/onnx_tools/onnx2py_helper.py index b49964271..7d796fcb2 100644 --- a/mlprodict/onnx_tools/onnx2py_helper.py +++ b/mlprodict/onnx_tools/onnx2py_helper.py @@ -172,7 +172,7 @@ def guess_numpy_type_from_string(name): return numpy.bool_ if name == 'str': return numpy.str_ - raise ValueError( + raise ValueError( # pragma: no cover "Unable to guess numpy dtype from %r." % name) @@ -196,7 +196,7 @@ def guess_numpy_type_from_dtype(dt): return numpy.int8 if dt == numpy.dtype('uint8'): return numpy.uint8 - raise ValueError( + raise ValueError( # pragma: no cover "Unable to guess numpy dtype from %r." % dt) diff --git a/mlprodict/onnx_tools/onnx_manipulations.py b/mlprodict/onnx_tools/onnx_manipulations.py index f553bd8cb..b7f6746eb 100644 --- a/mlprodict/onnx_tools/onnx_manipulations.py +++ b/mlprodict/onnx_tools/onnx_manipulations.py @@ -328,7 +328,7 @@ def hash_onnx_object(obj, max_size): obj.doc_string = '' try: m.update(obj.SerializeToString()) - except AttributeError as e: + except AttributeError as e: # pragma: no cover raise RuntimeError( "Unable to hash object type %r, value=%r." "" % (type(obj), obj)) from e diff --git a/mlprodict/onnx_tools/onnx_tools.py b/mlprodict/onnx_tools/onnx_tools.py index 7d5ca3b5b..81266470c 100644 --- a/mlprodict/onnx_tools/onnx_tools.py +++ b/mlprodict/onnx_tools/onnx_tools.py @@ -52,7 +52,7 @@ def insert_node(model, op_type, node, input_index=0, new_name=None, **attrs): if isinstance(input_index, str): input_index_ = find_node_input_name(node, input_index) if input_index_ == -1: - raise RuntimeError( + raise RuntimeError( # pragma: no cover "Unable to find input_index %r in node %r." % ( input_index, node.name)) # pylint: disable=E1120 input_index = input_index_ @@ -150,7 +150,7 @@ def ensure_topological_order(inputs, initializers, nodes): maxi += 1 for name in node.output: if name in order: - raise RuntimeError( + raise RuntimeError( # pragma: no cover "Unable to sort a node (cycle). An output was " "already ordered %r (iteration=%r)." % ( name, n_iter)) @@ -158,7 +158,7 @@ def ensure_topological_order(inputs, initializers, nodes): if len(missing_names) == 0: continue - if len(missing_ops) > 0: + if len(missing_ops) > 0: # pragma: no cover def nstr(name): if name in order: return "%s#%d" % (name, order[name]) diff --git a/mlprodict/onnxrt/onnx_inference_node.py b/mlprodict/onnxrt/onnx_inference_node.py index 26ecb95e3..65e90b82a 100644 --- a/mlprodict/onnxrt/onnx_inference_node.py +++ b/mlprodict/onnxrt/onnx_inference_node.py @@ -320,7 +320,7 @@ def _set_size_inference_runtime(self, values): res = self.ops_.infer_sizes(*args, context=context) else: res = self.ops_.infer_sizes(*args) - except (TypeError, ValueError) as e: + except (TypeError, ValueError) as e: # pragma: no cover raise TypeError( "Unable to call infer_sizes with {} arguments for class" " '{}' ({})".format(len(args), self.ops_.__class__.__name__, diff --git a/mlprodict/onnxrt/ops_cpu/op_constant.py b/mlprodict/onnxrt/ops_cpu/op_constant.py index 16119b31f..ab0beb611 100644 --- a/mlprodict/onnxrt/ops_cpu/op_constant.py +++ b/mlprodict/onnxrt/ops_cpu/op_constant.py @@ -113,7 +113,7 @@ def __init__(self, onnx_node, desc=None, **options): elif hasattr(self, 'value') and self.value is not None: self.cst = self.value else: - raise AttributeError( + raise AttributeError( # pragma: no cover "No constant is defined for operator 'Constant'.") _check_dtype(self.cst) diff --git a/mlprodict/onnxrt/ops_cpu/op_conv.py b/mlprodict/onnxrt/ops_cpu/op_conv.py index 04c774ae7..f996474b1 100644 --- a/mlprodict/onnxrt/ops_cpu/op_conv.py +++ b/mlprodict/onnxrt/ops_cpu/op_conv.py @@ -41,15 +41,15 @@ def _run(self, X, W, B=None): # pylint: disable=W0221 "X cannot be None for operator %r, ONNX=%r" % ( type(self), self.onnx_node)) if min(X.shape) == 0: - raise RuntimeError( + raise RuntimeError( # pragma: no cover "Unable to run operator Conv on an empty matrix. " "X.shape=%r." % (X.shape, )) if min(W.shape) == 0: - raise RuntimeError( + raise RuntimeError( # pragma: no cover "Unable to run operator Conv on an empty matrix. " "W.shape=%r." % (W.shape, )) if B is not None and min(B.shape) == 0: - raise RuntimeError( + raise RuntimeError( # pragma: no cover "Unable to run operator Conv on an empty matrix. " "B.shape=%r." % (B.shape, )) if X.dtype == numpy.float32: diff --git a/mlprodict/onnxrt/ops_cpu/op_squeeze.py b/mlprodict/onnxrt/ops_cpu/op_squeeze.py index 380074fb1..34526f11c 100644 --- a/mlprodict/onnxrt/ops_cpu/op_squeeze.py +++ b/mlprodict/onnxrt/ops_cpu/op_squeeze.py @@ -82,7 +82,7 @@ def _infer_sizes(self, *args, **kwargs): if onnx_opset_version() >= 13: Squeeze = Squeeze_13 -elif onnx_opset_version() >= 11: +elif onnx_opset_version() >= 11: # pragma: no cover Squeeze = Squeeze_11 -else: +else: # pragma: no cover Squeeze = Squeeze_1 diff --git a/mlprodict/onnxrt/ops_onnxruntime/_op.py b/mlprodict/onnxrt/ops_onnxruntime/_op.py index 81611cb21..76884e085 100644 --- a/mlprodict/onnxrt/ops_onnxruntime/_op.py +++ b/mlprodict/onnxrt/ops_onnxruntime/_op.py @@ -202,7 +202,7 @@ def _init(self, variables=None): self.onnx_ = self.inst_.to_onnx(inputs, outputs=outputs, target_opset=target_opset, domain=domain) - except NotImplementedError as e: + except NotImplementedError as e: # pragma: no cover raise NotImplementedError( "Unable to instantiate node {} inputs={} " "self.inputs={} outputs={} variables={} " @@ -238,13 +238,13 @@ def _init(self, variables=None): try: sess_options.session_log_severity_level = 3 # sess_options.sessions_log_verbosity_level = 0 - except AttributeError: + except AttributeError: # pragma: no cover # onnxruntime not recent enough. pass try: self.run_options.run_log_severity_level = 3 # self.run_options.run_log_verbosity_level = 0 - except AttributeError: + except AttributeError: # pragma: no cover # onnxruntime not recent enough. pass if disable_optimisation: diff --git a/mlprodict/sklapi/onnx_speed_up.py b/mlprodict/sklapi/onnx_speed_up.py index 0db704d6f..46d0ae7af 100644 --- a/mlprodict/sklapi/onnx_speed_up.py +++ b/mlprodict/sklapi/onnx_speed_up.py @@ -140,7 +140,7 @@ def _build_onnx_runtime_numpy_compile(self, opsets): try: compiled_code = compile( self.numpy_code_, '', 'exec') - except SyntaxError as e: + except SyntaxError as e: # pragma: no cover raise AssertionError( "Unable to compile a script due to %r. " "\n--CODE--\n%s" @@ -165,7 +165,7 @@ def _build_onnx_runtime_numpy_compile(self, opsets): with redirect_stderr(err): try: exec(compiled_code, glo, loc) # pylint: disable=W0122 - except Exception as e: + except Exception as e: # pragma: no cover raise AssertionError( "Unable to execute a script due to %r. " "\n--OUT--\n%s\n--ERR--\n%s\n--CODE--\n%s" @@ -173,7 +173,7 @@ def _build_onnx_runtime_numpy_compile(self, opsets): print_code(self.numpy_code_))) from e names = [k for k in loc if k.startswith('numpy_')] if len(names) != 1: - raise RuntimeError( + raise RuntimeError( # pragma: no cover "Unable to guess which function is the one, names=%r." "" % list(sorted(names))) fct = loc[names[0]] diff --git a/mlprodict/testing/einsum/blas_lapack.py b/mlprodict/testing/einsum/blas_lapack.py index 9fc5e1ec0..b09216591 100644 --- a/mlprodict/testing/einsum/blas_lapack.py +++ b/mlprodict/testing/einsum/blas_lapack.py @@ -13,11 +13,14 @@ def pygemm(transA, transB, M, N, K, alpha, A, lda, B, ldb, beta, C, ldc): Pure python implementatin of GEMM. """ if len(A.shape) != 1: - raise ValueError("A must be a vector.") + raise ValueError( # pragma: no cover + "A must be a vector.") if len(B.shape) != 1: - raise ValueError("B must be a vector.") + raise ValueError( # pragma: no cover + "B must be a vector.") if len(C.shape) != 1: - raise ValueError("C must be a vector.") + raise ValueError( + "C must be a vector.") if A.shape[0] != M * K: raise ValueError( "Dimension mismatch for A.shape=%r M=%r N=%r K=%r." % ( @@ -27,7 +30,7 @@ def pygemm(transA, transB, M, N, K, alpha, A, lda, B, ldb, beta, C, ldc): "Dimension mismatch for B.shape=%r M=%r N=%r K=%r." % ( B.shape, M, N, K)) if C.shape[0] != N * M: - raise ValueError( + raise ValueError( # pragma: no cover "Dimension mismatch for C.shape=%r M=%r N=%r K=%r." % ( C.shape, M, N, K)) @@ -79,7 +82,7 @@ def pygemm(transA, transB, M, N, K, alpha, A, lda, B, ldb, beta, C, ldc): C[c_index] = alpha * total + beta * C[c_index] if n_loop != M * N * K: - raise RuntimeError( + raise RuntimeError( # pragma: no cover "Unexpected number of loops: %d != %d = (%d * %d * %d) " "lda=%d ldb=%d ldc=%d" % ( n_loop, M * N * K, M, N, K, lda, ldb, ldc)) @@ -95,14 +98,14 @@ def gemm_dot(A, B, transA=False, transB=False): :param transB: is second matrix transposed? """ if A.dtype != B.dtype: - raise TypeError( + raise TypeError( # pragma: no cover "Matrices A and B must have the same dtype not " "%r, %r." % (A.dtype, B.dtype)) if len(A.shape) != 2: - raise ValueError( + raise ValueError( # pragma: no cover "Matrix A does not have 2 dimensions but %d." % len(A.shape)) if len(B.shape) != 2: - raise ValueError( + raise ValueError( # pragma: no cover "Matrix B does not have 2 dimensions but %d." % len(B.shape)) def _make_contiguous_(A, B): diff --git a/mlprodict/testing/einsum/einsum_fct.py b/mlprodict/testing/einsum/einsum_fct.py index d9c8b6e34..3656d20f6 100644 --- a/mlprodict/testing/einsum/einsum_fct.py +++ b/mlprodict/testing/einsum/einsum_fct.py @@ -116,15 +116,15 @@ def build(self): def _build_optimize(self): # loops over all permutations if self.equation.lower() != self.equation: - raise RuntimeError( + raise RuntimeError( # pragma: no cover "Only lower equation can be optimized, %r is not." % self.equation) letters = list( sorted(set(c for c in self.equation if "a" <= c <= "z"))) possible = list(permutations(letters)) possible.insert(0, letters) if self.verbose: - from tqdm import tqdm - subset = tqdm(possible) + from tqdm import tqdm # pragma: no cover + subset = tqdm(possible) # pragma: no cover else: subset = possible best = [] @@ -166,15 +166,15 @@ def _build_optimize(self): def _build_optimize_ml(self): # loops over all permutations if self.equation.lower() != self.equation: - raise RuntimeError( + raise RuntimeError( # pragma: no cover "Only lower equation can be optimized, %r is not." % self.equation) letters = list( sorted(set(c for c in self.equation if "a" <= c <= "z"))) possible = list(permutations(letters)) possible.insert(0, letters) if self.verbose: - from tqdm import tqdm - subset = tqdm(possible) + from tqdm import tqdm # pragma: no cover + subset = tqdm(possible) # pragma: no cover else: subset = possible best = [] @@ -282,7 +282,7 @@ def build_runtime(self): self.runtime_ = lambda *inputs: self.oinf_.run( {i: v for i, v in zip(self.onnx_names_, inputs)})['Y'] else: - raise ValueError( + raise ValueError( # pragma: no cover "Unexpected runtime %r." % self.runtime) else: if self.runtime in ('python', 'onnxruntime1'): @@ -298,7 +298,7 @@ def build_runtime(self): self.runtime_ = lambda *inputs: self.oinf_.run( {i: v for i, v in zip(self.onnx_names_, inputs)})['Y'] else: - raise ValueError( + raise ValueError( # pragma: no cover "Unexpected runtime %r." % self.runtime) def __call__(self, *inputs): @@ -306,7 +306,7 @@ def __call__(self, *inputs): Calls the runtime `self.runtime_`. """ if not hasattr(self, 'runtime_'): - raise RuntimeError( + raise RuntimeError( # pragma: no cover "Method build_runtime was not called.") return self.runtime_(*inputs) @@ -616,10 +616,10 @@ def fct5(): print(clean(res[1])) """ if len(inputs) == 0: - raise ValueError("No inputs found.") + raise ValueError("No inputs found.") # pragma: no cover dtypes = set(i.dtype for i in inputs) if len(dtypes) != 1: - raise ValueError( + raise ValueError( # pragma: no cover "All inputs do not have the same type (%r), " "all of them should be cast before called einsum." "" % dtypes) diff --git a/mlprodict/testing/einsum/einsum_impl_ext.py b/mlprodict/testing/einsum/einsum_impl_ext.py index cbcd5e998..2889c498b 100644 --- a/mlprodict/testing/einsum/einsum_impl_ext.py +++ b/mlprodict/testing/einsum/einsum_impl_ext.py @@ -340,11 +340,13 @@ def dispb(c): return "".join("o" if b else "." for b in c) if verbose: - print("[GENERICDOT] before broadcast %s,%s->%s or %s" % ( + print( # pragma: no cover + "[GENERICDOT] before broadcast %s,%s->%s or %s" % ( "".join(l1), "".join(l2), "".join(l3), _numpy_extended_dot_equation( len(m1.shape), len(m1.shape), axes, left, right))) - print("[GENERICDOT] names=%s kind=%r common=%s broadcast=%s" % ( + print( # pragma: no cover + "[GENERICDOT] names=%s kind=%r common=%s broadcast=%s" % ( "".join(names), kind.tolist(), dispb(common), dispb(broadcast))) @@ -362,9 +364,11 @@ def dispb(c): let = [l1[p], l2[p], l3[p]] inp = 1 if dim[0] == 1 else 0 if verbose: - print("[GENERICDOT] name=%s dim=%r let=%r inp=%r p=%r" % ( - names[i], dim, let, inp, p)) - print(" B0 l1=%r, l2=%r l3=%r" % (l1, l2, l3)) + print( # pragma: no cover + "[GENERICDOT] name=%s dim=%r let=%r inp=%r p=%r" % ( + names[i], dim, let, inp, p)) + print( # pragma: no cover + " B0 l1=%r, l2=%r l3=%r" % (l1, l2, l3)) if (kind[i] & 4) > 0: # Summation axis is part of the output. if let[inp].lower() == let[inp]: @@ -377,7 +381,8 @@ def dispb(c): else: l1[p] = let[inp] if verbose: - print(" B1 l1=%r, l2=%r l3=%r" % (l1, l2, l3)) + print( # pragma: no cover + " B1 l1=%r, l2=%r l3=%r" % (l1, l2, l3)) else: # Summation axis is not part of the output. if let[inp].lower() == let[inp]: @@ -528,18 +533,20 @@ def numpy_extended_dot_matrix(m1, m2, axes, left, right, verbose=False): _common_check_numpy_extended_dot(m1, m2, axes, left, right) if verbose: - print("[GENERICDOT] shape1=%r shape2=%r axes=%r " - "left=%r right=%r -- %s" % ( - m1.shape, m2.shape, axes, left, right, - _numpy_extended_dot_equation( - len(m1.shape), len(m1.shape), axes, left, right))) + print( # pragma: no cover + "[GENERICDOT] shape1=%r shape2=%r axes=%r " + "left=%r right=%r -- %s" % ( + m1.shape, m2.shape, axes, left, right, + _numpy_extended_dot_equation( + len(m1.shape), len(m1.shape), axes, left, right))) if len(axes) == 0 and len(set(left) & set(right)) == 0: # Simple multiplication res = m1 * m2 if verbose: - print("[GENERICDOT] Mul %r @ %r -> %r" % ( - m1.shape, m2.shape, res.shape)) + print( # pragma: no cover + "[GENERICDOT] Mul %r @ %r -> %r" % ( + m1.shape, m2.shape, res.shape)) return res if (len(set(axes) & set(left)) == 0 and @@ -655,7 +662,7 @@ def numpy_extended_dot_matrix(m1, m2, axes, left, right, verbose=False): "last_shape=%r" % (current_shape, final_shape, res.shape)) if len(current_shape) != len(final_shape): - raise RuntimeError( + raise RuntimeError( # pragma: no cover "Shapes mismatch %r > %r, " "shape1=%r shape2=%r axes=%r left=%r right=%r." % ( current_shape, final_shape, @@ -680,7 +687,7 @@ def numpy_extended_dot_matrix(m1, m2, axes, left, right, verbose=False): if r_axes and not l_axes: new_axes = list(a for a in axes if a not in right) new_left = list(sorted(set(left) | r_axes)) - if verbose: + if verbose: # pragma: no cover eq1 = _numpy_extended_dot_equation( len(m1.shape), len(m1.shape), axes, left, right) eq2 = _numpy_extended_dot_equation( diff --git a/mlprodict/testing/test_utils/quantized_tensor.py b/mlprodict/testing/test_utils/quantized_tensor.py index 3dec58aba..855f47d06 100644 --- a/mlprodict/testing/test_utils/quantized_tensor.py +++ b/mlprodict/testing/test_utils/quantized_tensor.py @@ -20,11 +20,13 @@ def __init__(self, data, scale=None, zero_point=None): "constructor" if data.dtype == numpy.float32: if scale is not None or zero_point is not None: - raise ValueError("scale and zero_point are ignored.") + raise ValueError( # pragma: no cover + "scale and zero_point are ignored.") self._init(data) elif data.dtype == numpy.uint8: if scale is None or zero_point is None: - raise ValueError("scale and zero_point must be specified.") + raise ValueError( # pragma: no cover + "scale and zero_point must be specified.") self.quantized_ = data self.scale_ = numpy.float32(scale) self.zero_point_ = numpy.uint8(zero_point) @@ -51,7 +53,7 @@ def _init(self, data): self.quantized_[i] = numpy.uint8(clamped_val) if self.quantized_.dtype != numpy.uint8: - raise TypeError( + raise TypeError( # pragma: no cover "dtype={} not uint8".format(self.quantized_.dtype)) @@ -77,7 +79,7 @@ def __init__(self, data, X_or_scale, W: QuantizedTensor = None): self.quantized_[i] = numpy.int32( numpy.floor(data[i] / (X_or_scale.scale_ * W.scale_))) if self.quantized_.dtype != numpy.int32: - raise TypeError( + raise TypeError( # pragma: no cover "dtype={} not int32".format(self.quantized_.dtype)) @@ -148,13 +150,13 @@ def test_qlinear_conv(x: QuantizedTensor, x_shape, got = oinf.run(inputs)['y'] expected = y.quantized_.reshape(y_shape) if got.dtype != expected.dtype: - raise TypeError( + raise TypeError( # pragma: no cover "Unexpected output dtype:\nEXPECTED\n{}\nGOT\n{}" "".format(expected, got)) diff = numpy.abs(got.ravel().astype(numpy.float32) - expected.ravel().astype(numpy.float32)) mdiff = diff.max() if mdiff > 1e-5: - raise ValueError( + raise ValueError( # pragma: no cover "Unexpected output maximum difference={}:\nEXPECTED\n{}\nGOT\n{}" "".format(mdiff, expected, got)) From adbf0d8d20935c5d4f69b1e18e4962a5a082bc9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xavier=20dupr=C3=A9?= Date: Fri, 12 Nov 2021 17:35:57 +0100 Subject: [PATCH 2/6] Update test_onnxrt_runtime_lightgbm.py --- .../test_onnxrt_runtime_lightgbm.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/_unittests/ut_onnx_conv/test_onnxrt_runtime_lightgbm.py b/_unittests/ut_onnx_conv/test_onnxrt_runtime_lightgbm.py index d6aa84c82..a4850c08b 100644 --- a/_unittests/ut_onnx_conv/test_onnxrt_runtime_lightgbm.py +++ b/_unittests/ut_onnx_conv/test_onnxrt_runtime_lightgbm.py @@ -26,6 +26,7 @@ def setUp(self): register_converters() @unittest.skipIf(sys.platform == 'darwin', 'stuck') + @ignore_warnings((RuntimeWarning, UserWarning)) def test_missing(self): from mlprodict.onnx_conv.operator_converters.parse_lightgbm import ( WrappedLightGbmBooster) @@ -372,6 +373,7 @@ def test_onnxrt_python_lightgbm_categorical_iris_dataframe(self): @skipif_circleci('stuck') @unittest.skipIf(sys.platform == 'darwin', 'stuck') + @ignore_warnings((RuntimeWarning, UserWarning)) def test_lightgbm_booster_classifier(self): from lightgbm import Dataset, train as lgb_train @@ -413,6 +415,7 @@ def _assert_almost_equal(self, actual, desired, decimal=7, frac=1.0, msg=""): @skipif_circleci('stuck') @unittest.skipIf(sys.platform == 'darwin', 'stuck') + @ignore_warnings((RuntimeWarning, UserWarning)) def test_missing_values(self): from lightgbm import LGBMRegressor @@ -470,6 +473,7 @@ def _predict_with_onnx(model, X): @skipif_circleci('stuck') @unittest.skipIf(sys.platform == 'darwin', 'stuck') + @ignore_warnings((RuntimeWarning, UserWarning)) def test_objective(self): from lightgbm import LGBMRegressor @@ -498,6 +502,40 @@ def test_objective(self): y_pred, y_pred_onnx, decimal=_N_DECIMALS, frac=_FRAC, msg="Objective=%r" % objective) + @skipif_circleci('stuck') + @unittest.skipIf(sys.platform == 'darwin', 'stuck') + @ignore_warnings((RuntimeWarning, UserWarning)) + def test_objective_boosting_rf(self): + from lightgbm import LGBMRegressor + + _N_ROWS = 10000 + _N_COLS = 10 + _N_DECIMALS = 5 + _FRAC = 0.9997 + + _X = pandas.DataFrame(numpy.random.random( + size=(_N_ROWS, _N_COLS)).astype(numpy.float32)) + _Y = pandas.Series(numpy.random.random(size=_N_ROWS)) + + _objectives = ("regression",) + + for objective in _objectives: + with self.subTest(X=_X, objective=objective): + initial_types = self._calc_initial_types(_X) + regressor = LGBMRegressor( + objective=objective, boosting='rf', bagging_freq=3, + bagging_fraction=0.5, n_estimators=10) + regressor.fit(_X, _Y) + regressor_onnx = to_onnx( + regressor, initial_types=initial_types, + rewrite_ops=True) + y_pred = regressor.predict(_X) + y_pred_onnx = self._predict_with_onnx(regressor_onnx, _X) / 10 + self._assert_almost_equal( + y_pred, y_pred_onnx, decimal=_N_DECIMALS, frac=_FRAC, + msg="Objective=%r" % objective) + + @ignore_warnings((RuntimeWarning, UserWarning)) def test_lgbm_regressor10(self): from lightgbm import LGBMRegressor data = load_iris() @@ -523,6 +561,7 @@ def test_lgbm_regressor10(self): self.assertEqualArray(expected, got1, decimal=5) self.assertEqualArray(expected, got2, decimal=5) + @ignore_warnings((RuntimeWarning, UserWarning)) def test_lgbm_regressor(self): from lightgbm import LGBMRegressor data = load_iris() From 91c3a45babda90836a463ab2750c47811673fa88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xavier=20dupr=C3=A9?= Date: Sat, 13 Nov 2021 12:16:55 +0100 Subject: [PATCH 3/6] fix tf2onnx exporter --- .../ut_onnxrt/test_onnxrt_python_runtime_.py | 2 +- _unittests/ut_tools/data/gslice.onnx | Bin 0 -> 262 bytes _unittests/ut_tools/data/gsqueeze.onnx | Bin 0 -> 152 bytes _unittests/ut_tools/test_export_onnx.py | 174 ++++++++++++++---- .../_onnx_export_templates_tf2onnx.tmpl | 5 +- .../onnx_tools/exports/tf2onnx_helper.py | 107 ++++++++--- mlprodict/onnx_tools/onnx_export.py | 4 +- mlprodict/testing/einsum/einsum_impl_ext.py | 10 +- 8 files changed, 236 insertions(+), 66 deletions(-) create mode 100644 _unittests/ut_tools/data/gslice.onnx create mode 100644 _unittests/ut_tools/data/gsqueeze.onnx diff --git a/_unittests/ut_onnxrt/test_onnxrt_python_runtime_.py b/_unittests/ut_onnxrt/test_onnxrt_python_runtime_.py index d39fb3fb2..ce22285aa 100644 --- a/_unittests/ut_onnxrt/test_onnxrt_python_runtime_.py +++ b/_unittests/ut_onnxrt/test_onnxrt_python_runtime_.py @@ -3511,7 +3511,7 @@ def test_onnxt_runtime_size(self): @wraplog() def test_onnxt_runtime_slice(self): - for opset in [9, get_opset_number_from_onnx()]: + for opset in range(9, get_opset_number_from_onnx() + 1): if opset > get_opset_number_from_onnx(): continue with self.subTest(opset=opset): diff --git a/_unittests/ut_tools/data/gslice.onnx b/_unittests/ut_tools/data/gslice.onnx new file mode 100644 index 0000000000000000000000000000000000000000..74d28fdb5a59e9d5c876ff03579e1dee9f1f5b9a GIT binary patch literal 262 zcmd;J7vd<+&N0f*%d3!LGt@IQ&@)tGPt4Q<31~1_-DBhm;9`v6;ttM<56;O{d(+3{J>4Xo*3DnYo-$ z?1G6iFrtVX!Ni%EP{fU+M1gJ>65!(FU=-rwV&G!p-~wXSBu*~INFf#=lhKKVi$Q=F E00-bb{Qv*} literal 0 HcmV?d00001 diff --git a/_unittests/ut_tools/data/gsqueeze.onnx b/_unittests/ut_tools/data/gsqueeze.onnx new file mode 100644 index 0000000000000000000000000000000000000000..5ddc2cf0925fcfb2f3293519ac14efbe48684e7b GIT binary patch literal 152 zcmd;J7vd<+&N0f*%d3!LGt@IQ&@)tGPt4Q<31~1_6>{ltF-CCl1{cN$7nY``R;4Bv zmk2RNN^v1Xl-QwCRt!Si{y=?Du9g@FqX4@VBcl_l_9#K1kwQFNTpWx-Y+MXnOdO0! SoLr2NLM$Mr6AKrE051Ru<|DfR literal 0 HcmV?d00001 diff --git a/_unittests/ut_tools/test_export_onnx.py b/_unittests/ut_tools/test_export_onnx.py index b6ddaeeb7..b75cbd7c4 100644 --- a/_unittests/ut_tools/test_export_onnx.py +++ b/_unittests/ut_tools/test_export_onnx.py @@ -501,7 +501,8 @@ def any_version(cls, opset, ctx, node, **kwargs): # pylint: disable=R0915 def version_13(cls, ctx, node, **kwargs): return cls.any_version(13, ctx, node, **kwargs) -class ConvertSliceOp: + +class ConvertSlice2Op: supported_dtypes = [ numpy.float32, ] @@ -510,15 +511,19 @@ class ConvertSliceOp: def version_1(cls, ctx, node, **kwargs): # T output = Slice(T input, Index begin, Index size) # T output = Slice(T input, Tind starts, Tind ends, Tind axes, Tind steps) - # "ends" are exclusive, "axes" and "steps" are optional, their default val are [0, ...] and 1 + # "ends" are exclusive, "axes" and "steps" are optional, + # their default val are [0, ...] and 1 input_tensor = node.input[0] starts = node.input[1] size = node.input[2] - # in tf, size can be -1 which means all elem are taken, so size can't be added starts directly. - # the way to make sure size are not less than 0: set "sizes"'s elem to be int_max if elem val is -1 + # in tf, size can be -1 which means all elem are taken, + # so size can't be added starts directly. + # the way to make sure size are not less than 0: + # set "sizes"'s elem to be int_max if elem val is -1 size_dtype = ctx.get_dtype(size) size_np_dtype = utils.map_onnx_to_numpy_type(size_dtype) - if ctx.get_node_by_output(size).is_const() and ctx.get_node_by_output(starts).is_const(): + if (ctx.get_node_by_output(size).is_const() and + ctx.get_node_by_output(starts).is_const()): starts = ctx.get_node_by_output(starts).get_tensor_value() sizes = ctx.get_node_by_output(size).get_tensor_value() ends = [] @@ -526,24 +531,35 @@ def version_1(cls, ctx, node, **kwargs): # get all elements if size == -1: dtype = ctx.get_dtype(node.input[1]) - utils.make_sure(dtype, "dtype of {} is None".format(node.input[1])) - utils.make_sure(dtype, "dtype of {} is None".format(node.input[1])) + utils.make_sure( + dtype, "dtype of {} is None".format(node.input[1])) + utils.make_sure( + dtype, "dtype of {} is None".format(node.input[1])) ends.append(np.iinfo(dtype).max) else: ends.append(start + size) else: neg_one_val = np.array([-1]).astype(size_np_dtype) - neg_one = ctx.make_const(utils.make_name("const"), neg_one_val).output[0] - - int_max_val = np.array([utils.get_max_value(size_np_dtype)]).astype(size_np_dtype) - int_max = ctx.make_const(utils.make_name("largest_int_val"), int_max_val).output[0] - - size_are_neg_one_flag = ctx.make_node("Equal", [neg_one, size]).output[0] - size_are_neg_one_flag = ctx.make_node("Cast", [size_are_neg_one_flag], attr={"to": size_dtype}).output[0] - value_to_add = ctx.make_node("Mul", [int_max, size_are_neg_one_flag]).output[0] - size_processed = ctx.make_node("Add", [size, value_to_add]).output[0] - ends = ctx.make_node("Add", [starts, size_processed]).output[0] + neg_one = ctx.make_const( + utils.make_name("const"), neg_one_val).output[0] + + int_max_val = np.array( + [utils.get_max_value(size_np_dtype)]).astype(size_np_dtype) + int_max = ctx.make_const( + utils.make_name("largest_int_val"), int_max_val).output[0] + + size_are_neg_one_flag = ctx.make_node( + "Equal", [neg_one, size]).output[0] + size_are_neg_one_flag = ctx.make_node( + "Cast", [size_are_neg_one_flag], + attr={"to": size_dtype}).output[0] + value_to_add = ctx.make_node( + "Mul", [int_max, size_are_neg_one_flag]).output[0] + size_processed = ctx.make_node( + "Add", [size, value_to_add]).output[0] + ends = ctx.make_node( + "Add", [starts, size_processed]).output[0] ctx.remove_node(node.name) inputs_map = {"data": input_tensor, "starts": starts, "ends": ends} @@ -559,6 +575,100 @@ def version_11(cls, ctx, node, **kwargs): cls.version_1(ctx, node, **kwargs) +class ConvertSqueeze2Op: + + supported_dtypes = [ + numpy.float32, + ] + + @classmethod + def any_version(cls, opset, ctx, node, **kwargs): + ''' + Converter for ``Squeeze2``. + + * producer: skl2onnx + * version: 0 + * description: + ''' + oldnode = node + input_name = node.input[0] + onnx_dtype = ctx.get_dtype(input_name) + np_dtype = map_onnx_to_numpy_type(onnx_dtype) + make_sure(np_dtype in ConvertSqueeze2Op.supported_dtypes, + "Unsupported input type.") + shape = ctx.get_shape(input_name) + varx = {x: x for x in node.input} + + # initializers + if getattr(ctx, 'verbose', False): + print('[initializers] %r' % cls) + + value = numpy.array([1], dtype=numpy.int64) + varx['Sq_Squeezecst'] = ctx.make_const( + name=make_name('init_Sq_Squeezecst'), np_val=value).name + + # nodes + if getattr(ctx, 'verbose', False): + print('[nodes] %r' % cls) + + node = GraphBuilder(ctx).make_squeeze( + {'data': varx['X'], 'axes': [1]}, return_node=True) + varx['Y'] = node.output[0] + + # finalize + if getattr(ctx, 'verbose', False): + print('[replace_all_inputs] %r' % cls) + ctx.replace_all_inputs(oldnode.output[0], node.output[0]) + ctx.remove_node(oldnode.name) + + @classmethod + def version_13(cls, ctx, node, **kwargs): + return cls.any_version(13, ctx, node, **kwargs) + + +def create_model(): + inputs = [] + outputs = [] + + # inputs + print('[inputs]') # verbose + + value = make_tensor_value_info('X', 1, [None, 1]) + inputs.append(value) + + # outputs + print('[outputs]') # verbose + + value = make_tensor_value_info('Y', 1, None) + outputs.append(value) + + inames = [i.name for i in inputs] + onames = [i.name for i in outputs] + node = make_node('Squeeze2', inames, onames, name='Squeeze2') + + # graph + print('[graph]') # verbose + graph = make_graph([node], 'Squeeze2', inputs, outputs) + onnx_model = make_model(graph) + onnx_model.ir_version = 7 + onnx_model.producer_name = 'skl2onnx' + onnx_model.producer_version = '' + onnx_model.domain = 'ai.onnx' + onnx_model.model_version = 0 + onnx_model.doc_string = '' + set_model_props(onnx_model, {}) + + # opsets + print('[opset]') # verbose + opsets = {'': 13} + del onnx_model.opset_import[:] # pylint: disable=E1101 + for dom, value in opsets.items(): + op_set = onnx_model.opset_import.add() + op_set.domain = dom + op_set.version = value + + return onnx_model + class TestExportOnnx(ExtTestCase): @@ -782,7 +892,7 @@ def verify_tf(self, content): 'collections': collections, 'inspect': inspect, 'helper': helper, "make_sure": make_sure, 'ConvertFFT2DOp': ConvertFFT2DOp, - 'ConvertSliceOp': ConvertSliceOp, + 'ConvertSlice2Op': ConvertSlice2Op, "make_name": make_name, 'map_onnx_to_numpy_type': map_onnx_to_numpy_type, 'GraphBuilder': GraphBuilder} @@ -808,18 +918,21 @@ def verify_tf(self, content): def test_export2tf2onnx(self): this = os.path.dirname(__file__) folder = os.path.join(this, "data") - names = [("fft2d_any.onnx", 'FFT2D'), ("slice.onnx", 'Slice')] + names = [("gslice.onnx", 'Slice2', 'X', (3, 10, 5), 'Y'), + ("gsqueeze.onnx", 'Squeeze2', 'X', (3, 1), 'Y'), + ("fft2d_any.onnx", 'FFT2D', 'x', (3, 1, 4), 'y')] for rt in ['python', 'onnxruntime1']: - for name, op_name in names: + for name, op_name, x_name, x_shape, y_name in names: with self.subTest(name=name, rt=rt): oinf0 = OnnxInference( os.path.join(folder, name), runtime=rt) - x = numpy.random.randn(3, 1, 4).astype(numpy.float32) - y = oinf0.run({'x': x}) + x = numpy.random.randn(*x_shape).astype(numpy.float32) + y = oinf0.run({x_name: x}) new_onnx = export2tf2onnx( - os.path.join(folder, name), name=op_name) + os.path.join(folder, name), name=op_name, + verbose=False) _, loc = self.verify_tf(new_onnx) model = loc['onnx_raw'] self.assertIn('op_type: "%s"' % op_name, str(model)) @@ -837,7 +950,7 @@ def test_export2tf2onnx(self): model, runtime=rt, runtime_options=opts) else: oinf = OnnxInference(model, runtime=rt) - y1 = oinf.run({'x': x}) + y1 = oinf.run({x_name: x}) new_onnx = export2tf2onnx( os.path.join(folder, name), name=op_name) @@ -845,11 +958,11 @@ def test_export2tf2onnx(self): model = loc['onnx_model'] self.assertNotIn('op_type: "%s"' % op_name, str(model)) oinf = OnnxInference(model, runtime=rt) - y2 = oinf.run({'x': x}) + y2 = oinf.run({x_name: x}) - if y1['y'].shape[0] > 0 and y['y'].shape[0] > 0: - self.assertEqualArray(y['y'], y1['y']) - self.assertEqualArray(y['y'], y2['y']) + if y1[y_name].shape[0] > 0 and y[y_name].shape[0] > 0: + self.assertEqualArray(y[y_name], y1[y_name]) + self.assertEqualArray(y[y_name], y2[y_name]) def verify_numpy(self, content): try: @@ -1187,7 +1300,7 @@ def onnx_rfft_2d_any_test(x, fft_length): f.write(onx.SerializeToString()) code = export2tf2onnx( onx, name="FFT2D", autopep_options={'max_line_length': 120}) - # print(code) + self.assertIn("make_sure", code) if __name__ == "__main__" and shape == (3, 1, 4): code = code.replace("make_sure(", "utils.make_sure(") @@ -1202,7 +1315,6 @@ def onnx_rfft_2d_any_test(x, fft_length): code = autopep8.fix_code( code, options={'max_line_length': 120}) self.assertNotIn("numpy.", code) - # print(code) def test_sub_graph(self): data = os.path.abspath(os.path.dirname(__file__)) @@ -1213,5 +1325,5 @@ def test_sub_graph(self): if __name__ == "__main__": - # TestExportOnnx().test_simple_configuration() + # TestExportOnnx().test_export2tf2onnx() unittest.main() diff --git a/mlprodict/onnx_tools/_onnx_export_templates_tf2onnx.tmpl b/mlprodict/onnx_tools/_onnx_export_templates_tf2onnx.tmpl index 891de32fe..aa4722f28 100644 --- a/mlprodict/onnx_tools/_onnx_export_templates_tf2onnx.tmpl +++ b/mlprodict/onnx_tools/_onnx_export_templates_tf2onnx.tmpl @@ -123,4 +123,7 @@ def create_model(): onnx_raw = create_model() -onnx_model = Tf2OnnxConvert(onnx_raw, tf_op, target_opset={{ opsets }}).run() +onnx_model = Tf2OnnxConvert( + onnx_raw, tf_op, + target_opset={{ opsets }}, + verbose={{ verbose }}).run() diff --git a/mlprodict/onnx_tools/exports/tf2onnx_helper.py b/mlprodict/onnx_tools/exports/tf2onnx_helper.py index a39ad5fec..50095aecd 100644 --- a/mlprodict/onnx_tools/exports/tf2onnx_helper.py +++ b/mlprodict/onnx_tools/exports/tf2onnx_helper.py @@ -39,7 +39,7 @@ def make_tf2onnx_code(opset, name=None, op_type=None, domain='', and following rows :return: code as str """ - def simplify(name, kind, force=True): + def simplify(name, kind, force=False): value = None if (used is not None and name in used and len(used[name]) == 1 and context is not None): @@ -54,6 +54,10 @@ def simplify(name, kind, force=True): if value is None and force: inits = context['initializers_dict'] + if name not in inits: + raise RuntimeError( # pragma: no cover + "Unable to find init %r in %r value=%r." % ( + name, list(sorted(inits)), value)) value = inits[name] if kind == 'list': if value is None: @@ -61,6 +65,12 @@ def simplify(name, kind, force=True): if len(value.shape) == 0: return str(value) return str(list(value)) + if kind == 'list_var': + if value is None: + return "varx[%r]" % name + if len(value.shape) == 0: + return str(value) + return str(list(value)) raise NotImplementedError( "Unknown scenario to simplify (%r)." % kind) @@ -70,7 +80,36 @@ def simplify(name, kind, force=True): rows.append( "node = GraphBuilder(ctx).make_unsqueeze(" "{'data': varx[%r], 'axes': %s}, return_node=True)" - "" % (inputs[0], simplify(inputs[1], 'list'))) + "" % (inputs[0], simplify(inputs[1], 'list_var'))) + else: + raise NotImplementedError( # pragma: no cover + "Unable to create code for operator %r (opset <= 12)" + "." % op_type) + elif op_type == 'Squeeze': + if len(inputs) == 1: + rows.append( + "node = GraphBuilder(ctx).make_squeeze(" + "{'data': varx[%r]}, return_node=True)" + "" % (inputs[0], simplify(inputs[1], 'list_var'))) + elif len(inputs) == 2: + rows.append( + "node = GraphBuilder(ctx).make_squeeze(" + "{'data': varx[%r], 'axes': %s}, return_node=True)" + "" % (inputs[0], simplify(inputs[1], 'list_var'))) + else: + raise NotImplementedError( # pragma: no cover + "Unable to create code for operator %r (opset <= 12)" + "." % op_type) + elif op_type == 'Slice': + atts = dict(zip(['starts', 'ends', 'axes', 'steps'], + inputs[1:])) + text = ", ".join("'%s': %s" % (k, simplify(v, 'list_var')) + for k, v in atts.items()) + if len(inputs) in (3, 4, 5): + rows.append( + "node = GraphBuilder(ctx).make_slice(" + "{'data': varx[%r], %s}, return_node=True)" + "" % (inputs[0], text)) else: raise NotImplementedError( # pragma: no cover "Unable to create code for operator %r (opset <= 12)" @@ -164,7 +203,7 @@ class Tf2OnnxConvert: """ def __init__(self, onnx_model, _tf_op=None, verbose=None, - target_opset=None, max_iter=100): + target_opset=None, max_iter=5): self._onnx_model = onnx_model self._tf_op = _tf_op or tf_op self.verbose = verbose @@ -223,7 +262,8 @@ def _add_node_name(self, obj): self._forbidden_new_names.add(obj.name) def make_node(self, op_type, inputs, attr=None, outputs=None, - name=None, domain='', output_count=1): + name=None, domain='', output_count=1, + shapes=None, dtypes=None): """ Adds a node to the list of nodes. @@ -235,6 +275,8 @@ def make_node(self, op_type, inputs, attr=None, outputs=None, the number of outputs of this node :param name: name of the node :param domain: domain + :param shapes: unused + :param dtypes: unused :return: created node """ if self.verbose: @@ -431,6 +473,10 @@ def run(self): fct(self, node, target_opset=target, **kwargs) modif += 1 + if turn >= self.max_iter: + raise RuntimeError( + "Too many iterations and no stable ONNX was reached, " + "iter=%d\n%s" % (turn, str(self.make_model()))) return self.make_model() def make_model(self): @@ -485,18 +531,22 @@ def graph(self): "Returns the graph." return self._g - def make_slice(self, kwargs, name=None, shapes=None, dtypes=None, return_node=False): + def make_slice(self, kwargs, name=None, shapes=None, dtypes=None, + return_node=False): """ - slice changes its schema at opset 10: it treats some attributes as dynamic input - so this function has to process inputs according to graph's opset version + slice changes its schema at opset 10: it treats some + attributes as dynamic input so this function has to process + inputs according to graph's opset version to get "inputs" and "attr" to feed "make_node" - kwargs: key could be ["data", "starts", "ends", "axes", "steps", "outputs"]. + kwargs: key could be `["data", "starts", "ends", + "axes", "steps", "outputs"]`. """ outputs = kwargs.pop("outputs", None) if self.graph.opset < 10: # "data" is string - # "starts", "ends" and "axes" are attributes, and "axes" is optional. + # "starts", "ends" and "axes" are attributes, + # and "axes" is optional. data = kwargs.pop("data") starts = self._convert_to_attribute(kwargs.pop("starts")) ends = self._convert_to_attribute(kwargs.pop("ends")) @@ -507,18 +557,21 @@ def make_slice(self, kwargs, name=None, shapes=None, dtypes=None, return_node=Fa else: # slice-10 has 3 required inputs "data", "starts", "ends"l # and 2 optional inputs "axes", "steps" - # input sequence should be "data", "starts", "ends", "axes", "steps" + # input sequence should be "data", "starts", "ends", + # "axes", "steps" attr = {} data = kwargs.pop("data") - starts = self._convert_to_input(kwargs.pop( - "starts"), "const_starts", dtype=numpy.int64) - ends = self._convert_to_input(kwargs.pop( - "ends"), "const_ends", dtype=numpy.int64) - axes = self._convert_to_input(kwargs.pop( - "axes", None), "const_axes", is_optional=True, dtype=numpy.int64) - steps = self._convert_to_input(kwargs.pop( - "steps", None), "const_steps", is_optional=True, dtype=numpy.int64) - inputs = [data, starts.name, ends.name, axes.name, steps.name] + starts = self._convert_to_input( + kwargs.pop("starts"), "const_starts", dtype=numpy.int64) + ends = self._convert_to_input( + kwargs.pop("ends"), "const_ends", dtype=numpy.int64) + axes = self._convert_to_input( + kwargs.pop("axes", None), "const_axes", + is_optional=True, dtype=numpy.int64) + steps = self._convert_to_input( + kwargs.pop("steps", None), "const_steps", + is_optional=True, dtype=numpy.int64) + inputs = [data, starts, ends, axes, steps] # pro-process inputs and attr make_sure(not kwargs, "kwargs contains un-used key") @@ -567,10 +620,11 @@ def make_squeeze(self, kwargs, name=None, shapes=None, dtypes=None, inputs = [data] else: data = kwargs.pop("data") - axes = self._convert_to_input(kwargs.pop( - "axes", None), "const_axes", is_optional=True, dtype=numpy.int64) + axes = self._convert_to_input( + kwargs.pop("axes", None), "const_axes", + is_optional=True, dtype=numpy.int64) attr = {} - inputs = [data, axes.name] + inputs = [data, axes] make_sure(not kwargs, "kwargs contains un-used key") @@ -611,10 +665,11 @@ def make_unsqueeze(self, kwargs, name=None, shapes=None, dtypes=None, inputs = [data] else: data = kwargs.pop("data") - axes = self._convert_to_input(kwargs.pop( - "axes", None), "const_axes", is_optional=True, dtype=numpy.int64) + axes = self._convert_to_input( + kwargs.pop("axes", None), "const_axes", + is_optional=True, dtype=numpy.int64) attr = {} - inputs = [data, axes.name] + inputs = [data, axes] make_sure(not kwargs, "kwargs contains un-used key") @@ -650,7 +705,7 @@ def _convert_to_input(self, tensor, const_name, is_optional=False, dtype=None): res = tensor if isinstance(tensor, list): res = self.graph.make_const( - make_name(const_name), numpy.array(tensor, dtype)) + make_name(const_name), numpy.array(tensor, dtype)).name return res def _convert_to_attribute(self, tensor, is_optional=False): diff --git a/mlprodict/onnx_tools/onnx_export.py b/mlprodict/onnx_tools/onnx_export.py index 1599b7213..e34d3e813 100644 --- a/mlprodict/onnx_tools/onnx_export.py +++ b/mlprodict/onnx_tools/onnx_export.py @@ -196,7 +196,7 @@ def rename_name(name): make_tf2onnx_code=lambda *args, **kwargs: make_tf2onnx_code( *args, context=context, used=used, mark_inits=mark_inits, **kwargs), - **context) + verbose=verbose, **context) skip_inits = set() for k, v in mark_inits.items(): @@ -217,7 +217,7 @@ def rename_name(name): make_tf2onnx_code=lambda *args, **kwargs: make_tf2onnx_code( *args, context=context, used=used, mark_inits=mark_inits, **kwargs), - **context) + verbose=verbose, **context) final += "\n" if not verbose: diff --git a/mlprodict/testing/einsum/einsum_impl_ext.py b/mlprodict/testing/einsum/einsum_impl_ext.py index 2889c498b..b96614bc8 100644 --- a/mlprodict/testing/einsum/einsum_impl_ext.py +++ b/mlprodict/testing/einsum/einsum_impl_ext.py @@ -342,13 +342,13 @@ def dispb(c): if verbose: print( # pragma: no cover "[GENERICDOT] before broadcast %s,%s->%s or %s" % ( - "".join(l1), "".join(l2), "".join(l3), - _numpy_extended_dot_equation( - len(m1.shape), len(m1.shape), axes, left, right))) + "".join(l1), "".join(l2), "".join(l3), + _numpy_extended_dot_equation( + len(m1.shape), len(m1.shape), axes, left, right))) print( # pragma: no cover "[GENERICDOT] names=%s kind=%r common=%s broadcast=%s" % ( - "".join(names), kind.tolist(), - dispb(common), dispb(broadcast))) + "".join(names), kind.tolist(), + dispb(common), dispb(broadcast))) for i in range(len(broadcast)): # pylint: disable=C0200 if broadcast[i] and not (kind[i] & 3) == 3: From abfae2b9fd3e69dfaf23857173020fbe79d18dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xavier=20dupr=C3=A9?= Date: Sat, 13 Nov 2021 12:21:23 +0100 Subject: [PATCH 4/6] lint --- mlprodict/onnx_tools/exports/tf2onnx_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mlprodict/onnx_tools/exports/tf2onnx_helper.py b/mlprodict/onnx_tools/exports/tf2onnx_helper.py index 50095aecd..c85517ead 100644 --- a/mlprodict/onnx_tools/exports/tf2onnx_helper.py +++ b/mlprodict/onnx_tools/exports/tf2onnx_helper.py @@ -90,7 +90,7 @@ def simplify(name, kind, force=False): rows.append( "node = GraphBuilder(ctx).make_squeeze(" "{'data': varx[%r]}, return_node=True)" - "" % (inputs[0], simplify(inputs[1], 'list_var'))) + "" % (inputs[0], )) elif len(inputs) == 2: rows.append( "node = GraphBuilder(ctx).make_squeeze(" From a6eb6c92bb20eb2d216d93ad3e4a2d2e21494f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xavier=20dupr=C3=A9?= Date: Sat, 13 Nov 2021 15:26:46 +0100 Subject: [PATCH 5/6] lint --- _unittests/ut_tools/test_export_onnx.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/_unittests/ut_tools/test_export_onnx.py b/_unittests/ut_tools/test_export_onnx.py index b75cbd7c4..3079ae46a 100644 --- a/_unittests/ut_tools/test_export_onnx.py +++ b/_unittests/ut_tools/test_export_onnx.py @@ -521,7 +521,7 @@ def version_1(cls, ctx, node, **kwargs): # the way to make sure size are not less than 0: # set "sizes"'s elem to be int_max if elem val is -1 size_dtype = ctx.get_dtype(size) - size_np_dtype = utils.map_onnx_to_numpy_type(size_dtype) + size_np_dtype = map_onnx_to_numpy_type(size_dtype) if (ctx.get_node_by_output(size).is_const() and ctx.get_node_by_output(starts).is_const()): starts = ctx.get_node_by_output(starts).get_tensor_value() @@ -535,16 +535,16 @@ def version_1(cls, ctx, node, **kwargs): dtype, "dtype of {} is None".format(node.input[1])) utils.make_sure( dtype, "dtype of {} is None".format(node.input[1])) - ends.append(np.iinfo(dtype).max) + ends.append(numpy.iinfo(dtype).max) else: ends.append(start + size) else: - neg_one_val = np.array([-1]).astype(size_np_dtype) + neg_one_val = numpy.array([-1]).astype(size_np_dtype) neg_one = ctx.make_const( utils.make_name("const"), neg_one_val).output[0] - int_max_val = np.array( + int_max_val = numpy.array( [utils.get_max_value(size_np_dtype)]).astype(size_np_dtype) int_max = ctx.make_const( utils.make_name("largest_int_val"), int_max_val).output[0] @@ -596,7 +596,7 @@ def any_version(cls, opset, ctx, node, **kwargs): np_dtype = map_onnx_to_numpy_type(onnx_dtype) make_sure(np_dtype in ConvertSqueeze2Op.supported_dtypes, "Unsupported input type.") - shape = ctx.get_shape(input_name) + # shape = ctx.get_shape(input_name) varx = {x: x for x in node.input} # initializers @@ -663,7 +663,7 @@ def create_model(): opsets = {'': 13} del onnx_model.opset_import[:] # pylint: disable=E1101 for dom, value in opsets.items(): - op_set = onnx_model.opset_import.add() + op_set = onnx_model.opset_import.add() # pylint: disable=E1101 op_set.domain = dom op_set.version = value @@ -1306,7 +1306,7 @@ def onnx_rfft_2d_any_test(x, fft_length): code = code.replace("make_sure(", "utils.make_sure(") code = code.replace("make_name(", "utils.make_name(") code = code.replace("map_onnx_to_numpy_type(", - "utils.map_onnx_to_numpy_type(") + "map_onnx_to_numpy_type(") code = code.replace("numpy.", "np.") code = code.replace("TensorProto.", "onnx_pb.TensorProto.") code = code.replace("dtype=np.float32", "dtype=np_dtype") From f0bcef554e19b7e821384097de80bab462b02633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xavier=20dupr=C3=A9?= Date: Sat, 13 Nov 2021 17:16:36 +0100 Subject: [PATCH 6/6] remove utils. --- _unittests/ut_tools/test_export_onnx.py | 20 +++++++++++-------- .../onnx_tools/exports/tf2onnx_helper.py | 5 +++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/_unittests/ut_tools/test_export_onnx.py b/_unittests/ut_tools/test_export_onnx.py index 3079ae46a..e616751e1 100644 --- a/_unittests/ut_tools/test_export_onnx.py +++ b/_unittests/ut_tools/test_export_onnx.py @@ -28,7 +28,8 @@ from mlprodict.testing.verify_code import verify_code from mlprodict.onnxrt import OnnxInference from mlprodict.onnx_tools.exports.tf2onnx_helper import ( - make_sure, make_name, map_onnx_to_numpy_type, GraphBuilder) + make_sure, make_name, map_onnx_to_numpy_type, get_max_value, + GraphBuilder) from mlprodict.tools.code_helper import print_code from mlprodict.onnx_tools.exports.numpy_helper import ( argmin_use_numpy_select_last_index, @@ -531,9 +532,9 @@ def version_1(cls, ctx, node, **kwargs): # get all elements if size == -1: dtype = ctx.get_dtype(node.input[1]) - utils.make_sure( + make_sure( dtype, "dtype of {} is None".format(node.input[1])) - utils.make_sure( + make_sure( dtype, "dtype of {} is None".format(node.input[1])) ends.append(numpy.iinfo(dtype).max) else: @@ -542,12 +543,12 @@ def version_1(cls, ctx, node, **kwargs): else: neg_one_val = numpy.array([-1]).astype(size_np_dtype) neg_one = ctx.make_const( - utils.make_name("const"), neg_one_val).output[0] + make_name("const"), neg_one_val).output[0] int_max_val = numpy.array( - [utils.get_max_value(size_np_dtype)]).astype(size_np_dtype) + [get_max_value(size_np_dtype)]).astype(size_np_dtype) int_max = ctx.make_const( - utils.make_name("largest_int_val"), int_max_val).output[0] + make_name("largest_int_val"), int_max_val).output[0] size_are_neg_one_flag = ctx.make_node( "Equal", [neg_one, size]).output[0] @@ -672,6 +673,9 @@ def create_model(): class TestExportOnnx(ExtTestCase): + def test_get_max_value(self): + self.assertEqual(get_max_value(numpy.int8), 127) + def test_model_data_slice(self): opv = 14 @@ -1303,8 +1307,8 @@ def onnx_rfft_2d_any_test(x, fft_length): self.assertIn("make_sure", code) if __name__ == "__main__" and shape == (3, 1, 4): - code = code.replace("make_sure(", "utils.make_sure(") - code = code.replace("make_name(", "utils.make_name(") + code = code.replace("make_sure(", "make_sure(") + code = code.replace("make_name(", "make_name(") code = code.replace("map_onnx_to_numpy_type(", "map_onnx_to_numpy_type(") code = code.replace("numpy.", "np.") diff --git a/mlprodict/onnx_tools/exports/tf2onnx_helper.py b/mlprodict/onnx_tools/exports/tf2onnx_helper.py index c85517ead..949cf0b9f 100644 --- a/mlprodict/onnx_tools/exports/tf2onnx_helper.py +++ b/mlprodict/onnx_tools/exports/tf2onnx_helper.py @@ -144,6 +144,11 @@ def make_name(name): return name +def get_max_value(np_dtype): + "Returns the maximum value for a specific type." + return numpy.iinfo(np_dtype).max + + def make_sure(cond, msg, *args): "Raises an exception if cond is not verified." if not cond: