From 8d083f8b464db943a1094b23bb51cceb916cae64 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Thu, 12 Apr 2018 09:31:09 -0700 Subject: [PATCH 01/82] Resolve conflicts --- python/mxnet/contrib/onnx/__init__.py | 2 + python/mxnet/contrib/onnx/_export/__init__.py | 23 + .../contrib/onnx/_export/export_helper.py | 16 + .../contrib/onnx/_export/export_model.py | 40 ++ .../mxnet/contrib/onnx/_export/export_onnx.py | 135 ++++++ .../contrib/onnx/_export/op_translations.py | 448 ++++++++++++++++++ 6 files changed, 664 insertions(+) create mode 100644 python/mxnet/contrib/onnx/_export/__init__.py create mode 100644 python/mxnet/contrib/onnx/_export/export_helper.py create mode 100644 python/mxnet/contrib/onnx/_export/export_model.py create mode 100644 python/mxnet/contrib/onnx/_export/export_onnx.py create mode 100644 python/mxnet/contrib/onnx/_export/op_translations.py diff --git a/python/mxnet/contrib/onnx/__init__.py b/python/mxnet/contrib/onnx/__init__.py index 4f9296d3c56e..304184c58e10 100644 --- a/python/mxnet/contrib/onnx/__init__.py +++ b/python/mxnet/contrib/onnx/__init__.py @@ -18,3 +18,5 @@ from ._import.import_model import import_model, get_model_metadata from ._import.import_to_gluon import import_to_gluon +from ._export.export_model import export_model + diff --git a/python/mxnet/contrib/onnx/_export/__init__.py b/python/mxnet/contrib/onnx/_export/__init__.py new file mode 100644 index 000000000000..fbf10a13bb8f --- /dev/null +++ b/python/mxnet/contrib/onnx/_export/__init__.py @@ -0,0 +1,23 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# coding: utf-8 +"""ONNX Export module""" +from __future__ import absolute_import + +from . import export_model +from . import export_onnx diff --git a/python/mxnet/contrib/onnx/_export/export_helper.py b/python/mxnet/contrib/onnx/_export/export_helper.py new file mode 100644 index 000000000000..13a83393a912 --- /dev/null +++ b/python/mxnet/contrib/onnx/_export/export_helper.py @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. diff --git a/python/mxnet/contrib/onnx/_export/export_model.py b/python/mxnet/contrib/onnx/_export/export_model.py new file mode 100644 index 000000000000..a56791dff663 --- /dev/null +++ b/python/mxnet/contrib/onnx/_export/export_model.py @@ -0,0 +1,40 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# coding: utf-8 + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from onnx import defs, checker, helper, numpy_helper, mapping +from .export_onnx import MxNetToONNXConverter + +import json + +import mxnet as mx +import numpy as np + +def export_model(model_file, weight_file, input_shape, input_type, log=False): + mx_weights = mx.ndarray.load(weight_file) + with open(model_file, 'r') as f: + graph = json.loads(f.read())["nodes"] + converter = MxNetToONNXConverter() + onnx_graph = converter.convert_mx2onnx_graph(graph, mx_weights, input_shape, mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype(input_type)], log=log) + onnx_model = helper.make_model(onnx_graph) + return onnx_model diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py new file mode 100644 index 000000000000..00381d24c0da --- /dev/null +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -0,0 +1,135 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# coding: utf-8 +# pylint: disable=invalid-name,too-many-locals,no-self-use + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy as np + +import sys + +from onnx import (defs, checker, helper, numpy_helper, mapping, onnx_pb2, + ModelProto, GraphProto, NodeProto, AttributeProto, TensorProto) + +from onnx.helper import make_tensor, make_tensor_value_info + +class MxNetToONNXConverter: + registry_ = {} + input_output_maps_ = {} + + def __init__(self): + # topologically sorted nodes + self.nodes = [] + self.input_tensors = [] + self.output_tensors = [] + + @staticmethod + def register(op_name): + + def wrapper(func): + MxNetToONNXConverter.registry_[op_name] = func + return func + + return wrapper + + @staticmethod + def convert_layer(node, **kwargs): + op = str(node["op"]) + if op not in MxNetToONNXConverter.registry_: + raise AttributeError("No conversion function registered for op type %s yet." % op) + convert_fun = MxNetToONNXConverter.registry_[op] + return convert_fun(node, **kwargs) + + # Add transpose? + @staticmethod + def convert_weights_to_numpy(weights_dict): + return dict([(k.replace("arg:", "").replace("aux:", ""), v.asnumpy()) for k, v in weights_dict.items()]) + + def convert_mx2onnx_graph(self, mx_graph, mx_weights, in_shape, in_type, log=False): + print("\nconverting weights from MxNet NDArrays to NumPy arrays.\n") + weights = MxNetToONNXConverter.convert_weights_to_numpy(mx_weights) + + onnx_graph = GraphProto() + + initializer = [] + all_processed_nodes = [] + onnx_processed_nodes = [] + onnx_processed_inputs = [] + onnx_processed_outputs = [] + + for idx, node in enumerate(mx_graph): + op = node["op"] + name = node["name"] + if log: + print("Converting idx: %d, op: %s, name: %s" % (idx, op, name)) + converted = MxNetToONNXConverter.convert_layer( + node, + mx_graph=mx_graph, + weights=weights, + in_shape=in_shape, + in_type=in_type, + proc_nodes=all_processed_nodes, + initializer=initializer + ) + + if isinstance(converted, onnx_pb2.ValueInfoProto): + if idx < (len(mx_graph) - 1): + onnx_processed_inputs.append(converted) + else: + onnx_processed_outputs.append(converted) + elif isinstance(converted, onnx_pb2.NodeProto): + if idx < (len(mx_graph) - 1): + onnx_processed_nodes.append(converted) + else: + onnx_processed_nodes.append(converted) + onnx_processed_outputs.append( + make_tensor_value_info( + name=converted.name, + elem_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')], + shape=(in_shape[0], -1) + ) + ) + if log: + print("Output node is: %s" % converted.name) + elif isinstance(converted, onnx_pb2.TensorProto): + raise ValueError("Did not expect TensorProto") + if idx < (len(mx_graph) - 1): + onnx_processed_inputs.append(converted) + else: + onnx_processed_outputs.append(converted) + else: + print(converted) + raise ValueError("node is of an unrecognized type: %s" % type(node)) + + all_processed_nodes.append(converted) + + graph = helper.make_graph( + onnx_processed_nodes, + "main", + onnx_processed_inputs, + onnx_processed_outputs + ) + + graph.initializer.extend(initializer) + + checker.check_graph(graph) + return graph diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py new file mode 100644 index 000000000000..3b03b4bbe539 --- /dev/null +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -0,0 +1,448 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# coding: utf-8 +""" +mx_to_uff_converter_functions.py + +Conversion Functions for common layers. +Add new functions here with a decorator. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from onnx import defs, checker, helper, numpy_helper, mapping + +from .export_onnx import MxNetToONNXConverter as mx2onnx + +import numpy as np + +import re + +import sys + + +def looks_like_weight(name): + """Internal helper to figure out if node should be hidden with `hide_weights`. + """ + if name.endswith("_weight"): + return True + if name.endswith("_bias"): + return True + if name.endswith("_beta") or name.endswith("_gamma") or name.endswith("_moving_var") or name.endswith( + "_moving_mean"): + return True + return False + + +@mx2onnx.register("null") +def convert_weights_and_inputs(node, **kwargs): + name = node["name"] + if looks_like_weight(name): + weights = kwargs["weights"] + initializer = kwargs["initializer"] + weights = kwargs["weights"] + np_arr = weights[name] + data_type = mapping.NP_TYPE_TO_TENSOR_TYPE[np_arr.dtype] + dims = np.shape(np_arr) + + tensor_node = helper.make_tensor_value_info(name, data_type, dims) + + initializer.append( + helper.make_tensor( + name=name, + data_type=data_type, + dims=dims, + vals=np_arr.flatten().tolist(), + raw=False, + ) + ) + + return tensor_node + else: + tval_node = helper.make_tensor_value_info(name, kwargs["in_type"], kwargs["in_shape"]) + return tval_node + + +@mx2onnx.register("Convolution") +def convert_convolution(node, **kwargs): + name = node["name"] + inputs = node["inputs"] + + num_inputs = len(inputs) + + proc_nodes = kwargs["proc_nodes"] + input_node = proc_nodes[inputs[0][0]].name + weights_node = proc_nodes[inputs[1][0]].name + + if num_inputs > 2: + bias_node = proc_nodes[inputs[2][0]].name + + attrs = node.get("attrs") + tuple_re = re.compile('\([0-9|,| ]+\)') + + def parse_helper(attrs_name, alt_value=None): + if attrs is None: + return alt_value + attrs_str = attrs.get(attrs_name) + if attrs_str is None: + return alt_value + attrs_match = tuple_re.search(attrs_str) + if attrs_match is not None: + if attrs_match.span() == (0, len(attrs_str)): + dims = eval(attrs_str) + return dims + else: + raise AttributeError("Malformed %s dimensions: %s" % (attrs_name, str(attrs_str))) + return alt_value + + num_filter = int(attrs["num_filter"]) + kernel_dims = list(parse_helper("kernel")) + stride_dims = list(parse_helper("stride", [1, 1])) + pad_dims = list(parse_helper("pad", [0, 0])) + num_group = int(attrs.get("num_group", 1)) + + if len(pad_dims) < 2 * len(kernel_dims): + pad_dims = [0] * (2 * len(kernel_dims) - len(pad_dims)) + pad_dims + + input_nodes = [input_node, weights_node] + if num_inputs > 2: + input_nodes.append(bias_node) + + conv_node = helper.make_node( + "Conv", + inputs=input_nodes, + outputs=[name], + kernel_shape=kernel_dims, + strides=stride_dims, + pads=pad_dims, + group=num_group, + name=name + ) + + return conv_node + + +@mx2onnx.register("FullyConnected") +def convert_fully_connected(node, **kwargs): + name = node["name"] + inputs = node["inputs"] + input_node_id = inputs[0][0] + weight_node_id = inputs[1][0] + bias_node_id = inputs[2][0] + proc_nodes = kwargs["proc_nodes"] + input_node = proc_nodes[input_node_id] + weights_node = proc_nodes[weight_node_id] + bias_node = proc_nodes[bias_node_id] + + input_name = input_node.name + weights_name = weights_node.name + bias_name = bias_node.name + + node = helper.make_node( + "Gemm", + [input_name, weights_name, bias_name], # input (A, B, C) - C can be in place + [name], # output + alpha=1.0, + beta=1.0, + broadcast=True, + transA=False, + transB=True, + name=name + ) + + return node + + +@mx2onnx.register("BatchNorm") +def convert_batchnorm(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + attrs = node["attrs"] + momentum = float(attrs["momentum"]) + eps = float(attrs["eps"]) + + data_idx = inputs[0][0] + gamma_idx = inputs[1][0] + beta_idx = inputs[2][0] + moving_mean_idx = inputs[3][0] + moving_var_idx = inputs[4][0] + + data_node = proc_nodes[data_idx].name + gamma_node = proc_nodes[gamma_idx].name + beta_node = proc_nodes[beta_idx].name + + mov_mean_node = proc_nodes[moving_mean_idx] + mov_mean_node = mov_mean_node.name + mov_var_node = proc_nodes[moving_var_idx].name + + bn_node = helper.make_node( + "BatchNormalization", + [data_node, + gamma_node, # scale + beta_node, # bias + mov_mean_node, + mov_var_node + ], + [name], + name=name, + epsilon=eps, + momentum=momentum, + is_test=1, + spatial=1, + consumed_inputs=(0, 0, 0, 1, 1) + ) + + return bn_node + + +@mx2onnx.register("Activation") +def convert_activation(node, **kwargs): + name = node["name"] + + proc_nodes = kwargs["proc_nodes"] + attrs = node["attrs"] + act_type = attrs["act_type"] + + inputs = node["inputs"] + input_node_idx = inputs[0][0] + input_node = proc_nodes[input_node_idx].output[0] + + # Creating a dictionary here, but if this titlecase pattern + # is consistent for other activations, this can be changed to + # mxnet_name.title() + act_types = { + "tanh": "Tanh", + "relu": "Relu" + } + + act_name = act_types.get(act_type) + if act_name: + node = helper.make_node( + act_name, + [input_node], + [name], + name=name + ) + else: + raise AttributeError( + "Activation %s not implemented or recognized in the converter" % act_type + ) + + return node + + +@mx2onnx.register("Pooling") +def convert_pooling(node, **kwargs): + proc_nodes = kwargs["proc_nodes"] + attrs = node["attrs"] + kernel = eval(attrs["kernel"]) + pool_type = attrs["pool_type"] + stride = eval(attrs["stride"]) if attrs.get("stride") else None + node_inputs = node["inputs"] + input_node_idx = node_inputs[0][0] + input_node = proc_nodes[input_node_idx] + name = node["name"] + + pool_types = {"max": "MaxPool", "avg": "AveragePool"} + global_pool_types = {"max": "GlobalMaxPool", "avg": "GlobalAveragePool"} + + if stride: + node = helper.make_node( + pool_types[pool_type], + [input_node.output[0]], # input + [name], + # dilations = [0, 0], + kernel_shape=kernel, + pads=[0, 0], + strides=stride, + name=name + ) + else: + node = helper.make_node( + global_pool_types[pool_type], + [input_node.output[0]], # input + [name], + name=name + ) + + return node + + +@mx2onnx.register("exp") +def convert_exp(node, **kwargs): + raise NotImplementedError + + +# There's also mx.sym.softmax(), which doesn't do cross-entropy loss, +# just softmax for inference - hence the name convert_softmax_output. +@mx2onnx.register("SoftmaxOutput") +def convert_softmax_output(node, **kwargs): + # print("\nIn convert_softmax_output") + inputs = node["inputs"] + input1_idx = inputs[0][0] + proc_nodes = kwargs["proc_nodes"] + input1 = proc_nodes[input1_idx] + name = node["name"] + + softmax_node = helper.make_node( + "Softmax", + [input1.output[0]], + [name], + axis=1, + name=name + ) + + return softmax_node + + +@mx2onnx.register("Concat") +def convert_concat(node, **kwargs): + name = node["name"] + inputs = node["inputs"] + proc_nodes = kwargs["proc_nodes"] + input_names = [proc_nodes[i[0]].name for i in inputs] + axis = int(node.get("attrs", {}).get("axis", 1)) + concat_node = helper.make_node( + "Concat", + input_names, + [name], + axis=axis, + name=name + ) + return concat_node + + +@mx2onnx.register("Dropout") +def convert_dropout(node, **kwargs): + name = node["name"] + input_id = node["inputs"][0][0] + input_name = kwargs["proc_nodes"][input_id].name + attrs = node["attrs"] + p = float(attrs["p"]) + dropout_node = helper.make_node( + "Dropout", + [input_name], + [name], + ratio=p, + is_test=0, + name=name + ) + return dropout_node + + +@mx2onnx.register("Flatten") +def convert_flatten(node, **kwargs): + name = node["name"] + input_idx = node["inputs"][0][0] + proc_nodes = kwargs["proc_nodes"] + input_node = proc_nodes[input_idx].name # .output[0] + + flatten_node = helper.make_node( + "Flatten", + [input_node], + [name], + name=name + ) + return flatten_node + + +@mx2onnx.register("_mul_scalar") +def convert_mul_scalar(node, **kwargs): + raise NotImplementedError + + +@mx2onnx.register("elemwise_add") +def convert_elementwise_add(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + weights = kwargs["weights"] + + a = inputs[0][0] + b = inputs[1][0] + + a_node = proc_nodes[a].name + b_node = proc_nodes[b].name + + add_node = helper.make_node( + "Add", + [a_node, b_node], + [name], + name=name, + ) + + return add_node + + +@mx2onnx.register("_sub") +def convert_elementwise_sub(node, **kwargs): + raise NotImplementedError + + +@mx2onnx.register("abs") +def convert_abs(node, **kwargs): + raise NotImplementedError + + +@mx2onnx.register("_mul") +def convert_mul(node, proc_nodes): + raise NotImplementedError + + +@mx2onnx.register("_div") +def convert_div(node, **kwargs): + raise NotImplementedError + + +@mx2onnx.register("log") +def convert_log(node, **kwargs): + raise NotImplementedError + + +@mx2onnx.register("max") +def convert_max(node, **kwargs): + raise NotImplementedError + + +@mx2onnx.register("_maximum") +def convert_maximum(node, **kwargs): + raise NotImplementedError + + +@mx2onnx.register("min") +def convert_min(node, **kwargs): + raise NotImplementedError + + +@mx2onnx.register("_minimum") +def convert_minimum(node, **kwargs): + raise NotImplementedError + + +@mx2onnx.register("_power") +def convert_power(node, **kwargs): + raise NotImplementedError + + +@mx2onnx.register("sqrt") +def convert_sqrt(node, **kwargs): + raise NotImplementedError From 4c04e19a39d7d2f64d567335cd56f06f02e99a82 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Tue, 17 Apr 2018 10:52:33 -0700 Subject: [PATCH 02/82] Export module Test Framework --- tests/python-pytest/onnx/export/backend.py | 204 ++++++++++++++++++ .../python-pytest/onnx/export/backend_rep.py | 87 ++++++++ .../onnx/export/onnx_backend_test.py | 84 ++++++++ 3 files changed, 375 insertions(+) create mode 100644 tests/python-pytest/onnx/export/backend.py create mode 100644 tests/python-pytest/onnx/export/backend_rep.py create mode 100644 tests/python-pytest/onnx/export/onnx_backend_test.py diff --git a/tests/python-pytest/onnx/export/backend.py b/tests/python-pytest/onnx/export/backend.py new file mode 100644 index 000000000000..91287c30afc4 --- /dev/null +++ b/tests/python-pytest/onnx/export/backend.py @@ -0,0 +1,204 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# coding: utf-8 +"""backend wrapper for onnx test infrastructure""" +import mxnet as mx +from mxnet.contrib.onnx._import.import_onnx import GraphProto +from mxnet.contrib.onnx._export.export_onnx import MxNetToONNXConverter +try: + from onnx import helper, TensorProto + from onnx.backend.base import Backend +except ImportError: + raise ImportError("Onnx and protobuf need to be installed") +from backend_rep import MXNetBackendRep + +# Using these functions for onnx test infrastructure. +# Implemented by following onnx docs guide: +# https://github.com/onnx/onnx/blob/master/docs/Implementing%20an%20ONNX%20backend.md +# MXNetBackend class will take an ONNX model with inputs, perform a computation, +# and then return the output. + +class MXNetBackend(Backend): + """MXNet backend for ONNX""" + + @staticmethod + def make_graph(node, inputs): + """ Created ONNX GraphProto from node""" + initializer = [] + tensor_input_info = [] + tensor_output_info = [] + + # Adding input tensor info. + for index in range(len(node.input)): + tensor_input_info.append( + helper.make_tensor_value_info(str(node.input[index]), TensorProto.FLOAT, [1])) + + # Creating an initializer for Weight params. + # Assumes that weight params is named as 'W'. + if node.input[index] == 'W': + dim = inputs[index].shape + param_tensor = helper.make_tensor( + name=node.input[index], + data_type=TensorProto.FLOAT, + dims=dim, + vals=inputs[index].flatten()) + + initializer.append(param_tensor) + + # Adding output tensor info. + for index in range(len(node.output)): + tensor_output_info.append( + helper.make_tensor_value_info(str(node.output[index]), TensorProto.FLOAT, [1])) + + # creating graph proto object. + graph_proto = helper.make_graph( + [node], + "test", + tensor_input_info, + tensor_output_info, + initializer=initializer) + + return graph_proto + + @staticmethod + def perform_import_export(graph_proto): + """ Import ONNX model to mxnet model and then export to ONNX model + and then import it back to mxnet for verifying the result""" + graph = GraphProto() + + sym, arg_params, aux_params = graph.from_onnx(graph_proto) + + params = {} + params.update(arg_params) + params.update(aux_params) + # exporting to onnx graph proto format + converter = MxNetToONNXConverter() + graph_proto = converter.convert_mx2onnx_graph(sym, params, in_shape=None, in_type=None) + + # importing back to MXNET for verfiying result. + sym, arg_params, aux_params = graph.from_onnx(graph_proto) + + return sym, arg_params, aux_params + + @classmethod + def run_node(cls, node, inputs, device='CPU'): + """Running individual node inference on mxnet engine and + return the result to onnx test infrastructure. + + Parameters + ---------- + node : onnx node object + loaded onnx node (individual layer) + inputs : numpy array + input to run a node on + device : 'CPU' + device to run a node on + + Returns + ------- + params : numpy array + result obtained after running the operator + """ + sym, arg_params, aux_params = MXNetBackend.perform_import_export(MXNetBackend.make_graph(node, inputs)) + + data_names = [graph_input for graph_input in sym.list_inputs() + if graph_input not in arg_params and graph_input not in aux_params] + data_shapes = [] + dim_change_op_types = set(['ReduceMin', 'ReduceMax', 'ReduceMean', + 'ReduceProd', 'ReduceSum', 'Slice', 'Pad', + 'Squeeze', 'Upsample', 'Reshape', 'Conv', + 'Concat', 'Softmax', 'Flatten', 'Transpose', + 'GlobalAveragePool', 'GlobalMaxPool']) + + # Adding extra dimension of batch_size 1 if the batch_size is different for multiple inputs. + for idx, input_name in enumerate(data_names): + batch_size = 1 + if len(inputs) > 1 and len(inputs[idx].shape) < 4 and \ + len(set(x.shape[0] for x in inputs)) != 1: + tuples = ((batch_size,), inputs[idx].shape) + new_shape = sum(tuples, ()) + data_shapes.append((input_name, new_shape)) + else: + data_shapes.append((input_name, inputs[idx].shape)) + + # create module, passing cpu context + if device == 'CPU': + ctx = mx.cpu() + else: + raise NotImplementedError("Only CPU context is supported for now") + + # create a module + mod = mx.mod.Module(symbol=sym, data_names=data_names, context=ctx, label_names=None) + mod.bind(for_training=False, data_shapes=data_shapes, label_shapes=None) + + # initializing parameters for calculating result of each individual node + if arg_params is None and aux_params is None: + mod.init_params() + else: + mod.set_params(arg_params=arg_params, aux_params=aux_params) + + data_forward = [] + for idx, input_name in enumerate(data_names): + # slice and pad operator tests needs 1 less dimension in forward pass + # otherwise it will throw an error. + # for squeeze operator, need to retain shape of input as provided + val = inputs[idx] + if node.op_type in dim_change_op_types: + data_forward.append(mx.nd.array(val)) + else: + data_forward.append(mx.nd.array([val])) + + mod.forward(mx.io.DataBatch(data_forward)) + result = mod.get_outputs()[0].asnumpy() + if node.op_type in dim_change_op_types: + return [result] + return result + + @classmethod + def prepare(cls, model, device='CPU', **kwargs): + """For running end to end model(used for onnx test backend) + + Parameters + ---------- + model : onnx ModelProto object + loaded onnx graph + device : 'CPU' + specifying device to run test on + kwargs : + other arguments + + Returns + ------- + MXNetBackendRep : object + Returns object of MXNetBackendRep class which will be in turn + used to run inference on the input model and return the result for comparison. + """ + graph = GraphProto() + sym, arg_params, aux_params = MXNetBackend.perform_import_export(model.graph) + return MXNetBackendRep(sym, arg_params, aux_params, device) + + @classmethod + def supports_device(cls, device): + """Supports only CPU for testing""" + return device == 'CPU' + +prepare = MXNetBackend.prepare + +run_node = MXNetBackend.run_node + +supports_device = MXNetBackend.supports_device diff --git a/tests/python-pytest/onnx/export/backend_rep.py b/tests/python-pytest/onnx/export/backend_rep.py new file mode 100644 index 000000000000..114a2eb79903 --- /dev/null +++ b/tests/python-pytest/onnx/export/backend_rep.py @@ -0,0 +1,87 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# coding: utf-8 +"""backend rep for onnx test infrastructure""" +from collections import namedtuple +import numpy as np +try: + from onnx.backend.base import BackendRep +except ImportError: + raise ImportError("Onnx and protobuf need to be installed") +import mxnet as mx + +# Using these functions for onnx test infrastructure. +# Implemented by following onnx docs guide: +# https://github.com/onnx/onnx/blob/master/docs/Implementing%20an%20ONNX%20backend.md +# MXNetBackendRep object will be returned by MXNetBackend's prepare method which is used to +# execute a model repeatedly. +# Inputs will be passed to the run method of MXNetBackendRep class, it will perform computation and +# retrieve the corresponding results for comparison to the onnx backend. +# https://github.com/onnx/onnx/blob/master/onnx/backend/test/runner/__init__.py. + +class MXNetBackendRep(BackendRep): + """Running model inference on mxnet engine and return the result + to onnx test infrastructure for comparison.""" + def __init__(self, symbol, arg_params, aux_params, device): + self.symbol = symbol + self.arg_params = arg_params + self.aux_params = aux_params + self.device = device + + def run(self, inputs, **kwargs): + """Run model inference and return the result + + Parameters + ---------- + inputs : numpy array + input to run a layer on + + Returns + ------- + params : numpy array + result obtained after running the inference on mxnet + """ + input_data = np.asarray(inputs[0], dtype='f') + + # create module, passing cpu context + if self.device == 'CPU': + ctx = mx.cpu() + else: + raise NotImplementedError("Only CPU context is supported for now") + + # To fetch the data names of the input to the model we list the inputs of the symbol graph + # and exclude the argument and auxiliary parameters from the list + data_names = [graph_input for graph_input in self.symbol.list_inputs() + if graph_input not in self.arg_params and graph_input not in self.aux_params] + + data_shapes = [] + for idx, input_name in enumerate(data_names): + data_shapes.append((input_name, inputs[idx].shape)) + + mod = mx.mod.Module(symbol=self.symbol, data_names=data_names, context=ctx, + label_names=None) + mod.bind(for_training=False, data_shapes=data_shapes, + label_shapes=None) + mod.set_params(arg_params=self.arg_params, aux_params=self.aux_params) + + # run inference + batch = namedtuple('Batch', ['data']) + + mod.forward(batch([mx.nd.array(input_data)])) + result = mod.get_outputs()[0].asnumpy() + return [result] diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py new file mode 100644 index 000000000000..4bcd82c2bef1 --- /dev/null +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -0,0 +1,84 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""ONNX test backend wrapper""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import unittest +try: + import onnx.backend.test +except ImportError: + raise ImportError("Onnx and protobuf need to be installed") + +import backend as mxnet_backend + +# This is a pytest magic variable to load extra plugins +pytest_plugins = "onnx.backend.test.report", + +BACKEND_TEST = onnx.backend.test.BackendTest(mxnet_backend, __name__) + +IMPLEMENTED_OPERATORS_TEST = [ + 'test_operator_maxpool', + + ] + +BASIC_MODEL_TESTS = [ + 'test_AvgPool2D', + 'test_BatchNorm', + 'test_ConstantPad2d' + 'test_Conv2d', + 'test_ELU', + 'test_LeakyReLU', + 'test_MaxPool', + 'test_PReLU', + 'test_ReLU', + 'test_Sigmoid', + 'test_Softmax', + 'test_softmax_functional', + 'test_softmax_lastdim', + 'test_Tanh' + ] + +STANDARD_MODEL = [ + 'test_bvlc_alexnet', + 'test_densenet121', + #'test_inception_v1', + #'test_inception_v2', + 'test_resnet50', + #'test_shufflenet', + 'test_squeezenet', + 'test_vgg16', + 'test_vgg19' + ] + +for op_test in IMPLEMENTED_OPERATORS_TEST: + BACKEND_TEST.include(op_test) + +# for std_model_test in STANDARD_MODEL: +# BACKEND_TEST.include(std_model_test) +# +# for basic_model_test in BASIC_MODEL_TESTS: +# BACKEND_TEST.include(basic_model_test) + +# import all test cases at global scope to make them visible to python.unittest +globals().update(BACKEND_TEST.enable_report().test_cases) + +if __name__ == '__main__': + unittest.main() From e66ef3b464155d2118a72d96a4d10e8bdfab3775 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 17 Apr 2018 15:40:55 -0700 Subject: [PATCH 03/82] refactoring export to work with pretrained models --- .../contrib/onnx/_export/export_model.py | 33 +++++++++++++++---- .../mxnet/contrib/onnx/_export/export_onnx.py | 6 ++-- .../contrib/onnx/_export/op_translations.py | 3 +- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_model.py b/python/mxnet/contrib/onnx/_export/export_model.py index a56791dff663..b4ef36461936 100644 --- a/python/mxnet/contrib/onnx/_export/export_model.py +++ b/python/mxnet/contrib/onnx/_export/export_model.py @@ -25,16 +25,35 @@ from onnx import defs, checker, helper, numpy_helper, mapping from .export_onnx import MxNetToONNXConverter -import json - import mxnet as mx import numpy as np -def export_model(model_file, weight_file, input_shape, input_type, log=False): - mx_weights = mx.ndarray.load(weight_file) - with open(model_file, 'r') as f: - graph = json.loads(f.read())["nodes"] +def load_module(json_path, params_path, input_shape): + if not (os.path.isfile(json_path) and os.path.isfile(params_path)): + raise ValueError("Provide valid path to the json and params file") + else: + model_name = json_path.rsplit('.')[0].rsplit('-', 1)[0] + num_epochs = int(params_path.rsplit('.')[0].rsplit('-', 1)[1]) + trained_model = mx.mod.Module.load(model_name, num_epochs) + trained_model.bind(data_shapes=[('data', input_shape)], label_shapes=None, for_training=False, force_rebind=True) + + sym = trained_model.symbol + arg_params, aux_params = trained_model.get_params() + + # Merging arg and aux parameters + arg_params.update(aux_params) + return sym, arg_params + +def export_model(model, weights, input_shape, input_type, log=False): converter = MxNetToONNXConverter() - onnx_graph = converter.convert_mx2onnx_graph(graph, mx_weights, input_shape, mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype(input_type)], log=log) + + if isinstance(model, basestring) and isinstance(weights, basestring): + print("Converting json and params file to sym and weights") + sym, params = load_module(model, weights, input_shape) + onnx_graph = converter.convert_mx2onnx_graph(sym, params, input_shape, + mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype(input_type)], log=log) + else: + onnx_graph = converter.convert_mx2onnx_graph(model, weights, input_shape, + mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype(input_type)], log=log) onnx_model = helper.make_model(onnx_graph) return onnx_model diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 00381d24c0da..3ebb7a235be2 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -25,7 +25,7 @@ import numpy as np -import sys +import json from onnx import (defs, checker, helper, numpy_helper, mapping, onnx_pb2, ModelProto, GraphProto, NodeProto, AttributeProto, TensorProto) @@ -64,11 +64,11 @@ def convert_layer(node, **kwargs): def convert_weights_to_numpy(weights_dict): return dict([(k.replace("arg:", "").replace("aux:", ""), v.asnumpy()) for k, v in weights_dict.items()]) - def convert_mx2onnx_graph(self, mx_graph, mx_weights, in_shape, in_type, log=False): + def convert_mx2onnx_graph(self, sym, mx_weights, in_shape, in_type, log=False): print("\nconverting weights from MxNet NDArrays to NumPy arrays.\n") weights = MxNetToONNXConverter.convert_weights_to_numpy(mx_weights) - onnx_graph = GraphProto() + mx_graph = json.loads(sym.tojson())["nodes"] initializer = [] all_processed_nodes = [] diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 3b03b4bbe539..67b771c56dde 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -207,8 +207,7 @@ def convert_batchnorm(node, **kwargs): epsilon=eps, momentum=momentum, is_test=1, - spatial=1, - consumed_inputs=(0, 0, 0, 1, 1) + spatial=1 ) return bn_node From aa4e7cb85a65d71974802d357e0ff8580d340548 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 17 Apr 2018 16:04:50 -0700 Subject: [PATCH 04/82] comments added --- .../contrib/onnx/_export/export_helper.py | 41 +++++++++++++++++++ .../contrib/onnx/_export/export_model.py | 41 +++++++++++-------- 2 files changed, 65 insertions(+), 17 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_helper.py b/python/mxnet/contrib/onnx/_export/export_helper.py index 13a83393a912..493680d7ac48 100644 --- a/python/mxnet/contrib/onnx/_export/export_helper.py +++ b/python/mxnet/contrib/onnx/_export/export_helper.py @@ -14,3 +14,44 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +"""export helper functions""" +# coding: utf-8 +import os +import mxnet as mx + +def load_module(json_path, params_path, input_shape): + """Loads the MXNet model file, retrieves symbol and parameters and returns. + + Parameters + ---------- + json_path : str + Path to the json file + params_path : str + Path to the params file + input_shape : + Input shape of the model e.g (1,3,224,224) + + Returns + ------- + sym : MXNet symbol + Model symbol object + + params : params object + Model weights including both arg and aux params. + """ + if not (os.path.isfile(json_path) and os.path.isfile(params_path)): + raise ValueError("Provide valid path to the json and params file") + else: + model_name = json_path.rsplit('.')[0].rsplit('-', 1)[0] + num_epochs = int(params_path.rsplit('.')[0].rsplit('-', 1)[1]) + trained_model = mx.mod.Module.load(model_name, num_epochs) + trained_model.bind(data_shapes=[('data', input_shape)], label_shapes=None, for_training=False, force_rebind=True) + + sym = trained_model.symbol + arg_params, aux_params = trained_model.get_params() + + # Merging arg and aux parameters + params = {} + params.update(arg_params) + params.update(aux_params) + return sym, params diff --git a/python/mxnet/contrib/onnx/_export/export_model.py b/python/mxnet/contrib/onnx/_export/export_model.py index b4ef36461936..69f8ff6d4b84 100644 --- a/python/mxnet/contrib/onnx/_export/export_model.py +++ b/python/mxnet/contrib/onnx/_export/export_model.py @@ -16,7 +16,7 @@ # under the License. # coding: utf-8 - +"""export function""" from __future__ import absolute_import from __future__ import division from __future__ import print_function @@ -24,27 +24,33 @@ from onnx import defs, checker, helper, numpy_helper, mapping from .export_onnx import MxNetToONNXConverter +from .export_helper import load_module -import mxnet as mx import numpy as np -def load_module(json_path, params_path, input_shape): - if not (os.path.isfile(json_path) and os.path.isfile(params_path)): - raise ValueError("Provide valid path to the json and params file") - else: - model_name = json_path.rsplit('.')[0].rsplit('-', 1)[0] - num_epochs = int(params_path.rsplit('.')[0].rsplit('-', 1)[1]) - trained_model = mx.mod.Module.load(model_name, num_epochs) - trained_model.bind(data_shapes=[('data', input_shape)], label_shapes=None, for_training=False, force_rebind=True) - - sym = trained_model.symbol - arg_params, aux_params = trained_model.get_params() +def export_model(model, weights, input_shape, input_type, log=False): + """Exports the MXNet model file, passed as a parameter, into ONNX model. + Accepts both symbol,parameter objects as well as json and params filepaths as input. + Operator support and coverage - https://cwiki.apache.org/confluence/display/MXNET/ONNX - # Merging arg and aux parameters - arg_params.update(aux_params) - return sym, arg_params + Parameters + ---------- + model : str or symbol object + Path to the json file or Symbol object + weights : str or symbol object + Path to the params file or Params object. (Including both arg_params and aux_params) + input_shape : + Input shape of the model e.g (1,3,224,224) + input_type : + Input data type e.g. np.float32 + log : Boolean + If true will print logs of the model conversion -def export_model(model, weights, input_shape, input_type, log=False): + Returns + ------- + onnx_model : onnx ModelProto + Onnx modelproto object + """ converter = MxNetToONNXConverter() if isinstance(model, basestring) and isinstance(weights, basestring): @@ -55,5 +61,6 @@ def export_model(model, weights, input_shape, input_type, log=False): else: onnx_graph = converter.convert_mx2onnx_graph(model, weights, input_shape, mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype(input_type)], log=log) + # Create the model (ModelProto) onnx_model = helper.make_model(onnx_graph) return onnx_model From f9e8cbf5bd28248b80780c875fc0dd0b061aca56 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Tue, 24 Apr 2018 15:24:59 -0700 Subject: [PATCH 05/82] 1. Refactored export module. 2. Refactored test framework to support ONNX backened tests. 2. Added Operator support: - Convolution2D - BatchNorm - Add --- python/mxnet/contrib/onnx/_export/__init__.py | 1 + .../mxnet/contrib/onnx/_export/export_onnx.py | 108 ++++++++++++++++-- .../contrib/onnx/_export/op_translations.py | 34 +++++- tests/python-pytest/onnx/export/backend.py | 45 +++++++- .../onnx/export/onnx_backend_test.py | 36 +++--- 5 files changed, 185 insertions(+), 39 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/__init__.py b/python/mxnet/contrib/onnx/_export/__init__.py index fbf10a13bb8f..a5dc33b3a0fb 100644 --- a/python/mxnet/contrib/onnx/_export/__init__.py +++ b/python/mxnet/contrib/onnx/_export/__init__.py @@ -21,3 +21,4 @@ from . import export_model from . import export_onnx +from . import op_translations diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 3ebb7a235be2..4893002aa069 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -26,12 +26,18 @@ import numpy as np import json +from .... import symbol +from .... import context +from .... import ndarray as nd +from .... import io +from .... import module as mod from onnx import (defs, checker, helper, numpy_helper, mapping, onnx_pb2, ModelProto, GraphProto, NodeProto, AttributeProto, TensorProto) from onnx.helper import make_tensor, make_tensor_value_info + class MxNetToONNXConverter: registry_ = {} input_output_maps_ = {} @@ -59,14 +65,79 @@ def convert_layer(node, **kwargs): convert_fun = MxNetToONNXConverter.registry_[op] return convert_fun(node, **kwargs) + @staticmethod + def forward_pass(inputs, sym, arg_params, aux_params): + """ Do a forward pass based on the sym and params""" + data_names = [graph_input for graph_input in sym.list_inputs() + if graph_input not in arg_params and graph_input not in aux_params] + + data_shapes = [] + dim_added = False; + # Adding extra dimension of batch_size 1 if the batch_size is different for multiple inputs. + for idx, input_name in enumerate(data_names): + batch_size = 1 + if len(inputs) > 1 and len(inputs[idx].shape) < 4 and \ + len(set(x.shape[0] for x in inputs)) != 1: + tuples = ((batch_size,), inputs[idx].shape) + new_shape = sum(tuples, ()) + data_shapes.append((input_name, new_shape)) + dim_added = True + else: + data_shapes.append((input_name, inputs[idx].shape)) + + # create module, passing cpu context + ctx = context.cpu() + test_mod = mod.Module(symbol=sym, data_names=data_names, context=ctx, label_names=None) + test_mod.bind(for_training=False, data_shapes=data_shapes, label_shapes=None) + + # initializing parameters for calculating result of each individual node + if arg_params is None and aux_params is None: + test_mod.init_params() + else: + test_mod.set_params(arg_params=arg_params, aux_params=aux_params) + + data_forward = [] + for idx, input_name in enumerate(data_names): + # slice and pad operator tests needs 1 less dimension in forward pass + # otherwise it will throw an error. + # for squeeze operator, need to retain shape of input as provided + val = inputs[idx] + if dim_added is True: + data_forward.append(nd.array([val])) + else: + data_forward.append(nd.array(val)) + + test_mod.forward(io.DataBatch(data_forward)) + result = test_mod.get_outputs()[0].asnumpy() + if dim_added is True: + return result[0].shape + else: + return result.shape + + @staticmethod + def infer_output_shape(sym, arg, aux, in_shape): + """Infer output shape by doing a forward pass using dummy inputs """ + #create dummy input + inputs = [np.random.randn(*input_shape) for input_shape in in_shape] + return MxNetToONNXConverter.forward_pass(inputs, sym, arg, aux) + # Add transpose? @staticmethod def convert_weights_to_numpy(weights_dict): return dict([(k.replace("arg:", "").replace("aux:", ""), v.asnumpy()) for k, v in weights_dict.items()]) - def convert_mx2onnx_graph(self, sym, mx_weights, in_shape, in_type, log=False): + def convert_mx2onnx_graph(self, sym, arg, aux, in_shape, in_type, log=False): + + # Determine output shape + output_shape = MxNetToONNXConverter.infer_output_shape(sym, arg, aux, in_shape) + print("\nconverting weights from MxNet NDArrays to NumPy arrays.\n") - weights = MxNetToONNXConverter.convert_weights_to_numpy(mx_weights) + params = {} + params.update(arg) + params.update(aux) + weights = MxNetToONNXConverter.convert_weights_to_numpy(params) + + mx_graph = json.loads(sym.tojson())["nodes"] @@ -81,14 +152,29 @@ def convert_mx2onnx_graph(self, sym, mx_weights, in_shape, in_type, log=False): name = node["name"] if log: print("Converting idx: %d, op: %s, name: %s" % (idx, op, name)) - converted = MxNetToONNXConverter.convert_layer( - node, - mx_graph=mx_graph, - weights=weights, - in_shape=in_shape, - in_type=in_type, - proc_nodes=all_processed_nodes, - initializer=initializer + + if op == "null" and name not in arg and name not in aux: + """ Handling graph input """ + converted = MxNetToONNXConverter.convert_layer( + node, + is_input=True, + mx_graph=mx_graph, + weights=weights, + in_shape=in_shape[idx], + in_type=in_type, + proc_nodes=all_processed_nodes, + initializer=initializer) + + else: + converted = MxNetToONNXConverter.convert_layer( + node, + is_input=False, + mx_graph=mx_graph, + weights=weights, + in_shape=in_shape, + in_type=in_type, + proc_nodes=all_processed_nodes, + initializer=initializer ) if isinstance(converted, onnx_pb2.ValueInfoProto): @@ -105,7 +191,7 @@ def convert_mx2onnx_graph(self, sym, mx_weights, in_shape, in_type, log=False): make_tensor_value_info( name=converted.name, elem_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')], - shape=(in_shape[0], -1) + shape=output_shape ) ) if log: diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 67b771c56dde..9bf9a8b71bda 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -41,9 +41,9 @@ def looks_like_weight(name): """Internal helper to figure out if node should be hidden with `hide_weights`. """ - if name.endswith("_weight"): + if name.endswith("_weight") or name == 'W': return True - if name.endswith("_bias"): + if name.endswith("_bias") or name == "B": return True if name.endswith("_beta") or name.endswith("_gamma") or name.endswith("_moving_var") or name.endswith( "_moving_mean"): @@ -54,10 +54,10 @@ def looks_like_weight(name): @mx2onnx.register("null") def convert_weights_and_inputs(node, **kwargs): name = node["name"] - if looks_like_weight(name): + + if kwargs["is_input"] is False: weights = kwargs["weights"] initializer = kwargs["initializer"] - weights = kwargs["weights"] np_arr = weights[name] data_type = mapping.NP_TYPE_TO_TENSOR_TYPE[np_arr.dtype] dims = np.shape(np_arr) @@ -95,12 +95,12 @@ def convert_convolution(node, **kwargs): bias_node = proc_nodes[inputs[2][0]].name attrs = node.get("attrs") - tuple_re = re.compile('\([0-9|,| ]+\)') + tuple_re = re.compile('\([0-9L|,| ]+\)') def parse_helper(attrs_name, alt_value=None): if attrs is None: return alt_value - attrs_str = attrs.get(attrs_name) + attrs_str = str(attrs.get(attrs_name)) if attrs_str is None: return alt_value attrs_match = tuple_re.search(attrs_str) @@ -391,6 +391,28 @@ def convert_elementwise_add(node, **kwargs): return add_node +@mx2onnx.register("broadcast_add") +def covert_broadcast_add(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + a = inputs[0][0] + b = inputs[1][0] + + a_node = proc_nodes[a].name + b_node = proc_nodes[b].name + + add_node = helper.make_node( + "Add", + [a_node, b_node], + [name], + broadcast=1, + name=name, + ) + + return add_node + @mx2onnx.register("_sub") def convert_elementwise_sub(node, **kwargs): diff --git a/tests/python-pytest/onnx/export/backend.py b/tests/python-pytest/onnx/export/backend.py index 91287c30afc4..4b89f1909f66 100644 --- a/tests/python-pytest/onnx/export/backend.py +++ b/tests/python-pytest/onnx/export/backend.py @@ -18,10 +18,11 @@ # coding: utf-8 """backend wrapper for onnx test infrastructure""" import mxnet as mx +import numpy as np from mxnet.contrib.onnx._import.import_onnx import GraphProto from mxnet.contrib.onnx._export.export_onnx import MxNetToONNXConverter try: - from onnx import helper, TensorProto + from onnx import helper, TensorProto, mapping from onnx.backend.base import Backend except ImportError: raise ImportError("Onnx and protobuf need to be installed") @@ -76,7 +77,35 @@ def make_graph(node, inputs): return graph_proto @staticmethod - def perform_import_export(graph_proto): + def get_graph_metadata(graph): + """ + Get metadata from a given onnx graph. + """ + _params = set() + for tensor_vals in graph.initializer: + _params.add(tensor_vals.name) + + input_data = [] + for graph_input in graph.input: + shape = [] + if graph_input.name not in _params: + for val in graph_input.type.tensor_type.shape.dim: + shape.append(val.dim_value) + input_data.append((graph_input.name, tuple(shape))) + + output_data = [] + for graph_out in graph.output: + shape = [] + for val in graph_out.type.tensor_type.shape.dim: + shape.append(val.dim_value) + output_data.append((graph_out.name, tuple(shape))) + metadata = {'input_tensor_data' : input_data, + 'output_tensor_data' : output_data + } + return metadata + + @staticmethod + def perform_import_export(graph_proto, input_shape): """ Import ONNX model to mxnet model and then export to ONNX model and then import it back to mxnet for verifying the result""" graph = GraphProto() @@ -88,7 +117,7 @@ def perform_import_export(graph_proto): params.update(aux_params) # exporting to onnx graph proto format converter = MxNetToONNXConverter() - graph_proto = converter.convert_mx2onnx_graph(sym, params, in_shape=None, in_type=None) + graph_proto = converter.convert_mx2onnx_graph(sym, arg_params, aux_params, in_shape=input_shape, in_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')]) # importing back to MXNET for verfiying result. sym, arg_params, aux_params = graph.from_onnx(graph_proto) @@ -114,7 +143,9 @@ def run_node(cls, node, inputs, device='CPU'): params : numpy array result obtained after running the operator """ - sym, arg_params, aux_params = MXNetBackend.perform_import_export(MXNetBackend.make_graph(node, inputs)) + + input_shape = [graph_input.shape for graph_input in inputs] + sym, arg_params, aux_params = MXNetBackend.perform_import_export(MXNetBackend.make_graph(node, inputs), input_shape) data_names = [graph_input for graph_input in sym.list_inputs() if graph_input not in arg_params and graph_input not in aux_params] @@ -189,7 +220,11 @@ def prepare(cls, model, device='CPU', **kwargs): used to run inference on the input model and return the result for comparison. """ graph = GraphProto() - sym, arg_params, aux_params = MXNetBackend.perform_import_export(model.graph) + + metadata = MXNetBackend.get_graph_metadata(model.graph) + input_data = metadata['input_tensor_data'] + input_shape = [ data[1] for data in input_data] + sym, arg_params, aux_params = MXNetBackend.perform_import_export(model.graph, input_shape) return MXNetBackendRep(sym, arg_params, aux_params, device) @classmethod diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index 4bcd82c2bef1..8932c7bea8bc 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -35,25 +35,26 @@ BACKEND_TEST = onnx.backend.test.BackendTest(mxnet_backend, __name__) IMPLEMENTED_OPERATORS_TEST = [ - 'test_operator_maxpool', + 'test_add', + 'test_conv', ] BASIC_MODEL_TESTS = [ - 'test_AvgPool2D', + # 'test_AvgPool2D', 'test_BatchNorm', - 'test_ConstantPad2d' - 'test_Conv2d', - 'test_ELU', - 'test_LeakyReLU', - 'test_MaxPool', - 'test_PReLU', - 'test_ReLU', - 'test_Sigmoid', - 'test_Softmax', - 'test_softmax_functional', - 'test_softmax_lastdim', - 'test_Tanh' + # 'test_ConstantPad2d' + # 'test_Conv2d', + # 'test_ELU', + # 'test_LeakyReLU', + # 'test_MaxPool', + # 'test_PReLU', + # 'test_ReLU', + # 'test_Sigmoid', + # 'test_Softmax', + # 'test_softmax_functional', + # 'test_softmax_lastdim', + # 'test_Tanh' ] STANDARD_MODEL = [ @@ -71,11 +72,12 @@ for op_test in IMPLEMENTED_OPERATORS_TEST: BACKEND_TEST.include(op_test) +for basic_model_test in BASIC_MODEL_TESTS: + BACKEND_TEST.include(basic_model_test) + # for std_model_test in STANDARD_MODEL: # BACKEND_TEST.include(std_model_test) -# -# for basic_model_test in BASIC_MODEL_TESTS: -# BACKEND_TEST.include(basic_model_test) + # import all test cases at global scope to make them visible to python.unittest globals().update(BACKEND_TEST.enable_report().test_cases) From de3632d92f90a639889c07f3b48cb2631c44c485 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Tue, 24 Apr 2018 17:56:36 -0700 Subject: [PATCH 06/82] Added Arithmetic operators: - Add, Sub, Mul, Div, Sum --- .../contrib/onnx/_export/op_translations.py | 227 +++++++++++++++++- .../onnx/export/onnx_backend_test.py | 11 +- 2 files changed, 226 insertions(+), 12 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 9bf9a8b71bda..d6e522e5fecd 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -213,6 +213,39 @@ def convert_batchnorm(node, **kwargs): return bn_node +@mx2onnx.register("tanh") +def convert_tanh(node, **kwargs): + name = node["name"] + inputs = node["inputs"] + input_node_idx = inputs[0][0] + proc_nodes = kwargs["proc_nodes"] + input_node = proc_nodes[input_node_idx].output[0] + + node = helper.make_node( + 'Tanh', + [input_node], + [name], + name=name + ) + return node + + +@mx2onnx.register("relu") +def convert_relu(node, **kwargs): + name = node["name"] + inputs = node["inputs"] + input_node_idx = inputs[0][0] + proc_nodes = kwargs["proc_nodes"] + input_node = proc_nodes[input_node_idx].output[0] + + node = helper.make_node( + 'Relu', + [input_node], + [name], + name=name + ) + return node + @mx2onnx.register("Activation") def convert_activation(node, **kwargs): name = node["name"] @@ -226,7 +259,6 @@ def convert_activation(node, **kwargs): input_node = proc_nodes[input_node_idx].output[0] # Creating a dictionary here, but if this titlecase pattern - # is consistent for other activations, this can be changed to # mxnet_name.title() act_types = { "tanh": "Tanh", @@ -368,7 +400,7 @@ def convert_flatten(node, **kwargs): def convert_mul_scalar(node, **kwargs): raise NotImplementedError - +# Arithmetic Operations @mx2onnx.register("elemwise_add") def convert_elementwise_add(node, **kwargs): name = node["name"] @@ -391,6 +423,7 @@ def convert_elementwise_add(node, **kwargs): return add_node + @mx2onnx.register("broadcast_add") def covert_broadcast_add(node, **kwargs): name = node["name"] @@ -414,24 +447,196 @@ def covert_broadcast_add(node, **kwargs): return add_node -@mx2onnx.register("_sub") +@mx2onnx.register("elemwise_sub") def convert_elementwise_sub(node, **kwargs): - raise NotImplementedError + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + a = inputs[0][0] + b = inputs[1][0] + + a_node = proc_nodes[a].name + b_node = proc_nodes[b].name + + sub_node = helper.make_node( + "Sub", + [a_node, b_node], + [name], + name=name, + ) + + return sub_node + +@mx2onnx.register("broadcast_sub") +def covert_broadcast_sub(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + a = inputs[0][0] + b = inputs[1][0] + + a_node = proc_nodes[a].name + b_node = proc_nodes[b].name + + sub_node = helper.make_node( + "Sub", + [a_node, b_node], + [name], + broadcast=1, + name=name, + ) + + return sub_node + +@mx2onnx.register("elemwise_mul") +def convert_mul(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + a = inputs[0][0] + b = inputs[1][0] + + a_node = proc_nodes[a].name + b_node = proc_nodes[b].name + + mul_node = helper.make_node( + "Mul", + [a_node, b_node], + [name], + name=name, + ) + + return mul_node + +@mx2onnx.register("broadcast_mul") +def convert_mul(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + a = inputs[0][0] + b = inputs[1][0] + + a_node = proc_nodes[a].name + b_node = proc_nodes[b].name + + mul_node = helper.make_node( + "Mul", + [a_node, b_node], + [name], + name=name, + broadcast=1 + ) + + return mul_node + + +@mx2onnx.register("elemwise_div") +def convert_mul(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + a = inputs[0][0] + b = inputs[1][0] + + a_node = proc_nodes[a].name + b_node = proc_nodes[b].name + + div_node = helper.make_node( + "Div", + [a_node, b_node], + [name], + name=name, + ) + return div_node + + +@mx2onnx.register("broadcast_div") +def convert_div(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + a = inputs[0][0] + b = inputs[1][0] + + a_node = proc_nodes[a].name + b_node = proc_nodes[b].name + + div_node = helper.make_node( + "Div", + [a_node, b_node], + [name], + name=name, + broadcast=1 + ) + + return div_node + + +@mx2onnx.register("negative") +def convert_negative(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + a = inputs[0][0] + + a_node = proc_nodes[a].name + + neg_node = helper.make_node( + "Neg", + [a_node], + [name], + name=name, + ) + + return neg_node @mx2onnx.register("abs") def convert_abs(node, **kwargs): - raise NotImplementedError + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + a = inputs[0][0] -@mx2onnx.register("_mul") -def convert_mul(node, proc_nodes): - raise NotImplementedError + a_node = proc_nodes[a].name + + abs_node = helper.make_node( + "Abs", + [a_node], + [name], + name=name, + ) + + return abs_node + +@mx2onnx.register("add_n") +def convert_addn(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + input_list = [] + for idx, input_val in enumerate(inputs): + input_list.append(proc_nodes[idx].name) + + sum_node = helper.make_node( + "Sum", + input_list, + [name], + name=name, + ) + + return sum_node -@mx2onnx.register("_div") -def convert_div(node, **kwargs): - raise NotImplementedError @mx2onnx.register("log") diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index 8932c7bea8bc..80114f1761a7 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -35,8 +35,17 @@ BACKEND_TEST = onnx.backend.test.BackendTest(mxnet_backend, __name__) IMPLEMENTED_OPERATORS_TEST = [ + #Arithmetic Operators 'test_add', - 'test_conv', + 'test_sub', + 'test_mul', + 'test_div', + 'test_neg', + 'test_abs', + 'test_sum', + + # Hyperbolic functions + # 'test_tanh', ] From 46931d31152da5ce46d49a85b29501d06d00cad4 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Wed, 25 Apr 2018 15:31:41 -0700 Subject: [PATCH 07/82] Added operator support: - sigmoid, relu, pad( constant, edge, reflect), tanh - enabled corresponding ONNX backend tests. --- .../contrib/onnx/_export/op_translations.py | 89 ++++++++++++++++++- .../onnx/export/onnx_backend_test.py | 18 ++-- 2 files changed, 98 insertions(+), 9 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index d6e522e5fecd..a609fca67686 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -219,7 +219,7 @@ def convert_tanh(node, **kwargs): inputs = node["inputs"] input_node_idx = inputs[0][0] proc_nodes = kwargs["proc_nodes"] - input_node = proc_nodes[input_node_idx].output[0] + input_node = proc_nodes[input_node_idx].name node = helper.make_node( 'Tanh', @@ -229,6 +229,22 @@ def convert_tanh(node, **kwargs): ) return node +#Basic neural network functions +@mx2onnx.register("sigmoid") +def convert_sigmoid(node, **kwargs): + name = node["name"] + inputs = node["inputs"] + input_node_idx = inputs[0][0] + proc_nodes = kwargs["proc_nodes"] + input_node = proc_nodes[input_node_idx].name + + node = helper.make_node( + 'Sigmoid', + [input_node], + [name], + name=name + ) + return node @mx2onnx.register("relu") def convert_relu(node, **kwargs): @@ -236,7 +252,7 @@ def convert_relu(node, **kwargs): inputs = node["inputs"] input_node_idx = inputs[0][0] proc_nodes = kwargs["proc_nodes"] - input_node = proc_nodes[input_node_idx].output[0] + input_node = proc_nodes[input_node_idx].name node = helper.make_node( 'Relu', @@ -280,6 +296,73 @@ def convert_activation(node, **kwargs): return node +def transform_padding(pad_width): + num_pad_values = len(pad_width) + pad_values_middle_index = int(num_pad_values/2) + + onnx_pad_width = [0]*num_pad_values + + start_index = 0 + end_index = int(num_pad_values/2) + for idx in range(0,num_pad_values): + if idx%2 == 0: + onnx_pad_width[start_index] = pad_width[idx] + start_index += 1 + else: + onnx_pad_width[end_index] = pad_width[idx] + end_index += 1 + + return onnx_pad_width + + +def convert_string_to_list(string_val): + list_string = list(string_val) + result_string = [] + + for val in list_string: + if val not in [',', 'L', ' ', '(', ')']: + result_string.append(int(val)) + + return result_string + + +@mx2onnx.register("Pad") +def convert_pad(node, **kwargs): + name = node["name"] + attrs = node["attrs"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + input_node_idx = inputs[0][0] + input_node = proc_nodes[input_node_idx].name + + mxnet_pad_width = convert_string_to_list(attrs.get("pad_width")) + onnx_pad_width = transform_padding(mxnet_pad_width) + + pad_mode = attrs.get("mode") + + if pad_mode == "constant": + pad_value = float(attrs.get("constant_value")) + node = helper.make_node( + 'Pad', + inputs=[input_node], + outputs=[name], + mode='constant', + value=pad_value, + pads=onnx_pad_width, + name=name + ) + else: + node = helper.make_node( + 'Pad', + inputs=[input_node], + outputs=[name], + mode=pad_mode, + pads=onnx_pad_width, + name=name + ) + + return node + @mx2onnx.register("Pooling") def convert_pooling(node, **kwargs): @@ -625,7 +708,7 @@ def convert_addn(node, **kwargs): input_list = [] for idx, input_val in enumerate(inputs): - input_list.append(proc_nodes[idx].name) + input_list.append(proc_nodes[input_val[0]].name) sum_node = helper.make_node( "Sum", diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index 80114f1761a7..33082da54caf 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -36,7 +36,7 @@ IMPLEMENTED_OPERATORS_TEST = [ #Arithmetic Operators - 'test_add', + 'test_add' 'test_sub', 'test_mul', 'test_div', @@ -45,8 +45,14 @@ 'test_sum', # Hyperbolic functions - # 'test_tanh', - + 'test_tanh', + + # Basic neural network functions + 'test_sigmoid', + 'test_relu', + 'test_constant_pad', + 'test_edge_pad', + 'test_reflect_pad', ] BASIC_MODEL_TESTS = [ @@ -58,12 +64,12 @@ # 'test_LeakyReLU', # 'test_MaxPool', # 'test_PReLU', - # 'test_ReLU', - # 'test_Sigmoid', + 'test_ReLU', + 'test_Sigmoid', # 'test_Softmax', # 'test_softmax_functional', # 'test_softmax_lastdim', - # 'test_Tanh' + 'test_Tanh' ] STANDARD_MODEL = [ From ca74b02879ea1aa59777ae49f9c74a4c8fd09579 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Mon, 30 Apr 2018 15:44:09 -0700 Subject: [PATCH 08/82] Enabled ONNX tests: test_conv, test_basic_conv Added Operators : Ceil, Floor --- .../contrib/onnx/_export/op_translations.py | 35 ++++++++++++++++++- .../onnx/export/onnx_backend_test.py | 7 ++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index a609fca67686..69d7785d3c6e 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -260,6 +260,7 @@ def convert_relu(node, **kwargs): [name], name=name ) + return node @mx2onnx.register("Activation") @@ -716,10 +717,42 @@ def convert_addn(node, **kwargs): [name], name=name, ) - return sum_node + #Rounding +@mx2onnx.register("ceil") +def convert_floor(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + a = inputs[0][0] + a_node = proc_nodes[a].name + + node = helper.make_node( + "Ceil", + [a_node], + [name], + name=name, + ) + return node + +@mx2onnx.register("floor") +def convert_floor(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + a = inputs[0][0] + a_node = proc_nodes[a].name + node = helper.make_node( + "Floor", + [a_node], + [name], + name=name, + ) + return node @mx2onnx.register("log") diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index 33082da54caf..d065d15c986c 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -47,12 +47,19 @@ # Hyperbolic functions 'test_tanh', + #Rounding + 'test_ceil', + 'test_floor', + # Basic neural network functions 'test_sigmoid', 'test_relu', + # 'test_elu', 'test_constant_pad', 'test_edge_pad', 'test_reflect_pad', + 'test_conv', + 'test_basic_conv', ] BASIC_MODEL_TESTS = [ From f653baf0617ddbc9459f996921754237104a082f Mon Sep 17 00:00:00 2001 From: spidyDev Date: Tue, 1 May 2018 12:28:58 -0700 Subject: [PATCH 09/82] Added support for: MaxPool, AvgPool, GlobalMaxPool, GlobalAvgPool, matmul --- .../contrib/onnx/_export/op_translations.py | 34 +++++++++++++++++-- tests/python-pytest/onnx/export/backend.py | 2 +- .../onnx/export/onnx_backend_test.py | 8 +++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 69d7785d3c6e..f16828704700 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -365,6 +365,36 @@ def convert_pad(node, **kwargs): return node +@mx2onnx.register("_linalg_gemm2") +def convert_linalg_gemm2(node, **kwargs): + proc_nodes = kwargs["proc_nodes"] + node_inputs = node["inputs"] + name = node["name"] + input_a_idx = node_inputs[0][0] + input_node_a = proc_nodes[input_a_idx].name + + input_b_idx = node_inputs[1][0] + input_node_b = proc_nodes[input_b_idx].name + + if "attrs" in node: + attrs = node["attrs"] + alpha = float(attrs.get("alpha")) + else: + alpha = 1.0 + + if alpha == 1.0: + node = helper.make_node( + 'MatMul', + inputs=[input_node_a, input_node_b], + outputs=[name], + name=name + ) + else: + raise AttributeError("TODO: Add support for alpha multiplication") + + return node + + @mx2onnx.register("Pooling") def convert_pooling(node, **kwargs): proc_nodes = kwargs["proc_nodes"] @@ -383,7 +413,7 @@ def convert_pooling(node, **kwargs): if stride: node = helper.make_node( pool_types[pool_type], - [input_node.output[0]], # input + [input_node.name], # input [name], # dilations = [0, 0], kernel_shape=kernel, @@ -394,7 +424,7 @@ def convert_pooling(node, **kwargs): else: node = helper.make_node( global_pool_types[pool_type], - [input_node.output[0]], # input + [input_node.name], # input [name], name=name ) diff --git a/tests/python-pytest/onnx/export/backend.py b/tests/python-pytest/onnx/export/backend.py index 4b89f1909f66..035be8b1907c 100644 --- a/tests/python-pytest/onnx/export/backend.py +++ b/tests/python-pytest/onnx/export/backend.py @@ -154,7 +154,7 @@ def run_node(cls, node, inputs, device='CPU'): 'ReduceProd', 'ReduceSum', 'Slice', 'Pad', 'Squeeze', 'Upsample', 'Reshape', 'Conv', 'Concat', 'Softmax', 'Flatten', 'Transpose', - 'GlobalAveragePool', 'GlobalMaxPool']) + 'GlobalAveragePool', 'GlobalMaxPool', 'MaxPool']) # Adding extra dimension of batch_size 1 if the batch_size is different for multiple inputs. for idx, input_name in enumerate(data_names): diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index d065d15c986c..fb15d6e9df3b 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -58,6 +58,13 @@ 'test_constant_pad', 'test_edge_pad', 'test_reflect_pad', + 'test_matmul', + 'test_maxpool_2d_default', + 'test_maxpool_2d_pads', + 'test_maxpool_2d_strides', + 'test_maxpool_3d_default', + 'test_globalmaxpool', + 'test_globalaveragepool', 'test_conv', 'test_basic_conv', ] @@ -91,6 +98,7 @@ 'test_vgg19' ] + for op_test in IMPLEMENTED_OPERATORS_TEST: BACKEND_TEST.include(op_test) From 2cf7be063e23cf0de94abed910b9f154b5e7008b Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 1 May 2018 15:07:10 -0700 Subject: [PATCH 10/82] adding more operators --- .../contrib/onnx/_export/op_translations.py | 195 +++++++++++++++++- .../onnx/export/onnx_backend_test.py | 30 ++- 2 files changed, 217 insertions(+), 8 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index f16828704700..5ae712d41306 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -434,14 +434,46 @@ def convert_pooling(node, **kwargs): @mx2onnx.register("exp") def convert_exp(node, **kwargs): - raise NotImplementedError + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + a = inputs[0][0] + a_node = proc_nodes[a].name + + node = helper.make_node( + "Exp", + [a_node], + [name], + name=name, + ) + return node + + +@mx2onnx.register("softmax") +def convert_softmax(node, **kwargs): + input_idx = node["inputs"][0][0] + proc_nodes = kwargs["proc_nodes"] + input_node = proc_nodes[input_idx].name +# input_names = [proc_nodes[i[0]].name for i in inputs] + name = node["name"] + axis = int(node.get("attrs", {}).get("axis", -1)) + + softmax_node = helper.make_node( + "Softmax", + input_node, + [name], + axis=axis, + name=name + ) + + return softmax_node # There's also mx.sym.softmax(), which doesn't do cross-entropy loss, # just softmax for inference - hence the name convert_softmax_output. @mx2onnx.register("SoftmaxOutput") def convert_softmax_output(node, **kwargs): - # print("\nIn convert_softmax_output") inputs = node["inputs"] input1_idx = inputs[0][0] proc_nodes = kwargs["proc_nodes"] @@ -465,7 +497,7 @@ def convert_concat(node, **kwargs): inputs = node["inputs"] proc_nodes = kwargs["proc_nodes"] input_names = [proc_nodes[i[0]].name for i in inputs] - axis = int(node.get("attrs", {}).get("axis", 1)) + axis = int(node.get("attrs", {}).get("dim", 1)) concat_node = helper.make_node( "Concat", input_names, @@ -476,6 +508,34 @@ def convert_concat(node, **kwargs): return concat_node +@mx2onnx.register("transpose") +def convert_transpose(node, **kwargs): + name = node["name"] + input_idx = node["inputs"][0][0] + proc_nodes = kwargs["proc_nodes"] + input_node = proc_nodes[input_idx].name + axes = node.get("attrs", {}).get("axes", ()) + if axes: + axes = tuple(map(int, re.findall(r'\d+', axes))) + + transpose_node = helper.make_node( + "Transpose", + [input_node], + [name], + perm=axes, + name=name + ) + else: + transpose_node = helper.make_node( + "Transpose", + [input_node], + [name], + name=name + ) + + return transpose_node + + @mx2onnx.register("Dropout") def convert_dropout(node, **kwargs): name = node["name"] @@ -785,9 +845,119 @@ def convert_floor(node, **kwargs): return node +@mx2onnx.register("Cast") +def convert_cast(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + dtype = node["attrs"]["dtype"] + + a = inputs[0][0] + a_node = proc_nodes[a].name + + node = helper.make_node( + "Cast", + [a_node], + [name], + to=dtype, + name=name, + ) + return node + + +@mx2onnx.register("slice_axis") +def convert_slice_axis(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + axes = int(node["attrs"]["axis"]) + starts = int(node["attrs"]["begin"]) + ends = int(node["attrs"]["end"]) + + a = inputs[0][0] + a_node = proc_nodes[a].name + + node = helper.make_node( + "Slice", + [a_node], + [name], + axes=[axes], + starts=[starts], + ends=[ends], + name=name, + ) + return node + + +# SliceChannel/split operators will be mapped to onnx's squeeze and split operator. +# [TODO] Address split with squeeze case +@mx2onnx.register("SliceChannel") +def convert_slice_channel(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + num_outputs = int(node.get("attrs", {})["num_outputs"]) + axis = int(node.get("attrs", {}).get("axis", 1)) + squeeze_axis = int(node.get("attrs", {}).get("squeeze_axis", 0)) + + a = inputs[0][0] + a_node = proc_nodes[a].name + + if num_outputs==1 and squeeze_axis==1: + node = helper.make_node( + "Squeeze", + [a_node], + [name], + axes=[axis], + name=name, + ) + else: + node = helper.make_node( + "Split", + [a_node], + [name], + axis=axis, + split=[num_outputs], + name=name, + ) + + return node + + @mx2onnx.register("log") def convert_log(node, **kwargs): - raise NotImplementedError + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + a = inputs[0][0] + a_node = proc_nodes[a].name + + node = helper.make_node( + "Log", + [a_node], + [name], + name=name, + ) + return node + + +@mx2onnx.register("reciprocal") +def convert_reciprocal(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + a = inputs[0][0] + a_node = proc_nodes[a].name + + node = helper.make_node( + "Reciprocal", + [a_node], + [name], + name=name, + ) + return node @mx2onnx.register("max") @@ -810,11 +980,24 @@ def convert_minimum(node, **kwargs): raise NotImplementedError -@mx2onnx.register("_power") +@mx2onnx.register("pow") def convert_power(node, **kwargs): raise NotImplementedError @mx2onnx.register("sqrt") def convert_sqrt(node, **kwargs): - raise NotImplementedError + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + a = inputs[0][0] + a_node = proc_nodes[a].name + + node = helper.make_node( + "Sqrt", + [a_node], + [name], + name=name, + ) + return node diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index fb15d6e9df3b..a8172f3459de 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -51,6 +51,9 @@ 'test_ceil', 'test_floor', + ## Joining and spliting + 'test_concat', + # Basic neural network functions 'test_sigmoid', 'test_relu', @@ -67,6 +70,29 @@ 'test_globalaveragepool', 'test_conv', 'test_basic_conv', + 'test_softmax_example', + 'test_softmax_large_number', + 'test_softmax_axis_2', + #'test_conv', + #'test_basic_conv', + + #Changing shape and type. + 'test_cast', + 'test_slice_cpu', + 'test_default_axes', #make PR against onnx to fix the test name(grep-able) + 'test_slice_neg', + 'test_transpose', + 'test_squeeze_', + 'test_flatten_default', + + #Powers + 'test_reciprocal', + 'test_sqrt', + 'test_log_', + 'test_exp', + + #pytorch operator tests + 'test_operator_exp', ] BASIC_MODEL_TESTS = [ @@ -102,8 +128,8 @@ for op_test in IMPLEMENTED_OPERATORS_TEST: BACKEND_TEST.include(op_test) -for basic_model_test in BASIC_MODEL_TESTS: - BACKEND_TEST.include(basic_model_test) +# for basic_model_test in BASIC_MODEL_TESTS: +# BACKEND_TEST.include(basic_model_test) # for std_model_test in STANDARD_MODEL: # BACKEND_TEST.include(std_model_test) From 8ecf9ddbfc2a38a4fd48c2c3f53fa7d173cfd31e Mon Sep 17 00:00:00 2001 From: spidyDev Date: Tue, 1 May 2018 21:42:46 -0700 Subject: [PATCH 11/82] Added Operator support: ArgMax, ArgMin, maximum, minimum --- .../mxnet/contrib/onnx/_export/export_onnx.py | 4 +- .../contrib/onnx/_export/op_translations.py | 114 ++++++++++++++---- .../onnx/export/onnx_backend_test.py | 6 + 3 files changed, 102 insertions(+), 22 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 4893002aa069..aefa160b2a94 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -147,6 +147,7 @@ def convert_mx2onnx_graph(self, sym, arg, aux, in_shape, in_type, log=False): onnx_processed_inputs = [] onnx_processed_outputs = [] + graph_input_idx=0 for idx, node in enumerate(mx_graph): op = node["op"] name = node["name"] @@ -160,10 +161,11 @@ def convert_mx2onnx_graph(self, sym, arg, aux, in_shape, in_type, log=False): is_input=True, mx_graph=mx_graph, weights=weights, - in_shape=in_shape[idx], + in_shape=in_shape[graph_input_idx], in_type=in_type, proc_nodes=all_processed_nodes, initializer=initializer) + graph_input_idx += 1 else: converted = MxNetToONNXConverter.convert_layer( diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 5ae712d41306..1cb3c10e048b 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -342,7 +342,8 @@ def convert_pad(node, **kwargs): pad_mode = attrs.get("mode") if pad_mode == "constant": - pad_value = float(attrs.get("constant_value")) + pad_value = float(attrs.get("constant_value")) \ + if "constant_value" in attrs else 0.0 node = helper.make_node( 'Pad', inputs=[input_node], @@ -574,6 +575,97 @@ def convert_flatten(node, **kwargs): def convert_mul_scalar(node, **kwargs): raise NotImplementedError + +# Sorting and Searching +@mx2onnx.register("argmax") +def convert_argmax(node, **kwargs): + proc_nodes = kwargs["proc_nodes"] + node_inputs = node["inputs"] + + input_node_idx = node_inputs[0][0] + input_node = proc_nodes[input_node_idx].name + name = node["name"] + attrs = node["attrs"] + + axis = int(attrs.get("axis")) + keepdims = int(attrs.get("keepdims")) if "keepdims" in attrs else 1 + + node = helper.make_node( + 'ArgMax', + inputs=[input_node], + axis=axis, + keepdims=keepdims, + outputs=[name], + name=name + ) + return node + +@mx2onnx.register("argmin") +def convert_argmin(node, **kwargs): + proc_nodes = kwargs["proc_nodes"] + node_inputs = node["inputs"] + + input_node_idx = node_inputs[0][0] + input_node = proc_nodes[input_node_idx].name + name = node["name"] + attrs = node["attrs"] + + axis = int(attrs.get("axis")) + keepdims = int(attrs.get("keepdims")) if "keepdims" in attrs else 1 + + node = helper.make_node( + 'ArgMin', + inputs=[input_node], + axis=axis, + keepdims=keepdims, + outputs=[name], + name=name + ) + return node + +@mx2onnx.register("_maximum") +def convert_max(node, **kwargs): + proc_nodes = kwargs["proc_nodes"] + node_inputs = node["inputs"] + + input_node_list = [] + for node_input in node_inputs: + input_node_list.append(proc_nodes[node_input[0]].name) + + name = node["name"] + + node = helper.make_node( + 'Max', + inputs=input_node_list, + outputs=[name], + name=name, + ) + + return node + + +@mx2onnx.register("_minimum") +def convert_min(node, **kwargs): + proc_nodes = kwargs["proc_nodes"] + node_inputs = node["inputs"] + + input_node_list = [] + for node_input in node_inputs: + input_node_list.append(proc_nodes[node_input[0]].name) + + name = node["name"] + + node = helper.make_node( + 'Min', + inputs=input_node_list, + outputs=[name], + name=name, + ) + + return node + + + # Arithmetic Operations @mx2onnx.register("elemwise_add") def convert_elementwise_add(node, **kwargs): @@ -960,26 +1052,6 @@ def convert_reciprocal(node, **kwargs): return node -@mx2onnx.register("max") -def convert_max(node, **kwargs): - raise NotImplementedError - - -@mx2onnx.register("_maximum") -def convert_maximum(node, **kwargs): - raise NotImplementedError - - -@mx2onnx.register("min") -def convert_min(node, **kwargs): - raise NotImplementedError - - -@mx2onnx.register("_minimum") -def convert_minimum(node, **kwargs): - raise NotImplementedError - - @mx2onnx.register("pow") def convert_power(node, **kwargs): raise NotImplementedError diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index a8172f3459de..73f3a54e1072 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -76,6 +76,12 @@ #'test_conv', #'test_basic_conv', + # Sorting and Searching + 'test_argmax', + 'test_argmin', + 'test_max_', + 'test_min', + #Changing shape and type. 'test_cast', 'test_slice_cpu', From af731730765d061fb5c7242838d5bc908222b18a Mon Sep 17 00:00:00 2001 From: spidyDev Date: Tue, 1 May 2018 21:48:41 -0700 Subject: [PATCH 12/82] Enabled more BASIC_MODEL tests --- .../python-pytest/onnx/export/onnx_backend_test.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index 73f3a54e1072..26ba5ff1db75 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -102,9 +102,9 @@ ] BASIC_MODEL_TESTS = [ - # 'test_AvgPool2D', + 'test_AvgPool2D', 'test_BatchNorm', - # 'test_ConstantPad2d' + 'test_ConstantPad2d', # 'test_Conv2d', # 'test_ELU', # 'test_LeakyReLU', @@ -112,9 +112,9 @@ # 'test_PReLU', 'test_ReLU', 'test_Sigmoid', - # 'test_Softmax', - # 'test_softmax_functional', - # 'test_softmax_lastdim', + 'test_Softmax', + 'test_softmax_functional', + 'test_softmax_lastdim', 'test_Tanh' ] @@ -134,8 +134,8 @@ for op_test in IMPLEMENTED_OPERATORS_TEST: BACKEND_TEST.include(op_test) -# for basic_model_test in BASIC_MODEL_TESTS: -# BACKEND_TEST.include(basic_model_test) +for basic_model_test in BASIC_MODEL_TESTS: + BACKEND_TEST.include(basic_model_test) # for std_model_test in STANDARD_MODEL: # BACKEND_TEST.include(std_model_test) From e851cf3c90842945b6f1b2d2b103bcde0e33133a Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Wed, 2 May 2018 16:36:55 -0700 Subject: [PATCH 13/82] Added power operator tests --- .../mxnet/contrib/onnx/_export/export_onnx.py | 21 ++++++--- .../contrib/onnx/_export/op_translations.py | 43 ++++++++++++++++++- .../onnx/export/onnx_backend_test.py | 3 ++ 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index aefa160b2a94..46605dda8f7c 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -189,13 +189,22 @@ def convert_mx2onnx_graph(self, sym, arg, aux, in_shape, in_type, log=False): onnx_processed_nodes.append(converted) else: onnx_processed_nodes.append(converted) - onnx_processed_outputs.append( - make_tensor_value_info( - name=converted.name, - elem_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')], - shape=output_shape + if not converted.name: + onnx_processed_outputs.append( + make_tensor_value_info( + name=converted.output[0], + elem_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')], + shape=output_shape + ) + ) + else: + onnx_processed_outputs.append( + make_tensor_value_info( + name=converted.name, + elem_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')], + shape=output_shape + ) ) - ) if log: print("Output node is: %s" % converted.name) elif isinstance(converted, onnx_pb2.TensorProto): diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 1cb3c10e048b..342c71d8dbb5 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -1052,9 +1052,48 @@ def convert_reciprocal(node, **kwargs): return node -@mx2onnx.register("pow") +@mx2onnx.register("_power") def convert_power(node, **kwargs): - raise NotImplementedError + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + a = inputs[0][0] + b = inputs[1][0] + + a_node = proc_nodes[a].name + b_node = proc_nodes[b].name + + node = helper.make_node( + "Pow", + [a_node, b_node], + [name], + name=None + ) + return node + +#[TODO] broadcast_power with axis +@mx2onnx.register("broadcast_power") +def convert_power(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + a = inputs[0][0] + b = inputs[1][0] + + a_node = proc_nodes[a].name + b_node = proc_nodes[b].name + + node = helper.make_node( + "Pow", + [a_node, b_node], + outputs=[name], + name=name, + axis=1, + broadcast=1, + ) + return node @mx2onnx.register("sqrt") diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index 26ba5ff1db75..97a0cfebd227 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -94,6 +94,9 @@ #Powers 'test_reciprocal', 'test_sqrt', + 'test_pow_example', + 'test_pow_cpu', + 'test_pow_bcast_cpu', 'test_log_', 'test_exp', From 54edda13f02806f79ce629e3db93dd930f09a24b Mon Sep 17 00:00:00 2001 From: spidyDev Date: Mon, 7 May 2018 16:09:45 -0700 Subject: [PATCH 14/82] Added support for reshape. ONNX only supports 0, -1 special values. Added only for these. Fixed logic error with convert_string_to_list() --- .../contrib/onnx/_export/op_translations.py | 41 ++++++++++++++++--- .../onnx/export/onnx_backend_test.py | 2 +- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 342c71d8dbb5..e9b63bf8ae5e 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -317,14 +317,18 @@ def transform_padding(pad_width): def convert_string_to_list(string_val): - list_string = list(string_val) - result_string = [] + result_list = [] + list_string = string_val.split(',') for val in list_string: - if val not in [',', 'L', ' ', '(', ')']: - result_string.append(int(val)) + val = str(val.strip()) + val = val.replace("(", "") + val = val.replace(")", "") + val = val.replace("L", "") + if val is not "": + result_list.append(int(val)) - return result_string + return result_list @mx2onnx.register("Pad") @@ -936,6 +940,33 @@ def convert_floor(node, **kwargs): ) return node +#Changing shape and type. +@mx2onnx.register("Reshape") +def convert_reshape(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + attrs = node["attrs"] + + output_shape = convert_string_to_list(attrs["shape"]) + input_node_idx = inputs[0][0] + input_node_name = proc_nodes[input_node_idx].name + + not_supported_shape = [ -2, -3, -4] + + for val in output_shape: + if val in not_supported_shape: + raise AttributeError("Shape value not supported in ONNX", val) + + node = helper.make_node( + "Reshape", + [input_node_name], + [name], + name=name, + shape=output_shape + ) + + return node @mx2onnx.register("Cast") def convert_cast(node, **kwargs): diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index 97a0cfebd227..faf4ccd84fe5 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -83,6 +83,7 @@ 'test_min', #Changing shape and type. + 'test_reshape_', 'test_cast', 'test_slice_cpu', 'test_default_axes', #make PR against onnx to fix the test name(grep-able) @@ -133,7 +134,6 @@ 'test_vgg19' ] - for op_test in IMPLEMENTED_OPERATORS_TEST: BACKEND_TEST.include(op_test) From bb0b76f439609d60d7b05be4647355801cdf92a0 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Mon, 7 May 2018 16:25:30 -0700 Subject: [PATCH 15/82] some tests enabled --- .../contrib/onnx/_export/op_translations.py | 39 ++++++++++++++++--- .../onnx/export/onnx_backend_test.py | 4 ++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index e9b63bf8ae5e..a3ca9d5ed769 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -177,7 +177,7 @@ def convert_batchnorm(node, **kwargs): inputs = node["inputs"] attrs = node["attrs"] - momentum = float(attrs["momentum"]) + momentum = float(node.get("attrs", {}).get("momentum", 0.9)) eps = float(attrs["eps"]) data_idx = inputs[0][0] @@ -457,16 +457,16 @@ def convert_exp(node, **kwargs): @mx2onnx.register("softmax") def convert_softmax(node, **kwargs): - input_idx = node["inputs"][0][0] + inputs = node["inputs"] + input_idx = inputs[0][0] proc_nodes = kwargs["proc_nodes"] - input_node = proc_nodes[input_idx].name -# input_names = [proc_nodes[i[0]].name for i in inputs] + input_node = proc_nodes[input_idx] name = node["name"] axis = int(node.get("attrs", {}).get("axis", -1)) softmax_node = helper.make_node( "Softmax", - input_node, + [input_node.name], [name], axis=axis, name=name @@ -577,7 +577,33 @@ def convert_flatten(node, **kwargs): @mx2onnx.register("_mul_scalar") def convert_mul_scalar(node, **kwargs): - raise NotImplementedError + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + scalar_mul_value = int(node.get("attrs", {}).get("scalar", 1)) + + a = inputs[0][0] + + print(type(proc_nodes[a].name)) + a_node = proc_nodes[a].name + # b_node = str(scalar_mul_value).decode("utf-8") + + b_node = helper.make_tensor( + name=name+"b", + data_type=1, + dims=[1], + vals=[scalar_mul_value], + raw=False, + ) + + mul_node = helper.make_node( + "Mul", + [a_node, b_node.name], + [name], + name=name, + ) + + return mul_node # Sorting and Searching @@ -760,6 +786,7 @@ def covert_broadcast_sub(node, **kwargs): return sub_node + @mx2onnx.register("elemwise_mul") def convert_mul(node, **kwargs): name = node["name"] diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index faf4ccd84fe5..63a85595014c 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -103,6 +103,10 @@ #pytorch operator tests 'test_operator_exp', + 'test_operator_conv', + 'test_operator_non_float_params', + 'test_operator_params', + 'test_operator_permute2', ] BASIC_MODEL_TESTS = [ From 917894b7e00b96a31a2b1ea7151b01a2b542240e Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Mon, 7 May 2018 16:39:09 -0700 Subject: [PATCH 16/82] enabling squeezenet --- .../contrib/onnx/_export/op_translations.py | 3 ++- .../onnx/export/onnx_backend_test.py | 22 +++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index a3ca9d5ed769..68d5e04008c7 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -548,12 +548,13 @@ def convert_dropout(node, **kwargs): input_name = kwargs["proc_nodes"][input_id].name attrs = node["attrs"] p = float(attrs["p"]) + is_test = 0 if str(attrs["mode"]) is "always" else 1 dropout_node = helper.make_node( "Dropout", [input_name], [name], ratio=p, - is_test=0, + is_test=is_test, name=name ) return dropout_node diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index 63a85595014c..d84955721342 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -83,7 +83,7 @@ 'test_min', #Changing shape and type. - 'test_reshape_', + #'test_reshape_', 'test_cast', 'test_slice_cpu', 'test_default_axes', #make PR against onnx to fix the test name(grep-able) @@ -127,15 +127,15 @@ ] STANDARD_MODEL = [ - 'test_bvlc_alexnet', - 'test_densenet121', - #'test_inception_v1', - #'test_inception_v2', - 'test_resnet50', - #'test_shufflenet', + # 'test_bvlc_alexnet', + # 'test_densenet121', + # 'test_inception_v1', + # 'test_inception_v2', + # 'test_resnet50', + # 'test_shufflenet', 'test_squeezenet', - 'test_vgg16', - 'test_vgg19' + # 'test_vgg16', + # 'test_vgg19' ] for op_test in IMPLEMENTED_OPERATORS_TEST: @@ -144,8 +144,8 @@ for basic_model_test in BASIC_MODEL_TESTS: BACKEND_TEST.include(basic_model_test) -# for std_model_test in STANDARD_MODEL: -# BACKEND_TEST.include(std_model_test) +for std_model_test in STANDARD_MODEL: + BACKEND_TEST.include(std_model_test) # import all test cases at global scope to make them visible to python.unittest From b7e671b1ab6ccc51a1828b7fd5dbebeb1f246571 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Mon, 7 May 2018 17:27:21 -0700 Subject: [PATCH 17/82] LRN Op support --- .../contrib/onnx/_export/op_translations.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 68d5e04008c7..4f3d36856ed7 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -541,6 +541,33 @@ def convert_transpose(node, **kwargs): return transpose_node +@mx2onnx.register("LRN") +def convert_lrn(node, **kwargs): + name = node["name"] + input_idx = node["inputs"][0][0] + proc_nodes = kwargs["proc_nodes"] + input_node = proc_nodes[input_idx].name + + attrs = node["attrs"] + alpha = float(attrs["alpha"]) if "alpha" in attrs else 0.0001 + beta = float(attrs["beta"]) if "beta" in attrs else 0.75 + bias = float(attrs["knorm"]) if "knorm" in attrs else 1.0 + size = int(attrs["nsize"]) + + lrn_node = helper.make_node( + "LRN", + inputs=[input_node], + outputs=[name], + name=name, + alpha=alpha, + beta=beta, + bias=bias, + size=size + ) + + return lrn_node + + @mx2onnx.register("Dropout") def convert_dropout(node, **kwargs): name = node["name"] From e44960d3917b592e1dd7410ca5200047aa7e1334 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 8 May 2018 15:55:54 -0700 Subject: [PATCH 18/82] mul_scalar modified to take scalar input --- .../mxnet/contrib/onnx/_export/export_onnx.py | 116 ++++++++++++------ .../contrib/onnx/_export/op_translations.py | 37 ++++-- 2 files changed, 105 insertions(+), 48 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 46605dda8f7c..9213671a086c 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -148,6 +148,7 @@ def convert_mx2onnx_graph(self, sym, arg, aux, in_shape, in_type, log=False): onnx_processed_outputs = [] graph_input_idx=0 + num_nodes_added = [0] for idx, node in enumerate(mx_graph): op = node["op"] name = node["name"] @@ -176,48 +177,91 @@ def convert_mx2onnx_graph(self, sym, arg, aux, in_shape, in_type, log=False): in_shape=in_shape, in_type=in_type, proc_nodes=all_processed_nodes, - initializer=initializer + initializer=initializer, + num_nodes_added=num_nodes_added ) - if isinstance(converted, onnx_pb2.ValueInfoProto): - if idx < (len(mx_graph) - 1): - onnx_processed_inputs.append(converted) - else: - onnx_processed_outputs.append(converted) - elif isinstance(converted, onnx_pb2.NodeProto): - if idx < (len(mx_graph) - 1): - onnx_processed_nodes.append(converted) - else: - onnx_processed_nodes.append(converted) - if not converted.name: - onnx_processed_outputs.append( - make_tensor_value_info( - name=converted.output[0], - elem_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')], - shape=output_shape - ) - ) + if isinstance(converted, list): + for converted_node in converted: + if isinstance(converted_node, onnx_pb2.ValueInfoProto): + if idx < (len(mx_graph) - 1): + onnx_processed_inputs.append(converted_node) + else: + onnx_processed_outputs.append(converted_node) + elif isinstance(converted_node, onnx_pb2.NodeProto): + if idx < (len(mx_graph) - 1): + onnx_processed_nodes.append(converted_node) + else: + onnx_processed_nodes.append(converted_node) + if not converted_node.name: + onnx_processed_outputs.append( + make_tensor_value_info( + name=converted_node.output[0], + elem_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')], + shape=output_shape + ) + ) + else: + onnx_processed_outputs.append( + make_tensor_value_info( + name=converted_node.name, + elem_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')], + shape=output_shape + ) + ) + if log: + print("Output node is: %s" % converted_node.name) + elif isinstance(converted_node, onnx_pb2.TensorProto): + raise ValueError("Did not expect TensorProto") + if idx < (len(mx_graph) - 1): + onnx_processed_inputs.append(converted_node) + else: + onnx_processed_outputs.append(converted_node) + else: + print(converted_node) + raise ValueError("node is of an unrecognized type: %s" % type(node)) + + all_processed_nodes.append(converted_node) + else: + if isinstance(converted, onnx_pb2.ValueInfoProto): + if idx < (len(mx_graph) - 1): + onnx_processed_inputs.append(converted) + else: + onnx_processed_outputs.append(converted) + elif isinstance(converted, onnx_pb2.NodeProto): + if idx < (len(mx_graph) - 1): + onnx_processed_nodes.append(converted) else: - onnx_processed_outputs.append( - make_tensor_value_info( - name=converted.name, - elem_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')], - shape=output_shape + onnx_processed_nodes.append(converted) + if not converted.name: + onnx_processed_outputs.append( + make_tensor_value_info( + name=converted.output[0], + elem_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')], + shape=output_shape + ) + ) + else: + onnx_processed_outputs.append( + make_tensor_value_info( + name=converted.name, + elem_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')], + shape=output_shape + ) ) - ) - if log: - print("Output node is: %s" % converted.name) - elif isinstance(converted, onnx_pb2.TensorProto): - raise ValueError("Did not expect TensorProto") - if idx < (len(mx_graph) - 1): - onnx_processed_inputs.append(converted) + if log: + print("Output node is: %s" % converted.name) + elif isinstance(converted, onnx_pb2.TensorProto): + raise ValueError("Did not expect TensorProto") + if idx < (len(mx_graph) - 1): + onnx_processed_inputs.append(converted) + else: + onnx_processed_outputs.append(converted) else: - onnx_processed_outputs.append(converted) - else: - print(converted) - raise ValueError("node is of an unrecognized type: %s" % type(node)) + print(converted) + raise ValueError("node is of an unrecognized type: %s" % type(node)) - all_processed_nodes.append(converted) + all_processed_nodes.append(converted) graph = helper.make_graph( onnx_processed_nodes, diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 4f3d36856ed7..20cabf069aef 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -460,7 +460,9 @@ def convert_softmax(node, **kwargs): inputs = node["inputs"] input_idx = inputs[0][0] proc_nodes = kwargs["proc_nodes"] - input_node = proc_nodes[input_idx] + num_nodes_added = kwargs["num_nodes_added"][0] + input_node = proc_nodes[input_idx+num_nodes_added] + name = node["name"] axis = int(node.get("attrs", {}).get("axis", -1)) @@ -603,35 +605,46 @@ def convert_flatten(node, **kwargs): return flatten_node +# Convert scalar value into node and pass it as input to mul_node @mx2onnx.register("_mul_scalar") def convert_mul_scalar(node, **kwargs): name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - scalar_mul_value = int(node.get("attrs", {}).get("scalar", 1)) + scalar_mul_value = [int(node.get("attrs", {}).get("scalar", 1))] a = inputs[0][0] - - print(type(proc_nodes[a].name)) a_node = proc_nodes[a].name - # b_node = str(scalar_mul_value).decode("utf-8") - b_node = helper.make_tensor( - name=name+"b", - data_type=1, - dims=[1], - vals=[scalar_mul_value], + initializer = kwargs["initializer"] + np_arr = np.array(scalar_mul_value) + data_type = mapping.NP_TYPE_TO_TENSOR_TYPE[np_arr.dtype] + dims = np.shape(np_arr) + + scalar_op_name = "scalar_op" + str(kwargs["num_nodes_added"][0]) + tensor_node = helper.make_tensor_value_info(scalar_op_name, data_type, dims) + + initializer.append( + helper.make_tensor( + name=scalar_op_name, + data_type=data_type, + dims=dims, + vals=scalar_mul_value, raw=False, ) + ) + + kwargs["num_nodes_added"][0] += 1 mul_node = helper.make_node( "Mul", - [a_node, b_node.name], + [a_node, scalar_op_name], [name], name=name, + broadcast=1 ) - return mul_node + return [mul_node, tensor_node] # Sorting and Searching From b918aa5e50524d3868420c24f77402ac9b8e1e9f Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 8 May 2018 16:09:37 -0700 Subject: [PATCH 19/82] cleaning some code --- .../mxnet/contrib/onnx/_export/export_onnx.py | 40 -------- .../contrib/onnx/_export/op_translations.py | 97 ++++++++++--------- 2 files changed, 49 insertions(+), 88 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 9213671a086c..7d6acdcd1310 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -222,46 +222,6 @@ def convert_mx2onnx_graph(self, sym, arg, aux, in_shape, in_type, log=False): raise ValueError("node is of an unrecognized type: %s" % type(node)) all_processed_nodes.append(converted_node) - else: - if isinstance(converted, onnx_pb2.ValueInfoProto): - if idx < (len(mx_graph) - 1): - onnx_processed_inputs.append(converted) - else: - onnx_processed_outputs.append(converted) - elif isinstance(converted, onnx_pb2.NodeProto): - if idx < (len(mx_graph) - 1): - onnx_processed_nodes.append(converted) - else: - onnx_processed_nodes.append(converted) - if not converted.name: - onnx_processed_outputs.append( - make_tensor_value_info( - name=converted.output[0], - elem_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')], - shape=output_shape - ) - ) - else: - onnx_processed_outputs.append( - make_tensor_value_info( - name=converted.name, - elem_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')], - shape=output_shape - ) - ) - if log: - print("Output node is: %s" % converted.name) - elif isinstance(converted, onnx_pb2.TensorProto): - raise ValueError("Did not expect TensorProto") - if idx < (len(mx_graph) - 1): - onnx_processed_inputs.append(converted) - else: - onnx_processed_outputs.append(converted) - else: - print(converted) - raise ValueError("node is of an unrecognized type: %s" % type(node)) - - all_processed_nodes.append(converted) graph = helper.make_graph( onnx_processed_nodes, diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 20cabf069aef..88c7336ad245 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -74,10 +74,10 @@ def convert_weights_and_inputs(node, **kwargs): ) ) - return tensor_node + return [tensor_node] else: tval_node = helper.make_tensor_value_info(name, kwargs["in_type"], kwargs["in_shape"]) - return tval_node + return [tval_node] @mx2onnx.register("Convolution") @@ -136,7 +136,7 @@ def parse_helper(attrs_name, alt_value=None): name=name ) - return conv_node + return [conv_node] @mx2onnx.register("FullyConnected") @@ -167,7 +167,7 @@ def convert_fully_connected(node, **kwargs): name=name ) - return node + return [node] @mx2onnx.register("BatchNorm") @@ -210,7 +210,7 @@ def convert_batchnorm(node, **kwargs): spatial=1 ) - return bn_node + return [bn_node] @mx2onnx.register("tanh") @@ -227,7 +227,7 @@ def convert_tanh(node, **kwargs): [name], name=name ) - return node + return [node] #Basic neural network functions @mx2onnx.register("sigmoid") @@ -244,7 +244,7 @@ def convert_sigmoid(node, **kwargs): [name], name=name ) - return node + return [node] @mx2onnx.register("relu") def convert_relu(node, **kwargs): @@ -261,7 +261,7 @@ def convert_relu(node, **kwargs): name=name ) - return node + return [node] @mx2onnx.register("Activation") def convert_activation(node, **kwargs): @@ -295,7 +295,7 @@ def convert_activation(node, **kwargs): "Activation %s not implemented or recognized in the converter" % act_type ) - return node + return [node] def transform_padding(pad_width): num_pad_values = len(pad_width) @@ -367,7 +367,7 @@ def convert_pad(node, **kwargs): name=name ) - return node + return [node] @mx2onnx.register("_linalg_gemm2") @@ -397,7 +397,7 @@ def convert_linalg_gemm2(node, **kwargs): else: raise AttributeError("TODO: Add support for alpha multiplication") - return node + return [node] @mx2onnx.register("Pooling") @@ -434,7 +434,7 @@ def convert_pooling(node, **kwargs): name=name ) - return node + return [node] @mx2onnx.register("exp") @@ -452,7 +452,7 @@ def convert_exp(node, **kwargs): [name], name=name, ) - return node + return [node] @mx2onnx.register("softmax") @@ -474,7 +474,7 @@ def convert_softmax(node, **kwargs): name=name ) - return softmax_node + return [softmax_node] # There's also mx.sym.softmax(), which doesn't do cross-entropy loss, @@ -495,7 +495,7 @@ def convert_softmax_output(node, **kwargs): name=name ) - return softmax_node + return [softmax_node] @mx2onnx.register("Concat") @@ -512,7 +512,7 @@ def convert_concat(node, **kwargs): axis=axis, name=name ) - return concat_node + return [concat_node] @mx2onnx.register("transpose") @@ -540,7 +540,7 @@ def convert_transpose(node, **kwargs): name=name ) - return transpose_node + return [transpose_node] @mx2onnx.register("LRN") @@ -567,7 +567,7 @@ def convert_lrn(node, **kwargs): size=size ) - return lrn_node + return [lrn_node] @mx2onnx.register("Dropout") @@ -586,7 +586,7 @@ def convert_dropout(node, **kwargs): is_test=is_test, name=name ) - return dropout_node + return [dropout_node] @mx2onnx.register("Flatten") @@ -602,7 +602,7 @@ def convert_flatten(node, **kwargs): [name], name=name ) - return flatten_node + return [flatten_node] # Convert scalar value into node and pass it as input to mul_node @@ -669,7 +669,7 @@ def convert_argmax(node, **kwargs): outputs=[name], name=name ) - return node + return [node] @mx2onnx.register("argmin") def convert_argmin(node, **kwargs): @@ -692,7 +692,7 @@ def convert_argmin(node, **kwargs): outputs=[name], name=name ) - return node + return [node] @mx2onnx.register("_maximum") def convert_max(node, **kwargs): @@ -712,7 +712,7 @@ def convert_max(node, **kwargs): name=name, ) - return node + return [node] @mx2onnx.register("_minimum") @@ -733,8 +733,7 @@ def convert_min(node, **kwargs): name=name, ) - return node - + return [node] # Arithmetic Operations @@ -758,7 +757,7 @@ def convert_elementwise_add(node, **kwargs): name=name, ) - return add_node + return [add_node] @mx2onnx.register("broadcast_add") @@ -781,7 +780,7 @@ def covert_broadcast_add(node, **kwargs): name=name, ) - return add_node + return [add_node] @mx2onnx.register("elemwise_sub") @@ -803,7 +802,7 @@ def convert_elementwise_sub(node, **kwargs): name=name, ) - return sub_node + return [sub_node] @mx2onnx.register("broadcast_sub") def covert_broadcast_sub(node, **kwargs): @@ -825,7 +824,7 @@ def covert_broadcast_sub(node, **kwargs): name=name, ) - return sub_node + return [sub_node] @mx2onnx.register("elemwise_mul") @@ -847,7 +846,7 @@ def convert_mul(node, **kwargs): name=name, ) - return mul_node + return [mul_node] @mx2onnx.register("broadcast_mul") def convert_mul(node, **kwargs): @@ -869,7 +868,7 @@ def convert_mul(node, **kwargs): broadcast=1 ) - return mul_node + return [mul_node] @mx2onnx.register("elemwise_div") @@ -891,7 +890,7 @@ def convert_mul(node, **kwargs): name=name, ) - return div_node + return [div_node] @mx2onnx.register("broadcast_div") @@ -914,7 +913,7 @@ def convert_div(node, **kwargs): broadcast=1 ) - return div_node + return [div_node] @mx2onnx.register("negative") @@ -934,7 +933,8 @@ def convert_negative(node, **kwargs): name=name, ) - return neg_node + return [neg_node] + @mx2onnx.register("abs") def convert_abs(node, **kwargs): @@ -953,7 +953,8 @@ def convert_abs(node, **kwargs): name=name, ) - return abs_node + return [abs_node] + @mx2onnx.register("add_n") def convert_addn(node, **kwargs): @@ -971,9 +972,9 @@ def convert_addn(node, **kwargs): [name], name=name, ) - return sum_node + return [sum_node] - #Rounding + # Rounding @mx2onnx.register("ceil") def convert_floor(node, **kwargs): name = node["name"] @@ -989,7 +990,7 @@ def convert_floor(node, **kwargs): [name], name=name, ) - return node + return [node] @mx2onnx.register("floor") def convert_floor(node, **kwargs): @@ -1006,7 +1007,7 @@ def convert_floor(node, **kwargs): [name], name=name, ) - return node + return [node] #Changing shape and type. @mx2onnx.register("Reshape") @@ -1034,7 +1035,7 @@ def convert_reshape(node, **kwargs): shape=output_shape ) - return node + return [node] @mx2onnx.register("Cast") def convert_cast(node, **kwargs): @@ -1053,7 +1054,7 @@ def convert_cast(node, **kwargs): to=dtype, name=name, ) - return node + return [node] @mx2onnx.register("slice_axis") @@ -1077,7 +1078,7 @@ def convert_slice_axis(node, **kwargs): ends=[ends], name=name, ) - return node + return [node] # SliceChannel/split operators will be mapped to onnx's squeeze and split operator. @@ -1112,7 +1113,7 @@ def convert_slice_channel(node, **kwargs): name=name, ) - return node + return [node] @mx2onnx.register("log") @@ -1130,7 +1131,7 @@ def convert_log(node, **kwargs): [name], name=name, ) - return node + return [node] @mx2onnx.register("reciprocal") @@ -1148,7 +1149,7 @@ def convert_reciprocal(node, **kwargs): [name], name=name, ) - return node + return [node] @mx2onnx.register("_power") @@ -1169,7 +1170,7 @@ def convert_power(node, **kwargs): [name], name=None ) - return node + return [node] #[TODO] broadcast_power with axis @mx2onnx.register("broadcast_power") @@ -1192,7 +1193,7 @@ def convert_power(node, **kwargs): axis=1, broadcast=1, ) - return node + return [node] @mx2onnx.register("sqrt") @@ -1210,4 +1211,4 @@ def convert_sqrt(node, **kwargs): [name], name=name, ) - return node + return [node] From 7b1e0ab201a68615bd7292017718be6adf9a5042 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Wed, 16 May 2018 13:08:58 -0700 Subject: [PATCH 20/82] Resolving conlicts on rebase --- .../mxnet/contrib/onnx/_export/export_onnx.py | 9 +- .../contrib/onnx/_export/op_translations.py | 108 +++++++++++++++--- .../contrib/onnx/_import/translation_utils.py | 2 +- 3 files changed, 100 insertions(+), 19 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 7d6acdcd1310..917d3446f657 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -146,6 +146,7 @@ def convert_mx2onnx_graph(self, sym, arg, aux, in_shape, in_type, log=False): onnx_processed_nodes = [] onnx_processed_inputs = [] onnx_processed_outputs = [] + index_lookup = [] graph_input_idx=0 num_nodes_added = [0] @@ -165,7 +166,8 @@ def convert_mx2onnx_graph(self, sym, arg, aux, in_shape, in_type, log=False): in_shape=in_shape[graph_input_idx], in_type=in_type, proc_nodes=all_processed_nodes, - initializer=initializer) + initializer=initializer, + index_lookup=index_lookup) graph_input_idx += 1 else: @@ -178,6 +180,7 @@ def convert_mx2onnx_graph(self, sym, arg, aux, in_shape, in_type, log=False): in_type=in_type, proc_nodes=all_processed_nodes, initializer=initializer, + index_lookup=index_lookup, num_nodes_added=num_nodes_added ) @@ -222,6 +225,10 @@ def convert_mx2onnx_graph(self, sym, arg, aux, in_shape, in_type, log=False): raise ValueError("node is of an unrecognized type: %s" % type(node)) all_processed_nodes.append(converted_node) + if idx>0: + index_lookup.append(index_lookup[idx-1]+len(converted)) + else: + index_lookup.append(len(converted) - 1) graph = helper.make_graph( onnx_processed_nodes, diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 88c7336ad245..5f58cd6c3251 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -375,29 +375,95 @@ def convert_linalg_gemm2(node, **kwargs): proc_nodes = kwargs["proc_nodes"] node_inputs = node["inputs"] name = node["name"] - input_a_idx = node_inputs[0][0] - input_node_a = proc_nodes[input_a_idx].name - input_b_idx = node_inputs[1][0] + input_a_idx = kwargs["index_lookup"][node_inputs[0][0]] + input_node_a = proc_nodes[input_a_idx].name + input_b_idx = kwargs["index_lookup"][node_inputs[1][0]] input_node_b = proc_nodes[input_b_idx].name + # Getting the attributes and assigning default values. if "attrs" in node: attrs = node["attrs"] - alpha = float(attrs.get("alpha")) + alpha = float(attrs["alpha"]) + transA = int(attrs["transpose_a"]) + transB = int(attrs["transpose_b"]) + beta = 0.0 else: alpha = 1.0 + transA = 0 + transB = 0 - if alpha == 1.0: - node = helper.make_node( + op_name = "transpose" + str(transA) + str(transB) + + if alpha == 1.0 and transA == 0 and transB == 0: + matmul_node = helper.make_node( 'MatMul', inputs=[input_node_a, input_node_b], outputs=[name], name=name ) + return [matmul_node] + elif transA == 1 and transB == 0: + transA_node = helper.make_node( + 'Transpose', + inputs=[input_node_a], + outputs=[op_name], + name=op_name + ) + + kwargs["num_nodes_added"][0] += 1 + + matmul_node = helper.make_node( + 'MatMul', + inputs=[transA_node.name, input_node_b], + outputs=[name], + name=name + ) + return [transA_node, matmul_node] + + elif transA == 0 and transB == 1: + transB_node = helper.make_node( + 'Transpose', + inputs=[input_node_b], + outputs=[op_name], + name=op_name + ) + + kwargs["num_nodes_added"][0] += 1 + + matmul_node = helper.make_node( + 'MatMul', + inputs=[input_node_a, transB_node.name], + outputs=[name], + name=name + ) + + return [transB_node, matmul_node] else: - raise AttributeError("TODO: Add support for alpha multiplication") + transA_node = helper.make_node( + 'Transpose', + inputs=[input_node_a], + outputs=[op_name], + name=op_name + ) - return [node] + transB_node = helper.make_node( + 'Transpose', + inputs=[input_node_b], + outputs=[op_name], + name=op_name + ) + kwargs["num_nodes_added"][0] += 1 + kwargs["num_nodes_added"][0] += 1 + + matmul_node = helper.make_node( + 'MatMul', + inputs=[transA_node.name, transB_node.name], + outputs=[name], + name=name + ) + + return [transA_node, transB_node, matmul_node] @mx2onnx.register("Pooling") @@ -458,10 +524,12 @@ def convert_exp(node, **kwargs): @mx2onnx.register("softmax") def convert_softmax(node, **kwargs): inputs = node["inputs"] - input_idx = inputs[0][0] + input_idx = kwargs["index_lookup"][inputs[0][0]] proc_nodes = kwargs["proc_nodes"] - num_nodes_added = kwargs["num_nodes_added"][0] - input_node = proc_nodes[input_idx+num_nodes_added] + input_node = proc_nodes[input_idx] + + for i in kwargs["index_lookup"]: + print (i, "\t", proc_nodes[i].name) name = node["name"] axis = int(node.get("attrs", {}).get("axis", -1)) @@ -613,7 +681,7 @@ def convert_mul_scalar(node, **kwargs): inputs = node["inputs"] scalar_mul_value = [int(node.get("attrs", {}).get("scalar", 1))] - a = inputs[0][0] + a = kwargs["index_lookup"][inputs[0][0]] a_node = proc_nodes[a].name initializer = kwargs["initializer"] @@ -644,7 +712,7 @@ def convert_mul_scalar(node, **kwargs): broadcast=1 ) - return [mul_node, tensor_node] + return [tensor_node, mul_node] # Sorting and Searching @@ -766,8 +834,11 @@ def covert_broadcast_add(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = inputs[0][0] - b = inputs[1][0] + # a = inputs[0][0] + # b = inputs[1][0] + + a = kwargs["index_lookup"][inputs[0][0]] + b = kwargs["index_lookup"][inputs[1][0]] a_node = proc_nodes[a].name b_node = proc_nodes[b].name @@ -854,8 +925,11 @@ def convert_mul(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = inputs[0][0] - b = inputs[1][0] + # a = inputs[0][0] + # b = inputs[1][0] + + a = kwargs["index_lookup"][inputs[0][0]] + b = kwargs["index_lookup"][inputs[1][0]] a_node = proc_nodes[a].name b_node = proc_nodes[b].name diff --git a/python/mxnet/contrib/onnx/_import/translation_utils.py b/python/mxnet/contrib/onnx/_import/translation_utils.py index fe25a94baa7d..fbacfad73a8f 100644 --- a/python/mxnet/contrib/onnx/_import/translation_utils.py +++ b/python/mxnet/contrib/onnx/_import/translation_utils.py @@ -165,7 +165,7 @@ def _fix_broadcast(op_name, inputs, broadcast_axis, proto_obj): reshape_op_sym = symbol.reshape(inputs[1], shape=reshape_shape) op_sym = getattr(symbol, op_name)(inputs[0], reshape_op_sym) else: - op_sym = op_name + op_sym = op_name return op_sym def _fix_channels(op_name, attrs, inputs, proto_obj): From 42c695c0a05aa073ff4c23d933daf92e2a1e54c2 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Wed, 16 May 2018 19:42:02 -0700 Subject: [PATCH 21/82] Resolving rebase conflicts --- .../mxnet/contrib/onnx/_export/export_onnx.py | 3 +- .../contrib/onnx/_export/op_translations.py | 100 ++++++++++-------- .../contrib/onnx/_import/translation_utils.py | 3 +- 3 files changed, 61 insertions(+), 45 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 917d3446f657..ab114f4dd3d5 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -149,7 +149,6 @@ def convert_mx2onnx_graph(self, sym, arg, aux, in_shape, in_type, log=False): index_lookup = [] graph_input_idx=0 - num_nodes_added = [0] for idx, node in enumerate(mx_graph): op = node["op"] name = node["name"] @@ -181,7 +180,7 @@ def convert_mx2onnx_graph(self, sym, arg, aux, in_shape, in_type, log=False): proc_nodes=all_processed_nodes, initializer=initializer, index_lookup=index_lookup, - num_nodes_added=num_nodes_added + idx=idx ) if isinstance(converted, list): diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 5f58cd6c3251..b8d921737d02 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -393,7 +393,7 @@ def convert_linalg_gemm2(node, **kwargs): transA = 0 transB = 0 - op_name = "transpose" + str(transA) + str(transB) + op_name = "transpose" + str(kwargs["idx"]) if alpha == 1.0 and transA == 0 and transB == 0: matmul_node = helper.make_node( @@ -404,15 +404,14 @@ def convert_linalg_gemm2(node, **kwargs): ) return [matmul_node] elif transA == 1 and transB == 0: + op_name = "transpose" + str(kwargs["idx"]) transA_node = helper.make_node( 'Transpose', inputs=[input_node_a], - outputs=[op_name], - name=op_name + outputs=[op_name+"_a"], + name=op_name+"_a" ) - kwargs["num_nodes_added"][0] += 1 - matmul_node = helper.make_node( 'MatMul', inputs=[transA_node.name, input_node_b], @@ -425,12 +424,10 @@ def convert_linalg_gemm2(node, **kwargs): transB_node = helper.make_node( 'Transpose', inputs=[input_node_b], - outputs=[op_name], - name=op_name + outputs=[op_name+"_b"], + name=op_name+"_b" ) - kwargs["num_nodes_added"][0] += 1 - matmul_node = helper.make_node( 'MatMul', inputs=[input_node_a, transB_node.name], @@ -443,18 +440,16 @@ def convert_linalg_gemm2(node, **kwargs): transA_node = helper.make_node( 'Transpose', inputs=[input_node_a], - outputs=[op_name], - name=op_name + outputs=[op_name+"_a"], + name=op_name+"_a" ) transB_node = helper.make_node( 'Transpose', inputs=[input_node_b], - outputs=[op_name], - name=op_name + outputs=[op_name+"_b"], + name=op_name+"_b" ) - kwargs["num_nodes_added"][0] += 1 - kwargs["num_nodes_added"][0] += 1 matmul_node = helper.make_node( 'MatMul', @@ -528,9 +523,6 @@ def convert_softmax(node, **kwargs): proc_nodes = kwargs["proc_nodes"] input_node = proc_nodes[input_idx] - for i in kwargs["index_lookup"]: - print (i, "\t", proc_nodes[i].name) - name = node["name"] axis = int(node.get("attrs", {}).get("axis", -1)) @@ -685,35 +677,59 @@ def convert_mul_scalar(node, **kwargs): a_node = proc_nodes[a].name initializer = kwargs["initializer"] - np_arr = np.array(scalar_mul_value) - data_type = mapping.NP_TYPE_TO_TENSOR_TYPE[np_arr.dtype] - dims = np.shape(np_arr) - - scalar_op_name = "scalar_op" + str(kwargs["num_nodes_added"][0]) - tensor_node = helper.make_tensor_value_info(scalar_op_name, data_type, dims) - - initializer.append( - helper.make_tensor( - name=scalar_op_name, - data_type=data_type, - dims=dims, - vals=scalar_mul_value, - raw=False, + flag = True + for i in initializer: + if i.name==a_node: + new_initializer = scalar_mul_value[0]*numpy_helper.to_array(i) + flag = False + break + + if flag == True: + np_arr = np.array(scalar_mul_value) + data_type = mapping.NP_TYPE_TO_TENSOR_TYPE[np_arr.dtype] + dims = np.shape(np_arr) + + scalar_op_name = "scalar_op" + str(kwargs["idx"]) + tensor_node = helper.make_tensor_value_info(scalar_op_name, data_type, dims) + + initializer.append( + helper.make_tensor( + name=scalar_op_name, + data_type=data_type, + dims=dims, + vals=scalar_mul_value, + raw=False, + ) + ) + + mul_node = helper.make_node( + "Mul", + [a_node, scalar_op_name], + [name], + name=name, + broadcast=1 ) - ) - kwargs["num_nodes_added"][0] += 1 + return [tensor_node, mul_node] - mul_node = helper.make_node( - "Mul", - [a_node, scalar_op_name], - [name], - name=name, - broadcast=1 - ) + else: + data_type = mapping.NP_TYPE_TO_TENSOR_TYPE[new_initializer.dtype] + dims = np.shape(new_initializer) - return [tensor_node, mul_node] + new_a_node = a_node + str(kwargs["idx"]) + tensor_node = helper.make_tensor_value_info(new_a_node, data_type, dims) + initializer.append( + helper.make_tensor( + name=new_a_node, + data_type=data_type, + dims=dims, + vals=new_initializer, + raw=False, + ) + ) + + return [tensor_node] # Sorting and Searching @mx2onnx.register("argmax") diff --git a/python/mxnet/contrib/onnx/_import/translation_utils.py b/python/mxnet/contrib/onnx/_import/translation_utils.py index fbacfad73a8f..f63c1e9e8e62 100644 --- a/python/mxnet/contrib/onnx/_import/translation_utils.py +++ b/python/mxnet/contrib/onnx/_import/translation_utils.py @@ -165,9 +165,10 @@ def _fix_broadcast(op_name, inputs, broadcast_axis, proto_obj): reshape_op_sym = symbol.reshape(inputs[1], shape=reshape_shape) op_sym = getattr(symbol, op_name)(inputs[0], reshape_op_sym) else: - op_sym = op_name + op_sym = op_name return op_sym + def _fix_channels(op_name, attrs, inputs, proto_obj): """A workaround for getting 'channels' or 'units' since onnx don't provide these attributes. We check the shape of weights provided to get the number. From 03226b3e9861528836317589237b259b5960818b Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Thu, 17 May 2018 15:29:46 -0700 Subject: [PATCH 22/82] id mapping updated for all operators --- .../contrib/onnx/_export/op_translations.py | 129 +++++++++--------- 1 file changed, 63 insertions(+), 66 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index b8d921737d02..5b80aad6e1e7 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -17,8 +17,6 @@ # coding: utf-8 """ -mx_to_uff_converter_functions.py - Conversion Functions for common layers. Add new functions here with a decorator. """ @@ -88,11 +86,11 @@ def convert_convolution(node, **kwargs): num_inputs = len(inputs) proc_nodes = kwargs["proc_nodes"] - input_node = proc_nodes[inputs[0][0]].name - weights_node = proc_nodes[inputs[1][0]].name + input_node = proc_nodes[kwargs["index_lookup"][inputs[0][0]]].name + weights_node = proc_nodes[kwargs["index_lookup"][inputs[1][0]]].name if num_inputs > 2: - bias_node = proc_nodes[inputs[2][0]].name + bias_node = proc_nodes[kwargs["index_lookup"][inputs[2][0]]].name attrs = node.get("attrs") tuple_re = re.compile('\([0-9L|,| ]+\)') @@ -143,9 +141,9 @@ def parse_helper(attrs_name, alt_value=None): def convert_fully_connected(node, **kwargs): name = node["name"] inputs = node["inputs"] - input_node_id = inputs[0][0] - weight_node_id = inputs[1][0] - bias_node_id = inputs[2][0] + input_node_id = kwargs["index_lookup"][inputs[0][0]] + weight_node_id = kwargs["index_lookup"][inputs[1][0]] + bias_node_id = kwargs["index_lookup"][inputs[2][0]] proc_nodes = kwargs["proc_nodes"] input_node = proc_nodes[input_node_id] weights_node = proc_nodes[weight_node_id] @@ -180,11 +178,11 @@ def convert_batchnorm(node, **kwargs): momentum = float(node.get("attrs", {}).get("momentum", 0.9)) eps = float(attrs["eps"]) - data_idx = inputs[0][0] - gamma_idx = inputs[1][0] - beta_idx = inputs[2][0] - moving_mean_idx = inputs[3][0] - moving_var_idx = inputs[4][0] + data_idx = kwargs["index_lookup"][inputs[0][0]] + gamma_idx = kwargs["index_lookup"][inputs[1][0]] + beta_idx = kwargs["index_lookup"][inputs[2][0]] + moving_mean_idx = kwargs["index_lookup"][inputs[3][0]] + moving_var_idx = kwargs["index_lookup"][inputs[4][0]] data_node = proc_nodes[data_idx].name gamma_node = proc_nodes[gamma_idx].name @@ -217,7 +215,7 @@ def convert_batchnorm(node, **kwargs): def convert_tanh(node, **kwargs): name = node["name"] inputs = node["inputs"] - input_node_idx = inputs[0][0] + input_node_idx = kwargs["index_lookup"][inputs[0][0]] proc_nodes = kwargs["proc_nodes"] input_node = proc_nodes[input_node_idx].name @@ -234,7 +232,7 @@ def convert_tanh(node, **kwargs): def convert_sigmoid(node, **kwargs): name = node["name"] inputs = node["inputs"] - input_node_idx = inputs[0][0] + input_node_idx = kwargs["index_lookup"][inputs[0][0]] proc_nodes = kwargs["proc_nodes"] input_node = proc_nodes[input_node_idx].name @@ -250,7 +248,7 @@ def convert_sigmoid(node, **kwargs): def convert_relu(node, **kwargs): name = node["name"] inputs = node["inputs"] - input_node_idx = inputs[0][0] + input_node_idx = kwargs["index_lookup"][inputs[0][0]] proc_nodes = kwargs["proc_nodes"] input_node = proc_nodes[input_node_idx].name @@ -272,7 +270,7 @@ def convert_activation(node, **kwargs): act_type = attrs["act_type"] inputs = node["inputs"] - input_node_idx = inputs[0][0] + input_node_idx = kwargs["index_lookup"][inputs[0][0]] input_node = proc_nodes[input_node_idx].output[0] # Creating a dictionary here, but if this titlecase pattern @@ -337,7 +335,7 @@ def convert_pad(node, **kwargs): attrs = node["attrs"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - input_node_idx = inputs[0][0] + input_node_idx = kwargs["index_lookup"][inputs[0][0]] input_node = proc_nodes[input_node_idx].name mxnet_pad_width = convert_string_to_list(attrs.get("pad_width")) @@ -469,7 +467,7 @@ def convert_pooling(node, **kwargs): pool_type = attrs["pool_type"] stride = eval(attrs["stride"]) if attrs.get("stride") else None node_inputs = node["inputs"] - input_node_idx = node_inputs[0][0] + input_node_idx = kwargs["index_lookup"][node_inputs[0][0]] input_node = proc_nodes[input_node_idx] name = node["name"] @@ -504,7 +502,7 @@ def convert_exp(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = inputs[0][0] + a = kwargs["index_lookup"][inputs[0][0]] a_node = proc_nodes[a].name node = helper.make_node( @@ -542,7 +540,7 @@ def convert_softmax(node, **kwargs): @mx2onnx.register("SoftmaxOutput") def convert_softmax_output(node, **kwargs): inputs = node["inputs"] - input1_idx = inputs[0][0] + input1_idx = kwargs["index_lookup"][inputs[0][0]] proc_nodes = kwargs["proc_nodes"] input1 = proc_nodes[input1_idx] name = node["name"] @@ -563,7 +561,7 @@ def convert_concat(node, **kwargs): name = node["name"] inputs = node["inputs"] proc_nodes = kwargs["proc_nodes"] - input_names = [proc_nodes[i[0]].name for i in inputs] + input_names = [proc_nodes[kwargs["index_lookup"][i[0]]].name for i in inputs] axis = int(node.get("attrs", {}).get("dim", 1)) concat_node = helper.make_node( "Concat", @@ -578,7 +576,7 @@ def convert_concat(node, **kwargs): @mx2onnx.register("transpose") def convert_transpose(node, **kwargs): name = node["name"] - input_idx = node["inputs"][0][0] + input_idx = kwargs["index_lookup"][node["inputs"][0][0]] proc_nodes = kwargs["proc_nodes"] input_node = proc_nodes[input_idx].name axes = node.get("attrs", {}).get("axes", ()) @@ -606,7 +604,7 @@ def convert_transpose(node, **kwargs): @mx2onnx.register("LRN") def convert_lrn(node, **kwargs): name = node["name"] - input_idx = node["inputs"][0][0] + input_idx = kwargs["index_lookup"][node["inputs"][0][0]] proc_nodes = kwargs["proc_nodes"] input_node = proc_nodes[input_idx].name @@ -633,7 +631,7 @@ def convert_lrn(node, **kwargs): @mx2onnx.register("Dropout") def convert_dropout(node, **kwargs): name = node["name"] - input_id = node["inputs"][0][0] + input_id = kwargs["index_lookup"][node["inputs"][0][0]] input_name = kwargs["proc_nodes"][input_id].name attrs = node["attrs"] p = float(attrs["p"]) @@ -652,7 +650,7 @@ def convert_dropout(node, **kwargs): @mx2onnx.register("Flatten") def convert_flatten(node, **kwargs): name = node["name"] - input_idx = node["inputs"][0][0] + input_idx = kwargs["index_lookup"][node["inputs"][0][0]] proc_nodes = kwargs["proc_nodes"] input_node = proc_nodes[input_idx].name # .output[0] @@ -707,7 +705,8 @@ def convert_mul_scalar(node, **kwargs): [a_node, scalar_op_name], [name], name=name, - broadcast=1 + broadcast=1, + axis=3 ) return [tensor_node, mul_node] @@ -737,7 +736,7 @@ def convert_argmax(node, **kwargs): proc_nodes = kwargs["proc_nodes"] node_inputs = node["inputs"] - input_node_idx = node_inputs[0][0] + input_node_idx = kwargs["index_lookup"][node_inputs[0][0]] input_node = proc_nodes[input_node_idx].name name = node["name"] attrs = node["attrs"] @@ -760,7 +759,7 @@ def convert_argmin(node, **kwargs): proc_nodes = kwargs["proc_nodes"] node_inputs = node["inputs"] - input_node_idx = node_inputs[0][0] + input_node_idx = kwargs["index_lookup"][node_inputs[0][0]] input_node = proc_nodes[input_node_idx].name name = node["name"] attrs = node["attrs"] @@ -785,7 +784,8 @@ def convert_max(node, **kwargs): input_node_list = [] for node_input in node_inputs: - input_node_list.append(proc_nodes[node_input[0]].name) + node_id = kwargs["index_lookup"][node_input[0]] + input_node_list.append(proc_nodes[node_id].name) name = node["name"] @@ -806,7 +806,8 @@ def convert_min(node, **kwargs): input_node_list = [] for node_input in node_inputs: - input_node_list.append(proc_nodes[node_input[0]].name) + node_id = kwargs["index_lookup"][node_input[0]] + input_node_list.append(proc_nodes[node_id].name) name = node["name"] @@ -828,8 +829,8 @@ def convert_elementwise_add(node, **kwargs): inputs = node["inputs"] weights = kwargs["weights"] - a = inputs[0][0] - b = inputs[1][0] + a = kwargs["index_lookup"][inputs[0][0]] + b = kwargs["index_lookup"][inputs[1][0]] a_node = proc_nodes[a].name b_node = proc_nodes[b].name @@ -850,9 +851,6 @@ def covert_broadcast_add(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - # a = inputs[0][0] - # b = inputs[1][0] - a = kwargs["index_lookup"][inputs[0][0]] b = kwargs["index_lookup"][inputs[1][0]] @@ -864,6 +862,7 @@ def covert_broadcast_add(node, **kwargs): [a_node, b_node], [name], broadcast=1, + axis=1, name=name, ) @@ -876,8 +875,8 @@ def convert_elementwise_sub(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = inputs[0][0] - b = inputs[1][0] + a = kwargs["index_lookup"][inputs[0][0]] + b = kwargs["index_lookup"][inputs[1][0]] a_node = proc_nodes[a].name b_node = proc_nodes[b].name @@ -897,8 +896,8 @@ def covert_broadcast_sub(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = inputs[0][0] - b = inputs[1][0] + a = kwargs["index_lookup"][inputs[0][0]] + b = kwargs["index_lookup"][inputs[1][0]] a_node = proc_nodes[a].name b_node = proc_nodes[b].name @@ -920,8 +919,8 @@ def convert_mul(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = inputs[0][0] - b = inputs[1][0] + a = kwargs["index_lookup"][inputs[0][0]] + b = kwargs["index_lookup"][inputs[1][0]] a_node = proc_nodes[a].name b_node = proc_nodes[b].name @@ -941,9 +940,6 @@ def convert_mul(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - # a = inputs[0][0] - # b = inputs[1][0] - a = kwargs["index_lookup"][inputs[0][0]] b = kwargs["index_lookup"][inputs[1][0]] @@ -955,7 +951,8 @@ def convert_mul(node, **kwargs): [a_node, b_node], [name], name=name, - broadcast=1 + broadcast=1, + axis=1 ) return [mul_node] @@ -967,8 +964,8 @@ def convert_mul(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = inputs[0][0] - b = inputs[1][0] + a = kwargs["index_lookup"][inputs[0][0]] + b = kwargs["index_lookup"][inputs[1][0]] a_node = proc_nodes[a].name b_node = proc_nodes[b].name @@ -989,8 +986,8 @@ def convert_div(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = inputs[0][0] - b = inputs[1][0] + a = kwargs["index_lookup"][inputs[0][0]] + b = kwargs["index_lookup"][inputs[1][0]] a_node = proc_nodes[a].name b_node = proc_nodes[b].name @@ -1012,7 +1009,7 @@ def convert_negative(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = inputs[0][0] + a = kwargs["index_lookup"][inputs[0][0]] a_node = proc_nodes[a].name @@ -1032,7 +1029,7 @@ def convert_abs(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = inputs[0][0] + a = kwargs["index_lookup"][inputs[0][0]] a_node = proc_nodes[a].name @@ -1054,7 +1051,7 @@ def convert_addn(node, **kwargs): input_list = [] for idx, input_val in enumerate(inputs): - input_list.append(proc_nodes[input_val[0]].name) + input_list.append(proc_nodes[kwargs["index_lookup"][input_val[0]]].name) sum_node = helper.make_node( "Sum", @@ -1071,7 +1068,7 @@ def convert_floor(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = inputs[0][0] + a = kwargs["index_lookup"][inputs[0][0]] a_node = proc_nodes[a].name node = helper.make_node( @@ -1088,7 +1085,7 @@ def convert_floor(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = inputs[0][0] + a = kwargs["index_lookup"][inputs[0][0]] a_node = proc_nodes[a].name node = helper.make_node( @@ -1108,7 +1105,7 @@ def convert_reshape(node, **kwargs): attrs = node["attrs"] output_shape = convert_string_to_list(attrs["shape"]) - input_node_idx = inputs[0][0] + input_node_idx = kwargs["index_lookup"][inputs[0][0]] input_node_name = proc_nodes[input_node_idx].name not_supported_shape = [ -2, -3, -4] @@ -1134,7 +1131,7 @@ def convert_cast(node, **kwargs): inputs = node["inputs"] dtype = node["attrs"]["dtype"] - a = inputs[0][0] + a = kwargs["index_lookup"][inputs[0][0]] a_node = proc_nodes[a].name node = helper.make_node( @@ -1156,7 +1153,7 @@ def convert_slice_axis(node, **kwargs): starts = int(node["attrs"]["begin"]) ends = int(node["attrs"]["end"]) - a = inputs[0][0] + a = kwargs["index_lookup"][inputs[0][0]] a_node = proc_nodes[a].name node = helper.make_node( @@ -1182,7 +1179,7 @@ def convert_slice_channel(node, **kwargs): axis = int(node.get("attrs", {}).get("axis", 1)) squeeze_axis = int(node.get("attrs", {}).get("squeeze_axis", 0)) - a = inputs[0][0] + a = kwargs["index_lookup"][inputs[0][0]] a_node = proc_nodes[a].name if num_outputs==1 and squeeze_axis==1: @@ -1212,7 +1209,7 @@ def convert_log(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = inputs[0][0] + a = kwargs["index_lookup"][inputs[0][0]] a_node = proc_nodes[a].name node = helper.make_node( @@ -1230,7 +1227,7 @@ def convert_reciprocal(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = inputs[0][0] + a = kwargs["index_lookup"][inputs[0][0]] a_node = proc_nodes[a].name node = helper.make_node( @@ -1248,8 +1245,8 @@ def convert_power(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = inputs[0][0] - b = inputs[1][0] + a = kwargs["index_lookup"][inputs[0][0]] + b = kwargs["index_lookup"][inputs[1][0]] a_node = proc_nodes[a].name b_node = proc_nodes[b].name @@ -1269,8 +1266,8 @@ def convert_power(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = inputs[0][0] - b = inputs[1][0] + a = kwargs["index_lookup"][inputs[0][0]] + b = kwargs["index_lookup"][inputs[1][0]] a_node = proc_nodes[a].name b_node = proc_nodes[b].name @@ -1292,7 +1289,7 @@ def convert_sqrt(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = inputs[0][0] + a = kwargs["index_lookup"][inputs[0][0]] a_node = proc_nodes[a].name node = helper.make_node( From 43dad9b0e3a6d8bcc0c7a6d58d89b31726882d34 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Thu, 24 May 2018 14:11:55 -0700 Subject: [PATCH 23/82] save onnx models added, some code cleanup --- .../contrib/onnx/_export/export_helper.py | 18 ++++--- .../contrib/onnx/_export/export_model.py | 9 +++- .../mxnet/contrib/onnx/_export/export_onnx.py | 48 +++++++++++++------ .../contrib/onnx/_export/op_translations.py | 5 +- tests/python-pytest/onnx/export/backend.py | 8 ++-- 5 files changed, 62 insertions(+), 26 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_helper.py b/python/mxnet/contrib/onnx/_export/export_helper.py index 493680d7ac48..c3e230c51600 100644 --- a/python/mxnet/contrib/onnx/_export/export_helper.py +++ b/python/mxnet/contrib/onnx/_export/export_helper.py @@ -18,6 +18,7 @@ # coding: utf-8 import os import mxnet as mx +import numpy as np def load_module(json_path, params_path, input_shape): """Loads the MXNet model file, retrieves symbol and parameters and returns. @@ -42,16 +43,21 @@ def load_module(json_path, params_path, input_shape): if not (os.path.isfile(json_path) and os.path.isfile(params_path)): raise ValueError("Provide valid path to the json and params file") else: - model_name = json_path.rsplit('.')[0].rsplit('-', 1)[0] - num_epochs = int(params_path.rsplit('.')[0].rsplit('-', 1)[1]) - trained_model = mx.mod.Module.load(model_name, num_epochs) - trained_model.bind(data_shapes=[('data', input_shape)], label_shapes=None, for_training=False, force_rebind=True) + try: + model_name = json_path.rsplit('.', 1)[0].rsplit('-', 1)[0] + num_epochs = int(params_path.rsplit('.', 1)[0].rsplit('-', 1)[1]) + except IndexError: + print("Model and params name should be in format: prefix-symbol.json, prefix-epoch.params") + raise - sym = trained_model.symbol - arg_params, aux_params = trained_model.get_params() + sym, arg_params, aux_params = mx.model.load_checkpoint(model_name, num_epochs) + trained_model = mx.mod.Module(symbol=sym, label_names=None) + trained_model.bind(data_shapes=[('data', input_shape[0])], label_shapes=trained_model._label_shapes, + for_training=False) # Merging arg and aux parameters params = {} params.update(arg_params) params.update(aux_params) + return sym, params diff --git a/python/mxnet/contrib/onnx/_export/export_model.py b/python/mxnet/contrib/onnx/_export/export_model.py index 69f8ff6d4b84..4d6437012165 100644 --- a/python/mxnet/contrib/onnx/_export/export_model.py +++ b/python/mxnet/contrib/onnx/_export/export_model.py @@ -28,7 +28,7 @@ import numpy as np -def export_model(model, weights, input_shape, input_type, log=False): +def export_model(model, weights, input_shape, input_type, onnx_file_path, log=False): """Exports the MXNet model file, passed as a parameter, into ONNX model. Accepts both symbol,parameter objects as well as json and params filepaths as input. Operator support and coverage - https://cwiki.apache.org/confluence/display/MXNET/ONNX @@ -63,4 +63,11 @@ def export_model(model, weights, input_shape, input_type, log=False): mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype(input_type)], log=log) # Create the model (ModelProto) onnx_model = helper.make_model(onnx_graph) + + # Save model on disk + with open(onnx_file_path, "wb") as f: + serialized = onnx_model.SerializeToString() + f.write(serialized) + print("\nONNX file %s serialized to disk" % onnx_file_path) + return onnx_model diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index ab114f4dd3d5..0478154f473c 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -69,10 +69,11 @@ def convert_layer(node, **kwargs): def forward_pass(inputs, sym, arg_params, aux_params): """ Do a forward pass based on the sym and params""" data_names = [graph_input for graph_input in sym.list_inputs() - if graph_input not in arg_params and graph_input not in aux_params] + if graph_input not in arg_params and graph_input not in aux_params + and graph_input != 'softmax_label'] data_shapes = [] - dim_added = False; + dim_added = False # Adding extra dimension of batch_size 1 if the batch_size is different for multiple inputs. for idx, input_name in enumerate(data_names): batch_size = 1 @@ -94,7 +95,7 @@ def forward_pass(inputs, sym, arg_params, aux_params): if arg_params is None and aux_params is None: test_mod.init_params() else: - test_mod.set_params(arg_params=arg_params, aux_params=aux_params) + test_mod.set_params(arg_params=arg_params, aux_params=aux_params, allow_missing=True) data_forward = [] for idx, input_name in enumerate(data_names): @@ -114,11 +115,27 @@ def forward_pass(inputs, sym, arg_params, aux_params): else: return result.shape + @staticmethod - def infer_output_shape(sym, arg, aux, in_shape): + def split_params(sym, params): + # splitting params into args and aux params + arg_params ={} + aux_params = {} + for args in sym.list_arguments(): + if args in params: + arg_params.update({args: nd.array(params[args])}) + for aux in sym.list_auxiliary_states(): + if aux in params: + aux_params.update({aux: nd.array(params[aux])}) + return arg_params, aux_params + + + @staticmethod + def infer_output_shape(sym, params, in_shape): """Infer output shape by doing a forward pass using dummy inputs """ #create dummy input inputs = [np.random.randn(*input_shape) for input_shape in in_shape] + arg, aux = MxNetToONNXConverter.split_params(sym, params) return MxNetToONNXConverter.forward_pass(inputs, sym, arg, aux) # Add transpose? @@ -126,18 +143,14 @@ def infer_output_shape(sym, arg, aux, in_shape): def convert_weights_to_numpy(weights_dict): return dict([(k.replace("arg:", "").replace("aux:", ""), v.asnumpy()) for k, v in weights_dict.items()]) - def convert_mx2onnx_graph(self, sym, arg, aux, in_shape, in_type, log=False): + def convert_mx2onnx_graph(self, sym, params, in_shape, in_type, log=False): # Determine output shape - output_shape = MxNetToONNXConverter.infer_output_shape(sym, arg, aux, in_shape) + output_shape = MxNetToONNXConverter.infer_output_shape(sym, params, in_shape) print("\nconverting weights from MxNet NDArrays to NumPy arrays.\n") - params = {} - params.update(arg) - params.update(aux) - weights = MxNetToONNXConverter.convert_weights_to_numpy(params) - + weights = MxNetToONNXConverter.convert_weights_to_numpy(params) mx_graph = json.loads(sym.tojson())["nodes"] @@ -155,8 +168,10 @@ def convert_mx2onnx_graph(self, sym, arg, aux, in_shape, in_type, log=False): if log: print("Converting idx: %d, op: %s, name: %s" % (idx, op, name)) - if op == "null" and name not in arg and name not in aux: + if op == "null" and name not in params: """ Handling graph input """ + if name == 'softmax_label': + continue converted = MxNetToONNXConverter.convert_layer( node, is_input=True, @@ -224,8 +239,13 @@ def convert_mx2onnx_graph(self, sym, arg, aux, in_shape, in_type, log=False): raise ValueError("node is of an unrecognized type: %s" % type(node)) all_processed_nodes.append(converted_node) - if idx>0: - index_lookup.append(index_lookup[idx-1]+len(converted)) + + if idx > 0: + if name != 'softmax': + prev_index = index_lookup[idx-1] + else: + prev_index = index_lookup[idx - 2] + index_lookup.append(prev_index+len(converted)) else: index_lookup.append(len(converted) - 1) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 5b80aad6e1e7..85bf563364a4 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -635,7 +635,10 @@ def convert_dropout(node, **kwargs): input_name = kwargs["proc_nodes"][input_id].name attrs = node["attrs"] p = float(attrs["p"]) - is_test = 0 if str(attrs["mode"]) is "always" else 1 + if "mode" in attrs: + is_test = 0 if str(attrs["mode"]) is "always" else 1 + else: + is_test = 1 dropout_node = helper.make_node( "Dropout", [input_name], diff --git a/tests/python-pytest/onnx/export/backend.py b/tests/python-pytest/onnx/export/backend.py index 035be8b1907c..9ac260f6d6a1 100644 --- a/tests/python-pytest/onnx/export/backend.py +++ b/tests/python-pytest/onnx/export/backend.py @@ -117,9 +117,9 @@ def perform_import_export(graph_proto, input_shape): params.update(aux_params) # exporting to onnx graph proto format converter = MxNetToONNXConverter() - graph_proto = converter.convert_mx2onnx_graph(sym, arg_params, aux_params, in_shape=input_shape, in_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')]) + graph_proto = converter.convert_mx2onnx_graph(sym, params, in_shape=input_shape, in_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')]) - # importing back to MXNET for verfiying result. + # importing back to MXNET for verifying result. sym, arg_params, aux_params = graph.from_onnx(graph_proto) return sym, arg_params, aux_params @@ -219,11 +219,10 @@ def prepare(cls, model, device='CPU', **kwargs): Returns object of MXNetBackendRep class which will be in turn used to run inference on the input model and return the result for comparison. """ - graph = GraphProto() metadata = MXNetBackend.get_graph_metadata(model.graph) input_data = metadata['input_tensor_data'] - input_shape = [ data[1] for data in input_data] + input_shape = [data[1] for data in input_data] sym, arg_params, aux_params = MXNetBackend.perform_import_export(model.graph, input_shape) return MXNetBackendRep(sym, arg_params, aux_params, device) @@ -232,6 +231,7 @@ def supports_device(cls, device): """Supports only CPU for testing""" return device == 'CPU' + prepare = MXNetBackend.prepare run_node = MXNetBackend.run_node From 7cebe802843e7fc311abe71e6e6fb81c698c4a89 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Thu, 24 May 2018 14:26:41 -0700 Subject: [PATCH 24/82] enabled more tests --- tests/python-pytest/onnx/export/onnx_backend_test.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index d84955721342..76647829253f 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -127,15 +127,15 @@ ] STANDARD_MODEL = [ - # 'test_bvlc_alexnet', - # 'test_densenet121', + #'test_bvlc_alexnet', + #'test_densenet121', # 'test_inception_v1', # 'test_inception_v2', - # 'test_resnet50', - # 'test_shufflenet', + 'test_resnet50', + # # 'test_shufflenet', 'test_squeezenet', - # 'test_vgg16', - # 'test_vgg19' + 'test_vgg16', + 'test_vgg19' ] for op_test in IMPLEMENTED_OPERATORS_TEST: From 5ce301bfa2a0dd9933b5ef0cc23f8008681d3d61 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Wed, 30 May 2018 10:22:47 -0700 Subject: [PATCH 25/82] conv pad calc fixed --- python/mxnet/contrib/onnx/_export/op_translations.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 85bf563364a4..1182681041f0 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -116,8 +116,7 @@ def parse_helper(attrs_name, alt_value=None): pad_dims = list(parse_helper("pad", [0, 0])) num_group = int(attrs.get("num_group", 1)) - if len(pad_dims) < 2 * len(kernel_dims): - pad_dims = [0] * (2 * len(kernel_dims) - len(pad_dims)) + pad_dims + pad_dims = pad_dims + pad_dims input_nodes = [input_node, weights_node] if num_inputs > 2: From f8c0afc5d617d94c4477016f359750bb63218db1 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Wed, 30 May 2018 13:03:02 -0700 Subject: [PATCH 26/82] reshape op fix --- .../contrib/onnx/_export/op_translations.py | 42 ++++++++++++++----- .../onnx/export/onnx_backend_test.py | 8 ++-- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 1182681041f0..91ce42ca998f 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -322,6 +322,8 @@ def convert_string_to_list(string_val): val = val.replace("(", "") val = val.replace(")", "") val = val.replace("L", "") + val = val.replace("[", "") + val = val.replace("]", "") if val is not "": result_list.append(int(val)) @@ -679,12 +681,12 @@ def convert_mul_scalar(node, **kwargs): initializer = kwargs["initializer"] flag = True for i in initializer: - if i.name==a_node: + if i.name == a_node: new_initializer = scalar_mul_value[0]*numpy_helper.to_array(i) flag = False break - if flag == True: + if flag is True: np_arr = np.array(scalar_mul_value) data_type = mapping.NP_TYPE_TO_TENSOR_TYPE[np_arr.dtype] dims = np.shape(np_arr) @@ -1098,7 +1100,7 @@ def convert_floor(node, **kwargs): ) return [node] -#Changing shape and type. +# Changing shape and type. @mx2onnx.register("Reshape") def convert_reshape(node, **kwargs): name = node["name"] @@ -1106,25 +1108,43 @@ def convert_reshape(node, **kwargs): inputs = node["inputs"] attrs = node["attrs"] - output_shape = convert_string_to_list(attrs["shape"]) + output_shape_list = convert_string_to_list(attrs["shape"]) + + initializer = kwargs["initializer"] + output_shape_np = np.array(output_shape_list) + data_type = mapping.NP_TYPE_TO_TENSOR_TYPE[output_shape_np.dtype] + dims = np.shape(output_shape_np) + + output_shape_name = "reshape_attr_tensor" + str(kwargs["idx"]) + tensor_node = helper.make_tensor_value_info(output_shape_name, data_type, dims) + + initializer.append( + helper.make_tensor( + name=output_shape_name, + data_type=data_type, + dims=dims, + vals=output_shape_list, + raw=False, + ) + ) + input_node_idx = kwargs["index_lookup"][inputs[0][0]] input_node_name = proc_nodes[input_node_idx].name - not_supported_shape = [ -2, -3, -4] + not_supported_shape = [-2, -3, -4] - for val in output_shape: + for val in output_shape_list: if val in not_supported_shape: raise AttributeError("Shape value not supported in ONNX", val) - node = helper.make_node( + reshape_node = helper.make_node( "Reshape", - [input_node_name], + [input_node_name, output_shape_name], [name], - name=name, - shape=output_shape + name=name ) - return [node] + return [tensor_node, reshape_node] @mx2onnx.register("Cast") def convert_cast(node, **kwargs): diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index 76647829253f..b2bdd1f492a2 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -113,12 +113,12 @@ 'test_AvgPool2D', 'test_BatchNorm', 'test_ConstantPad2d', - # 'test_Conv2d', + 'test_Conv2d', # 'test_ELU', # 'test_LeakyReLU', # 'test_MaxPool', # 'test_PReLU', - 'test_ReLU', + # 'test_ReLU', 'test_Sigmoid', 'test_Softmax', 'test_softmax_functional', @@ -127,8 +127,8 @@ ] STANDARD_MODEL = [ - #'test_bvlc_alexnet', - #'test_densenet121', + 'test_bvlc_alexnet', + 'test_densenet121', # 'test_inception_v1', # 'test_inception_v2', 'test_resnet50', From cfc081bcb5a29ce473665df97275e6e99e11725e Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Wed, 30 May 2018 16:40:31 -0700 Subject: [PATCH 27/82] valueinfoproto fix, googlenet test added --- .../contrib/onnx/_export/export_model.py | 12 +- .../mxnet/contrib/onnx/_export/export_onnx.py | 7 +- .../contrib/onnx/_export/op_translations.py | 22 ++- .../onnx/export/onnx_export_test.py | 127 ++++++++++++++++++ 4 files changed, 157 insertions(+), 11 deletions(-) create mode 100644 tests/python-pytest/onnx/export/onnx_export_test.py diff --git a/python/mxnet/contrib/onnx/_export/export_model.py b/python/mxnet/contrib/onnx/_export/export_model.py index 4d6437012165..bc90e86c1f54 100644 --- a/python/mxnet/contrib/onnx/_export/export_model.py +++ b/python/mxnet/contrib/onnx/_export/export_model.py @@ -39,17 +39,19 @@ def export_model(model, weights, input_shape, input_type, onnx_file_path, log=Fa Path to the json file or Symbol object weights : str or symbol object Path to the params file or Params object. (Including both arg_params and aux_params) - input_shape : + input_shape : List of tuple Input shape of the model e.g (1,3,224,224) input_type : Input data type e.g. np.float32 + onnx_file_path : str + Path where to save the generated onnx file log : Boolean If true will print logs of the model conversion Returns ------- - onnx_model : onnx ModelProto - Onnx modelproto object + onnx_file_path : str + Onnx file path """ converter = MxNetToONNXConverter() @@ -60,7 +62,7 @@ def export_model(model, weights, input_shape, input_type, onnx_file_path, log=Fa mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype(input_type)], log=log) else: onnx_graph = converter.convert_mx2onnx_graph(model, weights, input_shape, - mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype(input_type)], log=log) + mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype(input_type)], log=log) # Create the model (ModelProto) onnx_model = helper.make_model(onnx_graph) @@ -70,4 +72,4 @@ def export_model(model, weights, input_shape, input_type, onnx_file_path, log=Fa f.write(serialized) print("\nONNX file %s serialized to disk" % onnx_file_path) - return onnx_model + return onnx_file_path diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 0478154f473c..5dd12d34c5f0 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -199,12 +199,9 @@ def convert_mx2onnx_graph(self, sym, params, in_shape, in_type, log=False): ) if isinstance(converted, list): - for converted_node in converted: + for converted_idx, converted_node in enumerate(converted): if isinstance(converted_node, onnx_pb2.ValueInfoProto): - if idx < (len(mx_graph) - 1): - onnx_processed_inputs.append(converted_node) - else: - onnx_processed_outputs.append(converted_node) + onnx_processed_inputs.append(converted_node) elif isinstance(converted_node, onnx_pb2.NodeProto): if idx < (len(mx_graph) - 1): onnx_processed_nodes.append(converted_node) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 91ce42ca998f..9756fed9b82a 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -1067,7 +1067,7 @@ def convert_addn(node, **kwargs): # Rounding @mx2onnx.register("ceil") -def convert_floor(node, **kwargs): +def convert_ceil(node, **kwargs): name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1225,6 +1225,26 @@ def convert_slice_channel(node, **kwargs): return [node] +@mx2onnx.register("expand_dims") +def convert_expand_dims(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + axis = int(node["attrs"]["axis"]) + + a = kwargs["index_lookup"][inputs[0][0]] + a_node = proc_nodes[a].name + + node = helper.make_node( + "Unsqueeze", + [a_node], + [name], + axes=[axis], + name=name, + ) + return [node] + + @mx2onnx.register("log") def convert_log(node, **kwargs): name = node["name"] diff --git a/tests/python-pytest/onnx/export/onnx_export_test.py b/tests/python-pytest/onnx/export/onnx_export_test.py new file mode 100644 index 000000000000..7bed47991eb6 --- /dev/null +++ b/tests/python-pytest/onnx/export/onnx_export_test.py @@ -0,0 +1,127 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +""" +Tests for individual operators +This module contains operator tests which currently do not exist on +ONNX backend test framework. Once we have PRs on the ONNX repo and get +those PRs merged, this file will get EOL'ed. +""" +# pylint: disable=too-many-locals,wrong-import-position,import-error +from __future__ import absolute_import +import sys +import os +import unittest +import logging +import tarfile +from collections import namedtuple +import numpy as np +import numpy.testing as npt +from onnx import numpy_helper +from onnx import TensorProto +from mxnet.test_utils import download +from mxnet.contrib import onnx as onnx_mxnet +import mxnet as mx +CURR_PATH = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) +sys.path.insert(0, os.path.join(CURR_PATH, '../../python/unittest')) + +URLS = { + 'bvlc_googlenet' : + 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/bvlc_googlenet.tar.gz', + 'bvlc_reference_caffenet' : + 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/bvlc_reference_caffenet.tar.gz', + 'bvlc_reference_rcnn_ilsvrc13' : + 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/bvlc_reference_rcnn_ilsvrc13.tar.gz', +} + +def get_test_files(name): + """Extract tar file and returns model path and input, output data""" + tar_name = download(URLS.get(name), dirname=CURR_PATH.__str__()) + # extract tar file + tar_path = os.path.join(CURR_PATH, tar_name) + tar = tarfile.open(tar_path.__str__(), "r:*") + tar.extractall(path=CURR_PATH.__str__()) + tar.close() + data_dir = os.path.join(CURR_PATH, name) + model_path = os.path.join(data_dir, 'model.onnx') + + inputs = [] + outputs = [] + # get test files + for test_file in os.listdir(data_dir): + case_dir = os.path.join(data_dir, test_file) + # skip the non-dir files + if not os.path.isdir(case_dir): + continue + input_file = os.path.join(case_dir, 'input_0.pb') + input_tensor = TensorProto() + with open(input_file, 'rb') as proto_file: + input_tensor.ParseFromString(proto_file.read()) + inputs.append(numpy_helper.to_array(input_tensor)) + + output_tensor = TensorProto() + output_file = os.path.join(case_dir, 'output_0.pb') + with open(output_file, 'rb') as proto_file: + output_tensor.ParseFromString(proto_file.read()) + outputs.append(numpy_helper.to_array(output_tensor)) + + return model_path, inputs, outputs + +def test_bvlc_googlenet(): + """ Tests Googlenet model for both onnx import and export""" + model_path, inputs, outputs = get_test_files('bvlc_googlenet') + logging.info("Translating Googlenet model from ONNX model zoo to Mxnet") + sym, arg_params, aux_params = onnx_mxnet.import_model(model_path) + params = {} + params.update(arg_params) + params.update(aux_params) + + onnx_file = model_path.rsplit('/', 1)[0] + "/exported_googlenet.onnx" + + logging.info("Translating converted googlenet model from mxnet to ONNX") + converted_model_path = onnx_mxnet.export_model(sym, params, [(1, 3, 224, 224)], np.float32, onnx_file) + + sym, arg_params, aux_params = onnx_mxnet.import_model(converted_model_path) + + metadata = onnx_mxnet.get_model_metadata(converted_model_path) + assert len(metadata) == 2 + assert metadata.get('input_tensor_data') + assert metadata.get('input_tensor_data') == [(u'data_0', (1, 3, 224, 224))] + assert metadata.get('output_tensor_data') + assert metadata.get('output_tensor_data') == [(u'softmax0', (1, 1000))] + data_names = [input_name[0] for input_name in metadata.get('input_tensor_data')] + + logging.info("Running inference on onnx re-import model in mxnet") + # run test for each test file + for input_data, output_data in zip(inputs, outputs): + # create module + mod = mx.mod.Module(symbol=sym, data_names=data_names, context=mx.cpu(), label_names=None) + mod.bind(for_training=False, data_shapes=[(data_names[0], input_data.shape)], label_shapes=None) + mod.set_params(arg_params=arg_params, aux_params=aux_params, + allow_missing=True, allow_extra=True) + # run inference + batch = namedtuple('Batch', ['data']) + mod.forward(batch([mx.nd.array(input_data)]), is_train=False) + + # verify the results + npt.assert_equal(mod.get_outputs()[0].shape, output_data.shape) + npt.assert_almost_equal(output_data, mod.get_outputs()[0].asnumpy(), decimal=3) + logging.info("Googlenet model conversion Successful") + + +if __name__ == '__main__': + unittest.main() From f070c228ed21dda092d5ffc404f497de53df4310 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Wed, 30 May 2018 14:26:21 -0700 Subject: [PATCH 28/82] Added support for elu, leakyRelu, prelu --- .../contrib/onnx/_export/op_translations.py | 34 +++++++++++++++++++ .../onnx/export/onnx_backend_test.py | 8 ++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 9756fed9b82a..8736cffb6acf 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -515,6 +515,40 @@ def convert_exp(node, **kwargs): return [node] +@mx2onnx.register("LeakyReLU") +def convert_leakyrelu(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + a = kwargs["index_lookup"][inputs[0][0]] + a_node = proc_nodes[a].name + attrs = node["attrs"] + + act_type = attrs.get("act_type", "LeakyRelu") + alpha = float(attrs.get("slope", 0.25)) + + act_name = {"elu": "Elu", "LeakyRelu": "LeakyRelu", "prelu": "PRelu"} + + if act_type == "prelu": + alpha_node_index = kwargs["index_lookup"][inputs[1][0]] + alpha_node_name = proc_nodes[alpha_node_index].name + + node = helper.make_node( + act_name[act_type], + inputs=[a_node, alpha_node_name], + outputs=[name], + name=name) + else: + node = helper.make_node( + act_name[act_type], + inputs=[a_node], + outputs=[name], + name=name, + alpha=alpha) + + return [node] + + @mx2onnx.register("softmax") def convert_softmax(node, **kwargs): inputs = node["inputs"] diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index b2bdd1f492a2..2eed4c03c4db 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -114,11 +114,11 @@ 'test_BatchNorm', 'test_ConstantPad2d', 'test_Conv2d', - # 'test_ELU', - # 'test_LeakyReLU', + 'test_ELU', + 'test_LeakyReLU', # 'test_MaxPool', - # 'test_PReLU', - # 'test_ReLU', + 'test_PReLU', + 'test_ReLU', 'test_Sigmoid', 'test_Softmax', 'test_softmax_functional', From bb51c7f17625ec6d674a781ca9878b9c69b2ea3b Mon Sep 17 00:00:00 2001 From: spidyDev Date: Wed, 30 May 2018 16:07:04 -0700 Subject: [PATCH 29/82] Cleanup - Removed run_node, not needed anymore. - Used correct get_metadata api --- .../onnx/export/onnx_backend_test.py | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index 2eed4c03c4db..fbeab821ca7c 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -127,25 +127,26 @@ ] STANDARD_MODEL = [ - 'test_bvlc_alexnet', - 'test_densenet121', - # 'test_inception_v1', - # 'test_inception_v2', - 'test_resnet50', - # # 'test_shufflenet', - 'test_squeezenet', - 'test_vgg16', - 'test_vgg19' + 'test_bvlc_googlenet', + # 'test_bvlc_alexnet', + # 'test_densenet121', + # # 'test_inception_v1', + # # 'test_inception_v2', + # 'test_resnet50', + # # # 'test_shufflenet', + # 'test_squeezenet', + # 'test_vgg16', + # 'test_vgg19' ] -for op_test in IMPLEMENTED_OPERATORS_TEST: - BACKEND_TEST.include(op_test) - +# for op_test in IMPLEMENTED_OPERATORS_TEST: +# BACKEND_TEST.include(op_test) +# for basic_model_test in BASIC_MODEL_TESTS: BACKEND_TEST.include(basic_model_test) -for std_model_test in STANDARD_MODEL: - BACKEND_TEST.include(std_model_test) +# for std_model_test in STANDARD_MODEL: +# BACKEND_TEST.include(std_model_test) # import all test cases at global scope to make them visible to python.unittest From ae2f2c28ba67059baf551cad3c537029b69a99d9 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Thu, 31 May 2018 10:25:59 -0700 Subject: [PATCH 30/82] dilation added --- python/mxnet/contrib/onnx/_export/op_translations.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 8736cffb6acf..ea23fbeb9115 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -110,11 +110,11 @@ def parse_helper(attrs_name, alt_value=None): raise AttributeError("Malformed %s dimensions: %s" % (attrs_name, str(attrs_str))) return alt_value - num_filter = int(attrs["num_filter"]) kernel_dims = list(parse_helper("kernel")) stride_dims = list(parse_helper("stride", [1, 1])) pad_dims = list(parse_helper("pad", [0, 0])) num_group = int(attrs.get("num_group", 1)) + dilations = list(parse_helper("dilate", [1, 1])) pad_dims = pad_dims + pad_dims @@ -128,6 +128,7 @@ def parse_helper(attrs_name, alt_value=None): outputs=[name], kernel_shape=kernel_dims, strides=stride_dims, + dilations=dilations, pads=pad_dims, group=num_group, name=name From 94d8614c9f505d60a7b2ae178cb1fa3b4b4a7fef Mon Sep 17 00:00:00 2001 From: spidyDev Date: Thu, 31 May 2018 08:25:02 -0700 Subject: [PATCH 31/82] Removed redundant code. - run_node - Using correct get_metadata_api --- tests/python-pytest/onnx/export/backend.py | 145 +----------------- .../onnx/export/onnx_backend_test.py | 19 ++- 2 files changed, 11 insertions(+), 153 deletions(-) diff --git a/tests/python-pytest/onnx/export/backend.py b/tests/python-pytest/onnx/export/backend.py index 9ac260f6d6a1..1ee5b008445e 100644 --- a/tests/python-pytest/onnx/export/backend.py +++ b/tests/python-pytest/onnx/export/backend.py @@ -37,73 +37,6 @@ class MXNetBackend(Backend): """MXNet backend for ONNX""" - @staticmethod - def make_graph(node, inputs): - """ Created ONNX GraphProto from node""" - initializer = [] - tensor_input_info = [] - tensor_output_info = [] - - # Adding input tensor info. - for index in range(len(node.input)): - tensor_input_info.append( - helper.make_tensor_value_info(str(node.input[index]), TensorProto.FLOAT, [1])) - - # Creating an initializer for Weight params. - # Assumes that weight params is named as 'W'. - if node.input[index] == 'W': - dim = inputs[index].shape - param_tensor = helper.make_tensor( - name=node.input[index], - data_type=TensorProto.FLOAT, - dims=dim, - vals=inputs[index].flatten()) - - initializer.append(param_tensor) - - # Adding output tensor info. - for index in range(len(node.output)): - tensor_output_info.append( - helper.make_tensor_value_info(str(node.output[index]), TensorProto.FLOAT, [1])) - - # creating graph proto object. - graph_proto = helper.make_graph( - [node], - "test", - tensor_input_info, - tensor_output_info, - initializer=initializer) - - return graph_proto - - @staticmethod - def get_graph_metadata(graph): - """ - Get metadata from a given onnx graph. - """ - _params = set() - for tensor_vals in graph.initializer: - _params.add(tensor_vals.name) - - input_data = [] - for graph_input in graph.input: - shape = [] - if graph_input.name not in _params: - for val in graph_input.type.tensor_type.shape.dim: - shape.append(val.dim_value) - input_data.append((graph_input.name, tuple(shape))) - - output_data = [] - for graph_out in graph.output: - shape = [] - for val in graph_out.type.tensor_type.shape.dim: - shape.append(val.dim_value) - output_data.append((graph_out.name, tuple(shape))) - metadata = {'input_tensor_data' : input_data, - 'output_tensor_data' : output_data - } - return metadata - @staticmethod def perform_import_export(graph_proto, input_shape): """ Import ONNX model to mxnet model and then export to ONNX model @@ -124,81 +57,6 @@ def perform_import_export(graph_proto, input_shape): return sym, arg_params, aux_params - @classmethod - def run_node(cls, node, inputs, device='CPU'): - """Running individual node inference on mxnet engine and - return the result to onnx test infrastructure. - - Parameters - ---------- - node : onnx node object - loaded onnx node (individual layer) - inputs : numpy array - input to run a node on - device : 'CPU' - device to run a node on - - Returns - ------- - params : numpy array - result obtained after running the operator - """ - - input_shape = [graph_input.shape for graph_input in inputs] - sym, arg_params, aux_params = MXNetBackend.perform_import_export(MXNetBackend.make_graph(node, inputs), input_shape) - - data_names = [graph_input for graph_input in sym.list_inputs() - if graph_input not in arg_params and graph_input not in aux_params] - data_shapes = [] - dim_change_op_types = set(['ReduceMin', 'ReduceMax', 'ReduceMean', - 'ReduceProd', 'ReduceSum', 'Slice', 'Pad', - 'Squeeze', 'Upsample', 'Reshape', 'Conv', - 'Concat', 'Softmax', 'Flatten', 'Transpose', - 'GlobalAveragePool', 'GlobalMaxPool', 'MaxPool']) - - # Adding extra dimension of batch_size 1 if the batch_size is different for multiple inputs. - for idx, input_name in enumerate(data_names): - batch_size = 1 - if len(inputs) > 1 and len(inputs[idx].shape) < 4 and \ - len(set(x.shape[0] for x in inputs)) != 1: - tuples = ((batch_size,), inputs[idx].shape) - new_shape = sum(tuples, ()) - data_shapes.append((input_name, new_shape)) - else: - data_shapes.append((input_name, inputs[idx].shape)) - - # create module, passing cpu context - if device == 'CPU': - ctx = mx.cpu() - else: - raise NotImplementedError("Only CPU context is supported for now") - - # create a module - mod = mx.mod.Module(symbol=sym, data_names=data_names, context=ctx, label_names=None) - mod.bind(for_training=False, data_shapes=data_shapes, label_shapes=None) - - # initializing parameters for calculating result of each individual node - if arg_params is None and aux_params is None: - mod.init_params() - else: - mod.set_params(arg_params=arg_params, aux_params=aux_params) - - data_forward = [] - for idx, input_name in enumerate(data_names): - # slice and pad operator tests needs 1 less dimension in forward pass - # otherwise it will throw an error. - # for squeeze operator, need to retain shape of input as provided - val = inputs[idx] - if node.op_type in dim_change_op_types: - data_forward.append(mx.nd.array(val)) - else: - data_forward.append(mx.nd.array([val])) - - mod.forward(mx.io.DataBatch(data_forward)) - result = mod.get_outputs()[0].asnumpy() - if node.op_type in dim_change_op_types: - return [result] - return result @classmethod def prepare(cls, model, device='CPU', **kwargs): @@ -220,7 +78,8 @@ def prepare(cls, model, device='CPU', **kwargs): used to run inference on the input model and return the result for comparison. """ - metadata = MXNetBackend.get_graph_metadata(model.graph) + graph = GraphProto() + metadata = graph.get_graph_metadata(model.graph) input_data = metadata['input_tensor_data'] input_shape = [data[1] for data in input_data] sym, arg_params, aux_params = MXNetBackend.perform_import_export(model.graph, input_shape) diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index fbeab821ca7c..f04a4dcb2fb4 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -127,16 +127,15 @@ ] STANDARD_MODEL = [ - 'test_bvlc_googlenet', - # 'test_bvlc_alexnet', - # 'test_densenet121', - # # 'test_inception_v1', - # # 'test_inception_v2', - # 'test_resnet50', - # # # 'test_shufflenet', - # 'test_squeezenet', - # 'test_vgg16', - # 'test_vgg19' + 'test_bvlc_alexnet', + 'test_densenet121', + # 'test_inception_v1', + # 'test_inception_v2', + 'test_resnet50', + # # 'test_shufflenet', + 'test_squeezenet', + 'test_vgg16', + 'test_vgg19' ] # for op_test in IMPLEMENTED_OPERATORS_TEST: From f8943b3c096300033ad565414e7eb4e01f72e0bf Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Thu, 31 May 2018 14:55:43 -0700 Subject: [PATCH 32/82] some fixes to make export work with onx1.2.1 --- .../contrib/onnx/_export/op_translations.py | 61 +++---------------- 1 file changed, 9 insertions(+), 52 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index ea23fbeb9115..ce7a595eb70f 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -33,9 +33,6 @@ import re -import sys - - def looks_like_weight(name): """Internal helper to figure out if node should be hidden with `hide_weights`. """ @@ -159,7 +156,6 @@ def convert_fully_connected(node, **kwargs): [name], # output alpha=1.0, beta=1.0, - broadcast=True, transA=False, transB=True, name=name @@ -204,10 +200,8 @@ def convert_batchnorm(node, **kwargs): name=name, epsilon=eps, momentum=momentum, - is_test=1, spatial=1 ) - return [bn_node] @@ -481,7 +475,6 @@ def convert_pooling(node, **kwargs): pool_types[pool_type], [input_node.name], # input [name], - # dilations = [0, 0], kernel_shape=kernel, pads=[0, 0], strides=stride, @@ -671,16 +664,12 @@ def convert_dropout(node, **kwargs): input_name = kwargs["proc_nodes"][input_id].name attrs = node["attrs"] p = float(attrs["p"]) - if "mode" in attrs: - is_test = 0 if str(attrs["mode"]) is "always" else 1 - else: - is_test = 1 + dropout_node = helper.make_node( "Dropout", [input_name], [name], ratio=p, - is_test=is_test, name=name ) return [dropout_node] @@ -743,9 +732,7 @@ def convert_mul_scalar(node, **kwargs): "Mul", [a_node, scalar_op_name], [name], - name=name, - broadcast=1, - axis=3 + name=name ) return [tensor_node, mul_node] @@ -900,8 +887,6 @@ def covert_broadcast_add(node, **kwargs): "Add", [a_node, b_node], [name], - broadcast=1, - axis=1, name=name, ) @@ -945,7 +930,6 @@ def covert_broadcast_sub(node, **kwargs): "Sub", [a_node, b_node], [name], - broadcast=1, name=name, ) @@ -989,9 +973,7 @@ def convert_mul(node, **kwargs): "Mul", [a_node, b_node], [name], - name=name, - broadcast=1, - axis=1 + name=name ) return [mul_node] @@ -1013,7 +995,7 @@ def convert_mul(node, **kwargs): "Div", [a_node, b_node], [name], - name=name, + name=name ) return [div_node] @@ -1035,8 +1017,7 @@ def convert_div(node, **kwargs): "Div", [a_node, b_node], [name], - name=name, - broadcast=1 + name=name ) return [div_node] @@ -1076,7 +1057,7 @@ def convert_abs(node, **kwargs): "Abs", [a_node], [name], - name=name, + name=name ) return [abs_node] @@ -1096,7 +1077,7 @@ def convert_addn(node, **kwargs): "Sum", input_list, [name], - name=name, + name=name ) return [sum_node] @@ -1114,7 +1095,7 @@ def convert_ceil(node, **kwargs): "Ceil", [a_node], [name], - name=name, + name=name ) return [node] @@ -1131,7 +1112,7 @@ def convert_floor(node, **kwargs): "Floor", [a_node], [name], - name=name, + name=name ) return [node] @@ -1336,30 +1317,6 @@ def convert_power(node, **kwargs): ) return [node] -#[TODO] broadcast_power with axis -@mx2onnx.register("broadcast_power") -def convert_power(node, **kwargs): - name = node["name"] - proc_nodes = kwargs["proc_nodes"] - inputs = node["inputs"] - - a = kwargs["index_lookup"][inputs[0][0]] - b = kwargs["index_lookup"][inputs[1][0]] - - a_node = proc_nodes[a].name - b_node = proc_nodes[b].name - - node = helper.make_node( - "Pow", - [a_node, b_node], - outputs=[name], - name=name, - axis=1, - broadcast=1, - ) - return [node] - - @mx2onnx.register("sqrt") def convert_sqrt(node, **kwargs): name = node["name"] From 1794554676bc2b4d9d3a1f863bf52bed5e8a6479 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Thu, 31 May 2018 13:37:02 -0700 Subject: [PATCH 33/82] Lint fixes --- .../contrib/onnx/_export/export_helper.py | 8 ++++---- .../contrib/onnx/_export/export_model.py | 19 +++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_helper.py b/python/mxnet/contrib/onnx/_export/export_helper.py index c3e230c51600..427c78dce9c6 100644 --- a/python/mxnet/contrib/onnx/_export/export_helper.py +++ b/python/mxnet/contrib/onnx/_export/export_helper.py @@ -18,7 +18,6 @@ # coding: utf-8 import os import mxnet as mx -import numpy as np def load_module(json_path, params_path, input_shape): """Loads the MXNet model file, retrieves symbol and parameters and returns. @@ -47,13 +46,14 @@ def load_module(json_path, params_path, input_shape): model_name = json_path.rsplit('.', 1)[0].rsplit('-', 1)[0] num_epochs = int(params_path.rsplit('.', 1)[0].rsplit('-', 1)[1]) except IndexError: - print("Model and params name should be in format: prefix-symbol.json, prefix-epoch.params") + print("Model and params name should be in format: " + "prefix-symbol.json, prefix-epoch.params") raise sym, arg_params, aux_params = mx.model.load_checkpoint(model_name, num_epochs) trained_model = mx.mod.Module(symbol=sym, label_names=None) - trained_model.bind(data_shapes=[('data', input_shape[0])], label_shapes=trained_model._label_shapes, - for_training=False) + trained_model.bind(data_shapes=[('data', input_shape[0])], + label_shapes=trained_model.label_shapes, for_training=False) # Merging arg and aux parameters params = {} diff --git a/python/mxnet/contrib/onnx/_export/export_model.py b/python/mxnet/contrib/onnx/_export/export_model.py index bc90e86c1f54..595f34a11f35 100644 --- a/python/mxnet/contrib/onnx/_export/export_model.py +++ b/python/mxnet/contrib/onnx/_export/export_model.py @@ -21,12 +21,12 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals +import numpy as np -from onnx import defs, checker, helper, numpy_helper, mapping +from onnx import helper, mapping from .export_onnx import MxNetToONNXConverter from .export_helper import load_module -import numpy as np def export_model(model, weights, input_shape, input_type, onnx_file_path, log=False): """Exports the MXNet model file, passed as a parameter, into ONNX model. @@ -55,21 +55,24 @@ def export_model(model, weights, input_shape, input_type, onnx_file_path, log=Fa """ converter = MxNetToONNXConverter() + data_format = np.dtype(input_type) if isinstance(model, basestring) and isinstance(weights, basestring): print("Converting json and params file to sym and weights") sym, params = load_module(model, weights, input_shape) onnx_graph = converter.convert_mx2onnx_graph(sym, params, input_shape, - mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype(input_type)], log=log) + mapping.NP_TYPE_TO_TENSOR_TYPE[data_format], + log=log) else: onnx_graph = converter.convert_mx2onnx_graph(model, weights, input_shape, - mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype(input_type)], log=log) + mapping.NP_TYPE_TO_TENSOR_TYPE[data_format], + log=log) # Create the model (ModelProto) onnx_model = helper.make_model(onnx_graph) # Save model on disk - with open(onnx_file_path, "wb") as f: - serialized = onnx_model.SerializeToString() - f.write(serialized) - print("\nONNX file %s serialized to disk" % onnx_file_path) + with open(onnx_file_path, "wb") as file_handle: + serialized = onnx_model.SerializeToString() + file_handle.write(serialized) + print("\nONNX file %s serialized to disk" % onnx_file_path) return onnx_file_path From 628b1cd4b6f137258271300a222cda413f0ac807 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Thu, 31 May 2018 14:09:00 -0700 Subject: [PATCH 34/82] lint fixes --- .../mxnet/contrib/onnx/_export/export_onnx.py | 71 +++++++------------ 1 file changed, 25 insertions(+), 46 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 5dd12d34c5f0..0815369d5a43 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -23,22 +23,20 @@ from __future__ import print_function from __future__ import unicode_literals +import json import numpy as np -import json -from .... import symbol +from onnx import (checker, helper, onnx_pb2) +from onnx.helper import make_tensor_value_info + from .... import context from .... import ndarray as nd from .... import io from .... import module as mod -from onnx import (defs, checker, helper, numpy_helper, mapping, onnx_pb2, - ModelProto, GraphProto, NodeProto, AttributeProto, TensorProto) - -from onnx.helper import make_tensor, make_tensor_value_info - class MxNetToONNXConverter: + """Class to convert MXNet to ONNX graph""" registry_ = {} input_output_maps_ = {} @@ -50,8 +48,9 @@ def __init__(self): @staticmethod def register(op_name): - + """Register operator""" def wrapper(func): + """Helper function to map functions""" MxNetToONNXConverter.registry_[op_name] = func return func @@ -59,6 +58,7 @@ def wrapper(func): @staticmethod def convert_layer(node, **kwargs): + """Convert MXNet layer to ONNX""" op = str(node["op"]) if op not in MxNetToONNXConverter.registry_: raise AttributeError("No conversion function registered for op type %s yet." % op) @@ -73,18 +73,9 @@ def forward_pass(inputs, sym, arg_params, aux_params): and graph_input != 'softmax_label'] data_shapes = [] - dim_added = False # Adding extra dimension of batch_size 1 if the batch_size is different for multiple inputs. for idx, input_name in enumerate(data_names): - batch_size = 1 - if len(inputs) > 1 and len(inputs[idx].shape) < 4 and \ - len(set(x.shape[0] for x in inputs)) != 1: - tuples = ((batch_size,), inputs[idx].shape) - new_shape = sum(tuples, ()) - data_shapes.append((input_name, new_shape)) - dim_added = True - else: - data_shapes.append((input_name, inputs[idx].shape)) + data_shapes.append((input_name, inputs[idx].shape)) # create module, passing cpu context ctx = context.cpu() @@ -99,27 +90,19 @@ def forward_pass(inputs, sym, arg_params, aux_params): data_forward = [] for idx, input_name in enumerate(data_names): - # slice and pad operator tests needs 1 less dimension in forward pass - # otherwise it will throw an error. - # for squeeze operator, need to retain shape of input as provided val = inputs[idx] - if dim_added is True: - data_forward.append(nd.array([val])) - else: - data_forward.append(nd.array(val)) + data_forward.append(nd.array(val)) test_mod.forward(io.DataBatch(data_forward)) result = test_mod.get_outputs()[0].asnumpy() - if dim_added is True: - return result[0].shape - else: - return result.shape + + return result.shape @staticmethod def split_params(sym, params): - # splitting params into args and aux params - arg_params ={} + """splitting params into args and aux params""" + arg_params = {} aux_params = {} for args in sym.list_arguments(): if args in params: @@ -138,18 +121,19 @@ def infer_output_shape(sym, params, in_shape): arg, aux = MxNetToONNXConverter.split_params(sym, params) return MxNetToONNXConverter.forward_pass(inputs, sym, arg, aux) - # Add transpose? + @staticmethod def convert_weights_to_numpy(weights_dict): - return dict([(k.replace("arg:", "").replace("aux:", ""), v.asnumpy()) for k, v in weights_dict.items()]) + """Convert weights to numpy""" + return dict([(k.replace("arg:", "").replace("aux:", ""), v.asnumpy()) + for k, v in weights_dict.items()]) def convert_mx2onnx_graph(self, sym, params, in_shape, in_type, log=False): + """Convert MXNet graph to ONNX graph""" # Determine output shape output_shape = MxNetToONNXConverter.infer_output_shape(sym, params, in_shape) - print("\nconverting weights from MxNet NDArrays to NumPy arrays.\n") - weights = MxNetToONNXConverter.convert_weights_to_numpy(params) mx_graph = json.loads(sym.tojson())["nodes"] @@ -161,7 +145,7 @@ def convert_mx2onnx_graph(self, sym, params, in_shape, in_type, log=False): onnx_processed_outputs = [] index_lookup = [] - graph_input_idx=0 + graph_input_idx = 0 for idx, node in enumerate(mx_graph): op = node["op"] name = node["name"] @@ -169,7 +153,7 @@ def convert_mx2onnx_graph(self, sym, params, in_shape, in_type, log=False): print("Converting idx: %d, op: %s, name: %s" % (idx, op, name)) if op == "null" and name not in params: - """ Handling graph input """ + #Handling graph input if name == 'softmax_label': continue converted = MxNetToONNXConverter.convert_layer( @@ -196,10 +180,10 @@ def convert_mx2onnx_graph(self, sym, params, in_shape, in_type, log=False): initializer=initializer, index_lookup=index_lookup, idx=idx - ) + ) if isinstance(converted, list): - for converted_idx, converted_node in enumerate(converted): + for converted_node in converted: if isinstance(converted_node, onnx_pb2.ValueInfoProto): onnx_processed_inputs.append(converted_node) elif isinstance(converted_node, onnx_pb2.NodeProto): @@ -211,7 +195,7 @@ def convert_mx2onnx_graph(self, sym, params, in_shape, in_type, log=False): onnx_processed_outputs.append( make_tensor_value_info( name=converted_node.output[0], - elem_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')], + elem_type=in_type, shape=output_shape ) ) @@ -219,7 +203,7 @@ def convert_mx2onnx_graph(self, sym, params, in_shape, in_type, log=False): onnx_processed_outputs.append( make_tensor_value_info( name=converted_node.name, - elem_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')], + elem_type=in_type, shape=output_shape ) ) @@ -227,12 +211,7 @@ def convert_mx2onnx_graph(self, sym, params, in_shape, in_type, log=False): print("Output node is: %s" % converted_node.name) elif isinstance(converted_node, onnx_pb2.TensorProto): raise ValueError("Did not expect TensorProto") - if idx < (len(mx_graph) - 1): - onnx_processed_inputs.append(converted_node) - else: - onnx_processed_outputs.append(converted_node) else: - print(converted_node) raise ValueError("node is of an unrecognized type: %s" % type(node)) all_processed_nodes.append(converted_node) From 0d503e64e1eaba41453d1b61d68a2bb76a409bbd Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Thu, 31 May 2018 14:57:54 -0700 Subject: [PATCH 35/82] enabled more tests --- tests/python-pytest/onnx/export/onnx_backend_test.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index f04a4dcb2fb4..6ba897be5479 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -116,7 +116,7 @@ 'test_Conv2d', 'test_ELU', 'test_LeakyReLU', - # 'test_MaxPool', + 'test_MaxPool', 'test_PReLU', 'test_ReLU', 'test_Sigmoid', @@ -132,7 +132,7 @@ # 'test_inception_v1', # 'test_inception_v2', 'test_resnet50', - # # 'test_shufflenet', + # 'test_shufflenet', 'test_squeezenet', 'test_vgg16', 'test_vgg19' @@ -140,12 +140,12 @@ # for op_test in IMPLEMENTED_OPERATORS_TEST: # BACKEND_TEST.include(op_test) -# + for basic_model_test in BASIC_MODEL_TESTS: BACKEND_TEST.include(basic_model_test) -# for std_model_test in STANDARD_MODEL: -# BACKEND_TEST.include(std_model_test) +for std_model_test in STANDARD_MODEL: + BACKEND_TEST.include(std_model_test) # import all test cases at global scope to make them visible to python.unittest From d6e73dc86f1380c13d53393a7f3a2807f49a44b7 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Thu, 31 May 2018 15:59:42 -0700 Subject: [PATCH 36/82] mxnet_export_test file added --- .../onnx/export/mxnet_export_test.py | 129 ++++++++++++++++++ .../onnx/export/onnx_export_test.py | 26 ++-- 2 files changed, 143 insertions(+), 12 deletions(-) create mode 100644 tests/python-pytest/onnx/export/mxnet_export_test.py diff --git a/tests/python-pytest/onnx/export/mxnet_export_test.py b/tests/python-pytest/onnx/export/mxnet_export_test.py new file mode 100644 index 000000000000..82bec7b9dd21 --- /dev/null +++ b/tests/python-pytest/onnx/export/mxnet_export_test.py @@ -0,0 +1,129 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +""" +Tests for individual operators +This module contains operator tests which currently do not exist on +ONNX backend test framework. Once we have PRs on the ONNX repo and get +those PRs merged, this file will get EOL'ed. +""" +# pylint: disable=too-many-locals,wrong-import-position,import-error +from __future__ import absolute_import +import sys +import os +import logging +import tarfile +from collections import namedtuple +import numpy as np +import numpy.testing as npt +from onnx import numpy_helper +from onnx import TensorProto +from mxnet.test_utils import download +from mxnet.contrib import onnx as onnx_mxnet +import mxnet as mx +CURR_PATH = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) +sys.path.insert(0, os.path.join(CURR_PATH, '../../python/unittest')) + +URLS = { + 'bvlc_googlenet': + 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/bvlc_googlenet.tar.gz', + 'bvlc_reference_caffenet': + 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/bvlc_reference_caffenet.tar.gz', + 'bvlc_reference_rcnn_ilsvrc13': + 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/bvlc_reference_rcnn_ilsvrc13.tar.gz', +} + +def get_test_files(name): + """Extract tar file and returns model path and input, output data""" + tar_name = download(URLS.get(name), dirname=CURR_PATH.__str__()) + # extract tar file + tar_path = os.path.join(CURR_PATH, tar_name) + tar = tarfile.open(tar_path.__str__(), "r:*") + tar.extractall(path=CURR_PATH.__str__()) + tar.close() + data_dir = os.path.join(CURR_PATH, name) + model_path = os.path.join(data_dir, 'model.onnx') + + inputs = [] + outputs = [] + # get test files + for test_file in os.listdir(data_dir): + case_dir = os.path.join(data_dir, test_file) + # skip the non-dir files + if not os.path.isdir(case_dir): + continue + input_file = os.path.join(case_dir, 'input_0.pb') + input_tensor = TensorProto() + with open(input_file, 'rb') as proto_file: + input_tensor.ParseFromString(proto_file.read()) + inputs.append(numpy_helper.to_array(input_tensor)) + + output_tensor = TensorProto() + output_file = os.path.join(case_dir, 'output_0.pb') + with open(output_file, 'rb') as proto_file: + output_tensor.ParseFromString(proto_file.read()) + outputs.append(numpy_helper.to_array(output_tensor)) + + return model_path, inputs, outputs + +def test_models(model_name, input_shape, output_shape): + """ Tests Googlenet model for both onnx import and export""" + model_path, inputs, outputs = get_test_files(model_name) + logging.info("Translating Googlenet model from ONNX model zoo to Mxnet") + sym, arg_params, aux_params = onnx_mxnet.import_model(model_path) + params = {} + params.update(arg_params) + params.update(aux_params) + + onnx_file = model_path.rsplit('/', 1)[0] + "/exported_"+model_name+".onnx" + + logging.info("Translating converted googlenet model from mxnet to ONNX") + converted_model_path = onnx_mxnet.export_model(sym, params, [input_shape], np.float32, onnx_file) + + sym, arg_params, aux_params = onnx_mxnet.import_model(converted_model_path) + + metadata = onnx_mxnet.get_model_metadata(converted_model_path) + assert len(metadata) == 2 + assert metadata.get('input_tensor_data') + assert metadata.get('input_tensor_data')[0][1] == input_shape + assert metadata.get('output_tensor_data') + assert metadata.get('output_tensor_data')[0][1] == output_shape + data_names = [input_name[0] for input_name in metadata.get('input_tensor_data')] + + logging.info("Running inference on onnx re-import model in mxnet") + # run test for each test file + for input_data, output_data in zip(inputs, outputs): + # create module + mod = mx.mod.Module(symbol=sym, data_names=data_names, context=mx.cpu(), label_names=None) + mod.bind(for_training=False, data_shapes=[(data_names[0], input_data.shape)], label_shapes=None) + mod.set_params(arg_params=arg_params, aux_params=aux_params, + allow_missing=True, allow_extra=True) + # run inference + batch = namedtuple('Batch', ['data']) + mod.forward(batch([mx.nd.array(input_data)]), is_train=False) + + # verify the results + npt.assert_equal(mod.get_outputs()[0].shape, output_data.shape) + npt.assert_almost_equal(output_data, mod.get_outputs()[0].asnumpy(), decimal=3) + logging.info(model_name + " conversion successful") + + +if __name__ == '__main__': + test_models("bvlc_googlenet", (1, 3, 224, 224), (1, 1000)) + test_models("bvlc_reference_caffenet", (1, 3, 224, 224), (1, 1000)) + test_models("bvlc_reference_rcnn_ilsvrc13", (1, 3, 224, 224), (1, 200)) + diff --git a/tests/python-pytest/onnx/export/onnx_export_test.py b/tests/python-pytest/onnx/export/onnx_export_test.py index 7bed47991eb6..82bec7b9dd21 100644 --- a/tests/python-pytest/onnx/export/onnx_export_test.py +++ b/tests/python-pytest/onnx/export/onnx_export_test.py @@ -25,7 +25,6 @@ from __future__ import absolute_import import sys import os -import unittest import logging import tarfile from collections import namedtuple @@ -40,11 +39,11 @@ sys.path.insert(0, os.path.join(CURR_PATH, '../../python/unittest')) URLS = { - 'bvlc_googlenet' : + 'bvlc_googlenet': 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/bvlc_googlenet.tar.gz', - 'bvlc_reference_caffenet' : + 'bvlc_reference_caffenet': 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/bvlc_reference_caffenet.tar.gz', - 'bvlc_reference_rcnn_ilsvrc13' : + 'bvlc_reference_rcnn_ilsvrc13': 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/bvlc_reference_rcnn_ilsvrc13.tar.gz', } @@ -81,28 +80,28 @@ def get_test_files(name): return model_path, inputs, outputs -def test_bvlc_googlenet(): +def test_models(model_name, input_shape, output_shape): """ Tests Googlenet model for both onnx import and export""" - model_path, inputs, outputs = get_test_files('bvlc_googlenet') + model_path, inputs, outputs = get_test_files(model_name) logging.info("Translating Googlenet model from ONNX model zoo to Mxnet") sym, arg_params, aux_params = onnx_mxnet.import_model(model_path) params = {} params.update(arg_params) params.update(aux_params) - onnx_file = model_path.rsplit('/', 1)[0] + "/exported_googlenet.onnx" + onnx_file = model_path.rsplit('/', 1)[0] + "/exported_"+model_name+".onnx" logging.info("Translating converted googlenet model from mxnet to ONNX") - converted_model_path = onnx_mxnet.export_model(sym, params, [(1, 3, 224, 224)], np.float32, onnx_file) + converted_model_path = onnx_mxnet.export_model(sym, params, [input_shape], np.float32, onnx_file) sym, arg_params, aux_params = onnx_mxnet.import_model(converted_model_path) metadata = onnx_mxnet.get_model_metadata(converted_model_path) assert len(metadata) == 2 assert metadata.get('input_tensor_data') - assert metadata.get('input_tensor_data') == [(u'data_0', (1, 3, 224, 224))] + assert metadata.get('input_tensor_data')[0][1] == input_shape assert metadata.get('output_tensor_data') - assert metadata.get('output_tensor_data') == [(u'softmax0', (1, 1000))] + assert metadata.get('output_tensor_data')[0][1] == output_shape data_names = [input_name[0] for input_name in metadata.get('input_tensor_data')] logging.info("Running inference on onnx re-import model in mxnet") @@ -120,8 +119,11 @@ def test_bvlc_googlenet(): # verify the results npt.assert_equal(mod.get_outputs()[0].shape, output_data.shape) npt.assert_almost_equal(output_data, mod.get_outputs()[0].asnumpy(), decimal=3) - logging.info("Googlenet model conversion Successful") + logging.info(model_name + " conversion successful") if __name__ == '__main__': - unittest.main() + test_models("bvlc_googlenet", (1, 3, 224, 224), (1, 1000)) + test_models("bvlc_reference_caffenet", (1, 3, 224, 224), (1, 1000)) + test_models("bvlc_reference_rcnn_ilsvrc13", (1, 3, 224, 224), (1, 200)) + From 26128a04cab3d42d54150db10004758d0c46f929 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Thu, 31 May 2018 16:01:02 -0700 Subject: [PATCH 37/82] duplicate file deleted --- .../onnx/export/onnx_export_test.py | 129 ------------------ 1 file changed, 129 deletions(-) delete mode 100644 tests/python-pytest/onnx/export/onnx_export_test.py diff --git a/tests/python-pytest/onnx/export/onnx_export_test.py b/tests/python-pytest/onnx/export/onnx_export_test.py deleted file mode 100644 index 82bec7b9dd21..000000000000 --- a/tests/python-pytest/onnx/export/onnx_export_test.py +++ /dev/null @@ -1,129 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -""" -Tests for individual operators -This module contains operator tests which currently do not exist on -ONNX backend test framework. Once we have PRs on the ONNX repo and get -those PRs merged, this file will get EOL'ed. -""" -# pylint: disable=too-many-locals,wrong-import-position,import-error -from __future__ import absolute_import -import sys -import os -import logging -import tarfile -from collections import namedtuple -import numpy as np -import numpy.testing as npt -from onnx import numpy_helper -from onnx import TensorProto -from mxnet.test_utils import download -from mxnet.contrib import onnx as onnx_mxnet -import mxnet as mx -CURR_PATH = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) -sys.path.insert(0, os.path.join(CURR_PATH, '../../python/unittest')) - -URLS = { - 'bvlc_googlenet': - 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/bvlc_googlenet.tar.gz', - 'bvlc_reference_caffenet': - 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/bvlc_reference_caffenet.tar.gz', - 'bvlc_reference_rcnn_ilsvrc13': - 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/bvlc_reference_rcnn_ilsvrc13.tar.gz', -} - -def get_test_files(name): - """Extract tar file and returns model path and input, output data""" - tar_name = download(URLS.get(name), dirname=CURR_PATH.__str__()) - # extract tar file - tar_path = os.path.join(CURR_PATH, tar_name) - tar = tarfile.open(tar_path.__str__(), "r:*") - tar.extractall(path=CURR_PATH.__str__()) - tar.close() - data_dir = os.path.join(CURR_PATH, name) - model_path = os.path.join(data_dir, 'model.onnx') - - inputs = [] - outputs = [] - # get test files - for test_file in os.listdir(data_dir): - case_dir = os.path.join(data_dir, test_file) - # skip the non-dir files - if not os.path.isdir(case_dir): - continue - input_file = os.path.join(case_dir, 'input_0.pb') - input_tensor = TensorProto() - with open(input_file, 'rb') as proto_file: - input_tensor.ParseFromString(proto_file.read()) - inputs.append(numpy_helper.to_array(input_tensor)) - - output_tensor = TensorProto() - output_file = os.path.join(case_dir, 'output_0.pb') - with open(output_file, 'rb') as proto_file: - output_tensor.ParseFromString(proto_file.read()) - outputs.append(numpy_helper.to_array(output_tensor)) - - return model_path, inputs, outputs - -def test_models(model_name, input_shape, output_shape): - """ Tests Googlenet model for both onnx import and export""" - model_path, inputs, outputs = get_test_files(model_name) - logging.info("Translating Googlenet model from ONNX model zoo to Mxnet") - sym, arg_params, aux_params = onnx_mxnet.import_model(model_path) - params = {} - params.update(arg_params) - params.update(aux_params) - - onnx_file = model_path.rsplit('/', 1)[0] + "/exported_"+model_name+".onnx" - - logging.info("Translating converted googlenet model from mxnet to ONNX") - converted_model_path = onnx_mxnet.export_model(sym, params, [input_shape], np.float32, onnx_file) - - sym, arg_params, aux_params = onnx_mxnet.import_model(converted_model_path) - - metadata = onnx_mxnet.get_model_metadata(converted_model_path) - assert len(metadata) == 2 - assert metadata.get('input_tensor_data') - assert metadata.get('input_tensor_data')[0][1] == input_shape - assert metadata.get('output_tensor_data') - assert metadata.get('output_tensor_data')[0][1] == output_shape - data_names = [input_name[0] for input_name in metadata.get('input_tensor_data')] - - logging.info("Running inference on onnx re-import model in mxnet") - # run test for each test file - for input_data, output_data in zip(inputs, outputs): - # create module - mod = mx.mod.Module(symbol=sym, data_names=data_names, context=mx.cpu(), label_names=None) - mod.bind(for_training=False, data_shapes=[(data_names[0], input_data.shape)], label_shapes=None) - mod.set_params(arg_params=arg_params, aux_params=aux_params, - allow_missing=True, allow_extra=True) - # run inference - batch = namedtuple('Batch', ['data']) - mod.forward(batch([mx.nd.array(input_data)]), is_train=False) - - # verify the results - npt.assert_equal(mod.get_outputs()[0].shape, output_data.shape) - npt.assert_almost_equal(output_data, mod.get_outputs()[0].asnumpy(), decimal=3) - logging.info(model_name + " conversion successful") - - -if __name__ == '__main__': - test_models("bvlc_googlenet", (1, 3, 224, 224), (1, 1000)) - test_models("bvlc_reference_caffenet", (1, 3, 224, 224), (1, 1000)) - test_models("bvlc_reference_rcnn_ilsvrc13", (1, 3, 224, 224), (1, 200)) - From 2de3d5a79e696c6629767ccd90697a89ee433817 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Fri, 1 Jun 2018 15:21:59 -0700 Subject: [PATCH 38/82] reduce ops added --- .../contrib/onnx/_export/op_translations.py | 153 +++++++++++++++++- .../python-pytest/onnx/export/backend_rep.py | 11 +- 2 files changed, 155 insertions(+), 9 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index ce7a595eb70f..d6f97549c7db 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -319,7 +319,7 @@ def convert_string_to_list(string_val): val = val.replace("L", "") val = val.replace("[", "") val = val.replace("]", "") - if val is not "": + if val is not "" and val is not "None": result_list.append(int(val)) return result_list @@ -826,7 +826,7 @@ def convert_max(node, **kwargs): @mx2onnx.register("_minimum") -def convert_min(node, **kwargs): +def convert_minimum(node, **kwargs): proc_nodes = kwargs["proc_nodes"] node_inputs = node["inputs"] @@ -847,13 +847,160 @@ def convert_min(node, **kwargs): return [node] +@mx2onnx.register("min") +def convert_min(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + mx_axis = node.get("attrs", {}).get("axis", None) + axes = convert_string_to_list(str(mx_axis)) if mx_axis is not None else None + + keepdims = int(node.get("attrs", {}).get("keepdims", 0)) + + a = kwargs["index_lookup"][inputs[0][0]] + a_node = proc_nodes[a].name + + if axes is not None: + node = helper.make_node( + 'ReduceMin', + inputs=[a_node], + outputs=[name], + axes=axes, + keepdims=keepdims, + name=name + ) + + return [node] + else: + node = helper.make_node( + 'ReduceMin', + inputs=[a_node], + outputs=[name], + keepdims=keepdims, + name=name + ) + + return [node] + + +@mx2onnx.register("max") +def convert_max(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + mx_axis = node.get("attrs", {}).get("axis", None) + axes = convert_string_to_list(str(mx_axis)) if mx_axis is not None else None + + keepdims = int(node.get("attrs", {}).get("keepdims", 0)) + + a = kwargs["index_lookup"][inputs[0][0]] + a_node = proc_nodes[a].name + + if axes is not None: + node = helper.make_node( + 'ReduceMax', + inputs=[a_node], + outputs=[name], + axes=axes, + keepdims=keepdims, + name=name + ) + + return [node] + else: + node = helper.make_node( + 'ReduceMax', + inputs=[a_node], + outputs=[name], + keepdims=keepdims, + name=name + ) + + return [node] + + +@mx2onnx.register("mean") +def convert_mean(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + mx_axis = node.get("attrs", {}).get("axis", None) + axes = convert_string_to_list(str(mx_axis)) if mx_axis is not None else None + + keepdims = int(node.get("attrs", {}).get("keepdims", 0)) + + a = kwargs["index_lookup"][inputs[0][0]] + a_node = proc_nodes[a].name + + if axes is not None: + node = helper.make_node( + 'ReduceMean', + inputs=[a_node], + outputs=[name], + axes=axes, + keepdims=keepdims, + name=name + ) + + return [node] + else: + node = helper.make_node( + 'ReduceMean', + inputs=[a_node], + outputs=[name], + keepdims=keepdims, + name=name + ) + + return [node] + + +@mx2onnx.register("prod") +def convert_prod(node, **kwargs): + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + mx_axis = node.get("attrs", {}).get("axis", None) + axes = convert_string_to_list(str(mx_axis)) if mx_axis is not None else None + + keepdims = int(node.get("attrs", {}).get("keepdims", 0)) + + a = kwargs["index_lookup"][inputs[0][0]] + a_node = proc_nodes[a].name + + if axes is not None: + node = helper.make_node( + 'ReduceProd', + inputs=[a_node], + outputs=[name], + axes=axes, + keepdims=keepdims, + name=name + ) + + return [node] + else: + node = helper.make_node( + 'ReduceProd', + inputs=[a_node], + outputs=[name], + keepdims=keepdims, + name=name + ) + + return [node] + + # Arithmetic Operations @mx2onnx.register("elemwise_add") def convert_elementwise_add(node, **kwargs): name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - weights = kwargs["weights"] a = kwargs["index_lookup"][inputs[0][0]] b = kwargs["index_lookup"][inputs[1][0]] diff --git a/tests/python-pytest/onnx/export/backend_rep.py b/tests/python-pytest/onnx/export/backend_rep.py index 114a2eb79903..5046f8ad9a0b 100644 --- a/tests/python-pytest/onnx/export/backend_rep.py +++ b/tests/python-pytest/onnx/export/backend_rep.py @@ -56,13 +56,14 @@ def run(self, inputs, **kwargs): params : numpy array result obtained after running the inference on mxnet """ - input_data = np.asarray(inputs[0], dtype='f') - + data_forward = [] + for val in inputs: + data_forward.append(mx.nd.array(val)) # create module, passing cpu context if self.device == 'CPU': ctx = mx.cpu() else: - raise NotImplementedError("Only CPU context is supported for now") + raise NotImplementedError("ONNX tests are run only for CPU context.") # To fetch the data names of the input to the model we list the inputs of the symbol graph # and exclude the argument and auxiliary parameters from the list @@ -80,8 +81,6 @@ def run(self, inputs, **kwargs): mod.set_params(arg_params=self.arg_params, aux_params=self.aux_params) # run inference - batch = namedtuple('Batch', ['data']) - - mod.forward(batch([mx.nd.array(input_data)])) + mod.forward(mx.io.DataBatch(data_forward)) result = mod.get_outputs()[0].asnumpy() return [result] From 7e567445c479988adea68e3c09263e4f99807828 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Mon, 4 Jun 2018 16:32:16 -0700 Subject: [PATCH 39/82] some small fixes --- python/mxnet/contrib/onnx/_export/export_helper.py | 2 +- python/mxnet/contrib/onnx/_export/export_model.py | 3 ++- .../mxnet/contrib/onnx/_export/op_translations.py | 13 +++++++------ .../mxnet/contrib/onnx/_import/op_translations.py | 4 ++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_helper.py b/python/mxnet/contrib/onnx/_export/export_helper.py index 427c78dce9c6..5c4cb4eb8609 100644 --- a/python/mxnet/contrib/onnx/_export/export_helper.py +++ b/python/mxnet/contrib/onnx/_export/export_helper.py @@ -53,7 +53,7 @@ def load_module(json_path, params_path, input_shape): sym, arg_params, aux_params = mx.model.load_checkpoint(model_name, num_epochs) trained_model = mx.mod.Module(symbol=sym, label_names=None) trained_model.bind(data_shapes=[('data', input_shape[0])], - label_shapes=trained_model.label_shapes, for_training=False) + label_shapes=None, for_training=False) # Merging arg and aux parameters params = {} diff --git a/python/mxnet/contrib/onnx/_export/export_model.py b/python/mxnet/contrib/onnx/_export/export_model.py index 595f34a11f35..f5f1ab2e8ffc 100644 --- a/python/mxnet/contrib/onnx/_export/export_model.py +++ b/python/mxnet/contrib/onnx/_export/export_model.py @@ -24,6 +24,7 @@ import numpy as np from onnx import helper, mapping +from six import string_types from .export_onnx import MxNetToONNXConverter from .export_helper import load_module @@ -56,7 +57,7 @@ def export_model(model, weights, input_shape, input_type, onnx_file_path, log=Fa converter = MxNetToONNXConverter() data_format = np.dtype(input_type) - if isinstance(model, basestring) and isinstance(weights, basestring): + if isinstance(model, string_types) and isinstance(weights, string_types): print("Converting json and params file to sym and weights") sym, params = load_module(model, weights, input_shape) onnx_graph = converter.convert_mx2onnx_graph(sym, params, input_shape, diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index d6f97549c7db..bae8b967e753 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -462,6 +462,7 @@ def convert_pooling(node, **kwargs): kernel = eval(attrs["kernel"]) pool_type = attrs["pool_type"] stride = eval(attrs["stride"]) if attrs.get("stride") else None + global_pool = attrs.get("global_pool", 0) node_inputs = node["inputs"] input_node_idx = kwargs["index_lookup"][node_inputs[0][0]] input_node = proc_nodes[input_node_idx] @@ -470,21 +471,21 @@ def convert_pooling(node, **kwargs): pool_types = {"max": "MaxPool", "avg": "AveragePool"} global_pool_types = {"max": "GlobalMaxPool", "avg": "GlobalAveragePool"} - if stride: + if global_pool: node = helper.make_node( - pool_types[pool_type], + global_pool_types[pool_type], [input_node.name], # input [name], - kernel_shape=kernel, - pads=[0, 0], - strides=stride, name=name ) else: node = helper.make_node( - global_pool_types[pool_type], + pool_types[pool_type], [input_node.name], # input [name], + kernel_shape=kernel, + pads=[0, 0], + strides=stride, name=name ) diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index 0fad0080bef0..1a269ab07f93 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -130,7 +130,7 @@ def maximum(attrs, inputs, proto_obj): for op_input in inputs[2:]: mxnet_op = symbol.maximum(mxnet_op, op_input) else: - mxnet_op = inputs[0] + mxnet_op = symbol.maximum(inputs[0], inputs[0]) return mxnet_op, attrs, inputs def minimum(attrs, inputs, proto_obj): @@ -143,7 +143,7 @@ def minimum(attrs, inputs, proto_obj): for op_input in inputs[2:]: mxnet_op = symbol.minimum(mxnet_op, op_input) else: - mxnet_op = inputs[0] + mxnet_op = symbol.minimum(inputs[0], inputs[0]) return mxnet_op, attrs, inputs def lesser(attrs, inputs, proto_obj): From dc3d8e737df83637cf44b8ce81c943d5336f060f Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Mon, 4 Jun 2018 16:58:19 -0700 Subject: [PATCH 40/82] some lint fixes --- .../contrib/onnx/_export/op_translations.py | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index bae8b967e753..253c84c28375 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -24,14 +24,11 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals - -from onnx import defs, checker, helper, numpy_helper, mapping - -from .export_onnx import MxNetToONNXConverter as mx2onnx - +import re import numpy as np -import re +from onnx import helper, numpy_helper, mapping +from .export_onnx import MxNetToONNXConverter as mx2onnx def looks_like_weight(name): """Internal helper to figure out if node should be hidden with `hide_weights`. @@ -40,14 +37,16 @@ def looks_like_weight(name): return True if name.endswith("_bias") or name == "B": return True - if name.endswith("_beta") or name.endswith("_gamma") or name.endswith("_moving_var") or name.endswith( - "_moving_mean"): + if name.endswith("_beta") or name.endswith("_gamma") or \ + name.endswith("_moving_var") or name.endswith("_moving_mean"): return True return False @mx2onnx.register("null") def convert_weights_and_inputs(node, **kwargs): + """Helper function to convert weights and inputs. + """ name = node["name"] if kwargs["is_input"] is False: @@ -77,6 +76,9 @@ def convert_weights_and_inputs(node, **kwargs): @mx2onnx.register("Convolution") def convert_convolution(node, **kwargs): + """Map MXNet's convolution operator attributes to onnx + and return "Conv" onnx node + """ name = node["name"] inputs = node["inputs"] @@ -195,7 +197,7 @@ def convert_batchnorm(node, **kwargs): beta_node, # bias mov_mean_node, mov_var_node - ], + ], [name], name=name, epsilon=eps, @@ -297,8 +299,8 @@ def transform_padding(pad_width): start_index = 0 end_index = int(num_pad_values/2) - for idx in range(0,num_pad_values): - if idx%2 == 0: + for idx in range(0, num_pad_values): + if idx % 2 == 0: onnx_pad_width[start_index] = pad_width[idx] start_index += 1 else: @@ -640,9 +642,9 @@ def convert_lrn(node, **kwargs): attrs = node["attrs"] alpha = float(attrs["alpha"]) if "alpha" in attrs else 0.0001 - beta = float(attrs["beta"]) if "beta" in attrs else 0.75 - bias = float(attrs["knorm"]) if "knorm" in attrs else 1.0 - size = int(attrs["nsize"]) + beta = float(attrs["beta"]) if "beta" in attrs else 0.75 + bias = float(attrs["knorm"]) if "knorm" in attrs else 1.0 + size = int(attrs["nsize"]) lrn_node = helper.make_node( "LRN", @@ -1368,7 +1370,7 @@ def convert_slice_channel(node, **kwargs): a = kwargs["index_lookup"][inputs[0][0]] a_node = proc_nodes[a].name - if num_outputs==1 and squeeze_axis==1: + if num_outputs == 1 and squeeze_axis == 1: node = helper.make_node( "Squeeze", [a_node], From 4765b492f16f6045209ebbb240790c4b565318a5 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Mon, 4 Jun 2018 17:32:50 -0700 Subject: [PATCH 41/82] docstring added --- .../mxnet/contrib/onnx/_export/export_onnx.py | 2 +- .../contrib/onnx/_export/op_translations.py | 164 +++++++++++++++++- 2 files changed, 161 insertions(+), 5 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 0815369d5a43..23e5b2fc2a5a 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -17,7 +17,7 @@ # coding: utf-8 # pylint: disable=invalid-name,too-many-locals,no-self-use - +"""MXNet to ONNX graph converter functions""" from __future__ import absolute_import from __future__ import division from __future__ import print_function diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 253c84c28375..da6277e28050 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -76,8 +76,8 @@ def convert_weights_and_inputs(node, **kwargs): @mx2onnx.register("Convolution") def convert_convolution(node, **kwargs): - """Map MXNet's convolution operator attributes to onnx - and return "Conv" onnx node + """Map MXNet's convolution operator attributes to onnx's Conv operator + and return the created node. """ name = node["name"] inputs = node["inputs"] @@ -138,6 +138,9 @@ def parse_helper(attrs_name, alt_value=None): @mx2onnx.register("FullyConnected") def convert_fully_connected(node, **kwargs): + """Map MXNet's FullyConnected operator attributes to onnx's Gemm operator + and return the created node. + """ name = node["name"] inputs = node["inputs"] input_node_id = kwargs["index_lookup"][inputs[0][0]] @@ -168,6 +171,9 @@ def convert_fully_connected(node, **kwargs): @mx2onnx.register("BatchNorm") def convert_batchnorm(node, **kwargs): + """Map MXNet's BatchNorm operator attributes to onnx's BatchNormalization operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -209,6 +215,9 @@ def convert_batchnorm(node, **kwargs): @mx2onnx.register("tanh") def convert_tanh(node, **kwargs): + """Map MXNet's tanh operator attributes to onnx's Tanh operator + and return the created node. + """ name = node["name"] inputs = node["inputs"] input_node_idx = kwargs["index_lookup"][inputs[0][0]] @@ -226,6 +235,9 @@ def convert_tanh(node, **kwargs): #Basic neural network functions @mx2onnx.register("sigmoid") def convert_sigmoid(node, **kwargs): + """Map MXNet's sigmoid operator attributes to onnx's Sigmoid operator + and return the created node. + """ name = node["name"] inputs = node["inputs"] input_node_idx = kwargs["index_lookup"][inputs[0][0]] @@ -242,6 +254,9 @@ def convert_sigmoid(node, **kwargs): @mx2onnx.register("relu") def convert_relu(node, **kwargs): + """Map MXNet's relu operator attributes to onnx's Relu operator + and return the created node. + """ name = node["name"] inputs = node["inputs"] input_node_idx = kwargs["index_lookup"][inputs[0][0]] @@ -259,6 +274,9 @@ def convert_relu(node, **kwargs): @mx2onnx.register("Activation") def convert_activation(node, **kwargs): + """Map MXNet's Activation operator attributes to onnx's Tanh/Relu operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] @@ -292,9 +310,9 @@ def convert_activation(node, **kwargs): return [node] def transform_padding(pad_width): + """Helper function to convert padding format for pad operator. + """ num_pad_values = len(pad_width) - pad_values_middle_index = int(num_pad_values/2) - onnx_pad_width = [0]*num_pad_values start_index = 0 @@ -311,6 +329,9 @@ def transform_padding(pad_width): def convert_string_to_list(string_val): + """Helper function to convert string to list. + Used to convert shape attribute string to list format. + """ result_list = [] list_string = string_val.split(',') @@ -329,6 +350,9 @@ def convert_string_to_list(string_val): @mx2onnx.register("Pad") def convert_pad(node, **kwargs): + """Map MXNet's pad operator attributes to onnx's Pad operator + and return the created node. + """ name = node["name"] attrs = node["attrs"] proc_nodes = kwargs["proc_nodes"] @@ -368,6 +392,11 @@ def convert_pad(node, **kwargs): @mx2onnx.register("_linalg_gemm2") def convert_linalg_gemm2(node, **kwargs): + """Map MXNet's _linalg_gemm2 operator attributes to onnx's + MatMul and Transpose operators based on the values set for + transpose_a, transpose_b attributes. + Return multiple nodes created. + """ proc_nodes = kwargs["proc_nodes"] node_inputs = node["inputs"] name = node["name"] @@ -459,6 +488,10 @@ def convert_linalg_gemm2(node, **kwargs): @mx2onnx.register("Pooling") def convert_pooling(node, **kwargs): + """Map MXNet's Pooling operator attributes to onnx's + MaxPool/AveragePool/GlobalMaxPool/GlobalAveragePool operators + based on the input node's attributes and return the created node. + """ proc_nodes = kwargs["proc_nodes"] attrs = node["attrs"] kernel = eval(attrs["kernel"]) @@ -496,6 +529,9 @@ def convert_pooling(node, **kwargs): @mx2onnx.register("exp") def convert_exp(node, **kwargs): + """Map MXNet's exp operator attributes to onnx's Exp operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -514,6 +550,9 @@ def convert_exp(node, **kwargs): @mx2onnx.register("LeakyReLU") def convert_leakyrelu(node, **kwargs): + """Map MXNet's LeakyReLU operator attributes to onnx's Elu/LeakyRelu/PRelu operators + based on the input node's attributes and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -548,6 +587,9 @@ def convert_leakyrelu(node, **kwargs): @mx2onnx.register("softmax") def convert_softmax(node, **kwargs): + """Map MXNet's softmax operator attributes to onnx's Softmax operator + and return the created node. + """ inputs = node["inputs"] input_idx = kwargs["index_lookup"][inputs[0][0]] proc_nodes = kwargs["proc_nodes"] @@ -571,6 +613,9 @@ def convert_softmax(node, **kwargs): # just softmax for inference - hence the name convert_softmax_output. @mx2onnx.register("SoftmaxOutput") def convert_softmax_output(node, **kwargs): + """Map MXNet's SoftmaxOutput operator attributes to onnx's Softmax operator + and return the created node. + """ inputs = node["inputs"] input1_idx = kwargs["index_lookup"][inputs[0][0]] proc_nodes = kwargs["proc_nodes"] @@ -590,6 +635,9 @@ def convert_softmax_output(node, **kwargs): @mx2onnx.register("Concat") def convert_concat(node, **kwargs): + """Map MXNet's Concat operator attributes to onnx's Concat operator + and return the created node. + """ name = node["name"] inputs = node["inputs"] proc_nodes = kwargs["proc_nodes"] @@ -607,6 +655,9 @@ def convert_concat(node, **kwargs): @mx2onnx.register("transpose") def convert_transpose(node, **kwargs): + """Map MXNet's transpose operator attributes to onnx's Transpose operator + and return the created node. + """ name = node["name"] input_idx = kwargs["index_lookup"][node["inputs"][0][0]] proc_nodes = kwargs["proc_nodes"] @@ -635,6 +686,9 @@ def convert_transpose(node, **kwargs): @mx2onnx.register("LRN") def convert_lrn(node, **kwargs): + """Map MXNet's LRN operator attributes to onnx's LRN operator + and return the created node. + """ name = node["name"] input_idx = kwargs["index_lookup"][node["inputs"][0][0]] proc_nodes = kwargs["proc_nodes"] @@ -662,6 +716,9 @@ def convert_lrn(node, **kwargs): @mx2onnx.register("Dropout") def convert_dropout(node, **kwargs): + """Map MXNet's Dropout operator attributes to onnx's Dropout operator + and return the created node. + """ name = node["name"] input_id = kwargs["index_lookup"][node["inputs"][0][0]] input_name = kwargs["proc_nodes"][input_id].name @@ -680,6 +737,9 @@ def convert_dropout(node, **kwargs): @mx2onnx.register("Flatten") def convert_flatten(node, **kwargs): + """Map MXNet's Flatten operator attributes to onnx's Flatten operator + and return the created node. + """ name = node["name"] input_idx = kwargs["index_lookup"][node["inputs"][0][0]] proc_nodes = kwargs["proc_nodes"] @@ -697,6 +757,10 @@ def convert_flatten(node, **kwargs): # Convert scalar value into node and pass it as input to mul_node @mx2onnx.register("_mul_scalar") def convert_mul_scalar(node, **kwargs): + """Map MXNet's _mul_scalar operator attributes to onnx's Mul operator. + Creates a new node for the input scalar value, adds it to the initializer + and return multiple created nodes. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -762,6 +826,9 @@ def convert_mul_scalar(node, **kwargs): # Sorting and Searching @mx2onnx.register("argmax") def convert_argmax(node, **kwargs): + """Map MXNet's argmax operator attributes to onnx's ArgMax operator + and return the created node. + """ proc_nodes = kwargs["proc_nodes"] node_inputs = node["inputs"] @@ -785,6 +852,9 @@ def convert_argmax(node, **kwargs): @mx2onnx.register("argmin") def convert_argmin(node, **kwargs): + """Map MXNet's argmin operator attributes to onnx's ArgMin operator + and return the created node. + """ proc_nodes = kwargs["proc_nodes"] node_inputs = node["inputs"] @@ -808,6 +878,9 @@ def convert_argmin(node, **kwargs): @mx2onnx.register("_maximum") def convert_max(node, **kwargs): + """Map MXNet's _maximum operator attributes to onnx's Max operator + and return the created node. + """ proc_nodes = kwargs["proc_nodes"] node_inputs = node["inputs"] @@ -830,6 +903,9 @@ def convert_max(node, **kwargs): @mx2onnx.register("_minimum") def convert_minimum(node, **kwargs): + """Map MXNet's _minimum operator attributes to onnx's Min operator + and return the created node. + """ proc_nodes = kwargs["proc_nodes"] node_inputs = node["inputs"] @@ -852,6 +928,9 @@ def convert_minimum(node, **kwargs): @mx2onnx.register("min") def convert_min(node, **kwargs): + """Map MXNet's min operator attributes to onnx's ReduceMin operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -889,6 +968,9 @@ def convert_min(node, **kwargs): @mx2onnx.register("max") def convert_max(node, **kwargs): + """Map MXNet's max operator attributes to onnx's ReduceMax operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -926,6 +1008,9 @@ def convert_max(node, **kwargs): @mx2onnx.register("mean") def convert_mean(node, **kwargs): + """Map MXNet's mean operator attributes to onnx's ReduceMean operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -963,6 +1048,9 @@ def convert_mean(node, **kwargs): @mx2onnx.register("prod") def convert_prod(node, **kwargs): + """Map MXNet's prod operator attributes to onnx's ReduceProd operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1001,6 +1089,9 @@ def convert_prod(node, **kwargs): # Arithmetic Operations @mx2onnx.register("elemwise_add") def convert_elementwise_add(node, **kwargs): + """Map MXNet's elemwise_add operator attributes to onnx's Add operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1023,6 +1114,9 @@ def convert_elementwise_add(node, **kwargs): @mx2onnx.register("broadcast_add") def covert_broadcast_add(node, **kwargs): + """Map MXNet's broadcast_add operator attributes to onnx's Add operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1045,6 +1139,9 @@ def covert_broadcast_add(node, **kwargs): @mx2onnx.register("elemwise_sub") def convert_elementwise_sub(node, **kwargs): + """Map MXNet's elemwise_sub operator attributes to onnx's Sub operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1066,6 +1163,9 @@ def convert_elementwise_sub(node, **kwargs): @mx2onnx.register("broadcast_sub") def covert_broadcast_sub(node, **kwargs): + """Map MXNet's broadcast_sub operator attributes to onnx's Sub operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1088,6 +1188,9 @@ def covert_broadcast_sub(node, **kwargs): @mx2onnx.register("elemwise_mul") def convert_mul(node, **kwargs): + """Map MXNet's elemwise_mul operator attributes to onnx's Mul operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1109,6 +1212,9 @@ def convert_mul(node, **kwargs): @mx2onnx.register("broadcast_mul") def convert_mul(node, **kwargs): + """Map MXNet's broadcast_mul operator attributes to onnx's Mul operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1131,6 +1237,9 @@ def convert_mul(node, **kwargs): @mx2onnx.register("elemwise_div") def convert_mul(node, **kwargs): + """Map MXNet's elemwise_div operator attributes to onnx's Div operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1153,6 +1262,9 @@ def convert_mul(node, **kwargs): @mx2onnx.register("broadcast_div") def convert_div(node, **kwargs): + """Map MXNet's broadcast_div operator attributes to onnx's Div operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1175,6 +1287,9 @@ def convert_div(node, **kwargs): @mx2onnx.register("negative") def convert_negative(node, **kwargs): + """Map MXNet's negative operator attributes to onnx's Neg operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1195,6 +1310,9 @@ def convert_negative(node, **kwargs): @mx2onnx.register("abs") def convert_abs(node, **kwargs): + """Map MXNet's abs operator attributes to onnx's Abs operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1215,6 +1333,9 @@ def convert_abs(node, **kwargs): @mx2onnx.register("add_n") def convert_addn(node, **kwargs): + """Map MXNet's add_n operator attributes to onnx's Sum operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1234,6 +1355,9 @@ def convert_addn(node, **kwargs): # Rounding @mx2onnx.register("ceil") def convert_ceil(node, **kwargs): + """Map MXNet's ceil operator attributes to onnx's Ceil operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1251,6 +1375,9 @@ def convert_ceil(node, **kwargs): @mx2onnx.register("floor") def convert_floor(node, **kwargs): + """Map MXNet's floor operator attributes to onnx's Floor operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1269,6 +1396,10 @@ def convert_floor(node, **kwargs): # Changing shape and type. @mx2onnx.register("Reshape") def convert_reshape(node, **kwargs): + """Map MXNet's Reshape operator attributes to onnx's Reshape operator. + Converts output shape attribute to output shape tensor + and return multiple created nodes. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1314,6 +1445,9 @@ def convert_reshape(node, **kwargs): @mx2onnx.register("Cast") def convert_cast(node, **kwargs): + """Map MXNet's Cast operator attributes to onnx's Cast operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1334,6 +1468,9 @@ def convert_cast(node, **kwargs): @mx2onnx.register("slice_axis") def convert_slice_axis(node, **kwargs): + """Map MXNet's slice_axis operator attributes to onnx's Slice operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1360,6 +1497,10 @@ def convert_slice_axis(node, **kwargs): # [TODO] Address split with squeeze case @mx2onnx.register("SliceChannel") def convert_slice_channel(node, **kwargs): + """Map MXNet's SliceChannel operator attributes to onnx's Squeeze or Split + operator based on squeeze_axis attribute + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1393,6 +1534,9 @@ def convert_slice_channel(node, **kwargs): @mx2onnx.register("expand_dims") def convert_expand_dims(node, **kwargs): + """Map MXNet's expand_dims operator attributes to onnx's Unsqueeze operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1413,6 +1557,9 @@ def convert_expand_dims(node, **kwargs): @mx2onnx.register("log") def convert_log(node, **kwargs): + """Map MXNet's log operator attributes to onnx's Log operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1431,6 +1578,9 @@ def convert_log(node, **kwargs): @mx2onnx.register("reciprocal") def convert_reciprocal(node, **kwargs): + """Map MXNet's reciprocal operator attributes to onnx's Reciprocal operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1449,6 +1599,9 @@ def convert_reciprocal(node, **kwargs): @mx2onnx.register("_power") def convert_power(node, **kwargs): + """Map MXNet's _power operator attributes to onnx's Pow operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1469,6 +1622,9 @@ def convert_power(node, **kwargs): @mx2onnx.register("sqrt") def convert_sqrt(node, **kwargs): + """Map MXNet's sqrt operator attributes to onnx's Sqrt operator + and return the created node. + """ name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] From 25566e0ae82f7e610676df51d4731d260757aef3 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Mon, 4 Jun 2018 17:09:52 -0700 Subject: [PATCH 42/82] Add tests for inception_v1 and inception_v2 --- .../contrib/onnx/_export/op_translations.py | 2 +- .../onnx/export/mxnet_export_test.py | 85 ++++++++++++++++--- .../onnx/export/onnx_backend_test.py | 78 ++++++----------- 3 files changed, 101 insertions(+), 64 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index da6277e28050..41905f5ff0fa 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -180,7 +180,7 @@ def convert_batchnorm(node, **kwargs): attrs = node["attrs"] momentum = float(node.get("attrs", {}).get("momentum", 0.9)) - eps = float(attrs["eps"]) + eps = float(attrs.get("eps", 0.001)) data_idx = kwargs["index_lookup"][inputs[0][0]] gamma_idx = kwargs["index_lookup"][inputs[1][0]] diff --git a/tests/python-pytest/onnx/export/mxnet_export_test.py b/tests/python-pytest/onnx/export/mxnet_export_test.py index 82bec7b9dd21..305d5a9c4ea5 100644 --- a/tests/python-pytest/onnx/export/mxnet_export_test.py +++ b/tests/python-pytest/onnx/export/mxnet_export_test.py @@ -45,6 +45,12 @@ 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/bvlc_reference_caffenet.tar.gz', 'bvlc_reference_rcnn_ilsvrc13': 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/bvlc_reference_rcnn_ilsvrc13.tar.gz', + 'inception_v1': + 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/inception_v1.tar.gz', + 'inception_v2': + 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/inception_v2.tar.gz', + 'shufflenet': + 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/shufflenet.tar.gz' } def get_test_files(name): @@ -80,10 +86,25 @@ def get_test_files(name): return model_path, inputs, outputs + +def forward_pass(sym, arg, aux, data_names, input_data): + """ Perform forward pass on given data""" + # create module + mod = mx.mod.Module(symbol=sym, data_names=data_names, context=mx.cpu(), label_names=None) + mod.bind(for_training=False, data_shapes=[(data_names[0], input_data.shape)], label_shapes=None) + mod.set_params(arg_params=arg, aux_params=aux, + allow_missing=True, allow_extra=True) + # run inference + batch = namedtuple('Batch', ['data']) + mod.forward(batch([mx.nd.array(input_data)]), is_train=False) + + return mod.get_outputs()[0].asnumpy() + + def test_models(model_name, input_shape, output_shape): """ Tests Googlenet model for both onnx import and export""" model_path, inputs, outputs = get_test_files(model_name) - logging.info("Translating Googlenet model from ONNX model zoo to Mxnet") + logging.info("Translating model from ONNX model zoo to Mxnet") sym, arg_params, aux_params = onnx_mxnet.import_model(model_path) params = {} params.update(arg_params) @@ -91,7 +112,7 @@ def test_models(model_name, input_shape, output_shape): onnx_file = model_path.rsplit('/', 1)[0] + "/exported_"+model_name+".onnx" - logging.info("Translating converted googlenet model from mxnet to ONNX") + logging.info("Translating converted model from mxnet to ONNX") converted_model_path = onnx_mxnet.export_model(sym, params, [input_shape], np.float32, onnx_file) sym, arg_params, aux_params = onnx_mxnet.import_model(converted_model_path) @@ -107,23 +128,63 @@ def test_models(model_name, input_shape, output_shape): logging.info("Running inference on onnx re-import model in mxnet") # run test for each test file for input_data, output_data in zip(inputs, outputs): - # create module - mod = mx.mod.Module(symbol=sym, data_names=data_names, context=mx.cpu(), label_names=None) - mod.bind(for_training=False, data_shapes=[(data_names[0], input_data.shape)], label_shapes=None) - mod.set_params(arg_params=arg_params, aux_params=aux_params, - allow_missing=True, allow_extra=True) - # run inference - batch = namedtuple('Batch', ['data']) - mod.forward(batch([mx.nd.array(input_data)]), is_train=False) + result = forward_pass(sym, arg_params, aux_params, data_names, input_data) # verify the results - npt.assert_equal(mod.get_outputs()[0].shape, output_data.shape) - npt.assert_almost_equal(output_data, mod.get_outputs()[0].asnumpy(), decimal=3) + npt.assert_equal(result.shape, output_data.shape) + npt.assert_almost_equal(output_data, result, decimal=3) logging.info(model_name + " conversion successful") +def test_model_accuracy(model_name, input_shape): + """ Imports ONNX model, runs inference, exports and imports back + run inference, compare result with the previous inference result""" + model_path, inputs, outputs = get_test_files(model_name) + logging.info("Translating model from ONNX model zoo to Mxnet") + sym, arg_params, aux_params = onnx_mxnet.import_model(model_path) + + metadata = onnx_mxnet.get_model_metadata(model_path) + data_names = [input_name[0] for input_name in metadata.get('input_tensor_data')] + + expected_result= [] + for input_data, output_data in zip(inputs, outputs): + result = forward_pass(sym, arg_params, aux_params, data_names, input_data) + expected_result.append(result) + + params = {} + params.update(arg_params) + params.update(aux_params) + + onnx_file = model_path.rsplit('/', 1)[0] + "/exported_"+model_name+".onnx" + + logging.info("Translating converted model from mxnet to ONNX") + converted_model_path = onnx_mxnet.export_model(sym, params, [input_shape], np.float32, + onnx_file) + + sym, arg_params, aux_params = onnx_mxnet.import_model(converted_model_path) + + metadata = onnx_mxnet.get_model_metadata(converted_model_path) + data_names = [input_name[0] for input_name in metadata.get('input_tensor_data')] + + actual_result = [] + for input_data, output_data in zip(inputs, outputs): + result = forward_pass(sym, arg_params, aux_params, data_names, input_data) + actual_result.append(result) + + # verify the results + for expected, actual in zip(expected_result, actual_result): + npt.assert_equal(expected.shape, actual.shape) + npt.assert_almost_equal(expected, actual, decimal=3) + + if __name__ == '__main__': test_models("bvlc_googlenet", (1, 3, 224, 224), (1, 1000)) test_models("bvlc_reference_caffenet", (1, 3, 224, 224), (1, 1000)) test_models("bvlc_reference_rcnn_ilsvrc13", (1, 3, 224, 224), (1, 200)) + # Comparing MXNet inference result, since MXNet results don't match + # ONNX expected results due to AveragePool issue github issue(#10194) + test_model_accuracy("inception_v1", (1, 3, 224, 224)) + test_model_accuracy("inception_v2", (1, 3, 224, 224)) + + diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index 6ba897be5479..0f2301edd8ea 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -32,81 +32,54 @@ # This is a pytest magic variable to load extra plugins pytest_plugins = "onnx.backend.test.report", -BACKEND_TEST = onnx.backend.test.BackendTest(mxnet_backend, __name__) +BACKEND_TESTS = onnx.backend.test.BackendTest(mxnet_backend, __name__) IMPLEMENTED_OPERATORS_TEST = [ - #Arithmetic Operators - 'test_add' + 'test_random_uniform', + 'test_random_normal', + 'test_add', 'test_sub', 'test_mul', 'test_div', 'test_neg', 'test_abs', 'test_sum', - - # Hyperbolic functions 'test_tanh', - - #Rounding 'test_ceil', 'test_floor', - - ## Joining and spliting 'test_concat', - - # Basic neural network functions 'test_sigmoid', 'test_relu', - # 'test_elu', 'test_constant_pad', 'test_edge_pad', 'test_reflect_pad', - 'test_matmul', - 'test_maxpool_2d_default', - 'test_maxpool_2d_pads', - 'test_maxpool_2d_strides', - 'test_maxpool_3d_default', - 'test_globalmaxpool', - 'test_globalaveragepool', - 'test_conv', - 'test_basic_conv', + 'test_reduce_min', + 'test_reduce_max', + 'test_reduce_mean', + 'test_reduce_prod', + 'test_squeeze', 'test_softmax_example', 'test_softmax_large_number', 'test_softmax_axis_2', - #'test_conv', - #'test_basic_conv', - - # Sorting and Searching - 'test_argmax', - 'test_argmin', - 'test_max_', - 'test_min', - - #Changing shape and type. - #'test_reshape_', - 'test_cast', + 'test_transpose', + 'test_globalmaxpool', + 'test_globalaveragepool', 'test_slice_cpu', - 'test_default_axes', #make PR against onnx to fix the test name(grep-able) 'test_slice_neg', - 'test_transpose', 'test_squeeze_', - 'test_flatten_default', - - #Powers 'test_reciprocal', 'test_sqrt', - 'test_pow_example', - 'test_pow_cpu', - 'test_pow_bcast_cpu', - 'test_log_', + 'test_pow', 'test_exp', - + 'test_argmax', + 'test_argmin', + 'test_min', + 'test_max' #pytorch operator tests 'test_operator_exp', - 'test_operator_conv', - 'test_operator_non_float_params', + 'test_operator_maxpool', 'test_operator_params', - 'test_operator_permute2', + 'test_operator_permute2' ] BASIC_MODEL_TESTS = [ @@ -138,18 +111,21 @@ 'test_vgg19' ] -# for op_test in IMPLEMENTED_OPERATORS_TEST: -# BACKEND_TEST.include(op_test) +for op_test in IMPLEMENTED_OPERATORS_TEST: + BACKEND_TESTS.include(op_test) for basic_model_test in BASIC_MODEL_TESTS: - BACKEND_TEST.include(basic_model_test) + BACKEND_TESTS.include(basic_model_test) for std_model_test in STANDARD_MODEL: - BACKEND_TEST.include(std_model_test) + BACKEND_TESTS.include(std_model_test) + +BACKEND_TESTS.exclude('.*broadcast.*') +BACKEND_TESTS.exclude('.*bcast.*') # import all test cases at global scope to make them visible to python.unittest -globals().update(BACKEND_TEST.enable_report().test_cases) +globals().update(BACKEND_TESTS.enable_report().test_cases) if __name__ == '__main__': unittest.main() From 322b63a070c95e62ecd6ab7098a952d6752015e6 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Mon, 4 Jun 2018 17:27:38 -0700 Subject: [PATCH 43/82] Add CI runs for export module --- ci/docker/runtime_functions.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ci/docker/runtime_functions.sh b/ci/docker/runtime_functions.sh index 293ac64fff88..9a7ef94e2545 100755 --- a/ci/docker/runtime_functions.sh +++ b/ci/docker/runtime_functions.sh @@ -601,6 +601,9 @@ integrationtest_ubuntu_cpu_onnx() { pytest tests/python-pytest/onnx/import/mxnet_backend_test.py pytest tests/python-pytest/onnx/import/onnx_import_test.py pytest tests/python-pytest/onnx/import/gluon_backend_test.py + pytest tests/python-pytest/onnx/export/mxnet_export_test.py + pytest tests/python-pytest/onnx/export/onnx_backend_test.py + } integrationtest_ubuntu_gpu_python() { From 3de8a25bc31619a3b8272f084703da342b73e421 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 5 Jun 2018 14:13:57 -0700 Subject: [PATCH 44/82] lint fixes, pooling attr fix --- .../contrib/onnx/_export/export_model.py | 2 + .../mxnet/contrib/onnx/_export/export_onnx.py | 2 +- .../contrib/onnx/_export/op_translations.py | 269 +++++++++--------- 3 files changed, 138 insertions(+), 135 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_model.py b/python/mxnet/contrib/onnx/_export/export_model.py index f5f1ab2e8ffc..1d8423e85f75 100644 --- a/python/mxnet/contrib/onnx/_export/export_model.py +++ b/python/mxnet/contrib/onnx/_export/export_model.py @@ -16,6 +16,8 @@ # under the License. # coding: utf-8 +#pylint: disable-msg=too-many-arguments + """export function""" from __future__ import absolute_import from __future__ import division diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 23e5b2fc2a5a..b73af467a308 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -16,7 +16,7 @@ # under the License. # coding: utf-8 -# pylint: disable=invalid-name,too-many-locals,no-self-use +# pylint: disable=invalid-name,too-many-locals,no-self-use,too-many-arguments """MXNet to ONNX graph converter functions""" from __future__ import absolute_import from __future__ import division diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 41905f5ff0fa..dae18943195d 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -16,6 +16,7 @@ # under the License. # coding: utf-8 +# pylint: disable=too-many-locals """ Conversion Functions for common layers. Add new functions here with a decorator. @@ -95,6 +96,7 @@ def convert_convolution(node, **kwargs): tuple_re = re.compile('\([0-9L|,| ]+\)') def parse_helper(attrs_name, alt_value=None): + """Helper function to parse operator attributes in required format.""" if attrs is None: return alt_value attrs_str = str(attrs.get(attrs_name)) @@ -410,17 +412,16 @@ def convert_linalg_gemm2(node, **kwargs): if "attrs" in node: attrs = node["attrs"] alpha = float(attrs["alpha"]) - transA = int(attrs["transpose_a"]) - transB = int(attrs["transpose_b"]) - beta = 0.0 + trans_a = int(attrs["transpose_a"]) + trans_b = int(attrs["transpose_b"]) else: alpha = 1.0 - transA = 0 - transB = 0 + trans_a = 0 + trans_b = 0 op_name = "transpose" + str(kwargs["idx"]) - if alpha == 1.0 and transA == 0 and transB == 0: + if alpha == 1.0 and trans_a == 0 and trans_b == 0: matmul_node = helper.make_node( 'MatMul', inputs=[input_node_a, input_node_b], @@ -428,9 +429,9 @@ def convert_linalg_gemm2(node, **kwargs): name=name ) return [matmul_node] - elif transA == 1 and transB == 0: + elif trans_a == 1 and trans_b == 0: op_name = "transpose" + str(kwargs["idx"]) - transA_node = helper.make_node( + trans_a_node = helper.make_node( 'Transpose', inputs=[input_node_a], outputs=[op_name+"_a"], @@ -439,14 +440,14 @@ def convert_linalg_gemm2(node, **kwargs): matmul_node = helper.make_node( 'MatMul', - inputs=[transA_node.name, input_node_b], + inputs=[trans_a_node.name, input_node_b], outputs=[name], name=name ) - return [transA_node, matmul_node] + return [trans_a_node, matmul_node] - elif transA == 0 and transB == 1: - transB_node = helper.make_node( + elif trans_a == 0 and trans_b == 1: + trans_b_node = helper.make_node( 'Transpose', inputs=[input_node_b], outputs=[op_name+"_b"], @@ -455,21 +456,21 @@ def convert_linalg_gemm2(node, **kwargs): matmul_node = helper.make_node( 'MatMul', - inputs=[input_node_a, transB_node.name], + inputs=[input_node_a, trans_b_node.name], outputs=[name], name=name ) - return [transB_node, matmul_node] + return [trans_b_node, matmul_node] else: - transA_node = helper.make_node( + trans_a_node = helper.make_node( 'Transpose', inputs=[input_node_a], outputs=[op_name+"_a"], name=op_name+"_a" ) - transB_node = helper.make_node( + trans_b_node = helper.make_node( 'Transpose', inputs=[input_node_b], outputs=[op_name+"_b"], @@ -478,12 +479,12 @@ def convert_linalg_gemm2(node, **kwargs): matmul_node = helper.make_node( 'MatMul', - inputs=[transA_node.name, transB_node.name], + inputs=[trans_a_node.name, trans_b_node.name], outputs=[name], name=name ) - return [transA_node, transB_node, matmul_node] + return [trans_a_node, trans_b_node, matmul_node] @mx2onnx.register("Pooling") @@ -506,7 +507,7 @@ def convert_pooling(node, **kwargs): pool_types = {"max": "MaxPool", "avg": "AveragePool"} global_pool_types = {"max": "GlobalMaxPool", "avg": "GlobalAveragePool"} - if global_pool: + if global_pool == 1: node = helper.make_node( global_pool_types[pool_type], [input_node.name], # input @@ -536,12 +537,12 @@ def convert_exp(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = kwargs["index_lookup"][inputs[0][0]] - a_node = proc_nodes[a].name + input_node_id = kwargs["index_lookup"][inputs[0][0]] + input_node = proc_nodes[input_node_id].name node = helper.make_node( "Exp", - [a_node], + [input_node], [name], name=name, ) @@ -556,8 +557,8 @@ def convert_leakyrelu(node, **kwargs): name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = kwargs["index_lookup"][inputs[0][0]] - a_node = proc_nodes[a].name + input_node_id = kwargs["index_lookup"][inputs[0][0]] + input_node = proc_nodes[input_node_id].name attrs = node["attrs"] act_type = attrs.get("act_type", "LeakyRelu") @@ -571,13 +572,13 @@ def convert_leakyrelu(node, **kwargs): node = helper.make_node( act_name[act_type], - inputs=[a_node, alpha_node_name], + inputs=[input_node, alpha_node_name], outputs=[name], name=name) else: node = helper.make_node( act_name[act_type], - inputs=[a_node], + inputs=[input_node], outputs=[name], name=name, alpha=alpha) @@ -723,13 +724,13 @@ def convert_dropout(node, **kwargs): input_id = kwargs["index_lookup"][node["inputs"][0][0]] input_name = kwargs["proc_nodes"][input_id].name attrs = node["attrs"] - p = float(attrs["p"]) + probability = float(attrs["p"]) dropout_node = helper.make_node( "Dropout", [input_name], [name], - ratio=p, + ratio=probability, name=name ) return [dropout_node] @@ -766,13 +767,13 @@ def convert_mul_scalar(node, **kwargs): inputs = node["inputs"] scalar_mul_value = [int(node.get("attrs", {}).get("scalar", 1))] - a = kwargs["index_lookup"][inputs[0][0]] - a_node = proc_nodes[a].name + input_name_id = kwargs["index_lookup"][inputs[0][0]] + input_node = proc_nodes[input_name_id].name initializer = kwargs["initializer"] flag = True for i in initializer: - if i.name == a_node: + if i.name == input_node: new_initializer = scalar_mul_value[0]*numpy_helper.to_array(i) flag = False break @@ -797,7 +798,7 @@ def convert_mul_scalar(node, **kwargs): mul_node = helper.make_node( "Mul", - [a_node, scalar_op_name], + [input_node, scalar_op_name], [name], name=name ) @@ -808,7 +809,7 @@ def convert_mul_scalar(node, **kwargs): data_type = mapping.NP_TYPE_TO_TENSOR_TYPE[new_initializer.dtype] dims = np.shape(new_initializer) - new_a_node = a_node + str(kwargs["idx"]) + new_a_node = input_node + str(kwargs["idx"]) tensor_node = helper.make_tensor_value_info(new_a_node, data_type, dims) initializer.append( @@ -877,7 +878,7 @@ def convert_argmin(node, **kwargs): return [node] @mx2onnx.register("_maximum") -def convert_max(node, **kwargs): +def convert_maximum(node, **kwargs): """Map MXNet's _maximum operator attributes to onnx's Max operator and return the created node. """ @@ -940,13 +941,13 @@ def convert_min(node, **kwargs): keepdims = int(node.get("attrs", {}).get("keepdims", 0)) - a = kwargs["index_lookup"][inputs[0][0]] - a_node = proc_nodes[a].name + input_node_id = kwargs["index_lookup"][inputs[0][0]] + input_node = proc_nodes[input_node_id].name if axes is not None: node = helper.make_node( 'ReduceMin', - inputs=[a_node], + inputs=[input_node], outputs=[name], axes=axes, keepdims=keepdims, @@ -957,7 +958,7 @@ def convert_min(node, **kwargs): else: node = helper.make_node( 'ReduceMin', - inputs=[a_node], + inputs=[input_node], outputs=[name], keepdims=keepdims, name=name @@ -980,13 +981,13 @@ def convert_max(node, **kwargs): keepdims = int(node.get("attrs", {}).get("keepdims", 0)) - a = kwargs["index_lookup"][inputs[0][0]] - a_node = proc_nodes[a].name + input_node_id = kwargs["index_lookup"][inputs[0][0]] + input_node = proc_nodes[input_node_id].name if axes is not None: node = helper.make_node( 'ReduceMax', - inputs=[a_node], + inputs=[input_node], outputs=[name], axes=axes, keepdims=keepdims, @@ -997,7 +998,7 @@ def convert_max(node, **kwargs): else: node = helper.make_node( 'ReduceMax', - inputs=[a_node], + inputs=[input_node], outputs=[name], keepdims=keepdims, name=name @@ -1020,13 +1021,13 @@ def convert_mean(node, **kwargs): keepdims = int(node.get("attrs", {}).get("keepdims", 0)) - a = kwargs["index_lookup"][inputs[0][0]] - a_node = proc_nodes[a].name + input_node_id = kwargs["index_lookup"][inputs[0][0]] + input_node = proc_nodes[input_node_id].name if axes is not None: node = helper.make_node( 'ReduceMean', - inputs=[a_node], + inputs=[input_node], outputs=[name], axes=axes, keepdims=keepdims, @@ -1037,7 +1038,7 @@ def convert_mean(node, **kwargs): else: node = helper.make_node( 'ReduceMean', - inputs=[a_node], + inputs=[input_node], outputs=[name], keepdims=keepdims, name=name @@ -1060,13 +1061,13 @@ def convert_prod(node, **kwargs): keepdims = int(node.get("attrs", {}).get("keepdims", 0)) - a = kwargs["index_lookup"][inputs[0][0]] - a_node = proc_nodes[a].name + input_node_id = kwargs["index_lookup"][inputs[0][0]] + input_node = proc_nodes[input_node_id].name if axes is not None: node = helper.make_node( 'ReduceProd', - inputs=[a_node], + inputs=[input_node], outputs=[name], axes=axes, keepdims=keepdims, @@ -1077,7 +1078,7 @@ def convert_prod(node, **kwargs): else: node = helper.make_node( 'ReduceProd', - inputs=[a_node], + inputs=[input_node], outputs=[name], keepdims=keepdims, name=name @@ -1096,15 +1097,15 @@ def convert_elementwise_add(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = kwargs["index_lookup"][inputs[0][0]] - b = kwargs["index_lookup"][inputs[1][0]] + input_node_a_id = kwargs["index_lookup"][inputs[0][0]] + input_node_b_id = kwargs["index_lookup"][inputs[1][0]] - a_node = proc_nodes[a].name - b_node = proc_nodes[b].name + input_node_a = proc_nodes[input_node_a_id].name + input_node_b = proc_nodes[input_node_b_id].name add_node = helper.make_node( "Add", - [a_node, b_node], + [input_node_a, input_node_b], [name], name=name, ) @@ -1121,15 +1122,15 @@ def covert_broadcast_add(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = kwargs["index_lookup"][inputs[0][0]] - b = kwargs["index_lookup"][inputs[1][0]] + input_node_a_id = kwargs["index_lookup"][inputs[0][0]] + input_node_b_id = kwargs["index_lookup"][inputs[1][0]] - a_node = proc_nodes[a].name - b_node = proc_nodes[b].name + input_node_a = proc_nodes[input_node_a_id].name + input_node_b = proc_nodes[input_node_b_id].name add_node = helper.make_node( "Add", - [a_node, b_node], + [input_node_a, input_node_b], [name], name=name, ) @@ -1146,15 +1147,15 @@ def convert_elementwise_sub(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = kwargs["index_lookup"][inputs[0][0]] - b = kwargs["index_lookup"][inputs[1][0]] + input_node_a_id = kwargs["index_lookup"][inputs[0][0]] + input_node_b_id = kwargs["index_lookup"][inputs[1][0]] - a_node = proc_nodes[a].name - b_node = proc_nodes[b].name + input_node_a = proc_nodes[input_node_a_id].name + input_node_b = proc_nodes[input_node_b_id].name sub_node = helper.make_node( "Sub", - [a_node, b_node], + [input_node_a, input_node_b], [name], name=name, ) @@ -1170,15 +1171,15 @@ def covert_broadcast_sub(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = kwargs["index_lookup"][inputs[0][0]] - b = kwargs["index_lookup"][inputs[1][0]] + input_node_a_id = kwargs["index_lookup"][inputs[0][0]] + input_node_b_id = kwargs["index_lookup"][inputs[1][0]] - a_node = proc_nodes[a].name - b_node = proc_nodes[b].name + input_node_a = proc_nodes[input_node_a_id].name + input_node_b = proc_nodes[input_node_b_id].name sub_node = helper.make_node( "Sub", - [a_node, b_node], + [input_node_a, input_node_b], [name], name=name, ) @@ -1187,7 +1188,7 @@ def covert_broadcast_sub(node, **kwargs): @mx2onnx.register("elemwise_mul") -def convert_mul(node, **kwargs): +def convert_elemwise_mul(node, **kwargs): """Map MXNet's elemwise_mul operator attributes to onnx's Mul operator and return the created node. """ @@ -1195,15 +1196,15 @@ def convert_mul(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = kwargs["index_lookup"][inputs[0][0]] - b = kwargs["index_lookup"][inputs[1][0]] + input_node_a_id = kwargs["index_lookup"][inputs[0][0]] + input_node_b_id = kwargs["index_lookup"][inputs[1][0]] - a_node = proc_nodes[a].name - b_node = proc_nodes[b].name + input_node_a = proc_nodes[input_node_a_id].name + input_node_b = proc_nodes[input_node_b_id].name mul_node = helper.make_node( "Mul", - [a_node, b_node], + [input_node_a, input_node_b], [name], name=name, ) @@ -1211,7 +1212,7 @@ def convert_mul(node, **kwargs): return [mul_node] @mx2onnx.register("broadcast_mul") -def convert_mul(node, **kwargs): +def convert_broadcast_mul(node, **kwargs): """Map MXNet's broadcast_mul operator attributes to onnx's Mul operator and return the created node. """ @@ -1219,15 +1220,15 @@ def convert_mul(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = kwargs["index_lookup"][inputs[0][0]] - b = kwargs["index_lookup"][inputs[1][0]] + input_node_a_id = kwargs["index_lookup"][inputs[0][0]] + input_node_b_id = kwargs["index_lookup"][inputs[1][0]] - a_node = proc_nodes[a].name - b_node = proc_nodes[b].name + input_node_a = proc_nodes[input_node_a_id].name + input_node_b = proc_nodes[input_node_b_id].name mul_node = helper.make_node( "Mul", - [a_node, b_node], + [input_node_a, input_node_b], [name], name=name ) @@ -1236,7 +1237,7 @@ def convert_mul(node, **kwargs): @mx2onnx.register("elemwise_div") -def convert_mul(node, **kwargs): +def convert_elemwise_div(node, **kwargs): """Map MXNet's elemwise_div operator attributes to onnx's Div operator and return the created node. """ @@ -1244,15 +1245,15 @@ def convert_mul(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = kwargs["index_lookup"][inputs[0][0]] - b = kwargs["index_lookup"][inputs[1][0]] + input_node_a_id = kwargs["index_lookup"][inputs[0][0]] + input_node_b_id = kwargs["index_lookup"][inputs[1][0]] - a_node = proc_nodes[a].name - b_node = proc_nodes[b].name + input_node_a = proc_nodes[input_node_a_id].name + input_node_b = proc_nodes[input_node_b_id].name div_node = helper.make_node( "Div", - [a_node, b_node], + [input_node_a, input_node_b], [name], name=name ) @@ -1261,7 +1262,7 @@ def convert_mul(node, **kwargs): @mx2onnx.register("broadcast_div") -def convert_div(node, **kwargs): +def convert_broadcast_div(node, **kwargs): """Map MXNet's broadcast_div operator attributes to onnx's Div operator and return the created node. """ @@ -1269,15 +1270,15 @@ def convert_div(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = kwargs["index_lookup"][inputs[0][0]] - b = kwargs["index_lookup"][inputs[1][0]] + input_node_a_id = kwargs["index_lookup"][inputs[0][0]] + input_node_b_id = kwargs["index_lookup"][inputs[1][0]] - a_node = proc_nodes[a].name - b_node = proc_nodes[b].name + input_node_a = proc_nodes[input_node_a_id].name + input_node_b = proc_nodes[input_node_b_id].name div_node = helper.make_node( "Div", - [a_node, b_node], + [input_node_a, input_node_b], [name], name=name ) @@ -1294,13 +1295,13 @@ def convert_negative(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = kwargs["index_lookup"][inputs[0][0]] + input_node_id = kwargs["index_lookup"][inputs[0][0]] - a_node = proc_nodes[a].name + input_node = proc_nodes[input_node_id].name neg_node = helper.make_node( "Neg", - [a_node], + [input_node], [name], name=name, ) @@ -1317,13 +1318,13 @@ def convert_abs(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = kwargs["index_lookup"][inputs[0][0]] + input_node_id = kwargs["index_lookup"][inputs[0][0]] - a_node = proc_nodes[a].name + input_node = proc_nodes[input_node_id].name abs_node = helper.make_node( "Abs", - [a_node], + [input_node], [name], name=name ) @@ -1341,7 +1342,7 @@ def convert_addn(node, **kwargs): inputs = node["inputs"] input_list = [] - for idx, input_val in enumerate(inputs): + for input_val in inputs: input_list.append(proc_nodes[kwargs["index_lookup"][input_val[0]]].name) sum_node = helper.make_node( @@ -1362,12 +1363,12 @@ def convert_ceil(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = kwargs["index_lookup"][inputs[0][0]] - a_node = proc_nodes[a].name + input_node_id = kwargs["index_lookup"][inputs[0][0]] + input_node = proc_nodes[input_node_id].name node = helper.make_node( "Ceil", - [a_node], + [input_node], [name], name=name ) @@ -1382,12 +1383,12 @@ def convert_floor(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = kwargs["index_lookup"][inputs[0][0]] - a_node = proc_nodes[a].name + input_node_id = kwargs["index_lookup"][inputs[0][0]] + input_node = proc_nodes[input_node_id].name node = helper.make_node( "Floor", - [a_node], + [input_node], [name], name=name ) @@ -1453,12 +1454,12 @@ def convert_cast(node, **kwargs): inputs = node["inputs"] dtype = node["attrs"]["dtype"] - a = kwargs["index_lookup"][inputs[0][0]] - a_node = proc_nodes[a].name + input_node_id = kwargs["index_lookup"][inputs[0][0]] + input_node = proc_nodes[input_node_id].name node = helper.make_node( "Cast", - [a_node], + [input_node], [name], to=dtype, name=name, @@ -1478,12 +1479,12 @@ def convert_slice_axis(node, **kwargs): starts = int(node["attrs"]["begin"]) ends = int(node["attrs"]["end"]) - a = kwargs["index_lookup"][inputs[0][0]] - a_node = proc_nodes[a].name + input_node_id = kwargs["index_lookup"][inputs[0][0]] + input_node = proc_nodes[input_node_id].name node = helper.make_node( "Slice", - [a_node], + [input_node], [name], axes=[axes], starts=[starts], @@ -1508,13 +1509,13 @@ def convert_slice_channel(node, **kwargs): axis = int(node.get("attrs", {}).get("axis", 1)) squeeze_axis = int(node.get("attrs", {}).get("squeeze_axis", 0)) - a = kwargs["index_lookup"][inputs[0][0]] - a_node = proc_nodes[a].name + input_node_id = kwargs["index_lookup"][inputs[0][0]] + input_node = proc_nodes[input_node_id].name if num_outputs == 1 and squeeze_axis == 1: node = helper.make_node( "Squeeze", - [a_node], + [input_node], [name], axes=[axis], name=name, @@ -1522,7 +1523,7 @@ def convert_slice_channel(node, **kwargs): else: node = helper.make_node( "Split", - [a_node], + [input_node], [name], axis=axis, split=[num_outputs], @@ -1542,12 +1543,12 @@ def convert_expand_dims(node, **kwargs): inputs = node["inputs"] axis = int(node["attrs"]["axis"]) - a = kwargs["index_lookup"][inputs[0][0]] - a_node = proc_nodes[a].name + input_node_id = kwargs["index_lookup"][inputs[0][0]] + input_node = proc_nodes[input_node_id].name node = helper.make_node( "Unsqueeze", - [a_node], + [input_node], [name], axes=[axis], name=name, @@ -1564,12 +1565,12 @@ def convert_log(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = kwargs["index_lookup"][inputs[0][0]] - a_node = proc_nodes[a].name + input_node_id = kwargs["index_lookup"][inputs[0][0]] + input_node = proc_nodes[input_node_id].name node = helper.make_node( "Log", - [a_node], + [input_node], [name], name=name, ) @@ -1585,12 +1586,12 @@ def convert_reciprocal(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = kwargs["index_lookup"][inputs[0][0]] - a_node = proc_nodes[a].name + input_node_id = kwargs["index_lookup"][inputs[0][0]] + input_node = proc_nodes[input_node_id].name node = helper.make_node( "Reciprocal", - [a_node], + [input_node], [name], name=name, ) @@ -1606,15 +1607,15 @@ def convert_power(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = kwargs["index_lookup"][inputs[0][0]] - b = kwargs["index_lookup"][inputs[1][0]] + input_node_a_id = kwargs["index_lookup"][inputs[0][0]] + input_node_b_id = kwargs["index_lookup"][inputs[1][0]] - a_node = proc_nodes[a].name - b_node = proc_nodes[b].name + input_node_a = proc_nodes[input_node_a_id].name + input_node_b = proc_nodes[input_node_b_id].name node = helper.make_node( "Pow", - [a_node, b_node], + [input_node_a, input_node_b], [name], name=None ) @@ -1629,12 +1630,12 @@ def convert_sqrt(node, **kwargs): proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - a = kwargs["index_lookup"][inputs[0][0]] - a_node = proc_nodes[a].name + input_node_id = kwargs["index_lookup"][inputs[0][0]] + input_node = proc_nodes[input_node_id].name node = helper.make_node( "Sqrt", - [a_node], + [input_node], [name], name=name, ) From f95f022924bd2fde84bf50ad3d08937c89d8772e Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 5 Jun 2018 14:35:06 -0700 Subject: [PATCH 45/82] fix --- python/mxnet/contrib/onnx/_export/op_translations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index dae18943195d..3281449fdbb9 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -498,7 +498,7 @@ def convert_pooling(node, **kwargs): kernel = eval(attrs["kernel"]) pool_type = attrs["pool_type"] stride = eval(attrs["stride"]) if attrs.get("stride") else None - global_pool = attrs.get("global_pool", 0) + global_pool = attrs.get("global_pool", False) node_inputs = node["inputs"] input_node_idx = kwargs["index_lookup"][node_inputs[0][0]] input_node = proc_nodes[input_node_idx] @@ -507,7 +507,7 @@ def convert_pooling(node, **kwargs): pool_types = {"max": "MaxPool", "avg": "AveragePool"} global_pool_types = {"max": "GlobalMaxPool", "avg": "GlobalAveragePool"} - if global_pool == 1: + if global_pool: node = helper.make_node( global_pool_types[pool_type], [input_node.name], # input From e1f324bf9a292a34961421f49ff0a778dfa7483b Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 5 Jun 2018 14:58:05 -0700 Subject: [PATCH 46/82] fix global_pool --- python/mxnet/contrib/onnx/_export/op_translations.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 3281449fdbb9..deda7a5a00eb 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -498,7 +498,8 @@ def convert_pooling(node, **kwargs): kernel = eval(attrs["kernel"]) pool_type = attrs["pool_type"] stride = eval(attrs["stride"]) if attrs.get("stride") else None - global_pool = attrs.get("global_pool", False) + global_pool = True if "global_pool" in attrs and\ + attrs.get("global_pool") == "True" else False node_inputs = node["inputs"] input_node_idx = kwargs["index_lookup"][node_inputs[0][0]] input_node = proc_nodes[input_node_idx] From dc45f06712ee78a594cb40d5f61a23ff9625773e Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 5 Jun 2018 15:23:00 -0700 Subject: [PATCH 47/82] code cleanup --- .../contrib/onnx/_export/export_model.py | 16 ++++++------- .../mxnet/contrib/onnx/_export/export_onnx.py | 24 +++++++++---------- .../contrib/onnx/_export/op_translations.py | 6 ++--- tests/python-pytest/onnx/export/backend.py | 6 ++--- .../python-pytest/onnx/export/backend_rep.py | 1 - .../onnx/export/mxnet_export_test.py | 6 +---- 6 files changed, 26 insertions(+), 33 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_model.py b/python/mxnet/contrib/onnx/_export/export_model.py index 1d8423e85f75..1bbd10e68678 100644 --- a/python/mxnet/contrib/onnx/_export/export_model.py +++ b/python/mxnet/contrib/onnx/_export/export_model.py @@ -27,7 +27,7 @@ from onnx import helper, mapping from six import string_types -from .export_onnx import MxNetToONNXConverter +from .export_onnx import MXNetGraph from .export_helper import load_module @@ -56,19 +56,19 @@ def export_model(model, weights, input_shape, input_type, onnx_file_path, log=Fa onnx_file_path : str Onnx file path """ - converter = MxNetToONNXConverter() + converter = MXNetGraph() data_format = np.dtype(input_type) if isinstance(model, string_types) and isinstance(weights, string_types): print("Converting json and params file to sym and weights") sym, params = load_module(model, weights, input_shape) - onnx_graph = converter.convert_mx2onnx_graph(sym, params, input_shape, - mapping.NP_TYPE_TO_TENSOR_TYPE[data_format], - log=log) + onnx_graph = converter.create_onnx_graph_proto(sym, params, input_shape, + mapping.NP_TYPE_TO_TENSOR_TYPE[data_format], + log=log) else: - onnx_graph = converter.convert_mx2onnx_graph(model, weights, input_shape, - mapping.NP_TYPE_TO_TENSOR_TYPE[data_format], - log=log) + onnx_graph = converter.create_onnx_graph_proto(model, weights, input_shape, + mapping.NP_TYPE_TO_TENSOR_TYPE[data_format], + log=log) # Create the model (ModelProto) onnx_model = helper.make_model(onnx_graph) diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index b73af467a308..0477aa0f4e55 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -35,7 +35,7 @@ from .... import module as mod -class MxNetToONNXConverter: +class MXNetGraph(object): """Class to convert MXNet to ONNX graph""" registry_ = {} input_output_maps_ = {} @@ -51,7 +51,7 @@ def register(op_name): """Register operator""" def wrapper(func): """Helper function to map functions""" - MxNetToONNXConverter.registry_[op_name] = func + MXNetGraph.registry_[op_name] = func return func return wrapper @@ -60,9 +60,9 @@ def wrapper(func): def convert_layer(node, **kwargs): """Convert MXNet layer to ONNX""" op = str(node["op"]) - if op not in MxNetToONNXConverter.registry_: + if op not in MXNetGraph.registry_: raise AttributeError("No conversion function registered for op type %s yet." % op) - convert_fun = MxNetToONNXConverter.registry_[op] + convert_fun = MXNetGraph.registry_[op] return convert_fun(node, **kwargs) @staticmethod @@ -118,8 +118,8 @@ def infer_output_shape(sym, params, in_shape): """Infer output shape by doing a forward pass using dummy inputs """ #create dummy input inputs = [np.random.randn(*input_shape) for input_shape in in_shape] - arg, aux = MxNetToONNXConverter.split_params(sym, params) - return MxNetToONNXConverter.forward_pass(inputs, sym, arg, aux) + arg, aux = MXNetGraph.split_params(sym, params) + return MXNetGraph.forward_pass(inputs, sym, arg, aux) @staticmethod @@ -128,13 +128,13 @@ def convert_weights_to_numpy(weights_dict): return dict([(k.replace("arg:", "").replace("aux:", ""), v.asnumpy()) for k, v in weights_dict.items()]) - def convert_mx2onnx_graph(self, sym, params, in_shape, in_type, log=False): + def create_onnx_graph_proto(self, sym, params, in_shape, in_type, log=False): """Convert MXNet graph to ONNX graph""" # Determine output shape - output_shape = MxNetToONNXConverter.infer_output_shape(sym, params, in_shape) + output_shape = MXNetGraph.infer_output_shape(sym, params, in_shape) - weights = MxNetToONNXConverter.convert_weights_to_numpy(params) + weights = MXNetGraph.convert_weights_to_numpy(params) mx_graph = json.loads(sym.tojson())["nodes"] @@ -153,10 +153,10 @@ def convert_mx2onnx_graph(self, sym, params, in_shape, in_type, log=False): print("Converting idx: %d, op: %s, name: %s" % (idx, op, name)) if op == "null" and name not in params: - #Handling graph input + # Handling graph input if name == 'softmax_label': continue - converted = MxNetToONNXConverter.convert_layer( + converted = MXNetGraph.convert_layer( node, is_input=True, mx_graph=mx_graph, @@ -169,7 +169,7 @@ def convert_mx2onnx_graph(self, sym, params, in_shape, in_type, log=False): graph_input_idx += 1 else: - converted = MxNetToONNXConverter.convert_layer( + converted = MXNetGraph.convert_layer( node, is_input=False, mx_graph=mx_graph, diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index deda7a5a00eb..ed507f91c18a 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -16,7 +16,7 @@ # under the License. # coding: utf-8 -# pylint: disable=too-many-locals +# pylint: disable=too-many-locals,no-else-return,too-many-lines """ Conversion Functions for common layers. Add new functions here with a decorator. @@ -29,7 +29,7 @@ import numpy as np from onnx import helper, numpy_helper, mapping -from .export_onnx import MxNetToONNXConverter as mx2onnx +from .export_onnx import MXNetGraph as mx2onnx def looks_like_weight(name): """Internal helper to figure out if node should be hidden with `hide_weights`. @@ -805,7 +805,6 @@ def convert_mul_scalar(node, **kwargs): ) return [tensor_node, mul_node] - else: data_type = mapping.NP_TYPE_TO_TENSOR_TYPE[new_initializer.dtype] dims = np.shape(new_initializer) @@ -822,7 +821,6 @@ def convert_mul_scalar(node, **kwargs): raw=False, ) ) - return [tensor_node] # Sorting and Searching diff --git a/tests/python-pytest/onnx/export/backend.py b/tests/python-pytest/onnx/export/backend.py index 1ee5b008445e..459702724e75 100644 --- a/tests/python-pytest/onnx/export/backend.py +++ b/tests/python-pytest/onnx/export/backend.py @@ -20,7 +20,7 @@ import mxnet as mx import numpy as np from mxnet.contrib.onnx._import.import_onnx import GraphProto -from mxnet.contrib.onnx._export.export_onnx import MxNetToONNXConverter +from mxnet.contrib.onnx._export.export_onnx import MXNetGraph try: from onnx import helper, TensorProto, mapping from onnx.backend.base import Backend @@ -49,8 +49,8 @@ def perform_import_export(graph_proto, input_shape): params.update(arg_params) params.update(aux_params) # exporting to onnx graph proto format - converter = MxNetToONNXConverter() - graph_proto = converter.convert_mx2onnx_graph(sym, params, in_shape=input_shape, in_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')]) + converter = MXNetGraph() + graph_proto = converter.create_onnx_graph_proto(sym, params, in_shape=input_shape, in_type=mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')]) # importing back to MXNET for verifying result. sym, arg_params, aux_params = graph.from_onnx(graph_proto) diff --git a/tests/python-pytest/onnx/export/backend_rep.py b/tests/python-pytest/onnx/export/backend_rep.py index 5046f8ad9a0b..dc87c15134d9 100644 --- a/tests/python-pytest/onnx/export/backend_rep.py +++ b/tests/python-pytest/onnx/export/backend_rep.py @@ -17,7 +17,6 @@ # coding: utf-8 """backend rep for onnx test infrastructure""" -from collections import namedtuple import numpy as np try: from onnx.backend.base import BackendRep diff --git a/tests/python-pytest/onnx/export/mxnet_export_test.py b/tests/python-pytest/onnx/export/mxnet_export_test.py index 305d5a9c4ea5..31781076698d 100644 --- a/tests/python-pytest/onnx/export/mxnet_export_test.py +++ b/tests/python-pytest/onnx/export/mxnet_export_test.py @@ -48,9 +48,7 @@ 'inception_v1': 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/inception_v1.tar.gz', 'inception_v2': - 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/inception_v2.tar.gz', - 'shufflenet': - 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/shufflenet.tar.gz' + 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/inception_v2.tar.gz' } def get_test_files(name): @@ -186,5 +184,3 @@ def test_model_accuracy(model_name, input_shape): # ONNX expected results due to AveragePool issue github issue(#10194) test_model_accuracy("inception_v1", (1, 3, 224, 224)) test_model_accuracy("inception_v2", (1, 3, 224, 224)) - - From 17b7d85545227be95eedf2cd22588c770fc1dbbe Mon Sep 17 00:00:00 2001 From: spidyDev Date: Tue, 5 Jun 2018 15:14:46 -0700 Subject: [PATCH 48/82] CI run fix --- ci/docker/runtime_functions.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ci/docker/runtime_functions.sh b/ci/docker/runtime_functions.sh index 9a7ef94e2545..fa84a7cb2fda 100755 --- a/ci/docker/runtime_functions.sh +++ b/ci/docker/runtime_functions.sh @@ -601,9 +601,8 @@ integrationtest_ubuntu_cpu_onnx() { pytest tests/python-pytest/onnx/import/mxnet_backend_test.py pytest tests/python-pytest/onnx/import/onnx_import_test.py pytest tests/python-pytest/onnx/import/gluon_backend_test.py - pytest tests/python-pytest/onnx/export/mxnet_export_test.py pytest tests/python-pytest/onnx/export/onnx_backend_test.py - + python tests/python-pytest/onnx/export/mxnet_export_test.py } integrationtest_ubuntu_gpu_python() { From 33e5ab61307ba6f210ca9b6a131d9c0aee257ebc Mon Sep 17 00:00:00 2001 From: spidyDev Date: Tue, 5 Jun 2018 16:02:19 -0700 Subject: [PATCH 49/82] lint fix --- .../mxnet/contrib/onnx/_export/export_onnx.py | 3 ++- .../contrib/onnx/_export/op_translations.py | 18 +++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 0477aa0f4e55..771fca1e9dcc 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -16,7 +16,8 @@ # under the License. # coding: utf-8 -# pylint: disable=invalid-name,too-many-locals,no-self-use,too-many-arguments +# pylint: disable=invalid-name,too-many-locals,no-self-use,too-many-arguments, +# disable=maybe-no-member """MXNet to ONNX graph converter functions""" from __future__ import absolute_import from __future__ import division diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index ed507f91c18a..8bf10f6c1fba 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -431,55 +431,59 @@ def convert_linalg_gemm2(node, **kwargs): return [matmul_node] elif trans_a == 1 and trans_b == 0: op_name = "transpose" + str(kwargs["idx"]) + node_name = op_name+"_a" trans_a_node = helper.make_node( 'Transpose', inputs=[input_node_a], outputs=[op_name+"_a"], - name=op_name+"_a" + name=node_name ) matmul_node = helper.make_node( 'MatMul', - inputs=[trans_a_node.name, input_node_b], + inputs=[node_name, input_node_b], outputs=[name], name=name ) return [trans_a_node, matmul_node] elif trans_a == 0 and trans_b == 1: + node_name = op_name + "_b" trans_b_node = helper.make_node( 'Transpose', inputs=[input_node_b], outputs=[op_name+"_b"], - name=op_name+"_b" + name=node_name ) matmul_node = helper.make_node( 'MatMul', - inputs=[input_node_a, trans_b_node.name], + inputs=[input_node_a, node_name], outputs=[name], name=name ) return [trans_b_node, matmul_node] else: + node_name_a = op_name+"_a" trans_a_node = helper.make_node( 'Transpose', inputs=[input_node_a], outputs=[op_name+"_a"], - name=op_name+"_a" + name=node_name_a ) + node_name_b = op_name + "_b" trans_b_node = helper.make_node( 'Transpose', inputs=[input_node_b], outputs=[op_name+"_b"], - name=op_name+"_b" + name=node_name_b ) matmul_node = helper.make_node( 'MatMul', - inputs=[trans_a_node.name, trans_b_node.name], + inputs=[node_name_a, node_name_b], outputs=[name], name=name ) From 6de4403750caee02498e8e86b95b8be59227e230 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 5 Jun 2018 17:04:12 -0700 Subject: [PATCH 50/82] some code cleanup --- .../contrib/onnx/_export/export_helper.py | 5 +- .../contrib/onnx/_export/export_model.py | 8 +- .../contrib/onnx/_export/op_translations.py | 116 ++++++++---------- 3 files changed, 60 insertions(+), 69 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_helper.py b/python/mxnet/contrib/onnx/_export/export_helper.py index 5c4cb4eb8609..88d9113ed69e 100644 --- a/python/mxnet/contrib/onnx/_export/export_helper.py +++ b/python/mxnet/contrib/onnx/_export/export_helper.py @@ -17,6 +17,7 @@ """export helper functions""" # coding: utf-8 import os +import logging import mxnet as mx def load_module(json_path, params_path, input_shape): @@ -46,8 +47,8 @@ def load_module(json_path, params_path, input_shape): model_name = json_path.rsplit('.', 1)[0].rsplit('-', 1)[0] num_epochs = int(params_path.rsplit('.', 1)[0].rsplit('-', 1)[1]) except IndexError: - print("Model and params name should be in format: " - "prefix-symbol.json, prefix-epoch.params") + logging.info("Model and params name should be in format: " + "prefix-symbol.json, prefix-epoch.params") raise sym, arg_params, aux_params = mx.model.load_checkpoint(model_name, num_epochs) diff --git a/python/mxnet/contrib/onnx/_export/export_model.py b/python/mxnet/contrib/onnx/_export/export_model.py index 1bbd10e68678..d0c2ddecb49a 100644 --- a/python/mxnet/contrib/onnx/_export/export_model.py +++ b/python/mxnet/contrib/onnx/_export/export_model.py @@ -23,6 +23,7 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals +import logging import numpy as np from onnx import helper, mapping @@ -31,7 +32,8 @@ from .export_helper import load_module -def export_model(model, weights, input_shape, input_type, onnx_file_path, log=False): +def export_model(model, weights, input_shape, input_type=np.float32, + onnx_file_path='model.onnx', log=False): """Exports the MXNet model file, passed as a parameter, into ONNX model. Accepts both symbol,parameter objects as well as json and params filepaths as input. Operator support and coverage - https://cwiki.apache.org/confluence/display/MXNET/ONNX @@ -60,7 +62,7 @@ def export_model(model, weights, input_shape, input_type, onnx_file_path, log=Fa data_format = np.dtype(input_type) if isinstance(model, string_types) and isinstance(weights, string_types): - print("Converting json and params file to sym and weights") + logging.info("Converting json and params file to sym and weights") sym, params = load_module(model, weights, input_shape) onnx_graph = converter.create_onnx_graph_proto(sym, params, input_shape, mapping.NP_TYPE_TO_TENSOR_TYPE[data_format], @@ -76,6 +78,6 @@ def export_model(model, weights, input_shape, input_type, onnx_file_path, log=Fa with open(onnx_file_path, "wb") as file_handle: serialized = onnx_model.SerializeToString() file_handle.write(serialized) - print("\nONNX file %s serialized to disk" % onnx_file_path) + logging.info("Exported ONNX file %s saved to disk", onnx_file_path) return onnx_file_path diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 8bf10f6c1fba..b211dca0172a 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -29,22 +29,10 @@ import numpy as np from onnx import helper, numpy_helper, mapping -from .export_onnx import MXNetGraph as mx2onnx - -def looks_like_weight(name): - """Internal helper to figure out if node should be hidden with `hide_weights`. - """ - if name.endswith("_weight") or name == 'W': - return True - if name.endswith("_bias") or name == "B": - return True - if name.endswith("_beta") or name.endswith("_gamma") or \ - name.endswith("_moving_var") or name.endswith("_moving_mean"): - return True - return False +from .export_onnx import MXNetGraph as mx_op -@mx2onnx.register("null") +@mx_op.register("null") def convert_weights_and_inputs(node, **kwargs): """Helper function to convert weights and inputs. """ @@ -75,7 +63,7 @@ def convert_weights_and_inputs(node, **kwargs): return [tval_node] -@mx2onnx.register("Convolution") +@mx_op.register("Convolution") def convert_convolution(node, **kwargs): """Map MXNet's convolution operator attributes to onnx's Conv operator and return the created node. @@ -138,7 +126,7 @@ def parse_helper(attrs_name, alt_value=None): return [conv_node] -@mx2onnx.register("FullyConnected") +@mx_op.register("FullyConnected") def convert_fully_connected(node, **kwargs): """Map MXNet's FullyConnected operator attributes to onnx's Gemm operator and return the created node. @@ -171,7 +159,7 @@ def convert_fully_connected(node, **kwargs): return [node] -@mx2onnx.register("BatchNorm") +@mx_op.register("BatchNorm") def convert_batchnorm(node, **kwargs): """Map MXNet's BatchNorm operator attributes to onnx's BatchNormalization operator and return the created node. @@ -215,7 +203,7 @@ def convert_batchnorm(node, **kwargs): return [bn_node] -@mx2onnx.register("tanh") +@mx_op.register("tanh") def convert_tanh(node, **kwargs): """Map MXNet's tanh operator attributes to onnx's Tanh operator and return the created node. @@ -235,7 +223,7 @@ def convert_tanh(node, **kwargs): return [node] #Basic neural network functions -@mx2onnx.register("sigmoid") +@mx_op.register("sigmoid") def convert_sigmoid(node, **kwargs): """Map MXNet's sigmoid operator attributes to onnx's Sigmoid operator and return the created node. @@ -254,7 +242,7 @@ def convert_sigmoid(node, **kwargs): ) return [node] -@mx2onnx.register("relu") +@mx_op.register("relu") def convert_relu(node, **kwargs): """Map MXNet's relu operator attributes to onnx's Relu operator and return the created node. @@ -274,7 +262,7 @@ def convert_relu(node, **kwargs): return [node] -@mx2onnx.register("Activation") +@mx_op.register("Activation") def convert_activation(node, **kwargs): """Map MXNet's Activation operator attributes to onnx's Tanh/Relu operator and return the created node. @@ -350,7 +338,7 @@ def convert_string_to_list(string_val): return result_list -@mx2onnx.register("Pad") +@mx_op.register("Pad") def convert_pad(node, **kwargs): """Map MXNet's pad operator attributes to onnx's Pad operator and return the created node. @@ -392,7 +380,7 @@ def convert_pad(node, **kwargs): return [node] -@mx2onnx.register("_linalg_gemm2") +@mx_op.register("_linalg_gemm2") def convert_linalg_gemm2(node, **kwargs): """Map MXNet's _linalg_gemm2 operator attributes to onnx's MatMul and Transpose operators based on the values set for @@ -491,7 +479,7 @@ def convert_linalg_gemm2(node, **kwargs): return [trans_a_node, trans_b_node, matmul_node] -@mx2onnx.register("Pooling") +@mx_op.register("Pooling") def convert_pooling(node, **kwargs): """Map MXNet's Pooling operator attributes to onnx's MaxPool/AveragePool/GlobalMaxPool/GlobalAveragePool operators @@ -533,7 +521,7 @@ def convert_pooling(node, **kwargs): return [node] -@mx2onnx.register("exp") +@mx_op.register("exp") def convert_exp(node, **kwargs): """Map MXNet's exp operator attributes to onnx's Exp operator and return the created node. @@ -554,7 +542,7 @@ def convert_exp(node, **kwargs): return [node] -@mx2onnx.register("LeakyReLU") +@mx_op.register("LeakyReLU") def convert_leakyrelu(node, **kwargs): """Map MXNet's LeakyReLU operator attributes to onnx's Elu/LeakyRelu/PRelu operators based on the input node's attributes and return the created node. @@ -591,7 +579,7 @@ def convert_leakyrelu(node, **kwargs): return [node] -@mx2onnx.register("softmax") +@mx_op.register("softmax") def convert_softmax(node, **kwargs): """Map MXNet's softmax operator attributes to onnx's Softmax operator and return the created node. @@ -617,7 +605,7 @@ def convert_softmax(node, **kwargs): # There's also mx.sym.softmax(), which doesn't do cross-entropy loss, # just softmax for inference - hence the name convert_softmax_output. -@mx2onnx.register("SoftmaxOutput") +@mx_op.register("SoftmaxOutput") def convert_softmax_output(node, **kwargs): """Map MXNet's SoftmaxOutput operator attributes to onnx's Softmax operator and return the created node. @@ -639,7 +627,7 @@ def convert_softmax_output(node, **kwargs): return [softmax_node] -@mx2onnx.register("Concat") +@mx_op.register("Concat") def convert_concat(node, **kwargs): """Map MXNet's Concat operator attributes to onnx's Concat operator and return the created node. @@ -659,7 +647,7 @@ def convert_concat(node, **kwargs): return [concat_node] -@mx2onnx.register("transpose") +@mx_op.register("transpose") def convert_transpose(node, **kwargs): """Map MXNet's transpose operator attributes to onnx's Transpose operator and return the created node. @@ -690,7 +678,7 @@ def convert_transpose(node, **kwargs): return [transpose_node] -@mx2onnx.register("LRN") +@mx_op.register("LRN") def convert_lrn(node, **kwargs): """Map MXNet's LRN operator attributes to onnx's LRN operator and return the created node. @@ -720,7 +708,7 @@ def convert_lrn(node, **kwargs): return [lrn_node] -@mx2onnx.register("Dropout") +@mx_op.register("Dropout") def convert_dropout(node, **kwargs): """Map MXNet's Dropout operator attributes to onnx's Dropout operator and return the created node. @@ -741,7 +729,7 @@ def convert_dropout(node, **kwargs): return [dropout_node] -@mx2onnx.register("Flatten") +@mx_op.register("Flatten") def convert_flatten(node, **kwargs): """Map MXNet's Flatten operator attributes to onnx's Flatten operator and return the created node. @@ -761,7 +749,7 @@ def convert_flatten(node, **kwargs): # Convert scalar value into node and pass it as input to mul_node -@mx2onnx.register("_mul_scalar") +@mx_op.register("_mul_scalar") def convert_mul_scalar(node, **kwargs): """Map MXNet's _mul_scalar operator attributes to onnx's Mul operator. Creates a new node for the input scalar value, adds it to the initializer @@ -828,7 +816,7 @@ def convert_mul_scalar(node, **kwargs): return [tensor_node] # Sorting and Searching -@mx2onnx.register("argmax") +@mx_op.register("argmax") def convert_argmax(node, **kwargs): """Map MXNet's argmax operator attributes to onnx's ArgMax operator and return the created node. @@ -854,7 +842,7 @@ def convert_argmax(node, **kwargs): ) return [node] -@mx2onnx.register("argmin") +@mx_op.register("argmin") def convert_argmin(node, **kwargs): """Map MXNet's argmin operator attributes to onnx's ArgMin operator and return the created node. @@ -880,7 +868,7 @@ def convert_argmin(node, **kwargs): ) return [node] -@mx2onnx.register("_maximum") +@mx_op.register("_maximum") def convert_maximum(node, **kwargs): """Map MXNet's _maximum operator attributes to onnx's Max operator and return the created node. @@ -905,7 +893,7 @@ def convert_maximum(node, **kwargs): return [node] -@mx2onnx.register("_minimum") +@mx_op.register("_minimum") def convert_minimum(node, **kwargs): """Map MXNet's _minimum operator attributes to onnx's Min operator and return the created node. @@ -930,7 +918,7 @@ def convert_minimum(node, **kwargs): return [node] -@mx2onnx.register("min") +@mx_op.register("min") def convert_min(node, **kwargs): """Map MXNet's min operator attributes to onnx's ReduceMin operator and return the created node. @@ -970,7 +958,7 @@ def convert_min(node, **kwargs): return [node] -@mx2onnx.register("max") +@mx_op.register("max") def convert_max(node, **kwargs): """Map MXNet's max operator attributes to onnx's ReduceMax operator and return the created node. @@ -1010,7 +998,7 @@ def convert_max(node, **kwargs): return [node] -@mx2onnx.register("mean") +@mx_op.register("mean") def convert_mean(node, **kwargs): """Map MXNet's mean operator attributes to onnx's ReduceMean operator and return the created node. @@ -1050,7 +1038,7 @@ def convert_mean(node, **kwargs): return [node] -@mx2onnx.register("prod") +@mx_op.register("prod") def convert_prod(node, **kwargs): """Map MXNet's prod operator attributes to onnx's ReduceProd operator and return the created node. @@ -1091,7 +1079,7 @@ def convert_prod(node, **kwargs): # Arithmetic Operations -@mx2onnx.register("elemwise_add") +@mx_op.register("elemwise_add") def convert_elementwise_add(node, **kwargs): """Map MXNet's elemwise_add operator attributes to onnx's Add operator and return the created node. @@ -1116,7 +1104,7 @@ def convert_elementwise_add(node, **kwargs): return [add_node] -@mx2onnx.register("broadcast_add") +@mx_op.register("broadcast_add") def covert_broadcast_add(node, **kwargs): """Map MXNet's broadcast_add operator attributes to onnx's Add operator and return the created node. @@ -1141,7 +1129,7 @@ def covert_broadcast_add(node, **kwargs): return [add_node] -@mx2onnx.register("elemwise_sub") +@mx_op.register("elemwise_sub") def convert_elementwise_sub(node, **kwargs): """Map MXNet's elemwise_sub operator attributes to onnx's Sub operator and return the created node. @@ -1165,7 +1153,7 @@ def convert_elementwise_sub(node, **kwargs): return [sub_node] -@mx2onnx.register("broadcast_sub") +@mx_op.register("broadcast_sub") def covert_broadcast_sub(node, **kwargs): """Map MXNet's broadcast_sub operator attributes to onnx's Sub operator and return the created node. @@ -1190,7 +1178,7 @@ def covert_broadcast_sub(node, **kwargs): return [sub_node] -@mx2onnx.register("elemwise_mul") +@mx_op.register("elemwise_mul") def convert_elemwise_mul(node, **kwargs): """Map MXNet's elemwise_mul operator attributes to onnx's Mul operator and return the created node. @@ -1214,7 +1202,7 @@ def convert_elemwise_mul(node, **kwargs): return [mul_node] -@mx2onnx.register("broadcast_mul") +@mx_op.register("broadcast_mul") def convert_broadcast_mul(node, **kwargs): """Map MXNet's broadcast_mul operator attributes to onnx's Mul operator and return the created node. @@ -1239,7 +1227,7 @@ def convert_broadcast_mul(node, **kwargs): return [mul_node] -@mx2onnx.register("elemwise_div") +@mx_op.register("elemwise_div") def convert_elemwise_div(node, **kwargs): """Map MXNet's elemwise_div operator attributes to onnx's Div operator and return the created node. @@ -1264,7 +1252,7 @@ def convert_elemwise_div(node, **kwargs): return [div_node] -@mx2onnx.register("broadcast_div") +@mx_op.register("broadcast_div") def convert_broadcast_div(node, **kwargs): """Map MXNet's broadcast_div operator attributes to onnx's Div operator and return the created node. @@ -1289,7 +1277,7 @@ def convert_broadcast_div(node, **kwargs): return [div_node] -@mx2onnx.register("negative") +@mx_op.register("negative") def convert_negative(node, **kwargs): """Map MXNet's negative operator attributes to onnx's Neg operator and return the created node. @@ -1312,7 +1300,7 @@ def convert_negative(node, **kwargs): return [neg_node] -@mx2onnx.register("abs") +@mx_op.register("abs") def convert_abs(node, **kwargs): """Map MXNet's abs operator attributes to onnx's Abs operator and return the created node. @@ -1335,7 +1323,7 @@ def convert_abs(node, **kwargs): return [abs_node] -@mx2onnx.register("add_n") +@mx_op.register("add_n") def convert_addn(node, **kwargs): """Map MXNet's add_n operator attributes to onnx's Sum operator and return the created node. @@ -1357,7 +1345,7 @@ def convert_addn(node, **kwargs): return [sum_node] # Rounding -@mx2onnx.register("ceil") +@mx_op.register("ceil") def convert_ceil(node, **kwargs): """Map MXNet's ceil operator attributes to onnx's Ceil operator and return the created node. @@ -1377,7 +1365,7 @@ def convert_ceil(node, **kwargs): ) return [node] -@mx2onnx.register("floor") +@mx_op.register("floor") def convert_floor(node, **kwargs): """Map MXNet's floor operator attributes to onnx's Floor operator and return the created node. @@ -1398,7 +1386,7 @@ def convert_floor(node, **kwargs): return [node] # Changing shape and type. -@mx2onnx.register("Reshape") +@mx_op.register("Reshape") def convert_reshape(node, **kwargs): """Map MXNet's Reshape operator attributes to onnx's Reshape operator. Converts output shape attribute to output shape tensor @@ -1447,7 +1435,7 @@ def convert_reshape(node, **kwargs): return [tensor_node, reshape_node] -@mx2onnx.register("Cast") +@mx_op.register("Cast") def convert_cast(node, **kwargs): """Map MXNet's Cast operator attributes to onnx's Cast operator and return the created node. @@ -1470,7 +1458,7 @@ def convert_cast(node, **kwargs): return [node] -@mx2onnx.register("slice_axis") +@mx_op.register("slice_axis") def convert_slice_axis(node, **kwargs): """Map MXNet's slice_axis operator attributes to onnx's Slice operator and return the created node. @@ -1499,7 +1487,7 @@ def convert_slice_axis(node, **kwargs): # SliceChannel/split operators will be mapped to onnx's squeeze and split operator. # [TODO] Address split with squeeze case -@mx2onnx.register("SliceChannel") +@mx_op.register("SliceChannel") def convert_slice_channel(node, **kwargs): """Map MXNet's SliceChannel operator attributes to onnx's Squeeze or Split operator based on squeeze_axis attribute @@ -1536,7 +1524,7 @@ def convert_slice_channel(node, **kwargs): return [node] -@mx2onnx.register("expand_dims") +@mx_op.register("expand_dims") def convert_expand_dims(node, **kwargs): """Map MXNet's expand_dims operator attributes to onnx's Unsqueeze operator and return the created node. @@ -1559,7 +1547,7 @@ def convert_expand_dims(node, **kwargs): return [node] -@mx2onnx.register("log") +@mx_op.register("log") def convert_log(node, **kwargs): """Map MXNet's log operator attributes to onnx's Log operator and return the created node. @@ -1580,7 +1568,7 @@ def convert_log(node, **kwargs): return [node] -@mx2onnx.register("reciprocal") +@mx_op.register("reciprocal") def convert_reciprocal(node, **kwargs): """Map MXNet's reciprocal operator attributes to onnx's Reciprocal operator and return the created node. @@ -1601,7 +1589,7 @@ def convert_reciprocal(node, **kwargs): return [node] -@mx2onnx.register("_power") +@mx_op.register("_power") def convert_power(node, **kwargs): """Map MXNet's _power operator attributes to onnx's Pow operator and return the created node. @@ -1624,7 +1612,7 @@ def convert_power(node, **kwargs): ) return [node] -@mx2onnx.register("sqrt") +@mx_op.register("sqrt") def convert_sqrt(node, **kwargs): """Map MXNet's sqrt operator attributes to onnx's Sqrt operator and return the created node. From 21c96c840cfe73bee6f24f741733c81b47395e2e Mon Sep 17 00:00:00 2001 From: spidyDev Date: Fri, 8 Jun 2018 15:58:05 -0700 Subject: [PATCH 51/82] Added required license comments --- LICENSE | 24 ++++++++++ python/mxnet/contrib/onnx/_export/LICENSE | 44 +++++++++++++++++++ .../mxnet/contrib/onnx/_export/export_onnx.py | 28 ++++++++++++ .../contrib/onnx/_export/op_translations.py | 32 ++++++++++++-- 4 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 python/mxnet/contrib/onnx/_export/LICENSE diff --git a/LICENSE b/LICENSE index 158bd37f2787..427268409c06 100644 --- a/LICENSE +++ b/LICENSE @@ -298,6 +298,30 @@ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + 3. ONNX Export + For details, see, python/mxnet/contrib/onnx/_export/LICENSE.TXT + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the NVIDIA CORPORATION nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ======================================================================================= diff --git a/python/mxnet/contrib/onnx/_export/LICENSE b/python/mxnet/contrib/onnx/_export/LICENSE new file mode 100644 index 000000000000..3abe1ee8a8ee --- /dev/null +++ b/python/mxnet/contrib/onnx/_export/LICENSE @@ -0,0 +1,44 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# Based on +# https://github.com/NVIDIA/mxnet_to_onnx/blob/master/mx2onnx_converter/# +# Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 771fca1e9dcc..805cfc242803 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -14,6 +14,34 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +# +# Based on +# https://github.com/NVIDIA/mxnet_to_onnx/blob/master/mx2onnx_converter/mx2onnx_converter.py# +# Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # coding: utf-8 # pylint: disable=invalid-name,too-many-locals,no-self-use,too-many-arguments, diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index b211dca0172a..2211b29bf285 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -14,9 +14,35 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - -# coding: utf-8 -# pylint: disable=too-many-locals,no-else-return,too-many-lines +# +# Based on +# https://github.com/NVIDIA/mxnet_to_onnx/blob/master/mx2onnx_converter/ +# mx2onnx_converter_functions.py +# Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ Conversion Functions for common layers. Add new functions here with a decorator. From 4aa962fd634573e6ce7e1865db2170cbd214979d Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Wed, 6 Jun 2018 13:22:35 -0700 Subject: [PATCH 52/82] pad in pooling added --- .../contrib/onnx/_export/op_translations.py | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 2211b29bf285..394697318345 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -89,6 +89,24 @@ def convert_weights_and_inputs(node, **kwargs): return [tval_node] +def parse_helper(attrs, attrs_name, alt_value=None): + """Helper function to parse operator attributes in required format.""" + tuple_re = re.compile('\([0-9L|,| ]+\)') + if attrs is None: + return alt_value + attrs_str = str(attrs.get(attrs_name)) + if attrs_str is None: + return alt_value + attrs_match = tuple_re.search(attrs_str) + if attrs_match is not None: + if attrs_match.span() == (0, len(attrs_str)): + dims = eval(attrs_str) + return dims + else: + raise AttributeError("Malformed %s dimensions: %s" % (attrs_name, str(attrs_str))) + return alt_value + + @mx_op.register("Convolution") def convert_convolution(node, **kwargs): """Map MXNet's convolution operator attributes to onnx's Conv operator @@ -107,29 +125,12 @@ def convert_convolution(node, **kwargs): bias_node = proc_nodes[kwargs["index_lookup"][inputs[2][0]]].name attrs = node.get("attrs") - tuple_re = re.compile('\([0-9L|,| ]+\)') - def parse_helper(attrs_name, alt_value=None): - """Helper function to parse operator attributes in required format.""" - if attrs is None: - return alt_value - attrs_str = str(attrs.get(attrs_name)) - if attrs_str is None: - return alt_value - attrs_match = tuple_re.search(attrs_str) - if attrs_match is not None: - if attrs_match.span() == (0, len(attrs_str)): - dims = eval(attrs_str) - return dims - else: - raise AttributeError("Malformed %s dimensions: %s" % (attrs_name, str(attrs_str))) - return alt_value - - kernel_dims = list(parse_helper("kernel")) - stride_dims = list(parse_helper("stride", [1, 1])) - pad_dims = list(parse_helper("pad", [0, 0])) + kernel_dims = list(parse_helper(attrs, "kernel")) + stride_dims = list(parse_helper(attrs, "stride", [1, 1])) + pad_dims = list(parse_helper(attrs, "pad", [0, 0])) num_group = int(attrs.get("num_group", 1)) - dilations = list(parse_helper("dilate", [1, 1])) + dilations = list(parse_helper(attrs, "dilate", [1, 1])) pad_dims = pad_dims + pad_dims @@ -523,6 +524,8 @@ def convert_pooling(node, **kwargs): input_node = proc_nodes[input_node_idx] name = node["name"] + pad_dims = list(parse_helper(attrs, "pad", [0, 0])) + pad_dims = pad_dims + pad_dims pool_types = {"max": "MaxPool", "avg": "AveragePool"} global_pool_types = {"max": "GlobalMaxPool", "avg": "GlobalAveragePool"} @@ -539,7 +542,7 @@ def convert_pooling(node, **kwargs): [input_node.name], # input [name], kernel_shape=kernel, - pads=[0, 0], + pads=pad_dims, strides=stride, name=name ) From 31ba99213a0c946345945e5a23ec7c5e4aae3d37 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Fri, 8 Jun 2018 15:17:28 -0700 Subject: [PATCH 53/82] slicechannel notimplementederror raised --- .../mxnet/contrib/onnx/_export/op_translations.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 394697318345..3135e5c7a898 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -1514,8 +1514,6 @@ def convert_slice_axis(node, **kwargs): return [node] -# SliceChannel/split operators will be mapped to onnx's squeeze and split operator. -# [TODO] Address split with squeeze case @mx_op.register("SliceChannel") def convert_slice_channel(node, **kwargs): """Map MXNet's SliceChannel operator attributes to onnx's Squeeze or Split @@ -1532,7 +1530,7 @@ def convert_slice_channel(node, **kwargs): input_node_id = kwargs["index_lookup"][inputs[0][0]] input_node = proc_nodes[input_node_id].name - if num_outputs == 1 and squeeze_axis == 1: + if squeeze_axis == 1 and num_outputs == 1: node = helper.make_node( "Squeeze", [input_node], @@ -1540,7 +1538,8 @@ def convert_slice_channel(node, **kwargs): axes=[axis], name=name, ) - else: + return [node] + elif squeeze_axis == 0 and num_outputs > 1: node = helper.make_node( "Split", [input_node], @@ -1549,8 +1548,10 @@ def convert_slice_channel(node, **kwargs): split=[num_outputs], name=name, ) - - return [node] + return [node] + else: + raise NotImplementedError("SliceChannel operator with num_outputs>1 and" + "squeeze_axis true is not implemented.") @mx_op.register("expand_dims") From 6d150b4e20990643e62e714de46af976b8474840 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Fri, 8 Jun 2018 16:52:02 -0700 Subject: [PATCH 54/82] Lint fixes --- python/mxnet/contrib/onnx/__init__.py | 1 - python/mxnet/contrib/onnx/_export/export_onnx.py | 2 +- python/mxnet/contrib/onnx/_export/op_translations.py | 3 +++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/python/mxnet/contrib/onnx/__init__.py b/python/mxnet/contrib/onnx/__init__.py index 304184c58e10..bf8efad3540c 100644 --- a/python/mxnet/contrib/onnx/__init__.py +++ b/python/mxnet/contrib/onnx/__init__.py @@ -19,4 +19,3 @@ from ._import.import_model import import_model, get_model_metadata from ._import.import_to_gluon import import_to_gluon from ._export.export_model import export_model - diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 805cfc242803..4a8e4320ff7e 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -45,7 +45,7 @@ # coding: utf-8 # pylint: disable=invalid-name,too-many-locals,no-self-use,too-many-arguments, -# disable=maybe-no-member +# disable=maybe-no-member,too-many-nested-blocks """MXNet to ONNX graph converter functions""" from __future__ import absolute_import from __future__ import division diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 3135e5c7a898..50247a8db03b 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -43,6 +43,9 @@ # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# coding: utf-8 +# pylint: disable=too-many-locals,no-else-return,too-many-lines """ Conversion Functions for common layers. Add new functions here with a decorator. From db70ce3da7d01a96a385ef0ec81a301c8c503ad2 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Fri, 8 Jun 2018 18:56:02 -0700 Subject: [PATCH 55/82] lint fix --- python/mxnet/contrib/onnx/_export/export_onnx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 4a8e4320ff7e..756647dfe7d1 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -45,7 +45,7 @@ # coding: utf-8 # pylint: disable=invalid-name,too-many-locals,no-self-use,too-many-arguments, -# disable=maybe-no-member,too-many-nested-blocks +# maybe-no-member,too-many-nested-blocks """MXNet to ONNX graph converter functions""" from __future__ import absolute_import from __future__ import division From 69a3aa80db83dfe31bfb2511ac3696672aaf5dc6 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Fri, 8 Jun 2018 19:13:19 -0700 Subject: [PATCH 56/82] lint fix --- python/mxnet/contrib/onnx/_export/export_onnx.py | 2 +- python/mxnet/contrib/onnx/_export/op_translations.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 756647dfe7d1..adff4e4aa6ad 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -45,7 +45,7 @@ # coding: utf-8 # pylint: disable=invalid-name,too-many-locals,no-self-use,too-many-arguments, -# maybe-no-member,too-many-nested-blocks +# pylint: maybe-no-member,too-many-nested-blocks """MXNet to ONNX graph converter functions""" from __future__ import absolute_import from __future__ import division diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 50247a8db03b..ad04c5c9494c 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -362,7 +362,7 @@ def convert_string_to_list(string_val): val = val.replace("L", "") val = val.replace("[", "") val = val.replace("]", "") - if val is not "" and val is not "None": + if val != "" and val != "None": result_list.append(int(val)) return result_list From 4ecd786bfd832257ae25a2b781f503db2aff6571 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Fri, 8 Jun 2018 19:17:06 -0700 Subject: [PATCH 57/82] lint fix --- python/mxnet/contrib/onnx/_export/export_onnx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index adff4e4aa6ad..22d54c7a4124 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -45,7 +45,7 @@ # coding: utf-8 # pylint: disable=invalid-name,too-many-locals,no-self-use,too-many-arguments, -# pylint: maybe-no-member,too-many-nested-blocks +# pylint: disable=maybe-no-member,too-many-nested-blocks """MXNet to ONNX graph converter functions""" from __future__ import absolute_import from __future__ import division From ab0bdc64b6f89fa170a55d2989b5703c8361195f Mon Sep 17 00:00:00 2001 From: spidyDev Date: Fri, 8 Jun 2018 19:20:38 -0700 Subject: [PATCH 58/82] lint fix --- python/mxnet/contrib/onnx/_export/op_translations.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index ad04c5c9494c..751577d307a8 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -46,6 +46,7 @@ # coding: utf-8 # pylint: disable=too-many-locals,no-else-return,too-many-lines +# pylint: disable=anomalous-backslash-in-string,eval-used """ Conversion Functions for common layers. Add new functions here with a decorator. From 14c136248019d2d199d78c00f79201708526db7a Mon Sep 17 00:00:00 2001 From: spidyDev Date: Mon, 11 Jun 2018 08:14:48 -0700 Subject: [PATCH 59/82] Correct license statement --- LICENSE | 76 +++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 26 deletions(-) diff --git a/LICENSE b/LICENSE index 427268409c06..a8b57e583764 100644 --- a/LICENSE +++ b/LICENSE @@ -298,32 +298,6 @@ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - 3. ONNX Export - For details, see, python/mxnet/contrib/onnx/_export/LICENSE.TXT - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the NVIDIA CORPORATION nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ======================================================================================= Other Licenses ======================================================================================= @@ -536,3 +510,53 @@ For details, see, 3rdparty/dmlc-core/include/dmlc/concurrentqueue.h ======================================================================================= + + 11. ONNX Export module + For details, see, python/mxnet/contrib/onnx/_export/LICENSE + + # Licensed to the Apache Software Foundation (ASF) under one + # or more contributor license agreements. See the NOTICE file + # distributed with this work for additional information + # regarding copyright ownership. The ASF licenses this file + # to you under the Apache License, Version 2.0 (the + # "License"); you may not use this file except in compliance + # with the License. You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, + # software distributed under the License is distributed on an + # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + # KIND, either express or implied. See the License for the + # specific language governing permissions and limitations + # under the License. + # + # Based on + # https://github.com/NVIDIA/mxnet_to_onnx/blob/master/mx2onnx_converter/# + # Copyright (c) 2017, NVIDIA CORPORATION. All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: + # * Redistributions of source code must retain the above copyright + # notice, this list of conditions and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright + # notice, this list of conditions and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of NVIDIA CORPORATION nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + From acd3c798a2bd16ac3985eb956714ecb4a1c2b112 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Mon, 11 Jun 2018 08:28:49 -0700 Subject: [PATCH 60/82] Adding onnx a runtime dependency --- python/mxnet/contrib/onnx/_export/export_model.py | 9 ++++++++- python/mxnet/contrib/onnx/_export/export_onnx.py | 9 ++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_model.py b/python/mxnet/contrib/onnx/_export/export_model.py index d0c2ddecb49a..9d0dd49d72c9 100644 --- a/python/mxnet/contrib/onnx/_export/export_model.py +++ b/python/mxnet/contrib/onnx/_export/export_model.py @@ -26,7 +26,7 @@ import logging import numpy as np -from onnx import helper, mapping + from six import string_types from .export_onnx import MXNetGraph from .export_helper import load_module @@ -58,6 +58,13 @@ def export_model(model, weights, input_shape, input_type=np.float32, onnx_file_path : str Onnx file path """ + + try: + from onnx import helper, mapping + except ImportError: + raise ImportError("Onnx and protobuf need to be installed. " + + "Instructions to install - https://github.com/onnx/onnx") + converter = MXNetGraph() data_format = np.dtype(input_type) diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 22d54c7a4124..be59f966580d 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -55,9 +55,6 @@ import json import numpy as np -from onnx import (checker, helper, onnx_pb2) -from onnx.helper import make_tensor_value_info - from .... import context from .... import ndarray as nd from .... import io @@ -159,6 +156,12 @@ def convert_weights_to_numpy(weights_dict): def create_onnx_graph_proto(self, sym, params, in_shape, in_type, log=False): """Convert MXNet graph to ONNX graph""" + try: + from onnx import (checker, helper, onnx_pb2) + from onnx.helper import make_tensor_value_info + except ImportError: + raise ImportError("Onnx and protobuf need to be installed. " + + "Instructions to install - https://github.com/onnx/onnx") # Determine output shape output_shape = MXNetGraph.infer_output_shape(sym, params, in_shape) From ba2d6ba32e458dcbbff88e6cba6ad1bf25f9204d Mon Sep 17 00:00:00 2001 From: spidyDev Date: Mon, 11 Jun 2018 10:25:28 -0700 Subject: [PATCH 61/82] Fix import module error for string_types --- python/mxnet/contrib/onnx/_export/export_model.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_model.py b/python/mxnet/contrib/onnx/_export/export_model.py index 9d0dd49d72c9..91b6d4f54bd8 100644 --- a/python/mxnet/contrib/onnx/_export/export_model.py +++ b/python/mxnet/contrib/onnx/_export/export_model.py @@ -26,8 +26,7 @@ import logging import numpy as np - -from six import string_types +from ....base import string_types from .export_onnx import MXNetGraph from .export_helper import load_module From 0829e4ffbeadabf33f770cf6f9ef3e5b54361b1d Mon Sep 17 00:00:00 2001 From: spidyDev Date: Tue, 12 Jun 2018 11:32:01 -0700 Subject: [PATCH 62/82] Making ONNX runtime dependency --- .../contrib/onnx/_export/op_translations.py | 174 ++++++++++++------ 1 file changed, 117 insertions(+), 57 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 751577d307a8..f0507bec8076 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -58,14 +58,80 @@ import re import numpy as np -from onnx import helper, numpy_helper, mapping from .export_onnx import MXNetGraph as mx_op +def import_onnx_modules(): + """ To make sure ONNX is runtime dependency, it is imported used only when needed""" + try: + from onnx import helper, numpy_helper, mapping + except ImportError: + raise ImportError("Onnx and protobuf need to be installed. " + + "Instructions to install - https://github.com/onnx/onnx") + return helper, numpy_helper, mapping + + +def parse_helper(attrs, attrs_name, alt_value=None): + """Helper function to parse operator attributes in required format.""" + tuple_re = re.compile('\([0-9L|,| ]+\)') + if attrs is None: + return alt_value + attrs_str = str(attrs.get(attrs_name)) + if attrs_str is None: + return alt_value + attrs_match = tuple_re.search(attrs_str) + if attrs_match is not None: + if attrs_match.span() == (0, len(attrs_str)): + dims = eval(attrs_str) + return dims + else: + raise AttributeError("Malformed %s dimensions: %s" % (attrs_name, str(attrs_str))) + return alt_value + +def transform_padding(pad_width): + """Helper function to convert padding format for pad operator. + """ + num_pad_values = len(pad_width) + onnx_pad_width = [0]*num_pad_values + + start_index = 0 + end_index = int(num_pad_values/2) + for idx in range(0, num_pad_values): + if idx % 2 == 0: + onnx_pad_width[start_index] = pad_width[idx] + start_index += 1 + else: + onnx_pad_width[end_index] = pad_width[idx] + end_index += 1 + + return onnx_pad_width + + +def convert_string_to_list(string_val): + """Helper function to convert string to list. + Used to convert shape attribute string to list format. + """ + result_list = [] + + list_string = string_val.split(',') + for val in list_string: + val = str(val.strip()) + val = val.replace("(", "") + val = val.replace(")", "") + val = val.replace("L", "") + val = val.replace("[", "") + val = val.replace("]", "") + if val != "" and val != "None": + result_list.append(int(val)) + + return result_list + @mx_op.register("null") def convert_weights_and_inputs(node, **kwargs): """Helper function to convert weights and inputs. """ + + helper, _, mapping = import_onnx_modules() name = node["name"] if kwargs["is_input"] is False: @@ -93,29 +159,12 @@ def convert_weights_and_inputs(node, **kwargs): return [tval_node] -def parse_helper(attrs, attrs_name, alt_value=None): - """Helper function to parse operator attributes in required format.""" - tuple_re = re.compile('\([0-9L|,| ]+\)') - if attrs is None: - return alt_value - attrs_str = str(attrs.get(attrs_name)) - if attrs_str is None: - return alt_value - attrs_match = tuple_re.search(attrs_str) - if attrs_match is not None: - if attrs_match.span() == (0, len(attrs_str)): - dims = eval(attrs_str) - return dims - else: - raise AttributeError("Malformed %s dimensions: %s" % (attrs_name, str(attrs_str))) - return alt_value - - @mx_op.register("Convolution") def convert_convolution(node, **kwargs): """Map MXNet's convolution operator attributes to onnx's Conv operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] inputs = node["inputs"] @@ -162,6 +211,7 @@ def convert_fully_connected(node, **kwargs): """Map MXNet's FullyConnected operator attributes to onnx's Gemm operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] inputs = node["inputs"] input_node_id = kwargs["index_lookup"][inputs[0][0]] @@ -195,6 +245,7 @@ def convert_batchnorm(node, **kwargs): """Map MXNet's BatchNorm operator attributes to onnx's BatchNormalization operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -239,6 +290,7 @@ def convert_tanh(node, **kwargs): """Map MXNet's tanh operator attributes to onnx's Tanh operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] inputs = node["inputs"] input_node_idx = kwargs["index_lookup"][inputs[0][0]] @@ -259,6 +311,7 @@ def convert_sigmoid(node, **kwargs): """Map MXNet's sigmoid operator attributes to onnx's Sigmoid operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] inputs = node["inputs"] input_node_idx = kwargs["index_lookup"][inputs[0][0]] @@ -278,6 +331,7 @@ def convert_relu(node, **kwargs): """Map MXNet's relu operator attributes to onnx's Relu operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] inputs = node["inputs"] input_node_idx = kwargs["index_lookup"][inputs[0][0]] @@ -298,6 +352,7 @@ def convert_activation(node, **kwargs): """Map MXNet's Activation operator attributes to onnx's Tanh/Relu operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] @@ -330,50 +385,13 @@ def convert_activation(node, **kwargs): return [node] -def transform_padding(pad_width): - """Helper function to convert padding format for pad operator. - """ - num_pad_values = len(pad_width) - onnx_pad_width = [0]*num_pad_values - - start_index = 0 - end_index = int(num_pad_values/2) - for idx in range(0, num_pad_values): - if idx % 2 == 0: - onnx_pad_width[start_index] = pad_width[idx] - start_index += 1 - else: - onnx_pad_width[end_index] = pad_width[idx] - end_index += 1 - - return onnx_pad_width - - -def convert_string_to_list(string_val): - """Helper function to convert string to list. - Used to convert shape attribute string to list format. - """ - result_list = [] - - list_string = string_val.split(',') - for val in list_string: - val = str(val.strip()) - val = val.replace("(", "") - val = val.replace(")", "") - val = val.replace("L", "") - val = val.replace("[", "") - val = val.replace("]", "") - if val != "" and val != "None": - result_list.append(int(val)) - - return result_list - @mx_op.register("Pad") def convert_pad(node, **kwargs): """Map MXNet's pad operator attributes to onnx's Pad operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] attrs = node["attrs"] proc_nodes = kwargs["proc_nodes"] @@ -418,6 +436,7 @@ def convert_linalg_gemm2(node, **kwargs): transpose_a, transpose_b attributes. Return multiple nodes created. """ + helper, _, _ = import_onnx_modules() proc_nodes = kwargs["proc_nodes"] node_inputs = node["inputs"] name = node["name"] @@ -516,6 +535,7 @@ def convert_pooling(node, **kwargs): MaxPool/AveragePool/GlobalMaxPool/GlobalAveragePool operators based on the input node's attributes and return the created node. """ + helper, _, _ = import_onnx_modules() proc_nodes = kwargs["proc_nodes"] attrs = node["attrs"] kernel = eval(attrs["kernel"]) @@ -559,6 +579,7 @@ def convert_exp(node, **kwargs): """Map MXNet's exp operator attributes to onnx's Exp operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -580,6 +601,7 @@ def convert_leakyrelu(node, **kwargs): """Map MXNet's LeakyReLU operator attributes to onnx's Elu/LeakyRelu/PRelu operators based on the input node's attributes and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -617,6 +639,7 @@ def convert_softmax(node, **kwargs): """Map MXNet's softmax operator attributes to onnx's Softmax operator and return the created node. """ + helper, _, _ = import_onnx_modules() inputs = node["inputs"] input_idx = kwargs["index_lookup"][inputs[0][0]] proc_nodes = kwargs["proc_nodes"] @@ -643,6 +666,7 @@ def convert_softmax_output(node, **kwargs): """Map MXNet's SoftmaxOutput operator attributes to onnx's Softmax operator and return the created node. """ + helper, _, _ = import_onnx_modules() inputs = node["inputs"] input1_idx = kwargs["index_lookup"][inputs[0][0]] proc_nodes = kwargs["proc_nodes"] @@ -665,6 +689,7 @@ def convert_concat(node, **kwargs): """Map MXNet's Concat operator attributes to onnx's Concat operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] inputs = node["inputs"] proc_nodes = kwargs["proc_nodes"] @@ -685,6 +710,7 @@ def convert_transpose(node, **kwargs): """Map MXNet's transpose operator attributes to onnx's Transpose operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] input_idx = kwargs["index_lookup"][node["inputs"][0][0]] proc_nodes = kwargs["proc_nodes"] @@ -716,6 +742,7 @@ def convert_lrn(node, **kwargs): """Map MXNet's LRN operator attributes to onnx's LRN operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] input_idx = kwargs["index_lookup"][node["inputs"][0][0]] proc_nodes = kwargs["proc_nodes"] @@ -746,6 +773,7 @@ def convert_dropout(node, **kwargs): """Map MXNet's Dropout operator attributes to onnx's Dropout operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] input_id = kwargs["index_lookup"][node["inputs"][0][0]] input_name = kwargs["proc_nodes"][input_id].name @@ -767,6 +795,7 @@ def convert_flatten(node, **kwargs): """Map MXNet's Flatten operator attributes to onnx's Flatten operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] input_idx = kwargs["index_lookup"][node["inputs"][0][0]] proc_nodes = kwargs["proc_nodes"] @@ -788,6 +817,7 @@ def convert_mul_scalar(node, **kwargs): Creates a new node for the input scalar value, adds it to the initializer and return multiple created nodes. """ + helper, numpy_helper, mapping = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -854,6 +884,7 @@ def convert_argmax(node, **kwargs): """Map MXNet's argmax operator attributes to onnx's ArgMax operator and return the created node. """ + helper, _, _ = import_onnx_modules() proc_nodes = kwargs["proc_nodes"] node_inputs = node["inputs"] @@ -880,6 +911,7 @@ def convert_argmin(node, **kwargs): """Map MXNet's argmin operator attributes to onnx's ArgMin operator and return the created node. """ + helper, _, _ = import_onnx_modules() proc_nodes = kwargs["proc_nodes"] node_inputs = node["inputs"] @@ -906,6 +938,7 @@ def convert_maximum(node, **kwargs): """Map MXNet's _maximum operator attributes to onnx's Max operator and return the created node. """ + helper, _, _ = import_onnx_modules() proc_nodes = kwargs["proc_nodes"] node_inputs = node["inputs"] @@ -931,6 +964,7 @@ def convert_minimum(node, **kwargs): """Map MXNet's _minimum operator attributes to onnx's Min operator and return the created node. """ + helper, _, _ = import_onnx_modules() proc_nodes = kwargs["proc_nodes"] node_inputs = node["inputs"] @@ -956,6 +990,7 @@ def convert_min(node, **kwargs): """Map MXNet's min operator attributes to onnx's ReduceMin operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -996,6 +1031,7 @@ def convert_max(node, **kwargs): """Map MXNet's max operator attributes to onnx's ReduceMax operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1036,6 +1072,7 @@ def convert_mean(node, **kwargs): """Map MXNet's mean operator attributes to onnx's ReduceMean operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1076,6 +1113,7 @@ def convert_prod(node, **kwargs): """Map MXNet's prod operator attributes to onnx's ReduceProd operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1117,6 +1155,7 @@ def convert_elementwise_add(node, **kwargs): """Map MXNet's elemwise_add operator attributes to onnx's Add operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1142,6 +1181,7 @@ def covert_broadcast_add(node, **kwargs): """Map MXNet's broadcast_add operator attributes to onnx's Add operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1167,6 +1207,7 @@ def convert_elementwise_sub(node, **kwargs): """Map MXNet's elemwise_sub operator attributes to onnx's Sub operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1191,6 +1232,7 @@ def covert_broadcast_sub(node, **kwargs): """Map MXNet's broadcast_sub operator attributes to onnx's Sub operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1216,6 +1258,7 @@ def convert_elemwise_mul(node, **kwargs): """Map MXNet's elemwise_mul operator attributes to onnx's Mul operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1240,6 +1283,7 @@ def convert_broadcast_mul(node, **kwargs): """Map MXNet's broadcast_mul operator attributes to onnx's Mul operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1265,6 +1309,7 @@ def convert_elemwise_div(node, **kwargs): """Map MXNet's elemwise_div operator attributes to onnx's Div operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1290,6 +1335,7 @@ def convert_broadcast_div(node, **kwargs): """Map MXNet's broadcast_div operator attributes to onnx's Div operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1315,6 +1361,7 @@ def convert_negative(node, **kwargs): """Map MXNet's negative operator attributes to onnx's Neg operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1338,6 +1385,7 @@ def convert_abs(node, **kwargs): """Map MXNet's abs operator attributes to onnx's Abs operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1361,6 +1409,7 @@ def convert_addn(node, **kwargs): """Map MXNet's add_n operator attributes to onnx's Sum operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1383,6 +1432,7 @@ def convert_ceil(node, **kwargs): """Map MXNet's ceil operator attributes to onnx's Ceil operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1403,6 +1453,7 @@ def convert_floor(node, **kwargs): """Map MXNet's floor operator attributes to onnx's Floor operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1425,6 +1476,7 @@ def convert_reshape(node, **kwargs): Converts output shape attribute to output shape tensor and return multiple created nodes. """ + helper, _, mapping = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1473,6 +1525,7 @@ def convert_cast(node, **kwargs): """Map MXNet's Cast operator attributes to onnx's Cast operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1496,6 +1549,7 @@ def convert_slice_axis(node, **kwargs): """Map MXNet's slice_axis operator attributes to onnx's Slice operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1524,6 +1578,7 @@ def convert_slice_channel(node, **kwargs): operator based on squeeze_axis attribute and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1563,6 +1618,7 @@ def convert_expand_dims(node, **kwargs): """Map MXNet's expand_dims operator attributes to onnx's Unsqueeze operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1586,6 +1642,7 @@ def convert_log(node, **kwargs): """Map MXNet's log operator attributes to onnx's Log operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1607,6 +1664,7 @@ def convert_reciprocal(node, **kwargs): """Map MXNet's reciprocal operator attributes to onnx's Reciprocal operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1628,6 +1686,7 @@ def convert_power(node, **kwargs): """Map MXNet's _power operator attributes to onnx's Pow operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] @@ -1651,6 +1710,7 @@ def convert_sqrt(node, **kwargs): """Map MXNet's sqrt operator attributes to onnx's Sqrt operator and return the created node. """ + helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] From 8988165ece5ebc3160b5cf0091b2af5622e17bd7 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Mon, 11 Jun 2018 13:52:49 -0700 Subject: [PATCH 63/82] fixing some comments --- docs/api/python/contrib/onnx.md | 1 + .../mxnet/contrib/onnx/_export/export_helper.py | 15 +++++++++------ .../mxnet/contrib/onnx/_export/op_translations.py | 1 - 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/api/python/contrib/onnx.md b/docs/api/python/contrib/onnx.md index 6fb546fc2b4e..f1ef1aa7aa58 100644 --- a/docs/api/python/contrib/onnx.md +++ b/docs/api/python/contrib/onnx.md @@ -24,6 +24,7 @@ This document describes all the ONNX-MXNet APIs. mxnet.contrib.onnx.import_model mxnet.contrib.onnx.get_model_metadata + mxnet.contrib.onnx.export_model ``` ## ONNX Tutorials diff --git a/python/mxnet/contrib/onnx/_export/export_helper.py b/python/mxnet/contrib/onnx/_export/export_helper.py index 88d9113ed69e..ad58c848de54 100644 --- a/python/mxnet/contrib/onnx/_export/export_helper.py +++ b/python/mxnet/contrib/onnx/_export/export_helper.py @@ -20,8 +20,9 @@ import logging import mxnet as mx -def load_module(json_path, params_path, input_shape): - """Loads the MXNet model file, retrieves symbol and parameters and returns. +def load_module(sym_filepath, params_filepath, input_shape): + """Loads the MXNet model file, retrieves symbol and parameters and + returns MXNet symbol and params (weights). Parameters ---------- @@ -40,12 +41,14 @@ def load_module(json_path, params_path, input_shape): params : params object Model weights including both arg and aux params. """ - if not (os.path.isfile(json_path) and os.path.isfile(params_path)): - raise ValueError("Provide valid path to the json and params file") + if not (os.path.isfile(sym_filepath) and os.path.isfile(params_filepath)): + raise ValueError("symbol and params files provided are invalid") else: try: - model_name = json_path.rsplit('.', 1)[0].rsplit('-', 1)[0] - num_epochs = int(params_path.rsplit('.', 1)[0].rsplit('-', 1)[1]) + # reads symbol.json file from given path and + # retrieves model prefix and number of epochs + model_name = sym_filepath.rsplit('.', 1)[0].rsplit('-', 1)[0] + num_epochs = int(params_filepath.rsplit('.', 1)[0].rsplit('-', 1)[1]) except IndexError: logging.info("Model and params name should be in format: " "prefix-symbol.json, prefix-epoch.params") diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index f0507bec8076..5f6b8d7060c1 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -57,7 +57,6 @@ from __future__ import unicode_literals import re import numpy as np - from .export_onnx import MXNetGraph as mx_op From 98d5daef8abec65a645f16707e283d9af85c3f7d Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 12 Jun 2018 10:22:51 -0700 Subject: [PATCH 64/82] addressing some comments --- .../contrib/onnx/_export/export_helper.py | 20 ++++++++++++++----- .../contrib/onnx/_export/export_model.py | 12 ++++++----- .../mxnet/contrib/onnx/_export/export_onnx.py | 12 +++++------ 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_helper.py b/python/mxnet/contrib/onnx/_export/export_helper.py index ad58c848de54..887452f35505 100644 --- a/python/mxnet/contrib/onnx/_export/export_helper.py +++ b/python/mxnet/contrib/onnx/_export/export_helper.py @@ -30,8 +30,8 @@ def load_module(sym_filepath, params_filepath, input_shape): Path to the json file params_path : str Path to the params file - input_shape : - Input shape of the model e.g (1,3,224,224) + input_shape : List of tuple + Input shape of the model e.g [(1,3,224,224)] Returns ------- @@ -55,9 +55,19 @@ def load_module(sym_filepath, params_filepath, input_shape): raise sym, arg_params, aux_params = mx.model.load_checkpoint(model_name, num_epochs) - trained_model = mx.mod.Module(symbol=sym, label_names=None) - trained_model.bind(data_shapes=[('data', input_shape[0])], - label_shapes=None, for_training=False) + + # To fetch the data names of the input to the model we list the inputs of the symbol graph + # and exclude the argument and auxiliary parameters from the list + data_names = [graph_input for graph_input in sym.list_inputs() + if graph_input not in arg_params and graph_input not in aux_params] + + # Creating data_shapes list having data name and data shape tuples + data_shapes = [] + for idx, input_name in enumerate(data_names): + data_shapes.append((input_name, input_shape[idx])) + + trained_model = mx.mod.Module(symbol=sym, data_names=data_names, label_names=None) + trained_model.bind(data_shapes=data_shapes, label_shapes=None, for_training=False) # Merging arg and aux parameters params = {} diff --git a/python/mxnet/contrib/onnx/_export/export_model.py b/python/mxnet/contrib/onnx/_export/export_model.py index 91b6d4f54bd8..ec492ff5c84f 100644 --- a/python/mxnet/contrib/onnx/_export/export_model.py +++ b/python/mxnet/contrib/onnx/_export/export_model.py @@ -32,7 +32,7 @@ def export_model(model, weights, input_shape, input_type=np.float32, - onnx_file_path='model.onnx', log=False): + onnx_file_path='model.onnx', verbose=False): """Exports the MXNet model file, passed as a parameter, into ONNX model. Accepts both symbol,parameter objects as well as json and params filepaths as input. Operator support and coverage - https://cwiki.apache.org/confluence/display/MXNET/ONNX @@ -44,12 +44,12 @@ def export_model(model, weights, input_shape, input_type=np.float32, weights : str or symbol object Path to the params file or Params object. (Including both arg_params and aux_params) input_shape : List of tuple - Input shape of the model e.g (1,3,224,224) + Input shape of the model e.g [(1,3,224,224)] input_type : Input data type e.g. np.float32 onnx_file_path : str Path where to save the generated onnx file - log : Boolean + verbose : Boolean If true will print logs of the model conversion Returns @@ -67,16 +67,17 @@ def export_model(model, weights, input_shape, input_type=np.float32, converter = MXNetGraph() data_format = np.dtype(input_type) + # if input parameters are strings(file paths), load files and create symbol parameter objects if isinstance(model, string_types) and isinstance(weights, string_types): logging.info("Converting json and params file to sym and weights") sym, params = load_module(model, weights, input_shape) onnx_graph = converter.create_onnx_graph_proto(sym, params, input_shape, mapping.NP_TYPE_TO_TENSOR_TYPE[data_format], - log=log) + verbose=verbose) else: onnx_graph = converter.create_onnx_graph_proto(model, weights, input_shape, mapping.NP_TYPE_TO_TENSOR_TYPE[data_format], - log=log) + verbose=verbose) # Create the model (ModelProto) onnx_model = helper.make_model(onnx_graph) @@ -84,6 +85,7 @@ def export_model(model, weights, input_shape, input_type=np.float32, with open(onnx_file_path, "wb") as file_handle: serialized = onnx_model.SerializeToString() file_handle.write(serialized) + logging.info("Input shape of the model", input_shape) logging.info("Exported ONNX file %s saved to disk", onnx_file_path) return onnx_file_path diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index be59f966580d..57ed776f7385 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -51,7 +51,7 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals - +import logging import json import numpy as np @@ -154,7 +154,7 @@ def convert_weights_to_numpy(weights_dict): return dict([(k.replace("arg:", "").replace("aux:", ""), v.asnumpy()) for k, v in weights_dict.items()]) - def create_onnx_graph_proto(self, sym, params, in_shape, in_type, log=False): + def create_onnx_graph_proto(self, sym, params, in_shape, in_type, verbose=False): """Convert MXNet graph to ONNX graph""" try: from onnx import (checker, helper, onnx_pb2) @@ -181,8 +181,8 @@ def create_onnx_graph_proto(self, sym, params, in_shape, in_type, log=False): for idx, node in enumerate(mx_graph): op = node["op"] name = node["name"] - if log: - print("Converting idx: %d, op: %s, name: %s" % (idx, op, name)) + if verbose: + logging.info("Converting idx: %d, op: %s, name: %s" % (idx, op, name)) if op == "null" and name not in params: # Handling graph input @@ -239,8 +239,8 @@ def create_onnx_graph_proto(self, sym, params, in_shape, in_type, log=False): shape=output_shape ) ) - if log: - print("Output node is: %s" % converted_node.name) + if verbose: + logging.info("Output node is: %s" % converted_node.name) elif isinstance(converted_node, onnx_pb2.TensorProto): raise ValueError("Did not expect TensorProto") else: From 1ff47e13d5e51f174523febdf6af92656e84feaa Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 12 Jun 2018 12:13:38 -0700 Subject: [PATCH 65/82] params rename --- .../contrib/onnx/_export/export_model.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_model.py b/python/mxnet/contrib/onnx/_export/export_model.py index ec492ff5c84f..5945eb889c59 100644 --- a/python/mxnet/contrib/onnx/_export/export_model.py +++ b/python/mxnet/contrib/onnx/_export/export_model.py @@ -31,7 +31,7 @@ from .export_helper import load_module -def export_model(model, weights, input_shape, input_type=np.float32, +def export_model(sym, params, input_shape, input_type=np.float32, onnx_file_path='model.onnx', verbose=False): """Exports the MXNet model file, passed as a parameter, into ONNX model. Accepts both symbol,parameter objects as well as json and params filepaths as input. @@ -39,10 +39,10 @@ def export_model(model, weights, input_shape, input_type=np.float32, Parameters ---------- - model : str or symbol object + sym : str or symbol object Path to the json file or Symbol object - weights : str or symbol object - Path to the params file or Params object. (Including both arg_params and aux_params) + params : str or symbol object + Path to the params file or params dictionary. (Including both arg_params and aux_params) input_shape : List of tuple Input shape of the model e.g [(1,3,224,224)] input_type : @@ -68,14 +68,14 @@ def export_model(model, weights, input_shape, input_type=np.float32, data_format = np.dtype(input_type) # if input parameters are strings(file paths), load files and create symbol parameter objects - if isinstance(model, string_types) and isinstance(weights, string_types): - logging.info("Converting json and params file to sym and weights") - sym, params = load_module(model, weights, input_shape) - onnx_graph = converter.create_onnx_graph_proto(sym, params, input_shape, + if isinstance(sym, string_types) and isinstance(params, string_types): + logging.info("Converting json and weight file to sym and params") + sym_obj, params_obj = load_module(sym, params, input_shape) + onnx_graph = converter.create_onnx_graph_proto(sym_obj, params_obj, input_shape, mapping.NP_TYPE_TO_TENSOR_TYPE[data_format], verbose=verbose) else: - onnx_graph = converter.create_onnx_graph_proto(model, weights, input_shape, + onnx_graph = converter.create_onnx_graph_proto(sym, params, input_shape, mapping.NP_TYPE_TO_TENSOR_TYPE[data_format], verbose=verbose) # Create the model (ModelProto) @@ -85,7 +85,7 @@ def export_model(model, weights, input_shape, input_type=np.float32, with open(onnx_file_path, "wb") as file_handle: serialized = onnx_model.SerializeToString() file_handle.write(serialized) - logging.info("Input shape of the model", input_shape) + logging.info("Input shape of the model %s " % input_shape) logging.info("Exported ONNX file %s saved to disk", onnx_file_path) return onnx_file_path From 112c930d696bec42f93c99a1abf3c5a17c21ff28 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Tue, 12 Jun 2018 12:29:26 -0700 Subject: [PATCH 66/82] lint fixes --- python/mxnet/contrib/onnx/_export/export_model.py | 2 +- python/mxnet/contrib/onnx/_export/export_onnx.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_model.py b/python/mxnet/contrib/onnx/_export/export_model.py index 5945eb889c59..98b61461bb13 100644 --- a/python/mxnet/contrib/onnx/_export/export_model.py +++ b/python/mxnet/contrib/onnx/_export/export_model.py @@ -85,7 +85,7 @@ def export_model(sym, params, input_shape, input_type=np.float32, with open(onnx_file_path, "wb") as file_handle: serialized = onnx_model.SerializeToString() file_handle.write(serialized) - logging.info("Input shape of the model %s " % input_shape) + logging.info("Input shape of the model %s ", input_shape) logging.info("Exported ONNX file %s saved to disk", onnx_file_path) return onnx_file_path diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 57ed776f7385..2e8bb4509b05 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -182,7 +182,7 @@ def create_onnx_graph_proto(self, sym, params, in_shape, in_type, verbose=False) op = node["op"] name = node["name"] if verbose: - logging.info("Converting idx: %d, op: %s, name: %s" % (idx, op, name)) + logging.info("Converting idx: %d, op: %s, name: %s", idx, op, name) if op == "null" and name not in params: # Handling graph input @@ -240,7 +240,7 @@ def create_onnx_graph_proto(self, sym, params, in_shape, in_type, verbose=False) ) ) if verbose: - logging.info("Output node is: %s" % converted_node.name) + logging.info("Output node is: %s", converted_node.name) elif isinstance(converted_node, onnx_pb2.TensorProto): raise ValueError("Did not expect TensorProto") else: From 50ade3e1ac36c74bf6a9b1e52ff72b501f418039 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 12 Jun 2018 14:02:02 -0700 Subject: [PATCH 67/82] fixes --- .../contrib/onnx/_export/export_model.py | 2 +- .../mxnet/contrib/onnx/_export/export_onnx.py | 66 +++++++++++++++++-- 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_model.py b/python/mxnet/contrib/onnx/_export/export_model.py index 98b61461bb13..804d81d3805a 100644 --- a/python/mxnet/contrib/onnx/_export/export_model.py +++ b/python/mxnet/contrib/onnx/_export/export_model.py @@ -45,7 +45,7 @@ def export_model(sym, params, input_shape, input_type=np.float32, Path to the params file or params dictionary. (Including both arg_params and aux_params) input_shape : List of tuple Input shape of the model e.g [(1,3,224,224)] - input_type : + input_type : data type Input data type e.g. np.float32 onnx_file_path : str Path where to save the generated onnx file diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 2e8bb4509b05..08530e33a8bf 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -74,7 +74,7 @@ def __init__(self): @staticmethod def register(op_name): - """Register operator""" + """Register operators""" def wrapper(func): """Helper function to map functions""" MXNetGraph.registry_[op_name] = func @@ -88,12 +88,30 @@ def convert_layer(node, **kwargs): op = str(node["op"]) if op not in MXNetGraph.registry_: raise AttributeError("No conversion function registered for op type %s yet." % op) - convert_fun = MXNetGraph.registry_[op] - return convert_fun(node, **kwargs) + convert_func = MXNetGraph.registry_[op] + return convert_func(node, **kwargs) @staticmethod def forward_pass(inputs, sym, arg_params, aux_params): - """ Do a forward pass based on the sym and params""" + """Do a forward pass based on the sym and params to get the shape + of the output using dummy data + + Parameters + ---------- + inputs : json string + + sym : :class:`~mxnet.symbol.Symbol` + MXNet symbol object + arg_params : dict of ``str`` to :class:`~mxnet.ndarray.NDArray` + Dict of converted parameters stored in ``mxnet.ndarray.NDArray`` format + aux_params : dict of ``str`` to :class:`~mxnet.ndarray.NDArray` + Dict of converted parameters stored in ``mxnet.ndarray.NDArray`` format + + Returns + ------- + shape : Shape + Output shape + """ data_names = [graph_input for graph_input in sym.list_inputs() if graph_input not in arg_params and graph_input not in aux_params and graph_input != 'softmax_label'] @@ -127,7 +145,22 @@ def forward_pass(inputs, sym, arg_params, aux_params): @staticmethod def split_params(sym, params): - """splitting params into args and aux params""" + """Helper function to split params dictionary into args and aux params + + Parameters + ---------- + sym : :class:`~mxnet.symbol.Symbol` + MXNet symbol object + params : dict of ``str`` to :class:`~mxnet.ndarray.NDArray` + Dict of converted parameters stored in ``mxnet.ndarray.NDArray`` format + + Returns + ------- + arg_params : dict of ``str`` to :class:`~mxnet.ndarray.NDArray` + Dict of converted parameters stored in ``mxnet.ndarray.NDArray`` format + aux_params : dict of ``str`` to :class:`~mxnet.ndarray.NDArray` + Dict of converted parameters stored in ``mxnet.ndarray.NDArray`` format + """ arg_params = {} aux_params = {} for args in sym.list_arguments(): @@ -142,7 +175,7 @@ def split_params(sym, params): @staticmethod def infer_output_shape(sym, params, in_shape): """Infer output shape by doing a forward pass using dummy inputs """ - #create dummy input + # create dummy input inputs = [np.random.randn(*input_shape) for input_shape in in_shape] arg, aux = MXNetGraph.split_params(sym, params) return MXNetGraph.forward_pass(inputs, sym, arg, aux) @@ -155,7 +188,26 @@ def convert_weights_to_numpy(weights_dict): for k, v in weights_dict.items()]) def create_onnx_graph_proto(self, sym, params, in_shape, in_type, verbose=False): - """Convert MXNet graph to ONNX graph""" + """Convert MXNet graph to ONNX graph + + Parameters + ---------- + sym : :class:`~mxnet.symbol.Symbol` + MXNet symbol object + params : dict of ``str`` to :class:`~mxnet.ndarray.NDArray` + Dict of converted parameters stored in ``mxnet.ndarray.NDArray`` format + in_shape : List of tuple + Input shape of the model e.g [(1,3,224,224)] + in_type : data type + Input data type e.g. np.float32 + verbose : Boolean + If true will print logs of the model conversion + + Returns + ------- + graph : GraphProto + ONNX graph + """ try: from onnx import (checker, helper, onnx_pb2) from onnx.helper import make_tensor_value_info From 7d334e1f7f7cd58b9c24aeffe5d851eaf5151354 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 12 Jun 2018 14:30:29 -0700 Subject: [PATCH 68/82] spatial disabled, path fixed --- python/mxnet/contrib/onnx/_export/op_translations.py | 4 +++- tests/python-pytest/onnx/export/mxnet_export_test.py | 11 ++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 5f6b8d7060c1..8594a2fe7651 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -279,7 +279,9 @@ def convert_batchnorm(node, **kwargs): name=name, epsilon=eps, momentum=momentum, - spatial=1 + # MXNet computes mean and variance per feature for batchnorm + # Default for onnx is across all spatial features. So disabling the parameter. + spatial=0 ) return [bn_node] diff --git a/tests/python-pytest/onnx/export/mxnet_export_test.py b/tests/python-pytest/onnx/export/mxnet_export_test.py index 31781076698d..7e1df07cbaa1 100644 --- a/tests/python-pytest/onnx/export/mxnet_export_test.py +++ b/tests/python-pytest/onnx/export/mxnet_export_test.py @@ -37,7 +37,8 @@ import mxnet as mx CURR_PATH = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) sys.path.insert(0, os.path.join(CURR_PATH, '../../python/unittest')) - +logger = logging.getLogger() +logger.setLevel(logging.DEBUG) URLS = { 'bvlc_googlenet': 'https://s3.amazonaws.com/onnx-mxnet/model-zoo/bvlc_googlenet.tar.gz', @@ -108,7 +109,9 @@ def test_models(model_name, input_shape, output_shape): params.update(arg_params) params.update(aux_params) - onnx_file = model_path.rsplit('/', 1)[0] + "/exported_"+model_name+".onnx" + dir_path = os.path.dirname(model_path) + new_model_name = "exported_" + model_name + ".onnx" + onnx_file = os.path.join(dir_path, new_model_name) logging.info("Translating converted model from mxnet to ONNX") converted_model_path = onnx_mxnet.export_model(sym, params, [input_shape], np.float32, onnx_file) @@ -153,7 +156,9 @@ def test_model_accuracy(model_name, input_shape): params.update(arg_params) params.update(aux_params) - onnx_file = model_path.rsplit('/', 1)[0] + "/exported_"+model_name+".onnx" + dir_path = os.path.dirname(model_path) + new_model_name = "exported_" + model_name + ".onnx" + onnx_file = os.path.join(dir_path, new_model_name) logging.info("Translating converted model from mxnet to ONNX") converted_model_path = onnx_mxnet.export_model(sym, params, [input_shape], np.float32, From ff123eeb8ccf3b5589a5f4a9a427ac4a6451ca89 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 12 Jun 2018 16:31:28 -0700 Subject: [PATCH 69/82] fixing some comments --- .../contrib/onnx/_export/export_helper.py | 26 +++++-------------- .../contrib/onnx/_export/export_model.py | 8 ++++-- .../mxnet/contrib/onnx/_export/export_onnx.py | 6 ++++- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_helper.py b/python/mxnet/contrib/onnx/_export/export_helper.py index 887452f35505..781fb4cfbbc1 100644 --- a/python/mxnet/contrib/onnx/_export/export_helper.py +++ b/python/mxnet/contrib/onnx/_export/export_helper.py @@ -20,8 +20,9 @@ import logging import mxnet as mx -def load_module(sym_filepath, params_filepath, input_shape): - """Loads the MXNet model file, retrieves symbol and parameters and + +def load_module(sym_filepath, params_filepath): + """Loads the MXNet model file and returns MXNet symbol and params (weights). Parameters @@ -30,8 +31,6 @@ def load_module(sym_filepath, params_filepath, input_shape): Path to the json file params_path : str Path to the params file - input_shape : List of tuple - Input shape of the model e.g [(1,3,224,224)] Returns ------- @@ -42,13 +41,15 @@ def load_module(sym_filepath, params_filepath, input_shape): Model weights including both arg and aux params. """ if not (os.path.isfile(sym_filepath) and os.path.isfile(params_filepath)): - raise ValueError("symbol and params files provided are invalid") + raise ValueError("Symbol and params files provided are invalid") else: try: # reads symbol.json file from given path and # retrieves model prefix and number of epochs model_name = sym_filepath.rsplit('.', 1)[0].rsplit('-', 1)[0] - num_epochs = int(params_filepath.rsplit('.', 1)[0].rsplit('-', 1)[1]) + params_file_list = params_filepath.rsplit('.', 1)[0].rsplit('-', 1) + # Setting num_epochs to 0 if not present in filename + num_epochs = 0 if len(params_file_list) == 1 else int(params_file_list[1]) except IndexError: logging.info("Model and params name should be in format: " "prefix-symbol.json, prefix-epoch.params") @@ -56,19 +57,6 @@ def load_module(sym_filepath, params_filepath, input_shape): sym, arg_params, aux_params = mx.model.load_checkpoint(model_name, num_epochs) - # To fetch the data names of the input to the model we list the inputs of the symbol graph - # and exclude the argument and auxiliary parameters from the list - data_names = [graph_input for graph_input in sym.list_inputs() - if graph_input not in arg_params and graph_input not in aux_params] - - # Creating data_shapes list having data name and data shape tuples - data_shapes = [] - for idx, input_name in enumerate(data_names): - data_shapes.append((input_name, input_shape[idx])) - - trained_model = mx.mod.Module(symbol=sym, data_names=data_names, label_names=None) - trained_model.bind(data_shapes=data_shapes, label_shapes=None, for_training=False) - # Merging arg and aux parameters params = {} params.update(arg_params) diff --git a/python/mxnet/contrib/onnx/_export/export_model.py b/python/mxnet/contrib/onnx/_export/export_model.py index 804d81d3805a..510d1b703ad4 100644 --- a/python/mxnet/contrib/onnx/_export/export_model.py +++ b/python/mxnet/contrib/onnx/_export/export_model.py @@ -27,6 +27,7 @@ import numpy as np from ....base import string_types +from .... import symbol from .export_onnx import MXNetGraph from .export_helper import load_module @@ -70,14 +71,17 @@ def export_model(sym, params, input_shape, input_type=np.float32, # if input parameters are strings(file paths), load files and create symbol parameter objects if isinstance(sym, string_types) and isinstance(params, string_types): logging.info("Converting json and weight file to sym and params") - sym_obj, params_obj = load_module(sym, params, input_shape) + sym_obj, params_obj = load_module(sym, params) onnx_graph = converter.create_onnx_graph_proto(sym_obj, params_obj, input_shape, mapping.NP_TYPE_TO_TENSOR_TYPE[data_format], verbose=verbose) - else: + elif isinstance(sym, symbol.Symbol) and isinstance(params, dict): onnx_graph = converter.create_onnx_graph_proto(sym, params, input_shape, mapping.NP_TYPE_TO_TENSOR_TYPE[data_format], verbose=verbose) + else: + raise ValueError("Input sym and params should either be files or objects") + # Create the model (ModelProto) onnx_model = helper.make_model(onnx_graph) diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 08530e33a8bf..2c37e73bbd0d 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -112,6 +112,8 @@ def forward_pass(inputs, sym, arg_params, aux_params): shape : Shape Output shape """ + # if label is not provided, MXNet adds label "softmax_label" by default + # while running load_checkpoint which is not actually a graph input. So ignoring it here data_names = [graph_input for graph_input in sym.list_inputs() if graph_input not in arg_params and graph_input not in aux_params and graph_input != 'softmax_label'] @@ -308,10 +310,12 @@ def create_onnx_graph_proto(self, sym, params, in_shape, in_type, verbose=False) index_lookup.append(prev_index+len(converted)) else: index_lookup.append(len(converted) - 1) + else: + logging.info("Operator converter function should always return a list") graph = helper.make_graph( onnx_processed_nodes, - "main", + "mxnet_converted_model", onnx_processed_inputs, onnx_processed_outputs ) From 7e626c72310d54cd136f223498cb6c140ceb53e6 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Tue, 12 Jun 2018 14:51:51 -0700 Subject: [PATCH 70/82] Added support for remaining act_type(softsign, sigmoid, softrelu) in Activation operator --- python/mxnet/contrib/onnx/_export/op_translations.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 8594a2fe7651..c63f7b22666b 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -368,7 +368,10 @@ def convert_activation(node, **kwargs): # mxnet_name.title() act_types = { "tanh": "Tanh", - "relu": "Relu" + "relu": "Relu", + "sigmoid": "Sigmoid", + "softrelu": "Softplus", + "softsign": "Softsign" } act_name = act_types.get(act_type) From 4199ae8f2b694e3a8b546862eaef7ab23a571d1d Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Tue, 12 Jun 2018 16:36:15 -0700 Subject: [PATCH 71/82] changing import --- python/mxnet/contrib/onnx/_export/export_onnx.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 2c37e73bbd0d..802dce770e1d 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -211,7 +211,7 @@ def create_onnx_graph_proto(self, sym, params, in_shape, in_type, verbose=False) ONNX graph """ try: - from onnx import (checker, helper, onnx_pb2) + from onnx import (checker, helper, NodeProto, ValueInfoProto, TensorProto) from onnx.helper import make_tensor_value_info except ImportError: raise ImportError("Onnx and protobuf need to be installed. " @@ -270,9 +270,9 @@ def create_onnx_graph_proto(self, sym, params, in_shape, in_type, verbose=False) if isinstance(converted, list): for converted_node in converted: - if isinstance(converted_node, onnx_pb2.ValueInfoProto): + if isinstance(converted_node, ValueInfoProto): onnx_processed_inputs.append(converted_node) - elif isinstance(converted_node, onnx_pb2.NodeProto): + elif isinstance(converted_node, NodeProto): if idx < (len(mx_graph) - 1): onnx_processed_nodes.append(converted_node) else: @@ -295,7 +295,7 @@ def create_onnx_graph_proto(self, sym, params, in_shape, in_type, verbose=False) ) if verbose: logging.info("Output node is: %s", converted_node.name) - elif isinstance(converted_node, onnx_pb2.TensorProto): + elif isinstance(converted_node, TensorProto): raise ValueError("Did not expect TensorProto") else: raise ValueError("node is of an unrecognized type: %s" % type(node)) From 3d5f8be373b8cd60dcd6c7c14d6f6c8e42a88302 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Wed, 13 Jun 2018 11:32:18 -0700 Subject: [PATCH 72/82] adding some comments --- python/mxnet/contrib/onnx/_export/export_onnx.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 802dce770e1d..89488e725db7 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -255,6 +255,7 @@ def create_onnx_graph_proto(self, sym, params, in_shape, in_type, verbose=False) graph_input_idx += 1 else: + # Handling graph operators converted = MXNetGraph.convert_layer( node, is_input=False, @@ -269,14 +270,16 @@ def create_onnx_graph_proto(self, sym, params, in_shape, in_type, verbose=False) ) if isinstance(converted, list): + # Iterate for all converted nodes for converted_node in converted: + # If converted node is ValueInfoProto, add it in inputs if isinstance(converted_node, ValueInfoProto): onnx_processed_inputs.append(converted_node) + # If converted node is NodeProto, add it in processed nodes list elif isinstance(converted_node, NodeProto): - if idx < (len(mx_graph) - 1): - onnx_processed_nodes.append(converted_node) - else: - onnx_processed_nodes.append(converted_node) + onnx_processed_nodes.append(converted_node) + if idx == (len(mx_graph) - 1): + # If converted node doesnt have name, use it from output field if not converted_node.name: onnx_processed_outputs.append( make_tensor_value_info( From 74128206a240849dad8326b0274c4cd5306a7c9f Mon Sep 17 00:00:00 2001 From: spidyDev Date: Tue, 12 Jun 2018 22:38:30 -0700 Subject: [PATCH 73/82] Add squeeze op --- .../contrib/onnx/_export/op_translations.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index c63f7b22666b..5f4c95662ccc 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -1640,6 +1640,29 @@ def convert_expand_dims(node, **kwargs): ) return [node] +@mx_op.register("squeeze") +def convert_squeeze(node, **kwargs): + """Map MXNet's expand_dims operator attributes to onnx's Unsqueeze operator + and return the created node. + """ + helper, _, _ = import_onnx_modules() + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + axis = int(node["attrs"]["axis"]) + + input_node_id = kwargs["index_lookup"][inputs[0][0]] + input_node = proc_nodes[input_node_id].name + + node = helper.make_node( + "Squeeze", + [input_node], + [name], + axes=[axis], + name=name, + ) + return [node] + @mx_op.register("log") def convert_log(node, **kwargs): From a303eb1440dbb1479494b5bc5b7dfd829cec5503 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Wed, 13 Jun 2018 13:02:31 -0700 Subject: [PATCH 74/82] Refactored logic to handle extra node(output label node) for saved mxnet model Added comments --- .../mxnet/contrib/onnx/_export/export_onnx.py | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/_export/export_onnx.py index 89488e725db7..11847381ab24 100644 --- a/python/mxnet/contrib/onnx/_export/export_onnx.py +++ b/python/mxnet/contrib/onnx/_export/export_onnx.py @@ -92,7 +92,7 @@ def convert_layer(node, **kwargs): return convert_func(node, **kwargs) @staticmethod - def forward_pass(inputs, sym, arg_params, aux_params): + def forward_pass(inputs, sym, arg_params, aux_params, output_label): """Do a forward pass based on the sym and params to get the shape of the output using dummy data @@ -116,7 +116,7 @@ def forward_pass(inputs, sym, arg_params, aux_params): # while running load_checkpoint which is not actually a graph input. So ignoring it here data_names = [graph_input for graph_input in sym.list_inputs() if graph_input not in arg_params and graph_input not in aux_params - and graph_input != 'softmax_label'] + and graph_input != output_label] data_shapes = [] # Adding extra dimension of batch_size 1 if the batch_size is different for multiple inputs. @@ -175,12 +175,12 @@ def split_params(sym, params): @staticmethod - def infer_output_shape(sym, params, in_shape): + def infer_output_shape(sym, params, in_shape, output_label): """Infer output shape by doing a forward pass using dummy inputs """ # create dummy input inputs = [np.random.randn(*input_shape) for input_shape in in_shape] arg, aux = MXNetGraph.split_params(sym, params) - return MXNetGraph.forward_pass(inputs, sym, arg, aux) + return MXNetGraph.forward_pass(inputs, sym, arg, aux, output_label) @staticmethod @@ -217,8 +217,15 @@ def create_onnx_graph_proto(self, sym, params, in_shape, in_type, verbose=False) raise ImportError("Onnx and protobuf need to be installed. " + "Instructions to install - https://github.com/onnx/onnx") + # When MXNet model is saved to json file , MXNet adds a node for label. + # The name of this node is, name of the last node + "_label" ( i.e if last node + # name is "Softmax", this node will have a name "Softmax_label". Also, the new node + # will always be second last node in the json graph. + # Deriving the output_label name. + output_label = sym.get_internals()[len(sym.get_internals()) - 1].name + "_label" + # Determine output shape - output_shape = MXNetGraph.infer_output_shape(sym, params, in_shape) + output_shape = MXNetGraph.infer_output_shape(sym, params, in_shape, output_label) weights = MXNetGraph.convert_weights_to_numpy(params) @@ -238,9 +245,14 @@ def create_onnx_graph_proto(self, sym, params, in_shape, in_type, verbose=False) if verbose: logging.info("Converting idx: %d, op: %s, name: %s", idx, op, name) + # A node is an input node if its op_name is "null" and is not + # in params dict if op == "null" and name not in params: # Handling graph input - if name == 'softmax_label': + + # Skipping output_label node, as this node is not part of graph + # Refer "output_label" assignment above for more details. + if name == output_label: continue converted = MXNetGraph.convert_layer( node, @@ -255,7 +267,7 @@ def create_onnx_graph_proto(self, sym, params, in_shape, in_type, verbose=False) graph_input_idx += 1 else: - # Handling graph operators + # Handling graph layers converted = MXNetGraph.convert_layer( node, is_input=False, @@ -306,10 +318,16 @@ def create_onnx_graph_proto(self, sym, params, in_shape, in_type, verbose=False) all_processed_nodes.append(converted_node) if idx > 0: - if name != 'softmax': - prev_index = index_lookup[idx-1] - else: + # Handling extra node added to the graph if the MXNet model was + # saved to json file, + # refer "output_label" initialization above for more details. + # if extra node was added then prev_index to the last node is adjusted. + if idx == (len(mx_graph) - 1) and \ + mx_graph[len(mx_graph)-2]["name"] == output_label: prev_index = index_lookup[idx - 2] + else: + prev_index = index_lookup[idx - 1] + index_lookup.append(prev_index+len(converted)) else: index_lookup.append(len(converted) - 1) From 777144a4ac91158b16bb138fe672f8f73aa1ceb6 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Wed, 13 Jun 2018 16:26:46 -0700 Subject: [PATCH 75/82] identity operator added --- .../contrib/onnx/_export/op_translations.py | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 5f4c95662ccc..45b01f94cf47 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -75,7 +75,7 @@ def parse_helper(attrs, attrs_name, alt_value=None): tuple_re = re.compile('\([0-9L|,| ]+\)') if attrs is None: return alt_value - attrs_str = str(attrs.get(attrs_name)) + attrs_str = None if attrs.get(attrs_name) is None else str(attrs.get(attrs_name)) if attrs_str is None: return alt_value attrs_match = tuple_re.search(attrs_str) @@ -600,6 +600,28 @@ def convert_exp(node, **kwargs): return [node] +@mx_op.register("_copy") +def convert_identity(node, **kwargs): + """Map MXNet's _copy operator attributes to onnx's Identity operator + and return the created node. + """ + helper, _, _ = import_onnx_modules() + name = node["name"] + proc_nodes = kwargs["proc_nodes"] + inputs = node["inputs"] + + input_node_id = kwargs["index_lookup"][inputs[0][0]] + input_node = proc_nodes[input_node_id].name + + node = helper.make_node( + "Identity", + [input_node], + [name], + name=name, + ) + return [node] + + @mx_op.register("LeakyReLU") def convert_leakyrelu(node, **kwargs): """Map MXNet's LeakyReLU operator attributes to onnx's Elu/LeakyRelu/PRelu operators @@ -832,12 +854,15 @@ def convert_mul_scalar(node, **kwargs): initializer = kwargs["initializer"] flag = True + # If the input value is in initializer, just multiply with scalar input + # and create a new initializer for i in initializer: if i.name == input_node: - new_initializer = scalar_mul_value[0]*numpy_helper.to_array(i) + new_initializer = scalar_mul_value[0] * numpy_helper.to_array(i) flag = False break + # else create a new tensor of the scalar value, add it in initializer if flag is True: np_arr = np.array(scalar_mul_value) data_type = mapping.NP_TYPE_TO_TENSOR_TYPE[np_arr.dtype] From 4801fb3330d83dc605b3c36ac8e5d5842c62ba3f Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Wed, 13 Jun 2018 17:18:20 -0700 Subject: [PATCH 76/82] scalar ops added --- .../contrib/onnx/_export/op_translations.py | 65 +++++++++++++++---- .../contrib/onnx/_import/import_helper.py | 1 + .../onnx/export/onnx_backend_test.py | 1 + tests/python-pytest/onnx/import/test_cases.py | 1 + 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index 45b01f94cf47..a4d5fc1b60e8 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -836,18 +836,13 @@ def convert_flatten(node, **kwargs): return [flatten_node] -# Convert scalar value into node and pass it as input to mul_node -@mx_op.register("_mul_scalar") -def convert_mul_scalar(node, **kwargs): - """Map MXNet's _mul_scalar operator attributes to onnx's Mul operator. - Creates a new node for the input scalar value, adds it to the initializer - and return multiple created nodes. - """ +def scalar_op_helper(node, op_name, **kwargs): + """Helper function for scalar arithmetic operations""" helper, numpy_helper, mapping = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - scalar_mul_value = [int(node.get("attrs", {}).get("scalar", 1))] + scalar_value = [float(node.get("attrs", {}).get("scalar", 1))] input_name_id = kwargs["index_lookup"][inputs[0][0]] input_node = proc_nodes[input_name_id].name @@ -858,13 +853,20 @@ def convert_mul_scalar(node, **kwargs): # and create a new initializer for i in initializer: if i.name == input_node: - new_initializer = scalar_mul_value[0] * numpy_helper.to_array(i) + if op_name == 'Mul': + new_initializer = numpy_helper.to_array(i) * scalar_value[0] + elif op_name == 'Sub': + new_initializer = numpy_helper.to_array(i) - scalar_value[0] + elif op_name == 'Add': + new_initializer = numpy_helper.to_array(i) + scalar_value[0] + elif op_name == 'Div': + new_initializer = numpy_helper.to_array(i) / scalar_value[0] flag = False break # else create a new tensor of the scalar value, add it in initializer if flag is True: - np_arr = np.array(scalar_mul_value) + np_arr = np.array(scalar_value) data_type = mapping.NP_TYPE_TO_TENSOR_TYPE[np_arr.dtype] dims = np.shape(np_arr) @@ -876,13 +878,13 @@ def convert_mul_scalar(node, **kwargs): name=scalar_op_name, data_type=data_type, dims=dims, - vals=scalar_mul_value, + vals=scalar_value, raw=False, ) ) mul_node = helper.make_node( - "Mul", + op_name, [input_node, scalar_op_name], [name], name=name @@ -907,6 +909,45 @@ def convert_mul_scalar(node, **kwargs): ) return [tensor_node] +# Convert scalar value into node and pass it as input to mul_node +@mx_op.register("_mul_scalar") +def convert_mul_scalar(node, **kwargs): + """Map MXNet's _mul_scalar operator attributes to onnx's Mul operator. + Creates a new node for the input scalar value, adds it to the initializer + and return multiple created nodes. + """ + return scalar_op_helper(node, 'Mul', **kwargs) + + +# Convert scalar value into node and pass it as input to mul_node +@mx_op.register("_minus_scalar") +def convert_minus_scalar(node, **kwargs): + """Map MXNet's _minus_scalar operator attributes to onnx's Minus operator. + Creates a new node for the input scalar value, adds it to the initializer + and return multiple created nodes. + """ + return scalar_op_helper(node, 'Sub', **kwargs) + + +# Convert scalar value into node and pass it as input to mul_node +@mx_op.register("_plus_scalar") +def convert_add_scalar(node, **kwargs): + """Map MXNet's _plus_scalar operator attributes to onnx's Add operator. + Creates a new node for the input scalar value, adds it to the initializer + and return multiple created nodes. + """ + return scalar_op_helper(node, 'Add', **kwargs) + +# Convert scalar value into node and pass it as input to mul_node +@mx_op.register("_div_scalar") +def convert_div_scalar(node, **kwargs): + """Map MXNet's _div_scalar operator attributes to onnx's Div operator. + Creates a new node for the input scalar value, adds it to the initializer + and return multiple created nodes. + """ + return scalar_op_helper(node, 'Div', **kwargs) + + # Sorting and Searching @mx_op.register("argmax") def convert_argmax(node, **kwargs): diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index 3dfff3ed6818..48173df7f1c7 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -89,6 +89,7 @@ 'Squeeze' : squeeze, 'Unsqueeze' : unsqueeze, 'Flatten' : flatten, + 'Identity' : identity, #Powers 'Reciprocal' : reciprocal, 'Sqrt' : squareroot, diff --git a/tests/python-pytest/onnx/export/onnx_backend_test.py b/tests/python-pytest/onnx/export/onnx_backend_test.py index 0f2301edd8ea..803d290b9c69 100644 --- a/tests/python-pytest/onnx/export/onnx_backend_test.py +++ b/tests/python-pytest/onnx/export/onnx_backend_test.py @@ -48,6 +48,7 @@ 'test_ceil', 'test_floor', 'test_concat', + 'test_identity', 'test_sigmoid', 'test_relu', 'test_constant_pad', diff --git a/tests/python-pytest/onnx/import/test_cases.py b/tests/python-pytest/onnx/import/test_cases.py index 1a4d8c4fe37b..f7addbb29b32 100644 --- a/tests/python-pytest/onnx/import/test_cases.py +++ b/tests/python-pytest/onnx/import/test_cases.py @@ -31,6 +31,7 @@ 'test_ceil', 'test_floor', 'test_concat', + 'test_identity', 'test_sigmoid', 'test_relu', 'test_constant_pad', From c66868988e771d936647546043f6de180caea88a Mon Sep 17 00:00:00 2001 From: spidyDev Date: Wed, 13 Jun 2018 14:38:30 -0700 Subject: [PATCH 77/82] minor fix for squeeze operator. Also, added error handling --- python/mxnet/contrib/onnx/_export/op_translations.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/_export/op_translations.py index a4d5fc1b60e8..7f405e83838b 100644 --- a/python/mxnet/contrib/onnx/_export/op_translations.py +++ b/python/mxnet/contrib/onnx/_export/op_translations.py @@ -1708,14 +1708,18 @@ def convert_expand_dims(node, **kwargs): @mx_op.register("squeeze") def convert_squeeze(node, **kwargs): - """Map MXNet's expand_dims operator attributes to onnx's Unsqueeze operator + """Map MXNet's squeeze operator attributes to onnx's squeeze operator and return the created node. """ helper, _, _ = import_onnx_modules() name = node["name"] proc_nodes = kwargs["proc_nodes"] inputs = node["inputs"] - axis = int(node["attrs"]["axis"]) + if "axis" in node["attrs"]: + axis = convert_string_to_list(node["attrs"]["axis"]) + else: + raise AttributeError("Missing axis attribute: ONNX currently requires axis to " + "be specified for squeeze operator") input_node_id = kwargs["index_lookup"][inputs[0][0]] input_node = proc_nodes[input_node_id].name @@ -1724,7 +1728,7 @@ def convert_squeeze(node, **kwargs): "Squeeze", [input_node], [name], - axes=[axis], + axes=axis, name=name, ) return [node] From 20bb2fdd93f5f6d6bcf790da6236ff459e39e911 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Thu, 14 Jun 2018 14:38:46 -0700 Subject: [PATCH 78/82] Renamed onnx support folders to mark it public folders Changed underline files public or private as per usage Resolved conflicts with the latest --- python/mxnet/contrib/onnx/__init__.py | 6 +-- .../contrib/onnx/{_export => mx2onnx}/LICENSE | 0 .../onnx/{_export => mx2onnx}/__init__.py | 2 +- .../_export_helper.py} | 0 .../_op_translations.py} | 0 .../onnx/{_export => mx2onnx}/export_model.py | 2 +- .../onnx/{_export => mx2onnx}/export_onnx.py | 0 .../onnx/{_import => onnx2mx}/__init__.py | 0 .../_import_helper.py} | 38 +++++++++---------- .../_op_translations.py} | 2 +- .../_translation_utils.py} | 0 .../onnx/{_import => onnx2mx}/import_model.py | 0 .../onnx/{_import => onnx2mx}/import_onnx.py | 2 +- .../{_import => onnx2mx}/import_to_gluon.py | 0 tests/python-pytest/onnx/export/backend.py | 5 +-- .../python-pytest/onnx/export/backend_rep.py | 1 - .../onnx/import/gluon_backend.py | 6 +-- .../onnx/import/mxnet_backend.py | 3 +- .../onnx/import/mxnet_backend_rep.py | 1 - 19 files changed, 31 insertions(+), 37 deletions(-) rename python/mxnet/contrib/onnx/{_export => mx2onnx}/LICENSE (100%) rename python/mxnet/contrib/onnx/{_export => mx2onnx}/__init__.py (96%) rename python/mxnet/contrib/onnx/{_export/export_helper.py => mx2onnx/_export_helper.py} (100%) rename python/mxnet/contrib/onnx/{_export/op_translations.py => mx2onnx/_op_translations.py} (100%) rename python/mxnet/contrib/onnx/{_export => mx2onnx}/export_model.py (98%) rename python/mxnet/contrib/onnx/{_export => mx2onnx}/export_onnx.py (100%) rename python/mxnet/contrib/onnx/{_import => onnx2mx}/__init__.py (100%) rename python/mxnet/contrib/onnx/{_import/import_helper.py => onnx2mx/_import_helper.py} (75%) rename python/mxnet/contrib/onnx/{_import/op_translations.py => onnx2mx/_op_translations.py} (99%) rename python/mxnet/contrib/onnx/{_import/translation_utils.py => onnx2mx/_translation_utils.py} (100%) rename python/mxnet/contrib/onnx/{_import => onnx2mx}/import_model.py (100%) rename python/mxnet/contrib/onnx/{_import => onnx2mx}/import_onnx.py (99%) rename python/mxnet/contrib/onnx/{_import => onnx2mx}/import_to_gluon.py (100%) diff --git a/python/mxnet/contrib/onnx/__init__.py b/python/mxnet/contrib/onnx/__init__.py index bf8efad3540c..9f27060d3d6f 100644 --- a/python/mxnet/contrib/onnx/__init__.py +++ b/python/mxnet/contrib/onnx/__init__.py @@ -16,6 +16,6 @@ # under the License. """Module for ONNX model format support for Apache MXNet.""" -from ._import.import_model import import_model, get_model_metadata -from ._import.import_to_gluon import import_to_gluon -from ._export.export_model import export_model +from .onnx2mx.import_model import import_model, get_model_metadata +from .onnx2mx.import_to_gluon import import_to_gluon +from .mx2onnx.export_model import export_model diff --git a/python/mxnet/contrib/onnx/_export/LICENSE b/python/mxnet/contrib/onnx/mx2onnx/LICENSE similarity index 100% rename from python/mxnet/contrib/onnx/_export/LICENSE rename to python/mxnet/contrib/onnx/mx2onnx/LICENSE diff --git a/python/mxnet/contrib/onnx/_export/__init__.py b/python/mxnet/contrib/onnx/mx2onnx/__init__.py similarity index 96% rename from python/mxnet/contrib/onnx/_export/__init__.py rename to python/mxnet/contrib/onnx/mx2onnx/__init__.py index a5dc33b3a0fb..238174e4a079 100644 --- a/python/mxnet/contrib/onnx/_export/__init__.py +++ b/python/mxnet/contrib/onnx/mx2onnx/__init__.py @@ -21,4 +21,4 @@ from . import export_model from . import export_onnx -from . import op_translations +from . import _op_translations diff --git a/python/mxnet/contrib/onnx/_export/export_helper.py b/python/mxnet/contrib/onnx/mx2onnx/_export_helper.py similarity index 100% rename from python/mxnet/contrib/onnx/_export/export_helper.py rename to python/mxnet/contrib/onnx/mx2onnx/_export_helper.py diff --git a/python/mxnet/contrib/onnx/_export/op_translations.py b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py similarity index 100% rename from python/mxnet/contrib/onnx/_export/op_translations.py rename to python/mxnet/contrib/onnx/mx2onnx/_op_translations.py diff --git a/python/mxnet/contrib/onnx/_export/export_model.py b/python/mxnet/contrib/onnx/mx2onnx/export_model.py similarity index 98% rename from python/mxnet/contrib/onnx/_export/export_model.py rename to python/mxnet/contrib/onnx/mx2onnx/export_model.py index 510d1b703ad4..0dbfdc1d7b92 100644 --- a/python/mxnet/contrib/onnx/_export/export_model.py +++ b/python/mxnet/contrib/onnx/mx2onnx/export_model.py @@ -29,7 +29,7 @@ from ....base import string_types from .... import symbol from .export_onnx import MXNetGraph -from .export_helper import load_module +from ._export_helper import load_module def export_model(sym, params, input_shape, input_type=np.float32, diff --git a/python/mxnet/contrib/onnx/_export/export_onnx.py b/python/mxnet/contrib/onnx/mx2onnx/export_onnx.py similarity index 100% rename from python/mxnet/contrib/onnx/_export/export_onnx.py rename to python/mxnet/contrib/onnx/mx2onnx/export_onnx.py diff --git a/python/mxnet/contrib/onnx/_import/__init__.py b/python/mxnet/contrib/onnx/onnx2mx/__init__.py similarity index 100% rename from python/mxnet/contrib/onnx/_import/__init__.py rename to python/mxnet/contrib/onnx/onnx2mx/__init__.py diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/onnx2mx/_import_helper.py similarity index 75% rename from python/mxnet/contrib/onnx/_import/import_helper.py rename to python/mxnet/contrib/onnx/onnx2mx/_import_helper.py index 48173df7f1c7..c19f0f2cb246 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/onnx2mx/_import_helper.py @@ -15,27 +15,27 @@ # specific language governing permissions and limitations # under the License. -# coding: utf-8 +# coding: utf-8_ # pylint: disable=invalid-name """Operator attributes conversion""" -from .op_translations import identity, random_uniform, random_normal -from .op_translations import add, subtract, multiply, divide, absolute, negative, add_n -from .op_translations import tanh -from .op_translations import ceil, floor -from .op_translations import concat -from .op_translations import leaky_relu, _elu, _prelu, softmax, fully_connected -from .op_translations import global_avgpooling, global_maxpooling, linalg_gemm -from .op_translations import sigmoid, pad, relu, matrix_multiplication, batch_norm -from .op_translations import dropout, local_response_norm, conv, deconv -from .op_translations import reshape, cast, split, _slice, transpose, squeeze, flatten -from .op_translations import reciprocal, squareroot, power, exponent, _log, unsqueeze -from .op_translations import reduce_max, reduce_mean, reduce_min, reduce_sum -from .op_translations import reduce_prod, avg_pooling, max_pooling -from .op_translations import argmax, argmin, maximum, minimum -from .op_translations import clip, reduce_log_sum, reduce_log_sum_exp -from .op_translations import reduce_sum_square, reduce_l2, max_roi_pooling, instance_norm -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 identity, random_uniform, random_normal +from ._op_translations import add, subtract, multiply, divide, absolute, negative, add_n +from ._op_translations import tanh +from ._op_translations import ceil, floor +from ._op_translations import concat +from ._op_translations import leaky_relu, _elu, _prelu, softmax, fully_connected +from ._op_translations import global_avgpooling, global_maxpooling, linalg_gemm +from ._op_translations import sigmoid, pad, relu, matrix_multiplication, batch_norm +from ._op_translations import dropout, local_response_norm, conv, deconv +from ._op_translations import reshape, cast, split, _slice, transpose, squeeze, flatten +from ._op_translations import reciprocal, squareroot, power, exponent, _log, unsqueeze +from ._op_translations import reduce_max, reduce_mean, reduce_min, reduce_sum +from ._op_translations import reduce_prod, avg_pooling, max_pooling +from ._op_translations import argmax, argmin, maximum, minimum +from ._op_translations import clip, reduce_log_sum, reduce_log_sum_exp +from ._op_translations import reduce_sum_square, reduce_l2, max_roi_pooling, instance_norm +from ._op_translations import log_softmax, softsign, lesser, greater, equal +from ._op_translations import logical_and, logical_or, logical_xor, logical_not # convert_map defines maps of ONNX operator names to converter functor(callable) # defined in the op_translations module. diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py similarity index 99% rename from python/mxnet/contrib/onnx/_import/op_translations.py rename to python/mxnet/contrib/onnx/onnx2mx/_op_translations.py index 1a269ab07f93..2b98aa08febf 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/onnx2mx/_op_translations.py @@ -19,7 +19,7 @@ """ Module for translating ONNX operators into Mxnet operatoes""" # pylint: disable=unused-argument,protected-access import numpy as np -from . import translation_utils +from . import _translation_utils as translation_utils from .... import symbol # Method definitions for the callable objects mapped in the import_helper module diff --git a/python/mxnet/contrib/onnx/_import/translation_utils.py b/python/mxnet/contrib/onnx/onnx2mx/_translation_utils.py similarity index 100% rename from python/mxnet/contrib/onnx/_import/translation_utils.py rename to python/mxnet/contrib/onnx/onnx2mx/_translation_utils.py diff --git a/python/mxnet/contrib/onnx/_import/import_model.py b/python/mxnet/contrib/onnx/onnx2mx/import_model.py similarity index 100% rename from python/mxnet/contrib/onnx/_import/import_model.py rename to python/mxnet/contrib/onnx/onnx2mx/import_model.py diff --git a/python/mxnet/contrib/onnx/_import/import_onnx.py b/python/mxnet/contrib/onnx/onnx2mx/import_onnx.py similarity index 99% rename from python/mxnet/contrib/onnx/_import/import_onnx.py rename to python/mxnet/contrib/onnx/onnx2mx/import_onnx.py index d81ec96537f3..4e851712972f 100644 --- a/python/mxnet/contrib/onnx/_import/import_onnx.py +++ b/python/mxnet/contrib/onnx/onnx2mx/import_onnx.py @@ -23,7 +23,7 @@ from .... import cpu, gpu from .... import ndarray as nd from ....base import string_types -from .import_helper import _convert_map as convert_map +from ._import_helper import _convert_map as convert_map class GraphProto(object): # pylint: disable=too-few-public-methods """A helper class for handling mxnet symbol copying from pb2.GraphProto. diff --git a/python/mxnet/contrib/onnx/_import/import_to_gluon.py b/python/mxnet/contrib/onnx/onnx2mx/import_to_gluon.py similarity index 100% rename from python/mxnet/contrib/onnx/_import/import_to_gluon.py rename to python/mxnet/contrib/onnx/onnx2mx/import_to_gluon.py diff --git a/tests/python-pytest/onnx/export/backend.py b/tests/python-pytest/onnx/export/backend.py index 459702724e75..e23cc01494e9 100644 --- a/tests/python-pytest/onnx/export/backend.py +++ b/tests/python-pytest/onnx/export/backend.py @@ -17,10 +17,9 @@ # coding: utf-8 """backend wrapper for onnx test infrastructure""" -import mxnet as mx import numpy as np -from mxnet.contrib.onnx._import.import_onnx import GraphProto -from mxnet.contrib.onnx._export.export_onnx import MXNetGraph +from mxnet.contrib.onnx.onnx2mx.import_onnx import GraphProto +from mxnet.contrib.onnx.mx2onnx.export_onnx import MXNetGraph try: from onnx import helper, TensorProto, mapping from onnx.backend.base import Backend diff --git a/tests/python-pytest/onnx/export/backend_rep.py b/tests/python-pytest/onnx/export/backend_rep.py index dc87c15134d9..8729eafea1a1 100644 --- a/tests/python-pytest/onnx/export/backend_rep.py +++ b/tests/python-pytest/onnx/export/backend_rep.py @@ -17,7 +17,6 @@ # coding: utf-8 """backend rep for onnx test infrastructure""" -import numpy as np try: from onnx.backend.base import BackendRep except ImportError: diff --git a/tests/python-pytest/onnx/import/gluon_backend.py b/tests/python-pytest/onnx/import/gluon_backend.py index d2946f7bb541..302fd4dcf08f 100644 --- a/tests/python-pytest/onnx/import/gluon_backend.py +++ b/tests/python-pytest/onnx/import/gluon_backend.py @@ -17,10 +17,8 @@ # coding: utf-8 """Gluon backend wrapper for onnx test infrastructure""" -import mxnet as mx -from mxnet import nd -from mxnet.contrib.onnx._import.import_onnx import GraphProto -import numpy as np +from mxnet.contrib.onnx.onnx2mx.import_onnx import GraphProto + try: from onnx import helper, TensorProto from onnx.backend.base import Backend diff --git a/tests/python-pytest/onnx/import/mxnet_backend.py b/tests/python-pytest/onnx/import/mxnet_backend.py index bbe8899dee15..10f89ecbbbc7 100644 --- a/tests/python-pytest/onnx/import/mxnet_backend.py +++ b/tests/python-pytest/onnx/import/mxnet_backend.py @@ -17,8 +17,7 @@ # coding: utf-8 """MXNet backend wrapper for onnx test infrastructure""" -import mxnet as mx -from mxnet.contrib.onnx._import.import_onnx import GraphProto +from mxnet.contrib.onnx.onnx2mx.import_onnx import GraphProto try: from onnx import helper, TensorProto from onnx.backend.base import Backend diff --git a/tests/python-pytest/onnx/import/mxnet_backend_rep.py b/tests/python-pytest/onnx/import/mxnet_backend_rep.py index 5ce29f54150a..067ef1568309 100644 --- a/tests/python-pytest/onnx/import/mxnet_backend_rep.py +++ b/tests/python-pytest/onnx/import/mxnet_backend_rep.py @@ -17,7 +17,6 @@ # coding: utf-8 """MXNet backend rep for onnx test infrastructure""" -import numpy as np try: from onnx.backend.base import BackendRep except ImportError: From f4902e1d9c01af90e2deeff889db73378543b349 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Thu, 14 Jun 2018 17:09:57 -0700 Subject: [PATCH 79/82] Added support L2Normalization op Added some error checking --- .../contrib/onnx/mx2onnx/_op_translations.py | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py index 7f405e83838b..91a72fbc56ac 100644 --- a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py +++ b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py @@ -794,6 +794,31 @@ def convert_lrn(node, **kwargs): return [lrn_node] +@mx_op.register("L2Normalization") +def convert_l2normalization(node, **kwargs): + """Map MXNet's L2Normalization operator attributes to onnx's LpNormalization operator + and return the created node. + """ + helper, _, _ = import_onnx_modules() + name = node["name"] + input_id = kwargs["index_lookup"][node["inputs"][0][0]] + input_name = kwargs["proc_nodes"][input_id].name + attrs = node["attrs"] + mode = attrs.get("mode", "instance") + + if mode != "channel": + raise AttributeError("ONNX currently supports channel mode only") + + l2norm_node = helper.make_node( + "LpNormalization", + [input_name], + [name], + axis=1, # channel only + name=name + ) + return [l2norm_node] + + @mx_op.register("Dropout") def convert_dropout(node, **kwargs): """Map MXNet's Dropout operator attributes to onnx's Dropout operator @@ -1625,7 +1650,10 @@ def convert_slice_axis(node, **kwargs): inputs = node["inputs"] axes = int(node["attrs"]["axis"]) starts = int(node["attrs"]["begin"]) - ends = int(node["attrs"]["end"]) + if node["attrs"]["end"] == 'None': + raise ValueError("Slice: ONNX doesnt't support 'None' in 'end' attribute") + else: + ends = int(node["attrs"]["end"]) input_node_id = kwargs["index_lookup"][inputs[0][0]] input_node = proc_nodes[input_node_id].name From 84c93200f4d927dd7c39786326066b5ae720795f Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Fri, 15 Jun 2018 11:14:28 -0700 Subject: [PATCH 80/82] added comments and warning --- .../mxnet/contrib/onnx/mx2onnx/_op_translations.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py index 91a72fbc56ac..5f5561ab32b6 100644 --- a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py +++ b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py @@ -55,11 +55,12 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals + import re +import logging import numpy as np from .export_onnx import MXNetGraph as mx_op - def import_onnx_modules(): """ To make sure ONNX is runtime dependency, it is imported used only when needed""" try: @@ -94,6 +95,7 @@ def transform_padding(pad_width): onnx_pad_width = [0]*num_pad_values start_index = 0 + # num_pad_values will always be multiple of 2 end_index = int(num_pad_values/2) for idx in range(0, num_pad_values): if idx % 2 == 0: @@ -552,6 +554,15 @@ def convert_pooling(node, **kwargs): input_node = proc_nodes[input_node_idx] name = node["name"] + pooling_convention = attrs.get('pooling_convention', 'valid') + + if pooling_convention == 'full': + pooling_warning = "Pooling: ONNX currently doesn't support pooling_convention. " \ + "This might lead to shape or accuracy issues. " \ + "https://github.com/onnx/onnx/issues/549" + + logging.warning(pooling_warning) + pad_dims = list(parse_helper(attrs, "pad", [0, 0])) pad_dims = pad_dims + pad_dims pool_types = {"max": "MaxPool", "avg": "AveragePool"} From f36230d126850c972714aad7c5daad1974a124b3 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Fri, 15 Jun 2018 11:14:28 -0700 Subject: [PATCH 81/82] added comments and warning --- .../mxnet/contrib/onnx/mx2onnx/_op_translations.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py index 91a72fbc56ac..5f5561ab32b6 100644 --- a/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py +++ b/python/mxnet/contrib/onnx/mx2onnx/_op_translations.py @@ -55,11 +55,12 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals + import re +import logging import numpy as np from .export_onnx import MXNetGraph as mx_op - def import_onnx_modules(): """ To make sure ONNX is runtime dependency, it is imported used only when needed""" try: @@ -94,6 +95,7 @@ def transform_padding(pad_width): onnx_pad_width = [0]*num_pad_values start_index = 0 + # num_pad_values will always be multiple of 2 end_index = int(num_pad_values/2) for idx in range(0, num_pad_values): if idx % 2 == 0: @@ -552,6 +554,15 @@ def convert_pooling(node, **kwargs): input_node = proc_nodes[input_node_idx] name = node["name"] + pooling_convention = attrs.get('pooling_convention', 'valid') + + if pooling_convention == 'full': + pooling_warning = "Pooling: ONNX currently doesn't support pooling_convention. " \ + "This might lead to shape or accuracy issues. " \ + "https://github.com/onnx/onnx/issues/549" + + logging.warning(pooling_warning) + pad_dims = list(parse_helper(attrs, "pad", [0, 0])) pad_dims = pad_dims + pad_dims pool_types = {"max": "MaxPool", "avg": "AveragePool"} From 6327847056f99afc01b6296474f15b52b943b2f7 Mon Sep 17 00:00:00 2001 From: Roshani Nagmote Date: Mon, 18 Jun 2018 14:33:55 -0700 Subject: [PATCH 82/82] doc API ref added --- docs/api/python/contrib/onnx.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api/python/contrib/onnx.md b/docs/api/python/contrib/onnx.md index f1ef1aa7aa58..8cd619809c19 100644 --- a/docs/api/python/contrib/onnx.md +++ b/docs/api/python/contrib/onnx.md @@ -47,6 +47,7 @@ This document describes all the ONNX-MXNet APIs. .. automodule:: mxnet.contrib.onnx :members: import_model :members: get_model_metadata + :members: export_model ```