From 5121ab3f6b5b7d39b04845f317b43f4f92016681 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 8 Jan 2019 17:00:11 -0800 Subject: [PATCH 1/5] ReduceL1, l2 export, lpnormalization import added --- .../contrib/onnx/mx2onnx/_op_translations.py | 38 +++++++++++ .../contrib/onnx/onnx2mx/_import_helper.py | 5 +- .../contrib/onnx/onnx2mx/_op_translations.py | 10 +++ tests/python-pytest/onnx/test_cases.py | 5 +- tests/python-pytest/onnx/test_node.py | 66 ++++++++++++------- 5 files changed, 97 insertions(+), 27 deletions(-) diff --git a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py index 3baf10a10d39..45cd1848e405 100644 --- a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py +++ b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py @@ -1689,3 +1689,41 @@ def convert_logsoftmax(node, **kwargs): name=name ) return [node] + +@mx_op.register("norm") +def convert_norm(node, **kwargs): + """Map MXNet's norm operator attributes to onnx's ReduceL1 and ReduceL2 operators + and return the created node. + """ + name, input_nodes, attrs = get_inputs(node, kwargs) + + mx_axis = attrs.get("axis", None) + axes = convert_string_to_list(str(mx_axis)) if mx_axis is not None else None + + keepdims = get_boolean_attribute_value(attrs, "keepdims") + ord = int(attrs.get("ord", 2)) + + if ord == 1: + onnx_op_name = "ReduceL1" + else: + onnx_op_name = "ReduceL2" + + if axes is not None: + reduce_node = onnx.helper.make_node( + onnx_op_name, + input_nodes, + [name], + axes=axes, + keepdims=keepdims, + name=name + ) + return [reduce_node] + else: + reduce_node = onnx.helper.make_node( + onnx_op_name, + input_nodes, + [name], + keepdims=keepdims, + name=name + ) + return [reduce_node] diff --git a/python/mxnet/contrib/onnx/onnx2mx/_import_helper.py b/python/mxnet/contrib/onnx/onnx2mx/_import_helper.py index 5b33f9faac11..28ef79d0ac68 100644 --- a/python/mxnet/contrib/onnx/onnx2mx/_import_helper.py +++ b/python/mxnet/contrib/onnx/onnx2mx/_import_helper.py @@ -37,7 +37,7 @@ from ._op_translations import reduce_sum_square, reduce_l1, reduce_l2, max_roi_pooling from ._op_translations import log_softmax, softsign, lesser, greater, equal from ._op_translations import logical_and, logical_or, logical_xor, logical_not -from ._op_translations import mean, depthtospace, spacetodepth +from ._op_translations import mean, depthtospace, spacetodepth, lpnormalization # convert_map defines maps of ONNX operator names to converter functor(callable) # defined in the op_translations module. @@ -145,5 +145,6 @@ 'LpPool' : lp_pooling, 'DepthToSpace' : depthtospace, 'SpaceToDepth' : spacetodepth, - 'Hardmax' : hardmax + 'Hardmax' : hardmax, + 'LpNormalization' : lpnormalization } diff --git a/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py b/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py index ce0e0e51ef79..798298a0bf3d 100644 --- a/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py +++ b/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py @@ -740,3 +740,13 @@ def hardmax(attrs, inputs, proto_obj): one_hot = symbol.one_hot(amax, depth=new_shape[-1]) hardmax_op = symbol.reshape(one_hot, input_shape) return hardmax_op, attrs, inputs + +def lpnormalization(attrs, inputs, proto_obj): + """ONNX does not have eps attribute, so cannot map it to L2normalization in MXNet + without that, it works as norm operator discussion in PR: + https://github.com/onnx/onnx/pull/1330""" + new_attrs = translation_utils._fix_attribute_names(attrs, {'p': 'ord'}) + axis = int(attrs.get("axis", -1)) + new_attrs.update(axis=axis) + return 'norm', new_attrs, inputs + diff --git a/tests/python-pytest/onnx/test_cases.py b/tests/python-pytest/onnx/test_cases.py index 6a189b62492d..f6c17d69ff95 100644 --- a/tests/python-pytest/onnx/test_cases.py +++ b/tests/python-pytest/onnx/test_cases.py @@ -76,12 +76,13 @@ 'test_selu_default', 'test_elu', 'test_max_', - 'test_softplus' + 'test_softplus', + 'test_reduce_' ], 'import': ['test_gather', 'test_global_lppooling', 'test_softsign', - 'test_reduce_', + # 'test_reduce_', 'test_mean', 'test_averagepool_1d', 'test_averagepool_2d_pads_count_include_pad', diff --git a/tests/python-pytest/onnx/test_node.py b/tests/python-pytest/onnx/test_node.py index 07ae866b96cf..06e9715e1ae9 100644 --- a/tests/python-pytest/onnx/test_node.py +++ b/tests/python-pytest/onnx/test_node.py @@ -88,35 +88,35 @@ def forward_pass(sym, arg, aux, data_names, input_data): return mod.get_outputs()[0].asnumpy() -class TestNode(unittest.TestCase): - """ Tests for models. - Tests are dynamically added. - Therefore edit test_models to add more tests. - """ +def get_input_tensors(input_data): + input_tensor = [] + input_names = [] + input_sym = [] + for idx, ip in enumerate(input_data): + name = "input" + str(idx + 1) + input_sym.append(mx.sym.Variable(name)) + input_names.append(name) + input_tensor.append(helper.make_tensor_value_info(name, + TensorProto.FLOAT, shape=np.shape(ip))) + return input_names, input_tensor, input_sym - def test_import_export(self): - def get_input_tensors(input_data): - input_tensor = [] - input_names = [] - input_sym = [] - for idx, ip in enumerate(input_data): - name = "input" + str(idx + 1) - input_sym.append(mx.sym.Variable(name)) - input_names.append(name) - input_tensor.append(helper.make_tensor_value_info(name, - TensorProto.FLOAT, shape=np.shape(ip))) - return input_names, input_tensor, input_sym - def get_onnx_graph(testname, input_names, inputs, output_name, output_shape, attr): - outputs = [helper.make_tensor_value_info("output", TensorProto.FLOAT, shape=output_shape)] +def get_onnx_graph(testname, input_names, inputs, output_name, output_shape, attr): + outputs = [helper.make_tensor_value_info("output", TensorProto.FLOAT, shape=output_shape)] - nodes = [helper.make_node(output_name, input_names, ["output"], **attr)] + nodes = [helper.make_node(output_name, input_names, ["output"], **attr)] - graph = helper.make_graph(nodes, testname, inputs, outputs) + graph = helper.make_graph(nodes, testname, inputs, outputs) - model = helper.make_model(graph) - return model + model = helper.make_model(graph) + return model +class TestNode(unittest.TestCase): + """ Tests for models. + Tests are dynamically added. + Therefore edit test_models to add more tests. + """ + def test_import_export(self): for test in test_cases: test_name, mxnet_op, onnx_name, inputs, attrs, mxnet_specific = test with self.subTest(test_name): @@ -138,6 +138,18 @@ def get_onnx_graph(testname, input_names, inputs, output_name, output_shape, att npt.assert_almost_equal(output[0], mxnet_output) + def test_imports(self): + for test in import_test_cases: + test_name, onnx_name, inputs, np_op, attrs = test + with self.subTest(test_name): + names, input_tensors, inputsym = get_input_tensors(inputs) + np_out = [np_op(*inputs, **attrs)] + output_shape = np.shape(np_out) + onnx_model = get_onnx_graph(test_name, names, input_tensors, onnx_name, output_shape, attrs) + bkd_rep = backend.prepare(onnx_model, operation='import') + mxnet_out = bkd_rep.run(inputs) + npt.assert_almost_equal(np_out, mxnet_out) + # test_case = ("test_case_name", mxnet op, "ONNX_op_name", [input_list], attribute map, MXNet_specific=True/False) test_cases = [ @@ -160,5 +172,13 @@ def get_onnx_graph(testname, input_names, inputs, output_name, output_shape, att {'num_hidden': 4, 'name': 'FC'}, True) ] +# test_case = ("test_case_name", "ONNX_op_name", [input_list], np_op, attribute map) +import_test_cases = [ + ("test_lpnormalization_default", "LpNormalization", [get_rnd([5, 3, 3, 2])], np.linalg.norm, {'ord':2, 'axis':-1}), + ("test_lpnormalization_ord1", "LpNormalization", [get_rnd([5, 3, 3, 2])], np.linalg.norm, {'ord':1, 'axis':-1}), + ("test_lpnormalization_ord2", "LpNormalization", [get_rnd([5, 3, 3, 2])], np.linalg.norm, {'ord':2, 'axis':1}), + ("test_lpnormalization_ord_axis", "LpNormalization", [get_rnd([5, 3, 3, 2])], np.linalg.norm, {'ord':1, 'axis':2}) +] + if __name__ == '__main__': unittest.main() From d986cbebb3b22680366d7fe565e75b8253989909 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 8 Jan 2019 17:01:42 -0800 Subject: [PATCH 2/5] fix --- tests/python-pytest/onnx/test_cases.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/python-pytest/onnx/test_cases.py b/tests/python-pytest/onnx/test_cases.py index f6c17d69ff95..377aaa105616 100644 --- a/tests/python-pytest/onnx/test_cases.py +++ b/tests/python-pytest/onnx/test_cases.py @@ -82,7 +82,6 @@ 'import': ['test_gather', 'test_global_lppooling', 'test_softsign', - # 'test_reduce_', 'test_mean', 'test_averagepool_1d', 'test_averagepool_2d_pads_count_include_pad', From e1654d3ac45a63482ae69d7ab042213980e129e1 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 8 Jan 2019 17:17:28 -0800 Subject: [PATCH 3/5] fix --- python/mxnet/contrib/onnx/mx2onnx/_op_translations.py | 9 +++------ python/mxnet/contrib/onnx/onnx2mx/_op_translations.py | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py index 45cd1848e405..31a410be556a 100644 --- a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py +++ b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py @@ -1698,17 +1698,14 @@ def convert_norm(node, **kwargs): name, input_nodes, attrs = get_inputs(node, kwargs) mx_axis = attrs.get("axis", None) - axes = convert_string_to_list(str(mx_axis)) if mx_axis is not None else None + axes = convert_string_to_list(str(mx_axis)) if mx_axis else None keepdims = get_boolean_attribute_value(attrs, "keepdims") ord = int(attrs.get("ord", 2)) - if ord == 1: - onnx_op_name = "ReduceL1" - else: - onnx_op_name = "ReduceL2" + onnx_op_name = "ReduceL1" if ord == 1 else "ReduceL2" - if axes is not None: + if axes: reduce_node = onnx.helper.make_node( onnx_op_name, input_nodes, diff --git a/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py b/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py index 798298a0bf3d..ec6bcfee2100 100644 --- a/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py +++ b/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py @@ -747,6 +747,6 @@ def lpnormalization(attrs, inputs, proto_obj): https://github.com/onnx/onnx/pull/1330""" new_attrs = translation_utils._fix_attribute_names(attrs, {'p': 'ord'}) axis = int(attrs.get("axis", -1)) - new_attrs.update(axis=axis) + new_attrs = translation_utils._add_extra_attributes(new_attrs, {'axis', axis}) return 'norm', new_attrs, inputs From baded39d33a1cfbcb1d5bf1d01b999c2e1fe88cc Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 8 Jan 2019 18:16:15 -0800 Subject: [PATCH 4/5] fix --- python/mxnet/contrib/onnx/onnx2mx/_op_translations.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py b/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py index ec6bcfee2100..57ff4f322659 100644 --- a/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py +++ b/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py @@ -749,4 +749,3 @@ def lpnormalization(attrs, inputs, proto_obj): axis = int(attrs.get("axis", -1)) new_attrs = translation_utils._add_extra_attributes(new_attrs, {'axis', axis}) return 'norm', new_attrs, inputs - From 439c2f53d18195e9e4a0e98f7c7b944065d2edaa Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Wed, 9 Jan 2019 14:43:28 -0800 Subject: [PATCH 5/5] fix --- python/mxnet/contrib/onnx/onnx2mx/_op_translations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py b/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py index c5a065839875..ef3bda30df38 100644 --- a/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py +++ b/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py @@ -762,5 +762,5 @@ def lpnormalization(attrs, inputs, proto_obj): https://github.com/onnx/onnx/pull/1330""" new_attrs = translation_utils._fix_attribute_names(attrs, {'p': 'ord'}) axis = int(attrs.get("axis", -1)) - new_attrs = translation_utils._add_extra_attributes(new_attrs, {'axis', axis}) + new_attrs.update(axis=axis) return 'norm', new_attrs, inputs