From d80f95a61eaef7dfc8aeecb717de408637c19d4b Mon Sep 17 00:00:00 2001 From: Acharya Date: Mon, 26 Feb 2018 01:00:17 -0800 Subject: [PATCH 01/35] Onnx Module to import onnx models into mxnet * Change package name to onnx from serde. * Remove onnx install time dependency * Remove Renamer class * Add apache license to files. * Refactor test files to tests/python folder. * Removed export folder. * Refactor Attribute COnverter logic Signed-off-by: Acharya --- dmlc-core | 2 +- python/mxnet/contrib/__init__.py | 2 +- python/mxnet/contrib/onnx/__init__.py | 18 ++ python/mxnet/contrib/onnx/_import/__init__.py | 46 +++++ .../contrib/onnx/_import/import_helper.py | 29 +++ .../mxnet/contrib/onnx/_import/import_onnx.py | 166 ++++++++++++++++ .../onnx/_import/op_translations/__init__.py | 19 ++ .../onnx/_import/op_translations/_add.py | 25 +++ .../onnx/_import/op_translations/negative.py | 20 ++ .../_import/op_translations/reduce_max.py | 23 +++ .../onnx/_import/op_translations/reshape.py | 20 ++ .../_import/translation_utils/__init__.py | 141 +++++++++++++ tests/python/onnx_test_utils/backend.py | 186 ++++++++++++++++++ tests/python/onnx_test_utils/backend_rep.py | 78 ++++++++ tests/python/unittest/onnx_backend_test.py | 51 +++++ .../python/unittest/test_super_resolution.py | 61 ++++++ 16 files changed, 885 insertions(+), 2 deletions(-) create mode 100644 python/mxnet/contrib/onnx/__init__.py create mode 100644 python/mxnet/contrib/onnx/_import/__init__.py create mode 100644 python/mxnet/contrib/onnx/_import/import_helper.py create mode 100644 python/mxnet/contrib/onnx/_import/import_onnx.py create mode 100644 python/mxnet/contrib/onnx/_import/op_translations/__init__.py create mode 100644 python/mxnet/contrib/onnx/_import/op_translations/_add.py create mode 100644 python/mxnet/contrib/onnx/_import/op_translations/negative.py create mode 100644 python/mxnet/contrib/onnx/_import/op_translations/reduce_max.py create mode 100644 python/mxnet/contrib/onnx/_import/op_translations/reshape.py create mode 100644 python/mxnet/contrib/onnx/_import/translation_utils/__init__.py create mode 100644 tests/python/onnx_test_utils/backend.py create mode 100644 tests/python/onnx_test_utils/backend_rep.py create mode 100644 tests/python/unittest/onnx_backend_test.py create mode 100644 tests/python/unittest/test_super_resolution.py diff --git a/dmlc-core b/dmlc-core index 282b98663f59..a1fd6834c0cd 160000 --- a/dmlc-core +++ b/dmlc-core @@ -1 +1 @@ -Subproject commit 282b98663f59df6b26f906580af610dea3046f22 +Subproject commit a1fd6834c0cd3fd2cc586deec2dc24194924cada diff --git a/python/mxnet/contrib/__init__.py b/python/mxnet/contrib/__init__.py index 36ee21305bfd..63cd8ce26649 100644 --- a/python/mxnet/contrib/__init__.py +++ b/python/mxnet/contrib/__init__.py @@ -28,5 +28,5 @@ from . import tensorboard from . import text - +from . import onnx from . import io diff --git a/python/mxnet/contrib/onnx/__init__.py b/python/mxnet/contrib/onnx/__init__.py new file mode 100644 index 000000000000..e5402002bd80 --- /dev/null +++ b/python/mxnet/contrib/onnx/__init__.py @@ -0,0 +1,18 @@ +# 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. + +from . import _import diff --git a/python/mxnet/contrib/onnx/_import/__init__.py b/python/mxnet/contrib/onnx/_import/__init__.py new file mode 100644 index 000000000000..f1fe4cb408d4 --- /dev/null +++ b/python/mxnet/contrib/onnx/_import/__init__.py @@ -0,0 +1,46 @@ +# 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 +"""import function""" +try: + import onnx +except ImportError as ie: + raise ImportError("Onnx and protobuf need to be installed") +from .import_onnx import GraphProto + +def import_model(model_file): + """Imports the supplied ONNX model file into MXNet symbol and parameters. + + Parameters + ---------- + model_file : ONNX model file name + + Returns + ------- + sym : mx.symbol + Compatible mxnet symbol + + params : dict of str to mx.ndarray + Dict of converted parameters stored in mx.ndarray format + """ + graph = GraphProto() + + # loads model file and returns ONNX protobuf object + model_proto = onnx.load(model_file) + sym, params = graph.from_onnx(model_proto.graph) + return sym, params diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py new file mode 100644 index 000000000000..fd6c1d1a7e75 --- /dev/null +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -0,0 +1,29 @@ +# 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 +"""Operator attributes conversion""" +from . import * + +# _convert_map defines maps of name to converter functor(callable) +_convert_map = { + 'Add' : _add, + 'Neg' : negative, + 'ReduceMax' : reduce_max, + 'Reshape' : reshape, +} diff --git a/python/mxnet/contrib/onnx/_import/import_onnx.py b/python/mxnet/contrib/onnx/_import/import_onnx.py new file mode 100644 index 000000000000..8148ab378438 --- /dev/null +++ b/python/mxnet/contrib/onnx/_import/import_onnx.py @@ -0,0 +1,166 @@ +# 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 +""" Support import export formats.""" +from __future__ import absolute_import as _abs +from .... import symbol +from .... import ndarray as nd +from .import_helper import _convert_map, _pad_sequence_fix + +def _convert_operator(op_name, attrs, inputs, convert_map=None): + """Convert from onnx operator to mxnet operator. + The converter must specify conversions explicitly for incompatible name, and + apply handlers to operator attributes. + + Parameters + ---------- + op_name : str + Operator name, such as Convolution, FullyConnected + attrs : dict + Dict of operator attributes + inputs: list + list of inputs to the operator + convert_map : dict + Dict of name : callable, where name is the op's name that + require conversion to mxnet, callable are functions which + take attrs and return (new_op_name, new_attrs, inputs) + + Returns + ------- + (op_name, attrs) + Converted (op_name, attrs) for mxnet. + """ + convert_map = convert_map if convert_map else _convert_map + if op_name in convert_map: + op_name, attrs, inputs = convert_map[op_name](op_name, attrs, inputs) + else: + raise NotImplementedError("Operator {} not implemented.".format(op_name)) + op = getattr(symbol, op_name, None) + if not op: + raise RuntimeError("Unable to map op_name {} to sym".format(op_name)) + return op, attrs, inputs + +class GraphProto(object): # pylint: disable=too-few-public-methods + """A helper class for handling mxnet symbol copying from pb2.GraphProto. + Definition: https://github.com/onnx/onnx/blob/master/onnx/onnx.proto + """ + def __init__(self): + self._nodes = {} + self._params = {} + self._renames = {} + self._num_input = 0 + self._num_param = 0 + + def from_onnx(self, graph): + """Construct symbol from onnx graph. + The inputs from onnx graph is vague, only providing "1", "2"... + For convenience, we rename the `real` input names to "input_0", + "input_1"... And renaming parameters to "param_0", "param_1"... + + Parameters + ---------- + graph : onnx protobuf object + The loaded onnx graph + + Returns + ------- + sym :symbol.Symbol + The returned mxnet symbol + params : dict + A dict of name: nd.array pairs, used as pretrained weights + """ + # parse network inputs, aka parameters + for init_tensor in graph.initializer: + if not init_tensor.name.strip(): + raise ValueError("Tensor's name is required.") + self._params[init_tensor.name] = self._parse_array(init_tensor) + + # converting GraphProto message + for i in graph.input: + if i.name in self._params: + # i is a param instead of input + name_param = 'param_{}'.format(self._num_param) + self._num_param += 1 + self._params[name_param] = self._params.pop(i.name) + self._nodes[name_param] = symbol.Variable(name=name_param, + shape=self._params[name_param].shape) + self._renames[i.name] = name_param + else: + name_input = 'input_{}'.format(self._num_input) + self._num_input += 1 + self._nodes[name_input] = symbol.Variable(name=name_input) + self._renames[i.name] = name_input + + # constructing nodes, nodes are stored as directed acyclic graph + # converting NodeProto message + for node in graph.node: + op_name = node.op_type + node_name = node.name.strip() + node_name = node_name if node_name else None + onnx_attr = self._parse_attr(node.attribute) + inputs = [self._nodes[self._renames.get(i, i)] for i in node.input] + new_op, mx_attr, inputs = _convert_operator(op_name, onnx_attr, inputs) + + # calling again to get new symbols after some workarounds + inputs = [self._nodes[self._renames.get(i, i)] for i in node.input] + + op = new_op(name=node_name, *inputs, **mx_attr) + + assert len(node.output) == len(op.list_outputs()), ( + "Number of output mismatch {} vs {} in {}.".format( + len(node.output), len(op.list_outputs()), op_name)) + for k, i in zip(list(node.output), range(len(node.output))): + self._nodes[k] = op[i] + # now return the outputs + out = [self._nodes[i.name] for i in graph.output] + if len(out) > 1: + out = symbol.Group(out) + else: + out = out[0] + return out, self._params + + def _parse_array(self, tensor_proto): + """Grab data in TensorProto and convert to numpy array.""" + try: + from onnx.numpy_helper import to_array + except ImportError as e: + raise ImportError("Unable to import onnx which is required {}".format(e)) + np_array = to_array(tensor_proto).reshape(tuple(tensor_proto.dims)) + return mx.nd.array(np_array) + + def _parse_attr(self, attr_proto): + """Convert a list of AttributeProto to a dict, with names as keys.""" + attrs = {} + for a in attr_proto: + for f in ['f', 'i', 's']: + if a.HasField(f): + attrs[a.name] = getattr(a, f) + for f in ['floats', 'ints', 'strings']: + if list(getattr(a, f)): + assert a.name not in attrs, "Only one type of attr is allowed" + attrs[a.name] = tuple(getattr(a, f)) + for f in ['t', 'g']: + if a.HasField(f): + attrs[a.name] = getattr(a, f) + for f in ['tensors', 'graphs']: + if list(getattr(a, f)): + raise NotImplementedError("Filed {} is not supported in mxnet.".format(f)) + if a.name not in attrs: + raise ValueError("Cannot parse attribute: \n{}\n.".format(a)) + return attrs diff --git a/python/mxnet/contrib/onnx/_import/op_translations/__init__.py b/python/mxnet/contrib/onnx/_import/op_translations/__init__.py new file mode 100644 index 000000000000..8112c2ee42f9 --- /dev/null +++ b/python/mxnet/contrib/onnx/_import/op_translations/__init__.py @@ -0,0 +1,19 @@ +# 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 . import * diff --git a/python/mxnet/contrib/onnx/_import/op_translations/_add.py b/python/mxnet/contrib/onnx/_import/op_translations/_add.py new file mode 100644 index 000000000000..628c49c3e82b --- /dev/null +++ b/python/mxnet/contrib/onnx/_import/op_translations/_add.py @@ -0,0 +1,25 @@ +# 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 + +def _add(op_name, attrs, inputs): + new_attr = {} + if 'broadcast' in attrs and attrs['broadcast']==1: + return 'broadcast_add', new_attr, inputs + else: + return 'elemwise_add', new_attr, inputs diff --git a/python/mxnet/contrib/onnx/_import/op_translations/negative.py b/python/mxnet/contrib/onnx/_import/op_translations/negative.py new file mode 100644 index 000000000000..dd82af0aac00 --- /dev/null +++ b/python/mxnet/contrib/onnx/_import/op_translations/negative.py @@ -0,0 +1,20 @@ +# 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 +def negative(op_name, attrs, inputs): + return "negative", attrs, inputs diff --git a/python/mxnet/contrib/onnx/_import/op_translations/reduce_max.py b/python/mxnet/contrib/onnx/_import/op_translations/reduce_max.py new file mode 100644 index 000000000000..b5d91dc55285 --- /dev/null +++ b/python/mxnet/contrib/onnx/_import/op_translations/reduce_max.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 +from ..translation_utils import _fix_attribute_names + +def reduce_max(op_name, attrs, inputs): + new_attr = _fix_attrbute_names(attrs, {'axes':'axis'}) + return 'max', attrs, inputs \ No newline at end of file diff --git a/python/mxnet/contrib/onnx/_import/op_translations/reshape.py b/python/mxnet/contrib/onnx/_import/op_translations/reshape.py new file mode 100644 index 000000000000..d1e967145746 --- /dev/null +++ b/python/mxnet/contrib/onnx/_import/op_translations/reshape.py @@ -0,0 +1,20 @@ +# 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 +def reshape(op_name, attrs, inputs): + return "reshape", attrs, inputs diff --git a/python/mxnet/contrib/onnx/_import/translation_utils/__init__.py b/python/mxnet/contrib/onnx/_import/translation_utils/__init__.py new file mode 100644 index 000000000000..54f9d04b8d62 --- /dev/null +++ b/python/mxnet/contrib/onnx/_import/translation_utils/__init__.py @@ -0,0 +1,141 @@ +# 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,no-self-use,too-many-branches,too-few-public-methods,too-many-arguments +from __future__ import absolute_import as _abs +from ....base import string_types + +def _fix_attribute_names(attrs, change_map): + new_attr = {} + for k in attrs.keys(): + if k in change_map: + new_attr[change_map[k]] = attrs[k] + else: + new_attr[k] = attrs[k] + return new_attr + +def _fix_pooling(op_name, inputs, new_attr): + """onnx pooling operator supports asymmetrical padding + Adding pad operator before pooling in mxnet to work with onnx""" + pool_type = 'avg' if op_name == 'AveragePool' else 'max' + stride = new_attr.get('strides') + kernel = new_attr.get('kernel_shape') + padding = new_attr.get('pads') + pad_width = (0, 0, 0, 0) + _pad_sequence_fix(padding, len(kernel)) + new_pad_op = symbol.pad(inputs[0], mode='constant', pad_width=pad_width) + new_pooling_op = symbol.Pooling(new_pad_op, pool_type=pool_type, + stride=stride, kernel=kernel) + return new_pooling_op + +def _fix_slice(inputs, new_attr): + """onnx slice provides slicing on multiple axis. Adding multiple slice_axis operator + for multiple axes from mxnet""" + begin = new_attr.get('begin') + end = new_attr.get('end') + axes = new_attr.get('axis', tuple(range(len(begin)))) + slice_op = symbol.slice_axis(inputs[0], axis=axes[0], begin=begin[0], end=end[0]) + if len(axes) > 1: + for i, axis in enumerate(axes): + slice_op = symbol.slice_axis(slice_op, axis=axis, begin=begin[i], end=end[i]) + return slice_op + +def _fix_squeeze(inputs, new_attr): + """ + MXNet doesnt have a squeeze operator. + Using "split" to perform similar operation. + "split" can be slower compared to "reshape". + This can have performance impact. + TODO: Remove this implementation once mxnet adds the support. + """ + axes = new_attr.get('axis') + op = symbol.split(inputs[0], axis=axes[0], num_outputs=1, squeeze_axis=1) + for i in axes[1:]: + op = symbol.split(op, axis=i-1, num_outputs=1, squeeze_axis=1) + return op + +def _fix_gemm(op_name, inputs, old_attr): + """Using FullyConnected operator in place of linalg_gemm to perform same operation""" + op = getAttr(symbol, op_name, None) + alpha = float(old_attr.get('alpha', 1.0)) + beta = float(old_attr.get('beta', 1.0)) + transA = int(old_attr.get('transA', 0)) + transB = int(old_attr.get('transB', 0)) + if transA: + inputs[0] = symbol.transpose(inputs[0], axes=(1, 0)) + if not transB: + inputs[1] = symbol.transpose(inputs[1], axes=(1, 0)) + new_inputs = [alpha*inputs[0], inputs[1], beta*inputs[2]] + new_attr = {'num_hidden' : self._params[inputs[2].name].shape[0]} + return op, new_inputs, new_attr + +def _fix_outputs(op, outputs): + """A workaround to handle dropout or similar operator that have more than one out + in ONNX. + """ + if op == 'Dropout': + assert len(outputs) == 2, "ONNX have two outputs for dropout layer." + outputs = outputs[:-1] + return outputs + +def _fix_bias(op, attrs, num_inputs): + """A workaround for 'use_bias' attribute since onnx don't provide this attribute, + we have to check the number of inputs to decide it.""" + if op not in [symbol.Convolution, symbol.Deconvolution, symbol.FullyConnected]: + return attrs + if num_inputs == 3: + attrs['no_bias'] = False + elif num_inputs == 2: + attrs['no_bias'] = True + else: + raise ValueError("Unexpected number of inputs for: {}".format(op)) + return attrs + + +def _fix_bias_shape(op_name, inputs, attrs): + """A workaround to reshape bias term to (1, num_channel).""" + if (op_name == 'Add' or op_name == 'Mul') and (int(len(self._params)) > 0) and \ + ('broadcast' in attrs and attrs['broadcast'] == 1): + assert len(list(inputs)) == 2 + bias_name = self._renames.get(inputs[1], inputs[1]) + bias = self._params[bias_name] + assert len(bias.shape) == 1 + # reshape to (1, n) + bias = nd.array(bias.asnumpy().reshape((1, -1, 1, 1))) + # broadcast_add expects shape with sym.variable + self._nodes[bias_name] = symbol.Variable(name=bias_name, shape=bias.shape) + self._params[bias_name] = bias + + +def _fix_channels(op, attrs, inputs): + """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. + """ + if op not in [symbol.Convolution, symbol.Deconvolution, symbol.FullyConnected]: + return attrs + weight_name = self._renames[inputs[1]] + if not weight_name in self._params: + raise ValueError("Unable to get channels/units attr from onnx graph.") + else: + wshape = self._params[weight_name].shape + assert len(wshape) >= 2, "Weights shape is invalid: {}".format(wshape) + channels = wshape[0] + if op in [symbol.FullyConnected]: + attrs['num_hidden'] = channels + else: + attrs['num_filter'] = channels + return attrs diff --git a/tests/python/onnx_test_utils/backend.py b/tests/python/onnx_test_utils/backend.py new file mode 100644 index 000000000000..0a18236ad34c --- /dev/null +++ b/tests/python/onnx_test_utils/backend.py @@ -0,0 +1,186 @@ +# 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=too-many-locals,invalid-name +"""backend wrapper for onnx test infrastructure""" +from collections import namedtuple +import mxnet as mx +try: + from onnx import helper, TensorProto + from onnx.backend.base import Backend +except ImportError as ie: + raise ImportError("Onnx and protobuf need to be installed") +from mxnet.contrib.onnx._import.import_onnx import GraphProto +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'. + # TODO: Handle multiple weight params. + # TODO: Add for "bias" if needed + 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 + + @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 + """ + graph = GraphProto() + sym, params = graph.from_onnx(MXNetBackend.make_graph(node, inputs)) + data_names = [i for i in sym.get_internals().list_inputs() if i[:-1] == "input_"] + data_shapes = [] + dim_change_op_types = set(['ReduceMin', 'ReduceMax', 'ReduceMean', + 'ReduceProd', 'ReduceSum', 'Slice', 'Pad', + 'Squeeze', 'Upsample', 'Reshape', 'Conv']) + + # 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[idx].shape) < 4 and len(inputs) > 1 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 int(len(params)) > 0: + mod.set_params(arg_params=params, aux_params=params) + else: + mod.init_params() + + batch = namedtuple('Batch', ['data']) + + 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(batch(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, params = graph.from_onnx(model.graph) + return MXNetBackendRep(sym, 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/onnx_test_utils/backend_rep.py b/tests/python/onnx_test_utils/backend_rep.py new file mode 100644 index 000000000000..0ffc329538f8 --- /dev/null +++ b/tests/python/onnx_test_utils/backend_rep.py @@ -0,0 +1,78 @@ +# 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=too-few-public-methods +"""backend rep for onnx test infrastructure""" +from collections import namedtuple +import numpy as np +try: + from onnx.backend.base import BackendRep +except ImportError as ie: + 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, params, device): + self.symbol = symbol + self.params = 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") + + mod = mx.mod.Module(symbol=self.symbol, data_names=['input_0'], context=ctx, + label_names=None) + mod.bind(for_training=False, data_shapes=[('input_0', input_data.shape)], + label_shapes=None) + mod.set_params(arg_params=self.params, aux_params=None) + + # 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/unittest/onnx_backend_test.py b/tests/python/unittest/onnx_backend_test.py new file mode 100644 index 000000000000..f720b318148f --- /dev/null +++ b/tests/python/unittest/onnx_backend_test.py @@ -0,0 +1,51 @@ +# 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 as ie: + 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_add*', + 'test_neg*', + 'test_reshape_*', + ] + +for op_test in implemented_operators: + backend_test.include(op_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() diff --git a/tests/python/unittest/test_super_resolution.py b/tests/python/unittest/test_super_resolution.py new file mode 100644 index 000000000000..0fdfa63a63d6 --- /dev/null +++ b/tests/python/unittest/test_super_resolution.py @@ -0,0 +1,61 @@ +# 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. + +"""Testing super_resolution model conversion""" +from __future__ import absolute_import as _abs +from __future__ import print_function +from collections import namedtuple +import mxnet as mx +from mxnet.test_utils import download +import mxnet.contrib.onnx._import as onnx_mxnet +import numpy as np +from PIL import Image + +model_url = 'https://s3.amazonaws.com/onnx-mxnet/examples/super_resolution.onnx' + +download(model_url, 'super_resolution.onnx') + +print("Converting onnx format to mxnet's symbol and params...") +sym, params = onnx_mxnet.import_model('super_resolution.onnx') + +# Load test image +input_image_dim = 224 +output_image_dim = 672 +img_url = 'https://s3.amazonaws.com/onnx-mxnet/examples/super_res_input.jpg' +download(img_url, 'super_res_input.jpg') +img = Image.open('super_res_input.jpg').resize((input_image_dim, input_image_dim)) +img_ycbcr = img.convert("YCbCr") +img_y, img_cb, img_cr = img_ycbcr.split() +x = np.array(img_y)[np.newaxis, np.newaxis, :, :] + +# create module +mod = mx.mod.Module(symbol=sym, data_names=['input_0'], label_names=None) +mod.bind(for_training=False, data_shapes=[('input_0', x.shape)]) +mod.set_params(arg_params=params, aux_params=None) + +# run inference +Batch = namedtuple('Batch', ['data']) +mod.forward(Batch([mx.nd.array(x)])) + +# Save the result +img_out_y = Image.fromarray(np.uint8(mod.get_outputs()[0][0][0].asnumpy().clip(0, 255)), mode='L') + +result_img = Image.merge( + "YCbCr", [img_out_y, + img_cb.resize(img_out_y.size, Image.BICUBIC), + img_cr.resize(img_out_y.size, Image.BICUBIC)]).convert("RGB") +result_img.save("super_res_output.jpg") From 23c409a1845a3473ba4ece5515aa0fbc5eb09d44 Mon Sep 17 00:00:00 2001 From: Acharya Date: Mon, 5 Mar 2018 10:26:01 -0800 Subject: [PATCH 02/35] Changing the translation and utils file. Signed-off-by: Acharya --- python/mxnet/contrib/onnx/__init__.py | 2 +- python/mxnet/contrib/onnx/_import/__init__.py | 30 ++---------------- .../contrib/onnx/_import/import_helper.py | 4 ++- .../reduce_max.py => import_model.py} | 31 ++++++++++++++++--- .../mxnet/contrib/onnx/_import/import_onnx.py | 14 ++++----- .../_add.py => op_translations.py} | 17 ++++++++++ .../onnx/_import/op_translations/__init__.py | 19 ------------ .../onnx/_import/op_translations/negative.py | 20 ------------ .../onnx/_import/op_translations/reshape.py | 20 ------------ .../__init__.py => translation_utils.py} | 14 --------- tests/python/unittest/onnx_backend_test.py | 3 ++ 11 files changed, 59 insertions(+), 115 deletions(-) rename python/mxnet/contrib/onnx/_import/{op_translations/reduce_max.py => import_model.py} (52%) rename python/mxnet/contrib/onnx/_import/{op_translations/_add.py => op_translations.py} (67%) delete mode 100644 python/mxnet/contrib/onnx/_import/op_translations/__init__.py delete mode 100644 python/mxnet/contrib/onnx/_import/op_translations/negative.py delete mode 100644 python/mxnet/contrib/onnx/_import/op_translations/reshape.py rename python/mxnet/contrib/onnx/_import/{translation_utils/__init__.py => translation_utils.py} (91%) diff --git a/python/mxnet/contrib/onnx/__init__.py b/python/mxnet/contrib/onnx/__init__.py index e5402002bd80..dfbc9ac754df 100644 --- a/python/mxnet/contrib/onnx/__init__.py +++ b/python/mxnet/contrib/onnx/__init__.py @@ -15,4 +15,4 @@ # specific language governing permissions and limitations # under the License. -from . import _import +from ._import.import_model import import_model diff --git a/python/mxnet/contrib/onnx/_import/__init__.py b/python/mxnet/contrib/onnx/_import/__init__.py index f1fe4cb408d4..c679932fcf25 100644 --- a/python/mxnet/contrib/onnx/_import/__init__.py +++ b/python/mxnet/contrib/onnx/_import/__init__.py @@ -16,31 +16,5 @@ # under the License. # coding: utf-8 -"""import function""" -try: - import onnx -except ImportError as ie: - raise ImportError("Onnx and protobuf need to be installed") -from .import_onnx import GraphProto - -def import_model(model_file): - """Imports the supplied ONNX model file into MXNet symbol and parameters. - - Parameters - ---------- - model_file : ONNX model file name - - Returns - ------- - sym : mx.symbol - Compatible mxnet symbol - - params : dict of str to mx.ndarray - Dict of converted parameters stored in mx.ndarray format - """ - graph = GraphProto() - - # loads model file and returns ONNX protobuf object - model_proto = onnx.load(model_file) - sym, params = graph.from_onnx(model_proto.graph) - return sym, params +from . import import_model +from . import import_onnx diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index fd6c1d1a7e75..bf47910b1235 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -18,12 +18,14 @@ # coding: utf-8 # pylint: disable=invalid-name """Operator attributes conversion""" -from . import * +from .op_translations import _add, negative, reduce_max, reshape +from .op_translations import reduce_mean # _convert_map defines maps of name to converter functor(callable) _convert_map = { 'Add' : _add, 'Neg' : negative, 'ReduceMax' : reduce_max, + 'ReduceMean' : reduce_mean, 'Reshape' : reshape, } diff --git a/python/mxnet/contrib/onnx/_import/op_translations/reduce_max.py b/python/mxnet/contrib/onnx/_import/import_model.py similarity index 52% rename from python/mxnet/contrib/onnx/_import/op_translations/reduce_max.py rename to python/mxnet/contrib/onnx/_import/import_model.py index b5d91dc55285..f1fe4cb408d4 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations/reduce_max.py +++ b/python/mxnet/contrib/onnx/_import/import_model.py @@ -16,8 +16,31 @@ # under the License. # coding: utf-8 -from ..translation_utils import _fix_attribute_names +"""import function""" +try: + import onnx +except ImportError as ie: + raise ImportError("Onnx and protobuf need to be installed") +from .import_onnx import GraphProto -def reduce_max(op_name, attrs, inputs): - new_attr = _fix_attrbute_names(attrs, {'axes':'axis'}) - return 'max', attrs, inputs \ No newline at end of file +def import_model(model_file): + """Imports the supplied ONNX model file into MXNet symbol and parameters. + + Parameters + ---------- + model_file : ONNX model file name + + Returns + ------- + sym : mx.symbol + Compatible mxnet symbol + + params : dict of str to mx.ndarray + Dict of converted parameters stored in mx.ndarray format + """ + graph = GraphProto() + + # loads model file and returns ONNX protobuf object + model_proto = onnx.load(model_file) + sym, params = graph.from_onnx(model_proto.graph) + return sym, params diff --git a/python/mxnet/contrib/onnx/_import/import_onnx.py b/python/mxnet/contrib/onnx/_import/import_onnx.py index 8148ab378438..ef6fa9772c9b 100644 --- a/python/mxnet/contrib/onnx/_import/import_onnx.py +++ b/python/mxnet/contrib/onnx/_import/import_onnx.py @@ -21,7 +21,7 @@ from __future__ import absolute_import as _abs from .... import symbol from .... import ndarray as nd -from .import_helper import _convert_map, _pad_sequence_fix +from .import_helper import _convert_map def _convert_operator(op_name, attrs, inputs, convert_map=None): """Convert from onnx operator to mxnet operator. @@ -48,13 +48,13 @@ def _convert_operator(op_name, attrs, inputs, convert_map=None): """ convert_map = convert_map if convert_map else _convert_map if op_name in convert_map: - op_name, attrs, inputs = convert_map[op_name](op_name, attrs, inputs) + op_name, new_attrs, inputs = convert_map[op_name](op_name, attrs, inputs) else: raise NotImplementedError("Operator {} not implemented.".format(op_name)) op = getattr(symbol, op_name, None) if not op: raise RuntimeError("Unable to map op_name {} to sym".format(op_name)) - return op, attrs, inputs + return op, new_attrs, inputs class GraphProto(object): # pylint: disable=too-few-public-methods """A helper class for handling mxnet symbol copying from pb2.GraphProto. @@ -117,13 +117,11 @@ def from_onnx(self, graph): inputs = [self._nodes[self._renames.get(i, i)] for i in node.input] new_op, mx_attr, inputs = _convert_operator(op_name, onnx_attr, inputs) - # calling again to get new symbols after some workarounds - inputs = [self._nodes[self._renames.get(i, i)] for i in node.input] - op = new_op(name=node_name, *inputs, **mx_attr) assert len(node.output) == len(op.list_outputs()), ( - "Number of output mismatch {} vs {} in {}.".format( + "Output dimension mismatch between the onnx operator and the mxnet symbol " + + "{} vs {} for the operator - {}.".format( len(node.output), len(op.list_outputs()), op_name)) for k, i in zip(list(node.output), range(len(node.output))): self._nodes[k] = op[i] @@ -142,7 +140,7 @@ def _parse_array(self, tensor_proto): except ImportError as e: raise ImportError("Unable to import onnx which is required {}".format(e)) np_array = to_array(tensor_proto).reshape(tuple(tensor_proto.dims)) - return mx.nd.array(np_array) + return nd.array(np_array) def _parse_attr(self, attr_proto): """Convert a list of AttributeProto to a dict, with names as keys.""" diff --git a/python/mxnet/contrib/onnx/_import/op_translations/_add.py b/python/mxnet/contrib/onnx/_import/op_translations.py similarity index 67% rename from python/mxnet/contrib/onnx/_import/op_translations/_add.py rename to python/mxnet/contrib/onnx/_import/op_translations.py index 628c49c3e82b..5e85850ceacc 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations/_add.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -16,6 +16,7 @@ # under the License. # coding: utf-8 +from . import translation_utils def _add(op_name, attrs, inputs): new_attr = {} @@ -23,3 +24,19 @@ def _add(op_name, attrs, inputs): return 'broadcast_add', new_attr, inputs else: return 'elemwise_add', new_attr, inputs + +def negative(op_name, attrs, inputs): + return "negative", attrs, inputs + +def reduce_max(op_name, attrs, inputs): + new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':u'axis'}) + return 'max', new_attrs, inputs + +def reduce_mean(op_name, attrs, inputs): + new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':u'axis'}) + return 'mean', new_attrs, inputs + +def reshape(op_name, attrs, inputs): + return "reshape", attrs, inputs + + diff --git a/python/mxnet/contrib/onnx/_import/op_translations/__init__.py b/python/mxnet/contrib/onnx/_import/op_translations/__init__.py deleted file mode 100644 index 8112c2ee42f9..000000000000 --- a/python/mxnet/contrib/onnx/_import/op_translations/__init__.py +++ /dev/null @@ -1,19 +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. - -# coding: utf-8 -from . import * diff --git a/python/mxnet/contrib/onnx/_import/op_translations/negative.py b/python/mxnet/contrib/onnx/_import/op_translations/negative.py deleted file mode 100644 index dd82af0aac00..000000000000 --- a/python/mxnet/contrib/onnx/_import/op_translations/negative.py +++ /dev/null @@ -1,20 +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. - -# coding: utf-8 -def negative(op_name, attrs, inputs): - return "negative", attrs, inputs diff --git a/python/mxnet/contrib/onnx/_import/op_translations/reshape.py b/python/mxnet/contrib/onnx/_import/op_translations/reshape.py deleted file mode 100644 index d1e967145746..000000000000 --- a/python/mxnet/contrib/onnx/_import/op_translations/reshape.py +++ /dev/null @@ -1,20 +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. - -# coding: utf-8 -def reshape(op_name, attrs, inputs): - return "reshape", attrs, inputs diff --git a/python/mxnet/contrib/onnx/_import/translation_utils/__init__.py b/python/mxnet/contrib/onnx/_import/translation_utils.py similarity index 91% rename from python/mxnet/contrib/onnx/_import/translation_utils/__init__.py rename to python/mxnet/contrib/onnx/_import/translation_utils.py index 54f9d04b8d62..e5c4fcd1c9f2 100644 --- a/python/mxnet/contrib/onnx/_import/translation_utils/__init__.py +++ b/python/mxnet/contrib/onnx/_import/translation_utils.py @@ -54,20 +54,6 @@ def _fix_slice(inputs, new_attr): slice_op = symbol.slice_axis(slice_op, axis=axis, begin=begin[i], end=end[i]) return slice_op -def _fix_squeeze(inputs, new_attr): - """ - MXNet doesnt have a squeeze operator. - Using "split" to perform similar operation. - "split" can be slower compared to "reshape". - This can have performance impact. - TODO: Remove this implementation once mxnet adds the support. - """ - axes = new_attr.get('axis') - op = symbol.split(inputs[0], axis=axes[0], num_outputs=1, squeeze_axis=1) - for i in axes[1:]: - op = symbol.split(op, axis=i-1, num_outputs=1, squeeze_axis=1) - return op - def _fix_gemm(op_name, inputs, old_attr): """Using FullyConnected operator in place of linalg_gemm to perform same operation""" op = getAttr(symbol, op_name, None) diff --git a/tests/python/unittest/onnx_backend_test.py b/tests/python/unittest/onnx_backend_test.py index f720b318148f..2eaf9bb08df9 100644 --- a/tests/python/unittest/onnx_backend_test.py +++ b/tests/python/unittest/onnx_backend_test.py @@ -26,6 +26,9 @@ import onnx.backend.test except ImportError as ie: raise ImportError("Onnx and protobuf need to be installed") + +from os import sys, path +sys.path.append('../onnx_test_utils') import backend as mxnet_backend # This is a pytest magic variable to load extra plugins From 2d0c4e4c5b8e75d2cf724bb5d3d3fd056296ca5e Mon Sep 17 00:00:00 2001 From: Acharya Date: Mon, 5 Mar 2018 16:38:45 -0800 Subject: [PATCH 03/35] - Fixed Pylint issues - Added Sigmoid operator. - Add onnx, protobuf as CI pipeline dependencies. Signed-off-by: Acharya --- ci/docker/install/amzn_linux_testdeps.sh | 2 +- python/mxnet/contrib/onnx/__init__.py | 2 + python/mxnet/contrib/onnx/_import/__init__.py | 1 + .../contrib/onnx/_import/import_helper.py | 10 +- .../contrib/onnx/_import/import_model.py | 3 +- .../mxnet/contrib/onnx/_import/import_onnx.py | 2 +- .../contrib/onnx/_import/op_translations.py | 34 ++++-- .../contrib/onnx/_import/translation_utils.py | 115 +++--------------- tests/python/onnx_test_utils/backend.py | 16 +-- tests/python/onnx_test_utils/backend_rep.py | 2 +- tests/python/unittest/onnx_backend_test.py | 11 +- .../python/unittest/test_super_resolution.py | 3 +- 12 files changed, 73 insertions(+), 128 deletions(-) diff --git a/ci/docker/install/amzn_linux_testdeps.sh b/ci/docker/install/amzn_linux_testdeps.sh index f5c49d9e37bf..25afecc2055e 100755 --- a/ci/docker/install/amzn_linux_testdeps.sh +++ b/ci/docker/install/amzn_linux_testdeps.sh @@ -24,4 +24,4 @@ set -ex pip install cpplint 'pylint==1.4.4' 'astroid==1.3.6' pip3 install nose ln -s -f /opt/bin/nosetests /usr/local/bin/nosetests3 -ln -s -f /opt/bin/nosetests-3.4 /usr/local/bin/nosetests-3.4 \ No newline at end of file +ln -s -f /opt/bin/nosetests-3.4 /usr/local/bin/nosetests-3.4 diff --git a/python/mxnet/contrib/onnx/__init__.py b/python/mxnet/contrib/onnx/__init__.py index dfbc9ac754df..eff91206298f 100644 --- a/python/mxnet/contrib/onnx/__init__.py +++ b/python/mxnet/contrib/onnx/__init__.py @@ -15,4 +15,6 @@ # specific language governing permissions and limitations # under the License. +"""Module for importing and exporting ONNX models.""" + from ._import.import_model import import_model diff --git a/python/mxnet/contrib/onnx/_import/__init__.py b/python/mxnet/contrib/onnx/_import/__init__.py index c679932fcf25..002cfa925832 100644 --- a/python/mxnet/contrib/onnx/_import/__init__.py +++ b/python/mxnet/contrib/onnx/_import/__init__.py @@ -16,5 +16,6 @@ # under the License. # coding: utf-8 +"""ONNX Import module""" from . import import_model from . import import_onnx diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index bf47910b1235..470f6d98b913 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -20,12 +20,18 @@ """Operator attributes conversion""" from .op_translations import _add, negative, reduce_max, reshape from .op_translations import reduce_mean +from .op_translations import sigmoid # _convert_map defines maps of name to converter functor(callable) _convert_map = { + #Arithmetic Operators 'Add' : _add, 'Neg' : negative, - 'ReduceMax' : reduce_max, - 'ReduceMean' : reduce_mean, + #Basic neural network functions + 'Sigmoid' : sigmoid, + #Changing shape and type. 'Reshape' : reshape, + #Reduce Functions + 'ReduceMax' : reduce_max, + 'ReduceMean' : reduce_mean } diff --git a/python/mxnet/contrib/onnx/_import/import_model.py b/python/mxnet/contrib/onnx/_import/import_model.py index f1fe4cb408d4..be360cd34fd5 100644 --- a/python/mxnet/contrib/onnx/_import/import_model.py +++ b/python/mxnet/contrib/onnx/_import/import_model.py @@ -17,9 +17,10 @@ # coding: utf-8 """import function""" +# pylint: disable=no-member try: import onnx -except ImportError as ie: +except ImportError: raise ImportError("Onnx and protobuf need to be installed") from .import_onnx import GraphProto diff --git a/python/mxnet/contrib/onnx/_import/import_onnx.py b/python/mxnet/contrib/onnx/_import/import_onnx.py index ef6fa9772c9b..d161bdb6944b 100644 --- a/python/mxnet/contrib/onnx/_import/import_onnx.py +++ b/python/mxnet/contrib/onnx/_import/import_onnx.py @@ -132,7 +132,7 @@ def from_onnx(self, graph): else: out = out[0] return out, self._params - + def _parse_array(self, tensor_proto): """Grab data in TensorProto and convert to numpy array.""" try: diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index 5e85850ceacc..2be388d36eb2 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -16,27 +16,39 @@ # under the License. # coding: utf-8 +""" Module for translating ONNX operators into Mxnet operatoes""" +# pylint: disable=unused-argument,protected-access from . import translation_utils +# Arithmetic Operations def _add(op_name, attrs, inputs): + """Adding two tensors""" new_attr = {} - if 'broadcast' in attrs and attrs['broadcast']==1: + if 'broadcast' in attrs and attrs['broadcast'] == 1: return 'broadcast_add', new_attr, inputs - else: - return 'elemwise_add', new_attr, inputs + return 'elemwise_add', new_attr, inputs def negative(op_name, attrs, inputs): + """Negation of every element in a tensor""" return "negative", attrs, inputs -def reduce_max(op_name, attrs, inputs): - new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':u'axis'}) - return 'max', new_attrs, inputs - -def reduce_mean(op_name, attrs, inputs): - new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':u'axis'}) - return 'mean', new_attrs, inputs - +#Basic neural network functions +def sigmoid(op_name, attrs, inputs): + """Computes elementwise sigmoid of the input array""" + return "sigmoid", attrs, inputs + +#Changing shape and type. def reshape(op_name, attrs, inputs): + """Reshape the given array by the shape attribute.""" return "reshape", attrs, inputs +#Reduce Functions +def reduce_max(op_name, attrs, inputs): + """Reduce the array along a given axis by maximum value""" + new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':'axis'}) + return 'max', new_attrs, inputs +def reduce_mean(op_name, attrs, inputs): + """Reduce the array along a given axis by mean value""" + new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':'axis'}) + return 'mean', new_attrs, inputs \ No newline at end of file diff --git a/python/mxnet/contrib/onnx/_import/translation_utils.py b/python/mxnet/contrib/onnx/_import/translation_utils.py index e5c4fcd1c9f2..3a51308bef93 100644 --- a/python/mxnet/contrib/onnx/_import/translation_utils.py +++ b/python/mxnet/contrib/onnx/_import/translation_utils.py @@ -16,11 +16,25 @@ # under the License. # coding: utf-8 -# pylint: disable=invalid-name,no-self-use,too-many-branches,too-few-public-methods,too-many-arguments +"""Utilities used for translating operators from Onnx to Mxnet.""" +# pylint: disable= from __future__ import absolute_import as _abs -from ....base import string_types def _fix_attribute_names(attrs, change_map): + """ + Change attribute names as per values in change_map dictionary. + Parameters + ---------- + attrs : dict + Dict of operator attributes + change_map : dict + Dict of onnx attribute name to mxnet attribute names. + + Returns + ------- + new_attr : dict + Converted dict of operator attributes. + """ new_attr = {} for k in attrs.keys(): if k in change_map: @@ -28,100 +42,3 @@ def _fix_attribute_names(attrs, change_map): else: new_attr[k] = attrs[k] return new_attr - -def _fix_pooling(op_name, inputs, new_attr): - """onnx pooling operator supports asymmetrical padding - Adding pad operator before pooling in mxnet to work with onnx""" - pool_type = 'avg' if op_name == 'AveragePool' else 'max' - stride = new_attr.get('strides') - kernel = new_attr.get('kernel_shape') - padding = new_attr.get('pads') - pad_width = (0, 0, 0, 0) + _pad_sequence_fix(padding, len(kernel)) - new_pad_op = symbol.pad(inputs[0], mode='constant', pad_width=pad_width) - new_pooling_op = symbol.Pooling(new_pad_op, pool_type=pool_type, - stride=stride, kernel=kernel) - return new_pooling_op - -def _fix_slice(inputs, new_attr): - """onnx slice provides slicing on multiple axis. Adding multiple slice_axis operator - for multiple axes from mxnet""" - begin = new_attr.get('begin') - end = new_attr.get('end') - axes = new_attr.get('axis', tuple(range(len(begin)))) - slice_op = symbol.slice_axis(inputs[0], axis=axes[0], begin=begin[0], end=end[0]) - if len(axes) > 1: - for i, axis in enumerate(axes): - slice_op = symbol.slice_axis(slice_op, axis=axis, begin=begin[i], end=end[i]) - return slice_op - -def _fix_gemm(op_name, inputs, old_attr): - """Using FullyConnected operator in place of linalg_gemm to perform same operation""" - op = getAttr(symbol, op_name, None) - alpha = float(old_attr.get('alpha', 1.0)) - beta = float(old_attr.get('beta', 1.0)) - transA = int(old_attr.get('transA', 0)) - transB = int(old_attr.get('transB', 0)) - if transA: - inputs[0] = symbol.transpose(inputs[0], axes=(1, 0)) - if not transB: - inputs[1] = symbol.transpose(inputs[1], axes=(1, 0)) - new_inputs = [alpha*inputs[0], inputs[1], beta*inputs[2]] - new_attr = {'num_hidden' : self._params[inputs[2].name].shape[0]} - return op, new_inputs, new_attr - -def _fix_outputs(op, outputs): - """A workaround to handle dropout or similar operator that have more than one out - in ONNX. - """ - if op == 'Dropout': - assert len(outputs) == 2, "ONNX have two outputs for dropout layer." - outputs = outputs[:-1] - return outputs - -def _fix_bias(op, attrs, num_inputs): - """A workaround for 'use_bias' attribute since onnx don't provide this attribute, - we have to check the number of inputs to decide it.""" - if op not in [symbol.Convolution, symbol.Deconvolution, symbol.FullyConnected]: - return attrs - if num_inputs == 3: - attrs['no_bias'] = False - elif num_inputs == 2: - attrs['no_bias'] = True - else: - raise ValueError("Unexpected number of inputs for: {}".format(op)) - return attrs - - -def _fix_bias_shape(op_name, inputs, attrs): - """A workaround to reshape bias term to (1, num_channel).""" - if (op_name == 'Add' or op_name == 'Mul') and (int(len(self._params)) > 0) and \ - ('broadcast' in attrs and attrs['broadcast'] == 1): - assert len(list(inputs)) == 2 - bias_name = self._renames.get(inputs[1], inputs[1]) - bias = self._params[bias_name] - assert len(bias.shape) == 1 - # reshape to (1, n) - bias = nd.array(bias.asnumpy().reshape((1, -1, 1, 1))) - # broadcast_add expects shape with sym.variable - self._nodes[bias_name] = symbol.Variable(name=bias_name, shape=bias.shape) - self._params[bias_name] = bias - - -def _fix_channels(op, attrs, inputs): - """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. - """ - if op not in [symbol.Convolution, symbol.Deconvolution, symbol.FullyConnected]: - return attrs - weight_name = self._renames[inputs[1]] - if not weight_name in self._params: - raise ValueError("Unable to get channels/units attr from onnx graph.") - else: - wshape = self._params[weight_name].shape - assert len(wshape) >= 2, "Weights shape is invalid: {}".format(wshape) - channels = wshape[0] - if op in [symbol.FullyConnected]: - attrs['num_hidden'] = channels - else: - attrs['num_filter'] = channels - return attrs diff --git a/tests/python/onnx_test_utils/backend.py b/tests/python/onnx_test_utils/backend.py index 0a18236ad34c..360aa7810bb5 100644 --- a/tests/python/onnx_test_utils/backend.py +++ b/tests/python/onnx_test_utils/backend.py @@ -16,17 +16,17 @@ # under the License. # coding: utf-8 -# pylint: disable=too-many-locals,invalid-name +# pylint: disable=too-many-locals,invalid-name,no-member """backend wrapper for onnx test infrastructure""" from collections import namedtuple import mxnet as mx +from mxnet.contrib.onnx._import.import_onnx import GraphProto try: from onnx import helper, TensorProto from onnx.backend.base import Backend -except ImportError as ie: +except ImportError: raise ImportError("Onnx and protobuf need to be installed") -from mxnet.contrib.onnx._import.import_onnx import GraphProto -from .backend_rep import MXNetBackendRep +from backend_rep import MXNetBackendRep # Using these functions for onnx test infrastructure. # Implemented by following onnx docs guide: @@ -36,7 +36,7 @@ class MXNetBackend(Backend): """MXNet backend for ONNX""" - + @staticmethod def make_graph(node, inputs): """ Created ONNX GraphProto from node""" @@ -77,7 +77,7 @@ def make_graph(node, inputs): initializer=initializer) return graph_proto - + @classmethod def run_node(cls, node, inputs, device='CPU'): """Running individual node inference on mxnet engine and @@ -102,8 +102,8 @@ def run_node(cls, node, inputs, device='CPU'): data_names = [i for i in sym.get_internals().list_inputs() if i[:-1] == "input_"] data_shapes = [] dim_change_op_types = set(['ReduceMin', 'ReduceMax', 'ReduceMean', - 'ReduceProd', 'ReduceSum', 'Slice', 'Pad', - 'Squeeze', 'Upsample', 'Reshape', 'Conv']) + 'ReduceProd', 'ReduceSum', 'Slice', 'Pad', + 'Squeeze', 'Upsample', 'Reshape', 'Conv']) # 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/onnx_test_utils/backend_rep.py b/tests/python/onnx_test_utils/backend_rep.py index 0ffc329538f8..c6ceabc825fc 100644 --- a/tests/python/onnx_test_utils/backend_rep.py +++ b/tests/python/onnx_test_utils/backend_rep.py @@ -22,7 +22,7 @@ import numpy as np try: from onnx.backend.base import BackendRep -except ImportError as ie: +except ImportError: raise ImportError("Onnx and protobuf need to be installed") import mxnet as mx diff --git a/tests/python/unittest/onnx_backend_test.py b/tests/python/unittest/onnx_backend_test.py index 2eaf9bb08df9..9dac85556958 100644 --- a/tests/python/unittest/onnx_backend_test.py +++ b/tests/python/unittest/onnx_backend_test.py @@ -15,7 +15,8 @@ # specific language governing permissions and limitations # under the License. -"""onnx test backend wrapper""" +"""ONNX test backend wrapper""" +# pylint: disable=invalid-name,import-error,wrong-import-position from __future__ import absolute_import from __future__ import division from __future__ import print_function @@ -24,10 +25,10 @@ import unittest try: import onnx.backend.test -except ImportError as ie: +except ImportError: raise ImportError("Onnx and protobuf need to be installed") -from os import sys, path +from os import sys sys.path.append('../onnx_test_utils') import backend as mxnet_backend @@ -37,8 +38,12 @@ backend_test = onnx.backend.test.BackendTest(mxnet_backend, __name__) implemented_operators = [ + #Arithmetic Operators 'test_add*', 'test_neg*', + #Basic neural network functions + 'test_sigmoid*', + #Changing shape and type. 'test_reshape_*', ] diff --git a/tests/python/unittest/test_super_resolution.py b/tests/python/unittest/test_super_resolution.py index 0fdfa63a63d6..80c55ec32e70 100644 --- a/tests/python/unittest/test_super_resolution.py +++ b/tests/python/unittest/test_super_resolution.py @@ -16,12 +16,13 @@ # under the License. """Testing super_resolution model conversion""" +# pylint: disable=invalid-name from __future__ import absolute_import as _abs from __future__ import print_function from collections import namedtuple import mxnet as mx from mxnet.test_utils import download -import mxnet.contrib.onnx._import as onnx_mxnet +import mxnet.contrib.onnx as onnx_mxnet import numpy as np from PIL import Image From a350505f961204f810b21b5af1bc39959284801d Mon Sep 17 00:00:00 2001 From: Acharya Date: Mon, 5 Mar 2018 16:57:11 -0800 Subject: [PATCH 04/35] Add UTs for reduce ops. Signed-off-by: Acharya --- tests/python/unittest/test_layers.py | 54 ++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 tests/python/unittest/test_layers.py diff --git a/tests/python/unittest/test_layers.py b/tests/python/unittest/test_layers.py new file mode 100644 index 000000000000..d2272d06c76e --- /dev/null +++ b/tests/python/unittest/test_layers.py @@ -0,0 +1,54 @@ +# 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""" +# pylint: disable=import-error,no-self-use + +from __future__ import absolute_import +import unittest +import numpy as np +import numpy.testing as npt +from onnx import helper +import backend as mxnet_backend + +class TestLayers(unittest.TestCase): + """Tests for different layers comparing output with numpy operators. + Temporary file until we have a corresponding test in onnx-backend_test + for these operators.""" + + def _random_array(self, shape): + """Generate random array according to input shape""" + return np.random.ranf(shape).astype("float32") + + def test_reduce_max(self): + """Test for ReduceMax operator""" + node_def = helper.make_node("ReduceMax", ["input1"], ["output"], axes=[1, 0], keepdims=1) + input1 = self._random_array([3, 10]) + output = mxnet_backend.run_node(node_def, [input1])[0] + numpy_op = np.max(input1, axis=(1, 0), keepdims=True) + npt.assert_almost_equal(output, numpy_op) + + def test_reduce_mean(self): + """Test for ReduceMean operator""" + node_def = helper.make_node("ReduceMean", ["input1"], ["output"], axes=[1, 0], keepdims=1) + input1 = self._random_array([3, 10]) + output = mxnet_backend.run_node(node_def, [input1])[0] + numpy_op = np.mean(input1, axis=(1, 0), keepdims=True) + npt.assert_almost_equal(output, numpy_op, decimal=5) + +if __name__ == '__main__': + unittest.main() From 678cd7ad14e53b7b9004ff2dc73d9f63aeac88c1 Mon Sep 17 00:00:00 2001 From: Acharya Date: Mon, 5 Mar 2018 17:04:00 -0800 Subject: [PATCH 05/35] pylint - newline, whitespace. Signed-off-by: Acharya --- python/mxnet/contrib/onnx/_import/op_translations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index 2be388d36eb2..3c14ca483ad2 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -36,7 +36,7 @@ def negative(op_name, attrs, inputs): def sigmoid(op_name, attrs, inputs): """Computes elementwise sigmoid of the input array""" return "sigmoid", attrs, inputs - + #Changing shape and type. def reshape(op_name, attrs, inputs): """Reshape the given array by the shape attribute.""" @@ -51,4 +51,4 @@ def reduce_max(op_name, attrs, inputs): def reduce_mean(op_name, attrs, inputs): """Reduce the array along a given axis by mean value""" new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':'axis'}) - return 'mean', new_attrs, inputs \ No newline at end of file + return 'mean', new_attrs, inputs From 0c66f326b2498d9ac92521a1556e466c73300681 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Tue, 6 Mar 2018 08:26:37 -0800 Subject: [PATCH 06/35] Added operators: - AvgPool - ArgMax - ArgMin - Abs Minor changes in logic for import_onnx --- .../contrib/onnx/_import/import_helper.py | 22 +++++++---- .../contrib/onnx/_import/import_model.py | 1 + .../mxnet/contrib/onnx/_import/import_onnx.py | 20 ++++++---- .../contrib/onnx/_import/op_translations.py | 37 ++++++++++++++++-- .../contrib/onnx/_import/translation_utils.py | 39 +++++++++++++++++-- tests/python/onnx_test_utils/backend.py | 1 + tests/python/unittest/onnx_backend_test.py | 4 ++ 7 files changed, 101 insertions(+), 23 deletions(-) diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index 470f6d98b913..2f964bac5345 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -18,20 +18,26 @@ # coding: utf-8 # pylint: disable=invalid-name """Operator attributes conversion""" -from .op_translations import _add, negative, reduce_max, reshape -from .op_translations import reduce_mean +from .op_translations import add, absolute, negative, reduce_max, reshape +from .op_translations import reduce_mean, avg_pooling from .op_translations import sigmoid +from .op_translations import argmax, argmin # _convert_map defines maps of name to converter functor(callable) _convert_map = { - #Arithmetic Operators - 'Add' : _add, + # Arithmetic Operators + 'Add' : add, + 'Abs' : absolute, 'Neg' : negative, - #Basic neural network functions + # Basic neural network functions 'Sigmoid' : sigmoid, - #Changing shape and type. + # Changing shape and type. 'Reshape' : reshape, - #Reduce Functions + # Reduce Functions 'ReduceMax' : reduce_max, - 'ReduceMean' : reduce_mean + 'ReduceMean' : reduce_mean, + 'AveragePool' : avg_pooling, + # Sorting and Searching + 'ArgMax' : argmax, + 'ArgMin' : argmin } diff --git a/python/mxnet/contrib/onnx/_import/import_model.py b/python/mxnet/contrib/onnx/_import/import_model.py index be360cd34fd5..d35a27929cd0 100644 --- a/python/mxnet/contrib/onnx/_import/import_model.py +++ b/python/mxnet/contrib/onnx/_import/import_model.py @@ -24,6 +24,7 @@ raise ImportError("Onnx and protobuf need to be installed") from .import_onnx import GraphProto + def import_model(model_file): """Imports the supplied ONNX model file into MXNet symbol and parameters. diff --git a/python/mxnet/contrib/onnx/_import/import_onnx.py b/python/mxnet/contrib/onnx/_import/import_onnx.py index d161bdb6944b..d35172b06494 100644 --- a/python/mxnet/contrib/onnx/_import/import_onnx.py +++ b/python/mxnet/contrib/onnx/_import/import_onnx.py @@ -23,7 +23,7 @@ from .... import ndarray as nd from .import_helper import _convert_map -def _convert_operator(op_name, attrs, inputs, convert_map=None): +def _convert_operator(node_name, op_name, attrs, inputs, convert_map=None): """Convert from onnx operator to mxnet operator. The converter must specify conversions explicitly for incompatible name, and apply handlers to operator attributes. @@ -51,10 +51,16 @@ def _convert_operator(op_name, attrs, inputs, convert_map=None): op_name, new_attrs, inputs = convert_map[op_name](op_name, attrs, inputs) else: raise NotImplementedError("Operator {} not implemented.".format(op_name)) - op = getattr(symbol, op_name, None) - if not op: - raise RuntimeError("Unable to map op_name {} to sym".format(op_name)) - return op, new_attrs, inputs + if isinstance(op_name, str): + new_op = getattr(symbol, op_name, None) + op = new_op(name=node_name, *inputs, **new_attrs) + if not op: + raise RuntimeError("Unable to map op_name {} to sym".format(op_name)) + else: + op = op_name + + return op + class GraphProto(object): # pylint: disable=too-few-public-methods """A helper class for handling mxnet symbol copying from pb2.GraphProto. @@ -115,9 +121,7 @@ def from_onnx(self, graph): node_name = node_name if node_name else None onnx_attr = self._parse_attr(node.attribute) inputs = [self._nodes[self._renames.get(i, i)] for i in node.input] - new_op, mx_attr, inputs = _convert_operator(op_name, onnx_attr, inputs) - - op = new_op(name=node_name, *inputs, **mx_attr) + op = _convert_operator(node_name, op_name, onnx_attr, inputs) assert len(node.output) == len(op.list_outputs()), ( "Output dimension mismatch between the onnx operator and the mxnet symbol " + diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index 3c14ca483ad2..19c0e7a3dec2 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -20,34 +20,63 @@ # pylint: disable=unused-argument,protected-access from . import translation_utils + # Arithmetic Operations -def _add(op_name, attrs, inputs): +def add(op_name, attrs, inputs): """Adding two tensors""" new_attr = {} if 'broadcast' in attrs and attrs['broadcast'] == 1: return 'broadcast_add', new_attr, inputs return 'elemwise_add', new_attr, inputs + +def absolute(op_name, attrs, inputs): + return 'abs', attrs, inputs + + +def argmax(op_name, attrs, inputs): + return 'argmax', attrs, inputs + + +def argmin(op_name, attrs, inputs): + return 'argmin', attrs, inputs + + +def avg_pooling(op_name, attrs, inputs): + new_attrs = translation_utils._fix_attribute_names(attrs, + {'kernel_shape': 'kernel', + 'strides': 'stride', + 'pads': 'pad', + 'pool_type' : 'avg', + 'pooling_convention': 'valid'}) + op = translation_utils._fix_pooling(op_name, inputs, new_attrs) + return op, new_attrs, inputs + + def negative(op_name, attrs, inputs): """Negation of every element in a tensor""" return "negative", attrs, inputs -#Basic neural network functions + +# Basic neural network functions def sigmoid(op_name, attrs, inputs): """Computes elementwise sigmoid of the input array""" return "sigmoid", attrs, inputs -#Changing shape and type. + +# Changing shape and type. def reshape(op_name, attrs, inputs): """Reshape the given array by the shape attribute.""" return "reshape", attrs, inputs -#Reduce Functions + +# Reduce Functions def reduce_max(op_name, attrs, inputs): """Reduce the array along a given axis by maximum value""" new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':'axis'}) return 'max', new_attrs, inputs + def reduce_mean(op_name, attrs, inputs): """Reduce the array along a given axis by mean value""" new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':'axis'}) diff --git a/python/mxnet/contrib/onnx/_import/translation_utils.py b/python/mxnet/contrib/onnx/_import/translation_utils.py index 3a51308bef93..87d53ba398dd 100644 --- a/python/mxnet/contrib/onnx/_import/translation_utils.py +++ b/python/mxnet/contrib/onnx/_import/translation_utils.py @@ -19,6 +19,8 @@ """Utilities used for translating operators from Onnx to Mxnet.""" # pylint: disable= from __future__ import absolute_import as _abs +from .... import symbol + def _fix_attribute_names(attrs, change_map): """ @@ -36,9 +38,40 @@ def _fix_attribute_names(attrs, change_map): Converted dict of operator attributes. """ new_attr = {} - for k in attrs.keys(): - if k in change_map: + for k in change_map: + if k in attrs.keys(): new_attr[change_map[k]] = attrs[k] else: - new_attr[k] = attrs[k] + new_attr[k] = change_map[k] + + return new_attr + + +def _pad_sequence_fix(attr, kernelDim=None): + """Changing onnx's pads sequence to match with mxnet's pad_width + mxnet: (x1_begin, x1_end, ... , xn_begin, xn_end) + onnx: (x1_begin, x2_begin, ... , xn_end, xn_end)""" + new_attr = () + if len(attr) % 2 == 0: + for index in range(int(len(attr) / 2)): + new_attr = new_attr + attr[index::int(len(attr) / 2)] + # Making sure pad values are in the attr for all axes. + if kernelDim is not None: + while len(new_attr) < kernelDim*2: + new_attr = new_attr + (0, 0) + return new_attr + + +def _fix_pooling(op_name, inputs, new_attr): + """onnx pooling operator supports asymmetrical padding + Adding pad operator before pooling in mxnet to work with onnx""" + pool_type = 'avg' if op_name == 'AveragePool' else 'max' + stride = new_attr.get('stride') + kernel = new_attr.get('kernel') + padding = new_attr.get('pad') + pad_width = (0, 0, 0, 0) + _pad_sequence_fix(padding, len(kernel)) + new_pad_op = symbol.pad(inputs[0], mode='constant', pad_width=pad_width) + new_pooling_op = symbol.Pooling(new_pad_op, pool_type=pool_type, + stride=stride, kernel=kernel) + return new_pooling_op diff --git a/tests/python/onnx_test_utils/backend.py b/tests/python/onnx_test_utils/backend.py index 360aa7810bb5..e18036e54e1e 100644 --- a/tests/python/onnx_test_utils/backend.py +++ b/tests/python/onnx_test_utils/backend.py @@ -34,6 +34,7 @@ # MXNetBackend class will take an ONNX model with inputs, perform a computation, # and then return the output. + class MXNetBackend(Backend): """MXNet backend for ONNX""" diff --git a/tests/python/unittest/onnx_backend_test.py b/tests/python/unittest/onnx_backend_test.py index 9dac85556958..35a524299846 100644 --- a/tests/python/unittest/onnx_backend_test.py +++ b/tests/python/unittest/onnx_backend_test.py @@ -41,10 +41,14 @@ #Arithmetic Operators 'test_add*', 'test_neg*', + 'test_abs*', + 'test_argmax*', + 'test_argmin*', #Basic neural network functions 'test_sigmoid*', #Changing shape and type. 'test_reshape_*', + 'test_AvgPool2D*' ] for op_test in implemented_operators: From 5f91c6c44fae431bbd081b6358aad99bb09ae978 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Tue, 6 Mar 2018 12:01:38 -0800 Subject: [PATCH 07/35] Added operators: - Ceil - Cast - Constant --- .../contrib/onnx/_import/import_helper.py | 13 +++++- .../contrib/onnx/_import/op_translations.py | 45 +++++++++++++------ .../contrib/onnx/_import/translation_utils.py | 16 ++++++- tests/python/onnx_test_utils/backend.py | 16 +++---- tests/python/unittest/onnx_backend_test.py | 6 ++- 5 files changed, 66 insertions(+), 30 deletions(-) diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index 2f964bac5345..94c1415dee18 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -18,21 +18,30 @@ # coding: utf-8 # pylint: disable=invalid-name """Operator attributes conversion""" -from .op_translations import add, absolute, negative, reduce_max, reshape -from .op_translations import reduce_mean, avg_pooling +from .op_translations import add, absolute, negative +from .op_translations import ceil +from .op_translations import concat from .op_translations import sigmoid +from .op_translations import reshape, cast +from .op_translations import reduce_max, reduce_mean, avg_pooling from .op_translations import argmax, argmin + # _convert_map defines maps of name to converter functor(callable) _convert_map = { # Arithmetic Operators 'Add' : add, 'Abs' : absolute, 'Neg' : negative, + # Rounding + 'Ceil' : ceil, + # Joining and spliting + 'Concat' : concat, # Basic neural network functions 'Sigmoid' : sigmoid, # Changing shape and type. 'Reshape' : reshape, + 'Cast' : cast, # Reduce Functions 'ReduceMax' : reduce_max, 'ReduceMean' : reduce_mean, diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index 19c0e7a3dec2..ec1d2376269a 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -42,33 +42,35 @@ def argmin(op_name, attrs, inputs): return 'argmin', attrs, inputs -def avg_pooling(op_name, attrs, inputs): - new_attrs = translation_utils._fix_attribute_names(attrs, - {'kernel_shape': 'kernel', - 'strides': 'stride', - 'pads': 'pad', - 'pool_type' : 'avg', - 'pooling_convention': 'valid'}) - op = translation_utils._fix_pooling(op_name, inputs, new_attrs) - return op, new_attrs, inputs - - def negative(op_name, attrs, inputs): """Negation of every element in a tensor""" - return "negative", attrs, inputs + return 'negative', attrs, inputs + +# Rounding +def ceil(op_name, attrs, inputs): + return 'ceil', attrs, inputs +# Joining and spliting +def concat(op_name, attrs, inputs): + new_attrs = translation_utils._fix_attribute_names(attrs, {'axis': 'dim'}) + return 'concat', new_attrs, inputs + # Basic neural network functions def sigmoid(op_name, attrs, inputs): """Computes elementwise sigmoid of the input array""" - return "sigmoid", attrs, inputs + return 'sigmoid', attrs, inputs # Changing shape and type. def reshape(op_name, attrs, inputs): """Reshape the given array by the shape attribute.""" - return "reshape", attrs, inputs + return 'reshape', attrs, inputs +def cast(op_name, attrs, inputs): + """ Cast input to a given dtype""" + new_attrs = translation_utils._fix_attribute_names(attrs, {'to': 'dtype'}) + return 'cast', new_attrs, inputs # Reduce Functions def reduce_max(op_name, attrs, inputs): @@ -81,3 +83,18 @@ def reduce_mean(op_name, attrs, inputs): """Reduce the array along a given axis by mean value""" new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':'axis'}) return 'mean', new_attrs, inputs + + +def avg_pooling(op_name, attrs, inputs): + """ Average pooling""" + new_attrs = translation_utils._fix_attribute_names(attrs, + {'kernel_shape': 'kernel', + 'strides': 'stride', + 'pads': 'pad', + }) + new_attrs = translation_utils._add_extra_attributes(new_attrs, + {'pool_type': 'avg', + 'pooling_convention': 'valid' + }) + op = translation_utils._fix_pooling(op_name, inputs, new_attrs) + return op, new_attrs, inputs diff --git a/python/mxnet/contrib/onnx/_import/translation_utils.py b/python/mxnet/contrib/onnx/_import/translation_utils.py index 87d53ba398dd..cd65405cfdce 100644 --- a/python/mxnet/contrib/onnx/_import/translation_utils.py +++ b/python/mxnet/contrib/onnx/_import/translation_utils.py @@ -38,14 +38,26 @@ def _fix_attribute_names(attrs, change_map): Converted dict of operator attributes. """ new_attr = {} - for k in change_map: - if k in attrs.keys(): + for k in attrs.keys(): + if k in change_map: new_attr[change_map[k]] = attrs[k] else: new_attr[k] = change_map[k] return new_attr +def _add_extra_attributes(attrs, extraAttrMap): + """ + :param attrs: Current Attribute list + :param extraAttrMap: Additional attributes to be added + :return: new_attr + """ + + for attr in extraAttrMap: + attrs[attr] = extraAttrMap[attr] + + return attrs + def _pad_sequence_fix(attr, kernelDim=None): """Changing onnx's pads sequence to match with mxnet's pad_width diff --git a/tests/python/onnx_test_utils/backend.py b/tests/python/onnx_test_utils/backend.py index e18036e54e1e..f8f9157a00da 100644 --- a/tests/python/onnx_test_utils/backend.py +++ b/tests/python/onnx_test_utils/backend.py @@ -18,7 +18,6 @@ # coding: utf-8 # pylint: disable=too-many-locals,invalid-name,no-member """backend wrapper for onnx test infrastructure""" -from collections import namedtuple import mxnet as mx from mxnet.contrib.onnx._import.import_onnx import GraphProto try: @@ -99,8 +98,8 @@ def run_node(cls, node, inputs, device='CPU'): result obtained after running the operator """ graph = GraphProto() - sym, params = graph.from_onnx(MXNetBackend.make_graph(node, inputs)) - data_names = [i for i in sym.get_internals().list_inputs() if i[:-1] == "input_"] + sym, _ = graph.from_onnx(MXNetBackend.make_graph(node, inputs)) + data_names = [i for i in sym.get_internals().list_inputs()] data_shapes = [] dim_change_op_types = set(['ReduceMin', 'ReduceMax', 'ReduceMean', 'ReduceProd', 'ReduceSum', 'Slice', 'Pad', @@ -109,7 +108,7 @@ def run_node(cls, node, inputs, device='CPU'): # 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[idx].shape) < 4 and len(inputs) > 1 and \ + 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, ()) @@ -128,12 +127,7 @@ def run_node(cls, node, inputs, device='CPU'): mod.bind(for_training=False, data_shapes=data_shapes, label_shapes=None) # initializing parameters for calculating result of each individual node - if int(len(params)) > 0: - mod.set_params(arg_params=params, aux_params=params) - else: - mod.init_params() - - batch = namedtuple('Batch', ['data']) + mod.init_params() data_forward = [] for idx, input_name in enumerate(data_names): @@ -146,7 +140,7 @@ def run_node(cls, node, inputs, device='CPU'): else: data_forward.append(mx.nd.array([val])) - mod.forward(batch(data_forward)) + mod.forward(mx.io.DataBatch(data_forward)) result = mod.get_outputs()[0].asnumpy() if node.op_type in dim_change_op_types: return [result] diff --git a/tests/python/unittest/onnx_backend_test.py b/tests/python/unittest/onnx_backend_test.py index 35a524299846..eb4845745aab 100644 --- a/tests/python/unittest/onnx_backend_test.py +++ b/tests/python/unittest/onnx_backend_test.py @@ -44,11 +44,15 @@ 'test_abs*', 'test_argmax*', 'test_argmin*', + #Rounding + 'test_ceil*', + ## Joining and spliting + # 'test_concat*', ---Failing test #Basic neural network functions 'test_sigmoid*', #Changing shape and type. 'test_reshape_*', - 'test_AvgPool2D*' + 'test_AvgPool2D*', ] for op_test in implemented_operators: From 9a615b2ed21c539783a7992c5a19a773230426a6 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Tue, 6 Mar 2018 13:54:43 -0800 Subject: [PATCH 08/35] - Added Pad operator support. - Minor changes for comments --- .../contrib/onnx/_import/import_helper.py | 3 ++- .../contrib/onnx/_import/op_translations.py | 22 +++++++++++++++---- .../contrib/onnx/_import/translation_utils.py | 2 +- tests/python/unittest/onnx_backend_test.py | 3 +++ 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index 94c1415dee18..a071e4f4dcc1 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -21,7 +21,7 @@ from .op_translations import add, absolute, negative from .op_translations import ceil from .op_translations import concat -from .op_translations import sigmoid +from .op_translations import sigmoid, pad from .op_translations import reshape, cast from .op_translations import reduce_max, reduce_mean, avg_pooling from .op_translations import argmax, argmin @@ -39,6 +39,7 @@ 'Concat' : concat, # Basic neural network functions 'Sigmoid' : sigmoid, + 'Pad' : pad, # Changing shape and type. 'Reshape' : reshape, 'Cast' : cast, diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index ec1d2376269a..2ff9bcabc535 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -34,6 +34,12 @@ def absolute(op_name, attrs, inputs): return 'abs', attrs, inputs +def negative(op_name, attrs, inputs): + """Negation of every element in a tensor""" + return 'negative', attrs, inputs + + +# Sorting and Searching def argmax(op_name, attrs, inputs): return 'argmax', attrs, inputs @@ -42,26 +48,34 @@ def argmin(op_name, attrs, inputs): return 'argmin', attrs, inputs -def negative(op_name, attrs, inputs): - """Negation of every element in a tensor""" - return 'negative', attrs, inputs - # Rounding def ceil(op_name, attrs, inputs): + """ Calculate ceil value for input """ return 'ceil', attrs, inputs # Joining and spliting def concat(op_name, attrs, inputs): + """ Joins input arrays along a given axis. """ new_attrs = translation_utils._fix_attribute_names(attrs, {'axis': 'dim'}) return 'concat', new_attrs, inputs + # Basic neural network functions def sigmoid(op_name, attrs, inputs): """Computes elementwise sigmoid of the input array""" return 'sigmoid', attrs, inputs +def pad(op_name, attrs, inputs): + """ Add padding to input tensor""" + new_attrs = translation_utils._fix_attribute_names(attrs, {'pads' : 'pad_width', + 'value' : 'constant_value' + }) + new_attrs['pad_width'] = translation_utils._pad_sequence_fix(new_attrs.get('pad_width')) + return 'pad', new_attrs, inputs + + # Changing shape and type. def reshape(op_name, attrs, inputs): """Reshape the given array by the shape attribute.""" diff --git a/python/mxnet/contrib/onnx/_import/translation_utils.py b/python/mxnet/contrib/onnx/_import/translation_utils.py index cd65405cfdce..7be7e590dec0 100644 --- a/python/mxnet/contrib/onnx/_import/translation_utils.py +++ b/python/mxnet/contrib/onnx/_import/translation_utils.py @@ -42,7 +42,7 @@ def _fix_attribute_names(attrs, change_map): if k in change_map: new_attr[change_map[k]] = attrs[k] else: - new_attr[k] = change_map[k] + new_attr[k] = attrs[k] return new_attr diff --git a/tests/python/unittest/onnx_backend_test.py b/tests/python/unittest/onnx_backend_test.py index eb4845745aab..66ee2d06c106 100644 --- a/tests/python/unittest/onnx_backend_test.py +++ b/tests/python/unittest/onnx_backend_test.py @@ -50,6 +50,9 @@ # 'test_concat*', ---Failing test #Basic neural network functions 'test_sigmoid*', + 'test_constant_pad', + 'test_edge_pad', + 'test_reflect_pad', #Changing shape and type. 'test_reshape_*', 'test_AvgPool2D*', From 0be8eaeb09c62dabede82e9e02a3956d1a90098d Mon Sep 17 00:00:00 2001 From: Acharya Date: Tue, 6 Mar 2018 18:24:19 -0800 Subject: [PATCH 09/35] RandomUniform,Normal,Sub,Mul,Div,Tanh,Relu,Reciprocal,Sqrt operators added. Signed-off-by: Acharya --- .../contrib/onnx/_import/import_helper.py | 23 +++++- .../contrib/onnx/_import/op_translations.py | 70 +++++++++++++++++-- .../contrib/onnx/_import/translation_utils.py | 24 +++++-- tests/python/unittest/onnx_backend_test.py | 17 ++++- 4 files changed, 118 insertions(+), 16 deletions(-) diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index a071e4f4dcc1..ecd938c90f72 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -18,21 +18,34 @@ # coding: utf-8 # pylint: disable=invalid-name """Operator attributes conversion""" -from .op_translations import add, absolute, negative +from .op_translations import identity, random_uniform, random_normal +from .op_translations import add, subtract, multiply, divide, absolute, negative +from .op_translations import tanh from .op_translations import ceil from .op_translations import concat -from .op_translations import sigmoid, pad +from .op_translations import sigmoid, pad, relu from .op_translations import reshape, cast +from .op_translations import reciprocal, squareroot from .op_translations import reduce_max, reduce_mean, avg_pooling from .op_translations import argmax, argmin - # _convert_map defines maps of name to converter functor(callable) _convert_map = { + # Generator Functions + 'Constant' : identity, + 'RandomUniform' : random_uniform, + 'RandomNormal' : random_normal, + 'RandomUniformLike' : random_uniform, + 'RandomNormalLike' : random_normal, # Arithmetic Operators 'Add' : add, + 'Sub' : subtract, + 'Mul' : multiply, + 'Div' : divide, 'Abs' : absolute, 'Neg' : negative, + #Hyperbolic functions + 'Tanh' : tanh, # Rounding 'Ceil' : ceil, # Joining and spliting @@ -40,9 +53,13 @@ # Basic neural network functions 'Sigmoid' : sigmoid, 'Pad' : pad, + 'Relu' : relu, # Changing shape and type. 'Reshape' : reshape, 'Cast' : cast, + #Powers + 'Reciprocal' : reciprocal, + 'Sqrt' : squareroot, # Reduce Functions 'ReduceMax' : reduce_max, 'ReduceMean' : reduce_mean, diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index 2ff9bcabc535..7d61b8129273 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -20,6 +20,21 @@ # pylint: disable=unused-argument,protected-access from . import translation_utils +#Generator Functions +def identity(op_name, attrs, inputs): + """Returns the identity function of the the input.""" + return 'identity', attrs, inputs + +def random_uniform(op_name, attrs, inputs): + """Draw random samples from a uniform distribtuion.""" + new_attr = translation_utils._remove_attributes(attrs, ['seed']) + return 'random_uniform', new_attr, inputs + +def random_normal(op_name, attrs, inputs): + """Draw random samples from a Gaussian distribution.""" + new_attr = translation_utils._remove_attributes(attrs, ['seed']) + new_attr = translation_utils._fix_attribute_names(new_attr, {'mean' : 'loc'}) + return 'random_uniform', new_attr, inputs # Arithmetic Operations def add(op_name, attrs, inputs): @@ -29,6 +44,12 @@ def add(op_name, attrs, inputs): return 'broadcast_add', new_attr, inputs return 'elemwise_add', new_attr, inputs +def subtract(op_name, attrs, inputs): + """Subtracting two tensors""" + new_attr = {} + if 'broadcast' in attrs and attrs['broadcast'] == 1: + return 'broadcast_sub', new_attr, inputs + return 'elemwise_sub', new_attr, inputs def absolute(op_name, attrs, inputs): return 'abs', attrs, inputs @@ -43,17 +64,37 @@ def negative(op_name, attrs, inputs): def argmax(op_name, attrs, inputs): return 'argmax', attrs, inputs +def multiply(op_name, attrs, inputs): + """Multiply two tensors""" + new_attr = {} + if 'broadcast' in attrs and attrs['broadcast'] == 1: + return 'broadcast_mul', new_attr, inputs + return 'elemwise_mul', new_attr, inputs -def argmin(op_name, attrs, inputs): - return 'argmin', attrs, inputs +def divide(op_name, attrs, inputs): + """Divide two tensors""" + new_attr = {} + if 'broadcast' in attrs and attrs['broadcast'] == 1: + return 'broadcast_div', new_attr, inputs + return 'elemwise_div', new_attr, inputs +def absolute(op_name, attrs, inputs): + return 'abs', attrs, inputs + +def negative(op_name, attrs, inputs): + """Negation of every element in a tensor""" + return 'negative', attrs, inputs + +#Hyperbolic functions +def tanh(op_name, attrs, inputs): + """Returns the hyperbolic tangent of the input array.""" + return 'tanh', attrs, inputs # Rounding def ceil(op_name, attrs, inputs): """ Calculate ceil value for input """ return 'ceil', attrs, inputs - # Joining and spliting def concat(op_name, attrs, inputs): """ Joins input arrays along a given axis. """ @@ -66,6 +107,9 @@ def sigmoid(op_name, attrs, inputs): """Computes elementwise sigmoid of the input array""" return 'sigmoid', attrs, inputs +def relu(op_name, attrs, inputs): + """Computes rectified linear function.""" + return 'relu', attrs, inputs def pad(op_name, attrs, inputs): """ Add padding to input tensor""" @@ -86,6 +130,15 @@ def cast(op_name, attrs, inputs): new_attrs = translation_utils._fix_attribute_names(attrs, {'to': 'dtype'}) return 'cast', new_attrs, inputs +#Powers +def reciprocal(op_name, attrs, inputs): + """Returns the reciprocal of the argument, element-wise.""" + return 'reciprocal', attrs, inputs + +def squareroot(op_name, attrs, inputs): + """Returns element-wise square-root value of the input.""" + return 'sqrt', attrs, inputs + # Reduce Functions def reduce_max(op_name, attrs, inputs): """Reduce the array along a given axis by maximum value""" @@ -110,5 +163,12 @@ def avg_pooling(op_name, attrs, inputs): {'pool_type': 'avg', 'pooling_convention': 'valid' }) - op = translation_utils._fix_pooling(op_name, inputs, new_attrs) - return op, new_attrs, inputs + new_op = translation_utils._fix_pooling(op_name, inputs, new_attrs) + return new_op, new_attrs, inputs + +def argmax(op_name, attrs, inputs): + return 'argmax', attrs, inputs + + +def argmin(op_name, attrs, inputs): + return 'argmin', attrs, inputs diff --git a/python/mxnet/contrib/onnx/_import/translation_utils.py b/python/mxnet/contrib/onnx/_import/translation_utils.py index 7be7e590dec0..94913355a265 100644 --- a/python/mxnet/contrib/onnx/_import/translation_utils.py +++ b/python/mxnet/contrib/onnx/_import/translation_utils.py @@ -21,21 +21,17 @@ from __future__ import absolute_import as _abs from .... import symbol - def _fix_attribute_names(attrs, change_map): """ Change attribute names as per values in change_map dictionary. Parameters ---------- - attrs : dict - Dict of operator attributes - change_map : dict - Dict of onnx attribute name to mxnet attribute names. + :param attrs : dict Dict of operator attributes + :param change_map : dict Dict of onnx attribute name to mxnet attribute names. Returns ------- - new_attr : dict - Converted dict of operator attributes. + :return new_attr : dict Converted dict of operator attributes. """ new_attr = {} for k in attrs.keys(): @@ -46,6 +42,20 @@ def _fix_attribute_names(attrs, change_map): return new_attr +def _remove_attributes(attrs, remove_list): + """ + Removes attributes in the remove list from the input attribute dict + :param attrs : Dict of operator attributes + :param remove_list : list of attributes to be removed + + :return new_attr : Dict of operator attributes without the listed attributes. + """ + new_attrs = {} + for attr in attrs.keys(): + if attr not in remove_list: + new_attrs[attr] = attrs[attr] + return new_attrs + def _add_extra_attributes(attrs, extraAttrMap): """ :param attrs: Current Attribute list diff --git a/tests/python/unittest/onnx_backend_test.py b/tests/python/unittest/onnx_backend_test.py index 66ee2d06c106..2676be456358 100644 --- a/tests/python/unittest/onnx_backend_test.py +++ b/tests/python/unittest/onnx_backend_test.py @@ -38,12 +38,23 @@ backend_test = onnx.backend.test.BackendTest(mxnet_backend, __name__) implemented_operators = [ + #Generator Functions + #'test_constant*', # Identity Function + #'test_random_uniform', + #'test_random_normal' #Arithmetic Operators 'test_add*', + 'test_sub_bcast_cpu', + 'test_sub_cpu', + 'test_sub_example_cpu', + 'test_mul*', + 'test_div*', 'test_neg*', 'test_abs*', 'test_argmax*', 'test_argmin*', + #Hyperbolic functions + 'test_tanh*', #Rounding 'test_ceil*', ## Joining and spliting @@ -53,9 +64,13 @@ 'test_constant_pad', 'test_edge_pad', 'test_reflect_pad', + 'test_relu', #Changing shape and type. 'test_reshape_*', - 'test_AvgPool2D*', + 'test_AvgPool2D*' + #Powers + 'test_reciprocal*', + 'test_sqrt*' ] for op_test in implemented_operators: From 5936755974ce1ca91de2d95cce669d2dcb77b8a4 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Tue, 6 Mar 2018 21:05:36 -0800 Subject: [PATCH 10/35] lint fix --- .../mxnet/contrib/onnx/_import/op_translations.py | 13 +++++++++++++ .../mxnet/contrib/onnx/_import/translation_utils.py | 12 ++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index 7d61b8129273..615ef4343af3 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -79,12 +79,25 @@ def divide(op_name, attrs, inputs): return 'elemwise_div', new_attr, inputs def absolute(op_name, attrs, inputs): + """Returns element-wise absolute value of the input.""" return 'abs', attrs, inputs def negative(op_name, attrs, inputs): """Negation of every element in a tensor""" return 'negative', attrs, inputs + +# Sorting and Searching +def argmax(op_name, attrs, inputs): + """Returns indices of the maximum values along an axis""" + return 'argmax', attrs, inputs + + +def argmin(op_name, attrs, inputs): + """Returns indices of the minimum values along an axis.""" + return 'argmin', attrs, inputs + + #Hyperbolic functions def tanh(op_name, attrs, inputs): """Returns the hyperbolic tangent of the input array.""" diff --git a/python/mxnet/contrib/onnx/_import/translation_utils.py b/python/mxnet/contrib/onnx/_import/translation_utils.py index 94913355a265..782c4d27c6aa 100644 --- a/python/mxnet/contrib/onnx/_import/translation_utils.py +++ b/python/mxnet/contrib/onnx/_import/translation_utils.py @@ -56,20 +56,20 @@ def _remove_attributes(attrs, remove_list): new_attrs[attr] = attrs[attr] return new_attrs -def _add_extra_attributes(attrs, extraAttrMap): +def _add_extra_attributes(attrs, extra_attr_map): """ :param attrs: Current Attribute list :param extraAttrMap: Additional attributes to be added :return: new_attr """ - for attr in extraAttrMap: - attrs[attr] = extraAttrMap[attr] + for attr in extra_attr_map: + attrs[attr] = extra_attr_map[attr] return attrs -def _pad_sequence_fix(attr, kernelDim=None): +def _pad_sequence_fix(attr, kernel_dim=None): """Changing onnx's pads sequence to match with mxnet's pad_width mxnet: (x1_begin, x1_end, ... , xn_begin, xn_end) onnx: (x1_begin, x2_begin, ... , xn_end, xn_end)""" @@ -78,8 +78,8 @@ def _pad_sequence_fix(attr, kernelDim=None): for index in range(int(len(attr) / 2)): new_attr = new_attr + attr[index::int(len(attr) / 2)] # Making sure pad values are in the attr for all axes. - if kernelDim is not None: - while len(new_attr) < kernelDim*2: + if kernel_dim is not None: + while len(new_attr) < kernel_dim*2: new_attr = new_attr + (0, 0) return new_attr From 96d967f002643eed2316436c1556761594e1f415 Mon Sep 17 00:00:00 2001 From: Acharya Date: Wed, 7 Mar 2018 10:38:21 -0800 Subject: [PATCH 11/35] Add protobuf-compile to CI bash script. Add MatMul and Pow operator. Signed-off-by: Acharya --- docker/install/python.sh | 1 + .../contrib/onnx/_import/import_helper.py | 8 +- .../mxnet/contrib/onnx/_import/import_onnx.py | 83 +++++++++---------- .../contrib/onnx/_import/op_translations.py | 11 +++ .../contrib/onnx/_import/translation_utils.py | 1 - tests/python/unittest/onnx_backend_test.py | 7 +- 6 files changed, 62 insertions(+), 49 deletions(-) diff --git a/docker/install/python.sh b/docker/install/python.sh index ba71246babbf..3303ef2b8846 100755 --- a/docker/install/python.sh +++ b/docker/install/python.sh @@ -20,6 +20,7 @@ # install libraries for mxnet's python package on ubuntu apt-get update && apt-get install -y python-dev python3-dev +sudo apt-get install protobuf-compiler # the version of the pip shipped with ubuntu may be too lower, install a recent version here cd /tmp && wget https://bootstrap.pypa.io/get-pip.py && python3 get-pip.py && python2 get-pip.py diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index ecd938c90f72..748b688249e8 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -23,13 +23,13 @@ from .op_translations import tanh from .op_translations import ceil from .op_translations import concat -from .op_translations import sigmoid, pad, relu +from .op_translations import sigmoid, pad, relu, matrix_multiplication from .op_translations import reshape, cast -from .op_translations import reciprocal, squareroot +from .op_translations import reciprocal, squareroot, power from .op_translations import reduce_max, reduce_mean, avg_pooling from .op_translations import argmax, argmin -# _convert_map defines maps of name to converter functor(callable) +# convert_map defines maps of name to converter functor(callable) _convert_map = { # Generator Functions 'Constant' : identity, @@ -54,12 +54,14 @@ 'Sigmoid' : sigmoid, 'Pad' : pad, 'Relu' : relu, + 'MatMul' : matrix_multiplication, #linalg_gemm2 # Changing shape and type. 'Reshape' : reshape, 'Cast' : cast, #Powers 'Reciprocal' : reciprocal, 'Sqrt' : squareroot, + 'Pow' : power, # Reduce Functions 'ReduceMax' : reduce_max, 'ReduceMean' : reduce_mean, diff --git a/python/mxnet/contrib/onnx/_import/import_onnx.py b/python/mxnet/contrib/onnx/_import/import_onnx.py index d35172b06494..28d234b73a82 100644 --- a/python/mxnet/contrib/onnx/_import/import_onnx.py +++ b/python/mxnet/contrib/onnx/_import/import_onnx.py @@ -21,46 +21,8 @@ from __future__ import absolute_import as _abs from .... import symbol from .... import ndarray as nd -from .import_helper import _convert_map - -def _convert_operator(node_name, op_name, attrs, inputs, convert_map=None): - """Convert from onnx operator to mxnet operator. - The converter must specify conversions explicitly for incompatible name, and - apply handlers to operator attributes. - - Parameters - ---------- - op_name : str - Operator name, such as Convolution, FullyConnected - attrs : dict - Dict of operator attributes - inputs: list - list of inputs to the operator - convert_map : dict - Dict of name : callable, where name is the op's name that - require conversion to mxnet, callable are functions which - take attrs and return (new_op_name, new_attrs, inputs) - - Returns - ------- - (op_name, attrs) - Converted (op_name, attrs) for mxnet. - """ - convert_map = convert_map if convert_map else _convert_map - if op_name in convert_map: - op_name, new_attrs, inputs = convert_map[op_name](op_name, attrs, inputs) - else: - raise NotImplementedError("Operator {} not implemented.".format(op_name)) - if isinstance(op_name, str): - new_op = getattr(symbol, op_name, None) - op = new_op(name=node_name, *inputs, **new_attrs) - if not op: - raise RuntimeError("Unable to map op_name {} to sym".format(op_name)) - else: - op = op_name - - return op - +from ....base import string_types +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. @@ -73,6 +35,39 @@ def __init__(self): self._num_input = 0 self._num_param = 0 + def _convert_operator(self, node_name, op_name, attrs, inputs): + """Convert from onnx operator to mxnet operator. + The converter must specify conversions explicitly for incompatible name, and + apply handlers to operator attributes. + + Parameters + ---------- + :param node_name : str + name of the node to be translated. + :param op_name : str + Operator name, such as Convolution, FullyConnected + :param attrs : dict + Dict of operator attributes + :param inputs: list + list of inputs to the operator + Returns + ------- + :return mxnet_sym + Converted mxnet symbol + """ + if op_name in convert_map: + op_name, new_attrs, inputs = convert_map[op_name](op_name, attrs, inputs) + else: + raise NotImplementedError("Operator {} not implemented.".format(op_name)) + new_op = getattr(symbol, op_name, None) + if node_name is None: + mxnet_sym = new_op(*inputs, **new_attrs) + else: + mxnet_sym = new_op(name=node_name, *inputs, **new_attrs) + if not mxnet_sym: + raise RuntimeError("Unable to map op_name {} to sym".format(op_name)) + return mxnet_sym + def from_onnx(self, graph): """Construct symbol from onnx graph. The inputs from onnx graph is vague, only providing "1", "2"... @@ -121,14 +116,14 @@ def from_onnx(self, graph): node_name = node_name if node_name else None onnx_attr = self._parse_attr(node.attribute) inputs = [self._nodes[self._renames.get(i, i)] for i in node.input] - op = _convert_operator(node_name, op_name, onnx_attr, inputs) + mxnet_sym = self._convert_operator(node_name, op_name, onnx_attr, inputs) - assert len(node.output) == len(op.list_outputs()), ( + assert len(node.output) == len(mxnet_sym.list_outputs()), ( "Output dimension mismatch between the onnx operator and the mxnet symbol " + "{} vs {} for the operator - {}.".format( - len(node.output), len(op.list_outputs()), op_name)) + len(node.output), len(mxnet_sym.list_outputs()), op_name)) for k, i in zip(list(node.output), range(len(node.output))): - self._nodes[k] = op[i] + self._nodes[k] = mxnet_sym[i] # now return the outputs out = [self._nodes[i.name] for i in graph.output] if len(out) > 1: diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index 615ef4343af3..e92d5423c428 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -132,6 +132,9 @@ def pad(op_name, attrs, inputs): new_attrs['pad_width'] = translation_utils._pad_sequence_fix(new_attrs.get('pad_width')) return 'pad', new_attrs, inputs +def matrix_multiplication(op_name, attrs, inputs): + """Performs general matrix multiplication""" + return 'linalg_gemm2', attrs, inputs # Changing shape and type. def reshape(op_name, attrs, inputs): @@ -152,6 +155,14 @@ def squareroot(op_name, attrs, inputs): """Returns element-wise square-root value of the input.""" return 'sqrt', attrs, inputs +def power(op_name, attrs, inputs): + """Returns element-wise result of base element raised to powers from exp element.""" + new_attrs = translation_utils._fix_attribute_names(attrs, {'exponent':'exp'}) + if 'broadcast' in attrs and attrs['broadcast'] == 1: + new_attrs = translation_utils._remove_attributes(new_attrs, ['broadcast']) + return 'broadcast_power', new_attrs, inputs + return 'pow', new_attrs, inputs + # Reduce Functions def reduce_max(op_name, attrs, inputs): """Reduce the array along a given axis by maximum value""" diff --git a/python/mxnet/contrib/onnx/_import/translation_utils.py b/python/mxnet/contrib/onnx/_import/translation_utils.py index 782c4d27c6aa..c58d24c70290 100644 --- a/python/mxnet/contrib/onnx/_import/translation_utils.py +++ b/python/mxnet/contrib/onnx/_import/translation_utils.py @@ -39,7 +39,6 @@ def _fix_attribute_names(attrs, change_map): new_attr[change_map[k]] = attrs[k] else: new_attr[k] = attrs[k] - return new_attr def _remove_attributes(attrs, remove_list): diff --git a/tests/python/unittest/onnx_backend_test.py b/tests/python/unittest/onnx_backend_test.py index 2676be456358..6966427089e8 100644 --- a/tests/python/unittest/onnx_backend_test.py +++ b/tests/python/unittest/onnx_backend_test.py @@ -65,12 +65,17 @@ 'test_edge_pad', 'test_reflect_pad', 'test_relu', + 'test_matmul*', #Changing shape and type. 'test_reshape_*', 'test_AvgPool2D*' #Powers 'test_reciprocal*', - 'test_sqrt*' + 'test_sqrt*', + 'test_pow_example', + #'test_pow', + #'test_pow_bcast' + #'test_pow_bcast_axis0' ] for op_test in implemented_operators: From df61e4f9b956b0bfa4605a5040f0ff3e6a8b620a Mon Sep 17 00:00:00 2001 From: Acharya Date: Wed, 7 Mar 2018 13:39:45 -0800 Subject: [PATCH 12/35] Max,Min,Sum,Reduce operators. Signed-off-by: Acharya --- docker/install/python.sh | 2 +- .../contrib/onnx/_import/import_helper.py | 15 ++++-- .../mxnet/contrib/onnx/_import/import_onnx.py | 18 ++++--- .../contrib/onnx/_import/op_translations.py | 49 ++++++++++++++++++- tests/python/unittest/onnx_backend_test.py | 8 ++- tests/python/unittest/test_layers.py | 24 +++++++++ 6 files changed, 100 insertions(+), 16 deletions(-) diff --git a/docker/install/python.sh b/docker/install/python.sh index 3303ef2b8846..4f5aa95c764f 100755 --- a/docker/install/python.sh +++ b/docker/install/python.sh @@ -20,7 +20,7 @@ # install libraries for mxnet's python package on ubuntu apt-get update && apt-get install -y python-dev python3-dev -sudo apt-get install protobuf-compiler +apt-get install -y protobuf-compiler # the version of the pip shipped with ubuntu may be too lower, install a recent version here cd /tmp && wget https://bootstrap.pypa.io/get-pip.py && python3 get-pip.py && python2 get-pip.py diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index 748b688249e8..118f8e4ca6dd 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -19,15 +19,16 @@ # 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 +from .op_translations import add, subtract, multiply, divide, absolute, negative, add_n from .op_translations import tanh from .op_translations import ceil from .op_translations import concat from .op_translations import sigmoid, pad, relu, matrix_multiplication from .op_translations import reshape, cast from .op_translations import reciprocal, squareroot, power -from .op_translations import reduce_max, reduce_mean, avg_pooling -from .op_translations import argmax, argmin +from .op_translations import reduce_max, reduce_mean, reduce_min, reduce_sum +from .op_translations import reduce_prod, avg_pooling +from .op_translations import argmax, argmin, maximum, minimum # convert_map defines maps of name to converter functor(callable) _convert_map = { @@ -44,6 +45,7 @@ 'Div' : divide, 'Abs' : absolute, 'Neg' : negative, + 'Sum' : add_n, #elemwise sum #Hyperbolic functions 'Tanh' : tanh, # Rounding @@ -65,8 +67,13 @@ # Reduce Functions 'ReduceMax' : reduce_max, 'ReduceMean' : reduce_mean, + 'ReduceMin' : reduce_min, + 'ReduceSum' : reduce_sum, + 'ReduceProd' : reduce_prod, 'AveragePool' : avg_pooling, # Sorting and Searching 'ArgMax' : argmax, - 'ArgMin' : argmin + 'ArgMin' : argmin, + 'Max' : maximum, #elemwise maximum + 'Min' : minimum #elemwise minimum } diff --git a/python/mxnet/contrib/onnx/_import/import_onnx.py b/python/mxnet/contrib/onnx/_import/import_onnx.py index 28d234b73a82..09ea2e445bf2 100644 --- a/python/mxnet/contrib/onnx/_import/import_onnx.py +++ b/python/mxnet/contrib/onnx/_import/import_onnx.py @@ -59,14 +59,16 @@ def _convert_operator(self, node_name, op_name, attrs, inputs): op_name, new_attrs, inputs = convert_map[op_name](op_name, attrs, inputs) else: raise NotImplementedError("Operator {} not implemented.".format(op_name)) - new_op = getattr(symbol, op_name, None) - if node_name is None: - mxnet_sym = new_op(*inputs, **new_attrs) - else: - mxnet_sym = new_op(name=node_name, *inputs, **new_attrs) - if not mxnet_sym: - raise RuntimeError("Unable to map op_name {} to sym".format(op_name)) - return mxnet_sym + if isinstance(op_name, string_types): + new_op = getattr(symbol, op_name, None) + if node_name is None: + mxnet_sym = new_op(*inputs, **new_attrs) + else: + mxnet_sym = new_op(name=node_name, *inputs, **new_attrs) + if not mxnet_sym: + raise RuntimeError("Unable to map op_name {} to sym".format(op_name)) + return mxnet_sym + return op_name def from_onnx(self, graph): """Construct symbol from onnx graph. diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index e92d5423c428..c791fa1df09c 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -19,6 +19,7 @@ """ Module for translating ONNX operators into Mxnet operatoes""" # pylint: disable=unused-argument,protected-access from . import translation_utils +from .... import symbol #Generator Functions def identity(op_name, attrs, inputs): @@ -86,6 +87,9 @@ def negative(op_name, attrs, inputs): """Negation of every element in a tensor""" return 'negative', attrs, inputs +def add_n(op_name, attrs, inputs): + """Elementwise sum of arrays""" + return 'add_n', attrs, inputs # Sorting and Searching def argmax(op_name, attrs, inputs): @@ -97,6 +101,36 @@ def argmin(op_name, attrs, inputs): """Returns indices of the minimum values along an axis.""" return 'argmin', attrs, inputs +def maximum(op_name, attrs, inputs): + """ + Elementwise maximum of arrays. + MXNet maximum compares only two symbols at a time. + ONNX can send more than two to compare. + Breaking into multiple mxnet ops to compare two symbols at a time + """ + if len(inputs) > 1: + mxnet_op = symbol.maximum(inputs[0], inputs[1]) + for op_input in inputs[2:]: + mxnet_op = symbol.maximum(mxnet_op, op_input) + else: + mxnet_op = inputs[0] + return mxnet_op, attrs, inputs + #return 'maximum', attrs, inputs + +def minimum(op_name, attrs, inputs): + """Elementwise minimum of arrays. + MXNet minimum compares only two symbols at a time. + ONNX can send more than two to compare. + Breaking into multiple mxnet ops to compare two symbols at a time + """ + if len(inputs) > 1: + mxnet_op = symbol.minimum(inputs[0], inputs[1]) + for op_input in inputs[2:]: + mxnet_op = symbol.minimum(mxnet_op, op_input) + else: + mxnet_op = inputs[0] + return mxnet_op, attrs, inputs + #return 'minimum', attrs, inputs #Hyperbolic functions def tanh(op_name, attrs, inputs): @@ -169,12 +203,25 @@ def reduce_max(op_name, attrs, inputs): new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':'axis'}) return 'max', new_attrs, inputs - def reduce_mean(op_name, attrs, inputs): """Reduce the array along a given axis by mean value""" new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':'axis'}) return 'mean', new_attrs, inputs +def reduce_min(op_name, attrs, inputs): + """Reduce the array along a given axis by mean value""" + new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':'axis'}) + return 'min', new_attrs, inputs + +def reduce_sum(op_name, attrs, inputs): + """Reduce the array along a given axis by mean value""" + new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':'axis'}) + return 'sum', new_attrs, inputs + +def reduce_prod(op_name, attrs, inputs): + """Reduce the array along a given axis by mean value""" + new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':'axis'}) + return 'prod', new_attrs, inputs def avg_pooling(op_name, attrs, inputs): """ Average pooling""" diff --git a/tests/python/unittest/onnx_backend_test.py b/tests/python/unittest/onnx_backend_test.py index 6966427089e8..8ada6b49106d 100644 --- a/tests/python/unittest/onnx_backend_test.py +++ b/tests/python/unittest/onnx_backend_test.py @@ -51,8 +51,7 @@ 'test_div*', 'test_neg*', 'test_abs*', - 'test_argmax*', - 'test_argmin*', + 'test_sum*', #Hyperbolic functions 'test_tanh*', #Rounding @@ -76,6 +75,11 @@ #'test_pow', #'test_pow_bcast' #'test_pow_bcast_axis0' + # Sorting and Searching + 'test_argmax*', + 'test_argmin*', + 'test_max*', + 'test_min*' ] for op_test in implemented_operators: diff --git a/tests/python/unittest/test_layers.py b/tests/python/unittest/test_layers.py index d2272d06c76e..3597cf4a5ecb 100644 --- a/tests/python/unittest/test_layers.py +++ b/tests/python/unittest/test_layers.py @@ -50,5 +50,29 @@ def test_reduce_mean(self): numpy_op = np.mean(input1, axis=(1, 0), keepdims=True) npt.assert_almost_equal(output, numpy_op, decimal=5) + def test_reduce_min(self): + """Test for ReduceMin operator""" + node_def = helper.make_node("ReduceMin", ["input1"], ["output"], axes=[1, 0], keepdims=1) + input1 = self._random_array([3, 10]) + output = mxnet_backend.run_node(node_def, [input1])[0] + numpy_op = np.min(input1, axis=(1, 0), keepdims=True) + npt.assert_almost_equal(output, numpy_op) + + def test_reduce_sum(self): + """Test for ReduceSum operator""" + node_def = helper.make_node("ReduceSum", ["input1"], ["output"], axes=[1, 0], keepdims=1) + input1 = self._random_array([3, 10]) + output = mxnet_backend.run_node(node_def, [input1])[0] + numpy_op = np.sum(input1, axis=(1, 0), keepdims=True) + npt.assert_almost_equal(output, numpy_op, decimal=5) + + def test_reduce_prod(self): + """Test for ReduceProd operator""" + node_def = helper.make_node("ReduceProd", ["input1"], ["output"], axes=[1, 0], keepdims=1) + input1 = self._random_array([3, 10]) + output = mxnet_backend.run_node(node_def, [input1])[0] + numpy_op = np.prod(input1, axis=(1, 0), keepdims=True) + npt.assert_almost_equal(output, numpy_op, decimal=5) + if __name__ == '__main__': unittest.main() From 8e74bd834a83f61f4cd7ef95de4581267a1352de Mon Sep 17 00:00:00 2001 From: Acharya Date: Wed, 7 Mar 2018 14:41:39 -0800 Subject: [PATCH 13/35] BatchNorm,SpatialBN, Split Signed-off-by: Acharya --- docker/install/python.sh | 2 +- .../contrib/onnx/_import/import_helper.py | 65 ++++++++++--------- .../contrib/onnx/_import/op_translations.py | 18 ++++- .../contrib/onnx/_import/translation_utils.py | 2 - tests/python/unittest/onnx_backend_test.py | 3 + tests/python/unittest/test_layers.py | 20 +++--- 6 files changed, 65 insertions(+), 45 deletions(-) diff --git a/docker/install/python.sh b/docker/install/python.sh index 4f5aa95c764f..3608229fa07b 100755 --- a/docker/install/python.sh +++ b/docker/install/python.sh @@ -20,7 +20,7 @@ # install libraries for mxnet's python package on ubuntu apt-get update && apt-get install -y python-dev python3-dev -apt-get install -y protobuf-compiler +apt-get install -y libprotobuf-dev protobuf-compiler # the version of the pip shipped with ubuntu may be too lower, install a recent version here cd /tmp && wget https://bootstrap.pypa.io/get-pip.py && python3 get-pip.py && python2 get-pip.py diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index 118f8e4ca6dd..3d3a23899d4c 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -23,8 +23,8 @@ from .op_translations import tanh from .op_translations import ceil from .op_translations import concat -from .op_translations import sigmoid, pad, relu, matrix_multiplication -from .op_translations import reshape, cast +from .op_translations import sigmoid, pad, relu, matrix_multiplication, batch_norm +from .op_translations import reshape, cast, split from .op_translations import reciprocal, squareroot, power from .op_translations import reduce_max, reduce_mean, reduce_min, reduce_sum from .op_translations import reduce_prod, avg_pooling @@ -39,41 +39,44 @@ 'RandomUniformLike' : random_uniform, 'RandomNormalLike' : random_normal, # Arithmetic Operators - 'Add' : add, - 'Sub' : subtract, - 'Mul' : multiply, - 'Div' : divide, - 'Abs' : absolute, - 'Neg' : negative, - 'Sum' : add_n, #elemwise sum + 'Add' : add, + 'Sub' : subtract, + 'Mul' : multiply, + 'Div' : divide, + 'Abs' : absolute, + 'Neg' : negative, + 'Sum' : add_n, #elemwise sum #Hyperbolic functions - 'Tanh' : tanh, + 'Tanh' : tanh, # Rounding - 'Ceil' : ceil, + 'Ceil' : ceil, # Joining and spliting - 'Concat' : concat, + 'Concat' : concat, # Basic neural network functions - 'Sigmoid' : sigmoid, - 'Pad' : pad, - 'Relu' : relu, - 'MatMul' : matrix_multiplication, #linalg_gemm2 + 'Sigmoid' : sigmoid, + 'Relu' : relu, + 'Pad' : pad, + 'MatMul' : matrix_multiplication, #linalg_gemm2 + 'BatchNormalization': batch_norm, + 'SpatialBN' : batch_norm, # Changing shape and type. - 'Reshape' : reshape, - 'Cast' : cast, + 'Reshape' : reshape, + 'Cast' : cast, + 'Split' : split, #Powers - 'Reciprocal' : reciprocal, - 'Sqrt' : squareroot, - 'Pow' : power, + 'Reciprocal' : reciprocal, + 'Sqrt' : squareroot, + 'Pow' : power, # Reduce Functions - 'ReduceMax' : reduce_max, - 'ReduceMean' : reduce_mean, - 'ReduceMin' : reduce_min, - 'ReduceSum' : reduce_sum, - 'ReduceProd' : reduce_prod, - 'AveragePool' : avg_pooling, + 'ReduceMax' : reduce_max, + 'ReduceMean' : reduce_mean, + 'ReduceMin' : reduce_min, + 'ReduceSum' : reduce_sum, + 'ReduceProd' : reduce_prod, + 'AveragePool' : avg_pooling, # Sorting and Searching - 'ArgMax' : argmax, - 'ArgMin' : argmin, - 'Max' : maximum, #elemwise maximum - 'Min' : minimum #elemwise minimum + 'ArgMax' : argmax, + 'ArgMin' : argmin, + 'Max' : maximum, #elemwise maximum + 'Min' : minimum #elemwise minimum } diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index c791fa1df09c..e3d2d0fe9e7c 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -170,16 +170,30 @@ def matrix_multiplication(op_name, attrs, inputs): """Performs general matrix multiplication""" return 'linalg_gemm2', attrs, inputs +def batch_norm(op_name, attrs, inputs): + """Batch normalization.""" + new_attrs = translation_utils._fix_attribute_names(attrs, {'epsilon' : 'eps'}) + new_attrs = translation_utils._remove_attributes(new_attrs, + ['spatial', 'is_test', 'consumed_inputs']) + new_attrs = translation_utils._add_extra_attributes(new_attrs, {'cudnn_off': 1}) + return 'BatchNorm', new_attrs, inputs + # Changing shape and type. def reshape(op_name, attrs, inputs): """Reshape the given array by the shape attribute.""" return 'reshape', attrs, inputs -def cast(op_name, attrs, inputs): +def cast(op_name, attrs, inputs): """ Cast input to a given dtype""" - new_attrs = translation_utils._fix_attribute_names(attrs, {'to': 'dtype'}) + new_attrs = translation_utils._fix_attribute_names(attrs, {'to' : 'dtype'}) return 'cast', new_attrs, inputs +def split(op_name, attrs, inputs): + """Splits an array along a particular axis into multiple sub-arrays.""" + new_attrs = translation_utils._fix_attribute_names(attrs, + {'split' : 'num_outputs'}) + return 'split', new_attrs, inputs + #Powers def reciprocal(op_name, attrs, inputs): """Returns the reciprocal of the argument, element-wise.""" diff --git a/python/mxnet/contrib/onnx/_import/translation_utils.py b/python/mxnet/contrib/onnx/_import/translation_utils.py index c58d24c70290..7eb61cfc8a24 100644 --- a/python/mxnet/contrib/onnx/_import/translation_utils.py +++ b/python/mxnet/contrib/onnx/_import/translation_utils.py @@ -61,10 +61,8 @@ def _add_extra_attributes(attrs, extra_attr_map): :param extraAttrMap: Additional attributes to be added :return: new_attr """ - for attr in extra_attr_map: attrs[attr] = extra_attr_map[attr] - return attrs diff --git a/tests/python/unittest/onnx_backend_test.py b/tests/python/unittest/onnx_backend_test.py index 8ada6b49106d..a0ba4e4fadd4 100644 --- a/tests/python/unittest/onnx_backend_test.py +++ b/tests/python/unittest/onnx_backend_test.py @@ -65,9 +65,12 @@ 'test_reflect_pad', 'test_relu', 'test_matmul*', + #'test_batch_norm', #Changing shape and type. 'test_reshape_*', 'test_AvgPool2D*' + #'test_cast', + #'test_split', #Powers 'test_reciprocal*', 'test_sqrt*', diff --git a/tests/python/unittest/test_layers.py b/tests/python/unittest/test_layers.py index 3597cf4a5ecb..c68d811a9853 100644 --- a/tests/python/unittest/test_layers.py +++ b/tests/python/unittest/test_layers.py @@ -24,52 +24,54 @@ import numpy.testing as npt from onnx import helper import backend as mxnet_backend +from common import with_seed class TestLayers(unittest.TestCase): """Tests for different layers comparing output with numpy operators. Temporary file until we have a corresponding test in onnx-backend_test for these operators.""" - def _random_array(self, shape): - """Generate random array according to input shape""" - return np.random.ranf(shape).astype("float32") - + @with_seed(0) def test_reduce_max(self): """Test for ReduceMax operator""" node_def = helper.make_node("ReduceMax", ["input1"], ["output"], axes=[1, 0], keepdims=1) - input1 = self._random_array([3, 10]) + input1 = np.random.ranf([3, 10]).astype("float32") output = mxnet_backend.run_node(node_def, [input1])[0] numpy_op = np.max(input1, axis=(1, 0), keepdims=True) npt.assert_almost_equal(output, numpy_op) + @with_seed(0) def test_reduce_mean(self): """Test for ReduceMean operator""" node_def = helper.make_node("ReduceMean", ["input1"], ["output"], axes=[1, 0], keepdims=1) - input1 = self._random_array([3, 10]) + input1 = np.random.ranf([3, 10]).astype("float32") output = mxnet_backend.run_node(node_def, [input1])[0] numpy_op = np.mean(input1, axis=(1, 0), keepdims=True) npt.assert_almost_equal(output, numpy_op, decimal=5) + @with_seed(0) def test_reduce_min(self): """Test for ReduceMin operator""" node_def = helper.make_node("ReduceMin", ["input1"], ["output"], axes=[1, 0], keepdims=1) - input1 = self._random_array([3, 10]) + input1 = np.random.ranf([3, 10]).astype("float32") output = mxnet_backend.run_node(node_def, [input1])[0] numpy_op = np.min(input1, axis=(1, 0), keepdims=True) npt.assert_almost_equal(output, numpy_op) + @with_seed(0) def test_reduce_sum(self): """Test for ReduceSum operator""" node_def = helper.make_node("ReduceSum", ["input1"], ["output"], axes=[1, 0], keepdims=1) - input1 = self._random_array([3, 10]) + input1 = np.random.ranf([3, 10]).astype("float32") output = mxnet_backend.run_node(node_def, [input1])[0] numpy_op = np.sum(input1, axis=(1, 0), keepdims=True) npt.assert_almost_equal(output, numpy_op, decimal=5) + @with_seed(0) def test_reduce_prod(self): """Test for ReduceProd operator""" node_def = helper.make_node("ReduceProd", ["input1"], ["output"], axes=[1, 0], keepdims=1) - input1 = self._random_array([3, 10]) + input1 = np.random.ranf([3, 10]).astype("float32") output = mxnet_backend.run_node(node_def, [input1])[0] numpy_op = np.prod(input1, axis=(1, 0), keepdims=True) npt.assert_almost_equal(output, numpy_op, decimal=5) From 40d9d13b9837b0fe1e1c7e4312645521f1f85579 Mon Sep 17 00:00:00 2001 From: Acharya Date: Wed, 7 Mar 2018 18:46:18 -0800 Subject: [PATCH 14/35] Slice,Transpose and Squeeze Operators. Signed-off-by: Acharya --- docker/install/python.sh | 3 +- .../contrib/onnx/_import/import_helper.py | 5 +- .../contrib/onnx/_import/op_translations.py | 46 ++++++++++++++++--- .../{onnx_test_utils => unittest}/backend.py | 0 .../backend_rep.py | 0 tests/python/unittest/onnx_backend_test.py | 11 +++-- tests/python/unittest/test_layers.py | 8 ++++ 7 files changed, 60 insertions(+), 13 deletions(-) rename tests/python/{onnx_test_utils => unittest}/backend.py (100%) rename tests/python/{onnx_test_utils => unittest}/backend_rep.py (100%) diff --git a/docker/install/python.sh b/docker/install/python.sh index 3608229fa07b..5fe37b32f584 100755 --- a/docker/install/python.sh +++ b/docker/install/python.sh @@ -20,10 +20,9 @@ # install libraries for mxnet's python package on ubuntu apt-get update && apt-get install -y python-dev python3-dev -apt-get install -y libprotobuf-dev protobuf-compiler # the version of the pip shipped with ubuntu may be too lower, install a recent version here cd /tmp && wget https://bootstrap.pypa.io/get-pip.py && python3 get-pip.py && python2 get-pip.py pip2 install nose pylint numpy nose-timer requests Pillow -pip3 install nose pylint numpy nose-timer requests Pillow +pip3 install nose pylint numpy nose-timer requests Pillow \ No newline at end of file diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index 3d3a23899d4c..62d78d3ff19b 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -24,7 +24,7 @@ from .op_translations import ceil from .op_translations import concat from .op_translations import sigmoid, pad, relu, matrix_multiplication, batch_norm -from .op_translations import reshape, cast, split +from .op_translations import reshape, cast, split, _slice, transpose, squeeze from .op_translations import reciprocal, squareroot, power from .op_translations import reduce_max, reduce_mean, reduce_min, reduce_sum from .op_translations import reduce_prod, avg_pooling @@ -63,6 +63,9 @@ 'Reshape' : reshape, 'Cast' : cast, 'Split' : split, + 'Slice' : _slice, + 'Transpose' : transpose, + 'Squeeze' : squeeze, #Powers 'Reciprocal' : reciprocal, 'Sqrt' : squareroot, diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index e3d2d0fe9e7c..7aa9988279bf 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -115,14 +115,12 @@ def maximum(op_name, attrs, inputs): else: mxnet_op = inputs[0] return mxnet_op, attrs, inputs - #return 'maximum', attrs, inputs def minimum(op_name, attrs, inputs): - """Elementwise minimum of arrays. - MXNet minimum compares only two symbols at a time. - ONNX can send more than two to compare. - Breaking into multiple mxnet ops to compare two symbols at a time - """ + """Elementwise minimum of arrays.""" + # MXNet minimum compares only two symbols at a time. + # ONNX can send more than two to compare. + # Breaking into multiple mxnet ops to compare two symbols at a time if len(inputs) > 1: mxnet_op = symbol.minimum(inputs[0], inputs[1]) for op_input in inputs[2:]: @@ -130,7 +128,6 @@ def minimum(op_name, attrs, inputs): else: mxnet_op = inputs[0] return mxnet_op, attrs, inputs - #return 'minimum', attrs, inputs #Hyperbolic functions def tanh(op_name, attrs, inputs): @@ -194,6 +191,41 @@ def split(op_name, attrs, inputs): {'split' : 'num_outputs'}) return 'split', new_attrs, inputs +def _slice(op_name, attrs, inputs): + """Returns a slice of the input tensor along multiple axes.""" + new_attrs = translation_utils._fix_attribute_names(attrs, + {'axes' : 'axis', + 'ends' : 'end', + 'starts' : 'begin'}) + # onnx slice provides slicing on multiple axis. Adding multiple slice_axis operator + # for multiple axes from mxnet + begin = new_attrs.get('begin') + end = new_attrs.get('end') + axes = new_attrs.get('axis', tuple(range(len(begin)))) + slice_op = symbol.slice_axis(inputs[0], axis=axes[0], begin=begin[0], end=end[0]) + if len(axes) > 1: + for i, axis in enumerate(axes): + slice_op = symbol.slice_axis(slice_op, axis=axis, begin=begin[i], end=end[i]) + return slice_op, new_attrs, inputs + +def transpose(op_name, attrs, inputs): + """Transpose the input array.""" + new_attrs = translation_utils._fix_attribute_names(attrs, + {'perm' : 'axes'}) + return 'transpose', new_attrs, inputs + +def squeeze(op_name, attrs, inputs): + """Remove single-dimensional entries from the shape of a tensor.""" + # MXNet doesnt have a squeeze operator. + # Using "split" to perform similar operation. + new_attrs = translation_utils._fix_attribute_names(attrs, + {'axes' : 'axis'}) + axes = new_attrs.get('axis') + mxnet_op = symbol.split(inputs[0], axis=axes[0], num_outputs=1, squeeze_axis=1) + for i in axes[1:]: + mxnet_op = symbol.split(mxnet_op, axis=i-1, num_outputs=1, squeeze_axis=1) + return mxnet_op, new_attrs, inputs + #Powers def reciprocal(op_name, attrs, inputs): """Returns the reciprocal of the argument, element-wise.""" diff --git a/tests/python/onnx_test_utils/backend.py b/tests/python/unittest/backend.py similarity index 100% rename from tests/python/onnx_test_utils/backend.py rename to tests/python/unittest/backend.py diff --git a/tests/python/onnx_test_utils/backend_rep.py b/tests/python/unittest/backend_rep.py similarity index 100% rename from tests/python/onnx_test_utils/backend_rep.py rename to tests/python/unittest/backend_rep.py diff --git a/tests/python/unittest/onnx_backend_test.py b/tests/python/unittest/onnx_backend_test.py index a0ba4e4fadd4..a8adb04af822 100644 --- a/tests/python/unittest/onnx_backend_test.py +++ b/tests/python/unittest/onnx_backend_test.py @@ -28,8 +28,6 @@ except ImportError: raise ImportError("Onnx and protobuf need to be installed") -from os import sys -sys.path.append('../onnx_test_utils') import backend as mxnet_backend # This is a pytest magic variable to load extra plugins @@ -68,9 +66,16 @@ #'test_batch_norm', #Changing shape and type. 'test_reshape_*', - 'test_AvgPool2D*' + 'test_AvgPool2D*', #'test_cast', #'test_split', + #'test_slice', + 'test_default_axes', #make PR against onnx to fix the test name(grep-able) + 'test_slice_neg', + #'test_slice_start_out_of_bounds', + #'test_slice_end_out_of_bounds', + #'test_transpose*', + #'test_squeeze', #Powers 'test_reciprocal*', 'test_sqrt*', diff --git a/tests/python/unittest/test_layers.py b/tests/python/unittest/test_layers.py index c68d811a9853..1417964ca216 100644 --- a/tests/python/unittest/test_layers.py +++ b/tests/python/unittest/test_layers.py @@ -76,5 +76,13 @@ def test_reduce_prod(self): numpy_op = np.prod(input1, axis=(1, 0), keepdims=True) npt.assert_almost_equal(output, numpy_op, decimal=5) + @with_seed(0) + def test_squeeze(self): + """Test for Squeeze operator""" + node_def = helper.make_node("Squeeze", ["input1"], ["output"], axes=[1, 3]) + input1 = np.random.ranf([3, 1, 2, 1, 4]).astype("float32") + output = mxnet_backend.run_node(node_def, [input1])[0] + npt.assert_almost_equal(output, np.squeeze(input1, axis=[1, 3])) + if __name__ == '__main__': unittest.main() From a1f3782ad3c71707b2fbe33ca92b9ba2406fa8a0 Mon Sep 17 00:00:00 2001 From: Acharya Date: Wed, 7 Mar 2018 23:59:24 -0800 Subject: [PATCH 15/35] Onnx tests in CI integration tests. Signed-off-by: Acharya --- Jenkinsfile | 15 ++++++++++- .../onnx}/test_super_resolution.py | 0 tests/ci_build/Dockerfile.cpu | 14 ++++++++++ tests/ci_build/install/ubuntu_install_onnx.sh | 26 +++++++++++++++++++ .../{unittest => integrationtest}/backend.py | 0 .../backend_rep.py | 0 .../onnx_backend_test.py | 0 .../test_layers.py | 4 +++ 8 files changed, 58 insertions(+), 1 deletion(-) rename {tests/python/unittest => example/onnx}/test_super_resolution.py (100%) create mode 100644 tests/ci_build/Dockerfile.cpu create mode 100644 tests/ci_build/install/ubuntu_install_onnx.sh rename tests/python/{unittest => integrationtest}/backend.py (100%) rename tests/python/{unittest => integrationtest}/backend_rep.py (100%) rename tests/python/{unittest => integrationtest}/onnx_backend_test.py (100%) rename tests/python/{unittest => integrationtest}/test_layers.py (96%) diff --git a/Jenkinsfile b/Jenkinsfile index 2bac346c7659..48afe2828d38 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -560,7 +560,20 @@ try { } stage('Integration Test') { - parallel 'Python GPU': { + parallel 'Python CPU': { + node('mxnetlinux-cpu') { + ws('workspace/it-python-cpu') { + init_git() + unpack_lib('cpu') + timeout(time: max_time, unit: 'MINUTES') { + sh "${docker_run} cpu --dockerbinary docker PYTHONPATH=./python/ python tests/python/integrationtest/onnx_backend_test.py" + sh "${docker_run} cpu --dockerbinary docker PYTHONPATH=./python/ python tests/python/integrationtest/test_layers.py" + sh "${docker_run} cpu --dockerbinary docker PYTHONPATH=./python/ python example/onnx/test_super_resolution.py" + } + } + } + }, + 'Python GPU': { node('mxnetlinux-gpu') { ws('workspace/it-python-gpu') { init_git() diff --git a/tests/python/unittest/test_super_resolution.py b/example/onnx/test_super_resolution.py similarity index 100% rename from tests/python/unittest/test_super_resolution.py rename to example/onnx/test_super_resolution.py diff --git a/tests/ci_build/Dockerfile.cpu b/tests/ci_build/Dockerfile.cpu new file mode 100644 index 000000000000..ea636ed48462 --- /dev/null +++ b/tests/ci_build/Dockerfile.cpu @@ -0,0 +1,14 @@ +FROM ubuntu:16.04 + +COPY install/ubuntu_install_core.sh /install/ +RUN /install/ubuntu_install_core.sh +COPY install/ubuntu_install_python.sh /install/ +RUN /install/ubuntu_install_python.sh +COPY install/ubuntu_install_onnx.sh /install/ +RUN /install/ubuntu_install_onnx.sh +COPY install/ubuntu_install_scala.sh /install/ +RUN /install/ubuntu_install_scala.sh +COPY install/ubuntu_install_r.sh /install/ +RUN /install/ubuntu_install_r.sh +COPY install/ubuntu_install_perl.sh /install/ +RUN /install/ubuntu_install_perl.sh diff --git a/tests/ci_build/install/ubuntu_install_onnx.sh b/tests/ci_build/install/ubuntu_install_onnx.sh new file mode 100644 index 000000000000..4468857fae8b --- /dev/null +++ b/tests/ci_build/install/ubuntu_install_onnx.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# 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. +set -e +set -x + +# install libraries for onnx's python package on ubuntu +apt-get install -y libprotobuf-dev protobuf-compiler + +pip2 install pytest==3.4.0 pytest-cov==2.5.1 protobuf==3.0.0 onnx==1.0.1 Pillow==5.0.0 +pip3 install pytest==3.4.0 pytest-cov==2.5.1 protobuf==3.0.0 onnx==1.0.1 Pillow==5.0.0 diff --git a/tests/python/unittest/backend.py b/tests/python/integrationtest/backend.py similarity index 100% rename from tests/python/unittest/backend.py rename to tests/python/integrationtest/backend.py diff --git a/tests/python/unittest/backend_rep.py b/tests/python/integrationtest/backend_rep.py similarity index 100% rename from tests/python/unittest/backend_rep.py rename to tests/python/integrationtest/backend_rep.py diff --git a/tests/python/unittest/onnx_backend_test.py b/tests/python/integrationtest/onnx_backend_test.py similarity index 100% rename from tests/python/unittest/onnx_backend_test.py rename to tests/python/integrationtest/onnx_backend_test.py diff --git a/tests/python/unittest/test_layers.py b/tests/python/integrationtest/test_layers.py similarity index 96% rename from tests/python/unittest/test_layers.py rename to tests/python/integrationtest/test_layers.py index 1417964ca216..4e776f76798f 100644 --- a/tests/python/unittest/test_layers.py +++ b/tests/python/integrationtest/test_layers.py @@ -19,11 +19,15 @@ # pylint: disable=import-error,no-self-use from __future__ import absolute_import +import sys +import os import unittest import numpy as np import numpy.testing as npt from onnx import helper import backend as mxnet_backend +curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) +sys.path.insert(0, os.path.join(curr_path, '../unittest')) from common import with_seed class TestLayers(unittest.TestCase): From 65f627b0dfbe483e66f5baa581f289b3243720c6 Mon Sep 17 00:00:00 2001 From: Acharya Date: Thu, 8 Mar 2018 16:11:31 -0800 Subject: [PATCH 16/35] Addressing Marco's comments Signed-off-by: Acharya --- Jenkinsfile | 8 +- example/onnx/test_super_resolution.py | 82 +++++++++++------ tests/ci_build/install/ubuntu_install_onnx.sh | 0 .../backend.py | 1 - .../backend_rep.py | 1 - .../onnx_backend_test.py | 25 ++--- tests/integrationtests/test_onnx.py | 86 +++++++++++++++++ tests/python/integrationtest/test_layers.py | 92 ------------------- 8 files changed, 152 insertions(+), 143 deletions(-) mode change 100644 => 100755 tests/ci_build/install/ubuntu_install_onnx.sh rename tests/{python/integrationtest => integrationtests}/backend.py (99%) rename tests/{python/integrationtest => integrationtests}/backend_rep.py (98%) rename tests/{python/integrationtest => integrationtests}/onnx_backend_test.py (83%) create mode 100644 tests/integrationtests/test_onnx.py delete mode 100644 tests/python/integrationtest/test_layers.py diff --git a/Jenkinsfile b/Jenkinsfile index 48afe2828d38..e0d31eb50476 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -560,14 +560,14 @@ try { } stage('Integration Test') { - parallel 'Python CPU': { + parallel 'Onnx CPU': { node('mxnetlinux-cpu') { - ws('workspace/it-python-cpu') { + ws('workspace/it-onnx-cpu') { init_git() unpack_lib('cpu') timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} cpu --dockerbinary docker PYTHONPATH=./python/ python tests/python/integrationtest/onnx_backend_test.py" - sh "${docker_run} cpu --dockerbinary docker PYTHONPATH=./python/ python tests/python/integrationtest/test_layers.py" + sh "${docker_run} cpu --dockerbinary docker PYTHONPATH=./python/ pytest tests/integrationtests/onnx_backend_test.py" + sh "${docker_run} cpu --dockerbinary docker PYTHONPATH=./python/ pytest tests/integrationtests/test_onnx.py" sh "${docker_run} cpu --dockerbinary docker PYTHONPATH=./python/ python example/onnx/test_super_resolution.py" } } diff --git a/example/onnx/test_super_resolution.py b/example/onnx/test_super_resolution.py index 80c55ec32e70..354bdeaaab08 100644 --- a/example/onnx/test_super_resolution.py +++ b/example/onnx/test_super_resolution.py @@ -16,47 +16,69 @@ # under the License. """Testing super_resolution model conversion""" -# pylint: disable=invalid-name from __future__ import absolute_import as _abs from __future__ import print_function from collections import namedtuple +import logging +import numpy as np +from PIL import Image import mxnet as mx from mxnet.test_utils import download import mxnet.contrib.onnx as onnx_mxnet -import numpy as np -from PIL import Image -model_url = 'https://s3.amazonaws.com/onnx-mxnet/examples/super_resolution.onnx' +# set up logger +logging.basicConfig() +LOGGER = logging.getLogger() +LOGGER.setLevel(logging.INFO) + +def download_onnx_model(): + """Download the onnx model""" + model_url = 'https://s3.amazonaws.com/onnx-mxnet/examples/super_resolution.onnx' + download(model_url, 'super_resolution.onnx') -download(model_url, 'super_resolution.onnx') +def import_onnx(): + """Import the onnx model into mxnet""" + LOGGER.info("Converting onnx format to mxnet's symbol and params...") + sym, params = onnx_mxnet.import_model('super_resolution.onnx') + assert sym is not None + assert params is not None + return sym, params -print("Converting onnx format to mxnet's symbol and params...") -sym, params = onnx_mxnet.import_model('super_resolution.onnx') +def get_test_image(): + """Download and process the test image""" + # Load test image + input_image_dim = 224 + img_url = 'https://s3.amazonaws.com/onnx-mxnet/examples/super_res_input.jpg' + download(img_url, 'super_res_input.jpg') + img = Image.open('super_res_input.jpg').resize((input_image_dim, input_image_dim)) + img_ycbcr = img.convert("YCbCr") + img_y, img_cb, img_cr = img_ycbcr.split() + input_image = np.array(img_y)[np.newaxis, np.newaxis, :, :] + return input_image, img_cb, img_cr -# Load test image -input_image_dim = 224 -output_image_dim = 672 -img_url = 'https://s3.amazonaws.com/onnx-mxnet/examples/super_res_input.jpg' -download(img_url, 'super_res_input.jpg') -img = Image.open('super_res_input.jpg').resize((input_image_dim, input_image_dim)) -img_ycbcr = img.convert("YCbCr") -img_y, img_cb, img_cr = img_ycbcr.split() -x = np.array(img_y)[np.newaxis, np.newaxis, :, :] +def perform_inference((sym, params), (input_img, img_cb, img_cr)): + """Perform inference on image using mxnet""" + # create module + mod = mx.mod.Module(symbol=sym, data_names=['input_0'], label_names=None) + mod.bind(for_training=False, data_shapes=[('input_0', input_img.shape)]) + mod.set_params(arg_params=params, aux_params=None) -# create module -mod = mx.mod.Module(symbol=sym, data_names=['input_0'], label_names=None) -mod.bind(for_training=False, data_shapes=[('input_0', x.shape)]) -mod.set_params(arg_params=params, aux_params=None) + # run inference + batch = namedtuple('Batch', ['data']) + mod.forward(batch([mx.nd.array(input_img)])) -# run inference -Batch = namedtuple('Batch', ['data']) -mod.forward(Batch([mx.nd.array(x)])) + # Save the result + img_out_y = Image.fromarray(np.uint8(mod.get_outputs()[0][0][0]. + asnumpy().clip(0, 255)), mode='L') -# Save the result -img_out_y = Image.fromarray(np.uint8(mod.get_outputs()[0][0][0].asnumpy().clip(0, 255)), mode='L') + result_img = Image.merge( + "YCbCr", [img_out_y, + img_cb.resize(img_out_y.size, Image.BICUBIC), + img_cr.resize(img_out_y.size, Image.BICUBIC)]).convert("RGB") + output_img_dim = 672 + assert result_img.size == (output_img_dim, output_img_dim) + result_img.save("super_res_output.jpg") -result_img = Image.merge( - "YCbCr", [img_out_y, - img_cb.resize(img_out_y.size, Image.BICUBIC), - img_cr.resize(img_out_y.size, Image.BICUBIC)]).convert("RGB") -result_img.save("super_res_output.jpg") +if __name__ == '__main__': + download_onnx_model() + perform_inference(import_onnx(), get_test_image()) diff --git a/tests/ci_build/install/ubuntu_install_onnx.sh b/tests/ci_build/install/ubuntu_install_onnx.sh old mode 100644 new mode 100755 diff --git a/tests/python/integrationtest/backend.py b/tests/integrationtests/backend.py similarity index 99% rename from tests/python/integrationtest/backend.py rename to tests/integrationtests/backend.py index f8f9157a00da..40ad2425b63e 100644 --- a/tests/python/integrationtest/backend.py +++ b/tests/integrationtests/backend.py @@ -16,7 +16,6 @@ # under the License. # coding: utf-8 -# pylint: disable=too-many-locals,invalid-name,no-member """backend wrapper for onnx test infrastructure""" import mxnet as mx from mxnet.contrib.onnx._import.import_onnx import GraphProto diff --git a/tests/python/integrationtest/backend_rep.py b/tests/integrationtests/backend_rep.py similarity index 98% rename from tests/python/integrationtest/backend_rep.py rename to tests/integrationtests/backend_rep.py index c6ceabc825fc..a125086bce21 100644 --- a/tests/python/integrationtest/backend_rep.py +++ b/tests/integrationtests/backend_rep.py @@ -16,7 +16,6 @@ # under the License. # coding: utf-8 -# pylint: disable=too-few-public-methods """backend rep for onnx test infrastructure""" from collections import namedtuple import numpy as np diff --git a/tests/python/integrationtest/onnx_backend_test.py b/tests/integrationtests/onnx_backend_test.py similarity index 83% rename from tests/python/integrationtest/onnx_backend_test.py rename to tests/integrationtests/onnx_backend_test.py index a8adb04af822..9e2f949185bd 100644 --- a/tests/python/integrationtest/onnx_backend_test.py +++ b/tests/integrationtests/onnx_backend_test.py @@ -16,7 +16,6 @@ # under the License. """ONNX test backend wrapper""" -# pylint: disable=invalid-name,import-error,wrong-import-position from __future__ import absolute_import from __future__ import division from __future__ import print_function @@ -33,18 +32,16 @@ # 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_TEST = onnx.backend.test.BackendTest(mxnet_backend, __name__) -implemented_operators = [ +IMPLEMENTED_OPERATORS = [ #Generator Functions #'test_constant*', # Identity Function #'test_random_uniform', #'test_random_normal' #Arithmetic Operators 'test_add*', - 'test_sub_bcast_cpu', - 'test_sub_cpu', - 'test_sub_example_cpu', + 'test_sub*', 'test_mul*', 'test_div*', 'test_neg*', @@ -69,19 +66,19 @@ 'test_AvgPool2D*', #'test_cast', #'test_split', - #'test_slice', + 'test_slice$', 'test_default_axes', #make PR against onnx to fix the test name(grep-able) 'test_slice_neg', #'test_slice_start_out_of_bounds', #'test_slice_end_out_of_bounds', #'test_transpose*', - #'test_squeeze', + 'test_squeeze$', #Powers 'test_reciprocal*', 'test_sqrt*', 'test_pow_example', - #'test_pow', - #'test_pow_bcast' + 'test_pow$', + 'test_pow_bcast$' #'test_pow_bcast_axis0' # Sorting and Searching 'test_argmax*', @@ -90,13 +87,11 @@ 'test_min*' ] -for op_test in implemented_operators: - backend_test.include(op_test) +for op_test in IMPLEMENTED_OPERATORS: + BACKEND_TEST.include(op_test) # 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_TEST.enable_report().test_cases) if __name__ == '__main__': unittest.main() diff --git a/tests/integrationtests/test_onnx.py b/tests/integrationtests/test_onnx.py new file mode 100644 index 000000000000..83115c186ba9 --- /dev/null +++ b/tests/integrationtests/test_onnx.py @@ -0,0 +1,86 @@ +# 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""" + +from __future__ import absolute_import +import sys +import os +import unittest +import numpy as np +import numpy.testing as npt +from onnx import helper +import backend as mxnet_backend +CURR_PATH = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) +sys.path.insert(0, os.path.join(CURR_PATH, '../python/unittest')) +from common import with_seed + +@with_seed() +def test_reduce_max(): + """Test for ReduceMax operator""" + node_def = helper.make_node("ReduceMax", ["input1"], ["output"], axes=[1, 0], keepdims=1) + input1 = np.random.ranf([3, 10]).astype("float32") + output = mxnet_backend.run_node(node_def, [input1])[0] + numpy_op = np.max(input1, axis=(1, 0), keepdims=True) + npt.assert_almost_equal(output, numpy_op) + +@with_seed() +def test_reduce_mean(): + """Test for ReduceMean operator""" + node_def = helper.make_node("ReduceMean", ["input1"], ["output"], axes=[1, 0], keepdims=1) + input1 = np.random.ranf([3, 10]).astype("float32") + output = mxnet_backend.run_node(node_def, [input1])[0] + numpy_op = np.mean(input1, axis=(1, 0), keepdims=True) + npt.assert_almost_equal(output, numpy_op, decimal=5) + +@with_seed() +def test_reduce_min(): + """Test for ReduceMin operator""" + node_def = helper.make_node("ReduceMin", ["input1"], ["output"], axes=[1, 0], keepdims=1) + input1 = np.random.ranf([3, 10]).astype("float32") + output = mxnet_backend.run_node(node_def, [input1])[0] + numpy_op = np.min(input1, axis=(1, 0), keepdims=True) + npt.assert_almost_equal(output, numpy_op) + +@with_seed() +def test_reduce_sum(): + """Test for ReduceSum operator""" + node_def = helper.make_node("ReduceSum", ["input1"], ["output"], axes=[1, 0], keepdims=1) + input1 = np.random.ranf([3, 10]).astype("float32") + output = mxnet_backend.run_node(node_def, [input1])[0] + numpy_op = np.sum(input1, axis=(1, 0), keepdims=True) + npt.assert_almost_equal(output, numpy_op, decimal=5) + +@with_seed() +def test_reduce_prod(): + """Test for ReduceProd operator""" + node_def = helper.make_node("ReduceProd", ["input1"], ["output"], axes=[1, 0], keepdims=1) + input1 = np.random.ranf([3, 10]).astype("float32") + output = mxnet_backend.run_node(node_def, [input1])[0] + numpy_op = np.prod(input1, axis=(1, 0), keepdims=True) + npt.assert_almost_equal(output, numpy_op, decimal=5) + +@with_seed() +def test_squeeze(): + """Test for Squeeze operator""" + node_def = helper.make_node("Squeeze", ["input1"], ["output"], axes=[1, 3]) + input1 = np.random.ranf([3, 1, 2, 1, 4]).astype("float32") + output = mxnet_backend.run_node(node_def, [input1])[0] + npt.assert_almost_equal(output, np.squeeze(input1, axis=[1, 3])) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/python/integrationtest/test_layers.py b/tests/python/integrationtest/test_layers.py deleted file mode 100644 index 4e776f76798f..000000000000 --- a/tests/python/integrationtest/test_layers.py +++ /dev/null @@ -1,92 +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""" -# pylint: disable=import-error,no-self-use - -from __future__ import absolute_import -import sys -import os -import unittest -import numpy as np -import numpy.testing as npt -from onnx import helper -import backend as mxnet_backend -curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) -sys.path.insert(0, os.path.join(curr_path, '../unittest')) -from common import with_seed - -class TestLayers(unittest.TestCase): - """Tests for different layers comparing output with numpy operators. - Temporary file until we have a corresponding test in onnx-backend_test - for these operators.""" - - @with_seed(0) - def test_reduce_max(self): - """Test for ReduceMax operator""" - node_def = helper.make_node("ReduceMax", ["input1"], ["output"], axes=[1, 0], keepdims=1) - input1 = np.random.ranf([3, 10]).astype("float32") - output = mxnet_backend.run_node(node_def, [input1])[0] - numpy_op = np.max(input1, axis=(1, 0), keepdims=True) - npt.assert_almost_equal(output, numpy_op) - - @with_seed(0) - def test_reduce_mean(self): - """Test for ReduceMean operator""" - node_def = helper.make_node("ReduceMean", ["input1"], ["output"], axes=[1, 0], keepdims=1) - input1 = np.random.ranf([3, 10]).astype("float32") - output = mxnet_backend.run_node(node_def, [input1])[0] - numpy_op = np.mean(input1, axis=(1, 0), keepdims=True) - npt.assert_almost_equal(output, numpy_op, decimal=5) - - @with_seed(0) - def test_reduce_min(self): - """Test for ReduceMin operator""" - node_def = helper.make_node("ReduceMin", ["input1"], ["output"], axes=[1, 0], keepdims=1) - input1 = np.random.ranf([3, 10]).astype("float32") - output = mxnet_backend.run_node(node_def, [input1])[0] - numpy_op = np.min(input1, axis=(1, 0), keepdims=True) - npt.assert_almost_equal(output, numpy_op) - - @with_seed(0) - def test_reduce_sum(self): - """Test for ReduceSum operator""" - node_def = helper.make_node("ReduceSum", ["input1"], ["output"], axes=[1, 0], keepdims=1) - input1 = np.random.ranf([3, 10]).astype("float32") - output = mxnet_backend.run_node(node_def, [input1])[0] - numpy_op = np.sum(input1, axis=(1, 0), keepdims=True) - npt.assert_almost_equal(output, numpy_op, decimal=5) - - @with_seed(0) - def test_reduce_prod(self): - """Test for ReduceProd operator""" - node_def = helper.make_node("ReduceProd", ["input1"], ["output"], axes=[1, 0], keepdims=1) - input1 = np.random.ranf([3, 10]).astype("float32") - output = mxnet_backend.run_node(node_def, [input1])[0] - numpy_op = np.prod(input1, axis=(1, 0), keepdims=True) - npt.assert_almost_equal(output, numpy_op, decimal=5) - - @with_seed(0) - def test_squeeze(self): - """Test for Squeeze operator""" - node_def = helper.make_node("Squeeze", ["input1"], ["output"], axes=[1, 3]) - input1 = np.random.ranf([3, 1, 2, 1, 4]).astype("float32") - output = mxnet_backend.run_node(node_def, [input1])[0] - npt.assert_almost_equal(output, np.squeeze(input1, axis=[1, 3])) - -if __name__ == '__main__': - unittest.main() From 495169ea74b363f9b8abc44672229ff18d131eac Mon Sep 17 00:00:00 2001 From: Acharya Date: Thu, 8 Mar 2018 18:49:32 -0800 Subject: [PATCH 17/35] Floor, LeakyRelu, Elu, PRelu, Softmax, Exp, Log operator. --- .../contrib/onnx/_import/import_helper.py | 12 +++++- .../contrib/onnx/_import/import_model.py | 9 +++-- .../contrib/onnx/_import/op_translations.py | 40 +++++++++++++++++++ tests/integrationtests/onnx_backend_test.py | 13 ++++-- 4 files changed, 65 insertions(+), 9 deletions(-) diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index 62d78d3ff19b..fae9695664c4 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -21,11 +21,12 @@ 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 +from .op_translations import ceil, floor from .op_translations import concat from .op_translations import sigmoid, pad, relu, matrix_multiplication, batch_norm +from .op_translations import leaky_relu, _elu, _prelu, softmax from .op_translations import reshape, cast, split, _slice, transpose, squeeze -from .op_translations import reciprocal, squareroot, power +from .op_translations import reciprocal, squareroot, power, exponent, _log from .op_translations import reduce_max, reduce_mean, reduce_min, reduce_sum from .op_translations import reduce_prod, avg_pooling from .op_translations import argmax, argmin, maximum, minimum @@ -50,6 +51,7 @@ 'Tanh' : tanh, # Rounding 'Ceil' : ceil, + 'Floor' : floor, # Joining and spliting 'Concat' : concat, # Basic neural network functions @@ -59,6 +61,10 @@ 'MatMul' : matrix_multiplication, #linalg_gemm2 'BatchNormalization': batch_norm, 'SpatialBN' : batch_norm, + 'LeakyRelu' : leaky_relu, + 'Elu' : _elu, + 'PRelu' : _prelu, + 'Softmax' : softmax, # Changing shape and type. 'Reshape' : reshape, 'Cast' : cast, @@ -70,6 +76,8 @@ 'Reciprocal' : reciprocal, 'Sqrt' : squareroot, 'Pow' : power, + 'Exp' : exponent, + 'Log' : _log, # Reduce Functions 'ReduceMax' : reduce_max, 'ReduceMean' : reduce_mean, diff --git a/python/mxnet/contrib/onnx/_import/import_model.py b/python/mxnet/contrib/onnx/_import/import_model.py index d35a27929cd0..dd6f46b6f804 100644 --- a/python/mxnet/contrib/onnx/_import/import_model.py +++ b/python/mxnet/contrib/onnx/_import/import_model.py @@ -18,10 +18,7 @@ # coding: utf-8 """import function""" # pylint: disable=no-member -try: - import onnx -except ImportError: - raise ImportError("Onnx and protobuf need to be installed") + from .import_onnx import GraphProto @@ -43,6 +40,10 @@ def import_model(model_file): graph = GraphProto() # loads model file and returns ONNX protobuf object + try: + import onnx + except ImportError: + raise ImportError("Onnx and protobuf need to be installed") model_proto = onnx.load(model_file) sym, params = graph.from_onnx(model_proto.graph) return sym, params diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index 7aa9988279bf..19722859f006 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -139,6 +139,10 @@ def ceil(op_name, attrs, inputs): """ Calculate ceil value for input """ return 'ceil', attrs, inputs +def floor(op_name, attrs, inputs): + """ Calculate floor value for input """ + return 'floor', attrs, inputs + # Joining and spliting def concat(op_name, attrs, inputs): """ Joins input arrays along a given axis. """ @@ -175,6 +179,34 @@ def batch_norm(op_name, attrs, inputs): new_attrs = translation_utils._add_extra_attributes(new_attrs, {'cudnn_off': 1}) return 'BatchNorm', new_attrs, inputs +def leaky_relu(op_name, attrs, inputs): + """Leaky Relu function""" + if 'alpha' in attrs: + new_attrs = translation_utils._fix_attribute_names(attrs, {'alpha' : 'slope'}) + else: + new_attrs = translation_utils._add_extra_attributes(attrs, {'slope': 0.01}) + return 'LeakyReLU', new_attrs, inputs + +def _elu(op_name, attrs, inputs): + """Elu function""" + if 'alpha' in attrs: + new_attrs = translation_utils._fix_attribute_names(attrs, {'alpha' : 'slope'}) + else: + new_attrs = translation_utils._add_extra_attributes(attrs, {'slope': 1.0}) + new_attrs = translation_utils._add_extra_attributes(new_attrs, {'act_type': 'elu'}) + return 'LeakyReLU', new_attrs, inputs + +def _prelu(op_name, attrs, inputs): + """PRelu function""" + new_attrs = translation_utils._add_extra_attributes(attrs, {'act_type': 'prelu'}) + return 'LeakyReLU', new_attrs, inputs + +def softmax(op_name, attrs, inputs): + """Softmax function.""" + if 'axis' not in attrs: + attrs = translation_utils._add_extra_attributes(attrs, {'axis': 1}) + return 'softmax', attrs, inputs + # Changing shape and type. def reshape(op_name, attrs, inputs): """Reshape the given array by the shape attribute.""" @@ -243,6 +275,14 @@ def power(op_name, attrs, inputs): return 'broadcast_power', new_attrs, inputs return 'pow', new_attrs, inputs +def exponent(op_name, attrs, inputs): + """Elementwise exponent of input array.""" + return 'exp', attrs, inputs + +def _log(op_name, attrs, inputs): + """Elementwise log of input array.""" + return 'log', attrs, inputs + # Reduce Functions def reduce_max(op_name, attrs, inputs): """Reduce the array along a given axis by maximum value""" diff --git a/tests/integrationtests/onnx_backend_test.py b/tests/integrationtests/onnx_backend_test.py index 9e2f949185bd..c0978219ac28 100644 --- a/tests/integrationtests/onnx_backend_test.py +++ b/tests/integrationtests/onnx_backend_test.py @@ -38,7 +38,7 @@ #Generator Functions #'test_constant*', # Identity Function #'test_random_uniform', - #'test_random_normal' + #'test_random_normal', #Arithmetic Operators 'test_add*', 'test_sub*', @@ -51,6 +51,7 @@ 'test_tanh*', #Rounding 'test_ceil*', + 'test_floor*', ## Joining and spliting # 'test_concat*', ---Failing test #Basic neural network functions @@ -60,6 +61,9 @@ 'test_reflect_pad', 'test_relu', 'test_matmul*', + 'test_leakyrelu*', + 'test_elu*', + #'test_softmax*', #'test_batch_norm', #Changing shape and type. 'test_reshape_*', @@ -78,8 +82,11 @@ 'test_sqrt*', 'test_pow_example', 'test_pow$', - 'test_pow_bcast$' - #'test_pow_bcast_axis0' + 'test_pow_bcast$', + 'test_log$', + 'test_log_example', + 'test_exp*', + #'test_pow_bcast_axis0', # Sorting and Searching 'test_argmax*', 'test_argmin*', From 48b2a7c5d758f84529ee685f1bfa38803ec5ee19 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Thu, 8 Mar 2018 23:00:43 -0800 Subject: [PATCH 18/35] Added operators: - Convolution - Deconvolution Refactored convert_operator --- .../contrib/onnx/_import/import_helper.py | 4 +- .../mxnet/contrib/onnx/_import/import_onnx.py | 2 +- .../contrib/onnx/_import/op_translations.py | 140 ++++++++++-------- .../contrib/onnx/_import/translation_utils.py | 57 ++++++- tests/integrationtests/onnx_backend_test.py | 1 + 5 files changed, 142 insertions(+), 62 deletions(-) diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index fae9695664c4..307403470b5d 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -23,8 +23,8 @@ from .op_translations import tanh from .op_translations import ceil, floor from .op_translations import concat -from .op_translations import sigmoid, pad, relu, matrix_multiplication, batch_norm from .op_translations import leaky_relu, _elu, _prelu, softmax +from .op_translations import sigmoid, pad, relu, matrix_multiplication, batch_norm, conv, deconv from .op_translations import reshape, cast, split, _slice, transpose, squeeze from .op_translations import reciprocal, squareroot, power, exponent, _log from .op_translations import reduce_max, reduce_mean, reduce_min, reduce_sum @@ -59,6 +59,8 @@ 'Relu' : relu, 'Pad' : pad, 'MatMul' : matrix_multiplication, #linalg_gemm2 + 'Conv' : conv, + 'ConvTranspose' : deconv, 'BatchNormalization': batch_norm, 'SpatialBN' : batch_norm, 'LeakyRelu' : leaky_relu, diff --git a/python/mxnet/contrib/onnx/_import/import_onnx.py b/python/mxnet/contrib/onnx/_import/import_onnx.py index 09ea2e445bf2..00e3c4a2e356 100644 --- a/python/mxnet/contrib/onnx/_import/import_onnx.py +++ b/python/mxnet/contrib/onnx/_import/import_onnx.py @@ -56,7 +56,7 @@ def _convert_operator(self, node_name, op_name, attrs, inputs): Converted mxnet symbol """ if op_name in convert_map: - op_name, new_attrs, inputs = convert_map[op_name](op_name, attrs, inputs) + op_name, new_attrs, inputs = convert_map[op_name](attrs, inputs, self) else: raise NotImplementedError("Operator {} not implemented.".format(op_name)) if isinstance(op_name, string_types): diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index 19722859f006..7bf19a8713bd 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -22,86 +22,76 @@ from .... import symbol #Generator Functions -def identity(op_name, attrs, inputs): +def identity(attrs, inputs, cls): """Returns the identity function of the the input.""" return 'identity', attrs, inputs -def random_uniform(op_name, attrs, inputs): +def random_uniform(attrs, inputs, cls): """Draw random samples from a uniform distribtuion.""" new_attr = translation_utils._remove_attributes(attrs, ['seed']) return 'random_uniform', new_attr, inputs -def random_normal(op_name, attrs, inputs): +def random_normal(attrs, inputs, cls): """Draw random samples from a Gaussian distribution.""" new_attr = translation_utils._remove_attributes(attrs, ['seed']) new_attr = translation_utils._fix_attribute_names(new_attr, {'mean' : 'loc'}) return 'random_uniform', new_attr, inputs # Arithmetic Operations -def add(op_name, attrs, inputs): +def add(attrs, inputs, cls): """Adding two tensors""" new_attr = {} if 'broadcast' in attrs and attrs['broadcast'] == 1: - return 'broadcast_add', new_attr, inputs + op = translation_utils._fix_bias_shape('broadcast_add', inputs, attrs, cls) + return op, new_attr, inputs return 'elemwise_add', new_attr, inputs -def subtract(op_name, attrs, inputs): +def subtract(attrs, inputs, cls): """Subtracting two tensors""" new_attr = {} if 'broadcast' in attrs and attrs['broadcast'] == 1: return 'broadcast_sub', new_attr, inputs return 'elemwise_sub', new_attr, inputs -def absolute(op_name, attrs, inputs): - return 'abs', attrs, inputs - - -def negative(op_name, attrs, inputs): - """Negation of every element in a tensor""" - return 'negative', attrs, inputs - -# Sorting and Searching -def argmax(op_name, attrs, inputs): - return 'argmax', attrs, inputs - -def multiply(op_name, attrs, inputs): +def multiply(attrs, inputs, cls): """Multiply two tensors""" new_attr = {} if 'broadcast' in attrs and attrs['broadcast'] == 1: - return 'broadcast_mul', new_attr, inputs + op = translation_utils._fix_bias_shape('broadcast_mul', inputs, attrs, cls) + return op, new_attr, inputs return 'elemwise_mul', new_attr, inputs -def divide(op_name, attrs, inputs): +def divide(attrs, inputs, cls): """Divide two tensors""" new_attr = {} if 'broadcast' in attrs and attrs['broadcast'] == 1: return 'broadcast_div', new_attr, inputs return 'elemwise_div', new_attr, inputs -def absolute(op_name, attrs, inputs): +def absolute(attrs, inputs, cls): """Returns element-wise absolute value of the input.""" return 'abs', attrs, inputs -def negative(op_name, attrs, inputs): +def negative(attrs, inputs, cls): """Negation of every element in a tensor""" return 'negative', attrs, inputs -def add_n(op_name, attrs, inputs): +def add_n(attrs, inputs, cls): """Elementwise sum of arrays""" return 'add_n', attrs, inputs # Sorting and Searching -def argmax(op_name, attrs, inputs): +def argmax(attrs, inputs, cls): """Returns indices of the maximum values along an axis""" return 'argmax', attrs, inputs -def argmin(op_name, attrs, inputs): +def argmin(attrs, inputs, cls): """Returns indices of the minimum values along an axis.""" return 'argmin', attrs, inputs -def maximum(op_name, attrs, inputs): +def maximum(attrs, inputs, cls): """ Elementwise maximum of arrays. MXNet maximum compares only two symbols at a time. @@ -116,7 +106,7 @@ def maximum(op_name, attrs, inputs): mxnet_op = inputs[0] return mxnet_op, attrs, inputs -def minimum(op_name, attrs, inputs): +def minimum(attrs, inputs, cls): """Elementwise minimum of arrays.""" # MXNet minimum compares only two symbols at a time. # ONNX can send more than two to compare. @@ -130,36 +120,36 @@ def minimum(op_name, attrs, inputs): return mxnet_op, attrs, inputs #Hyperbolic functions -def tanh(op_name, attrs, inputs): +def tanh(attrs, inputs, cls): """Returns the hyperbolic tangent of the input array.""" return 'tanh', attrs, inputs # Rounding -def ceil(op_name, attrs, inputs): +def ceil(attrs, inputs, cls): """ Calculate ceil value for input """ return 'ceil', attrs, inputs -def floor(op_name, attrs, inputs): +def floor(attrs, inputs): """ Calculate floor value for input """ return 'floor', attrs, inputs # Joining and spliting -def concat(op_name, attrs, inputs): +def concat(attrs, inputs, cls): """ Joins input arrays along a given axis. """ new_attrs = translation_utils._fix_attribute_names(attrs, {'axis': 'dim'}) return 'concat', new_attrs, inputs # Basic neural network functions -def sigmoid(op_name, attrs, inputs): +def sigmoid(attrs, inputs, cls): """Computes elementwise sigmoid of the input array""" return 'sigmoid', attrs, inputs -def relu(op_name, attrs, inputs): +def relu(attrs, inputs, cls): """Computes rectified linear function.""" return 'relu', attrs, inputs -def pad(op_name, attrs, inputs): +def pad(attrs, inputs, cls): """ Add padding to input tensor""" new_attrs = translation_utils._fix_attribute_names(attrs, {'pads' : 'pad_width', 'value' : 'constant_value' @@ -167,11 +157,11 @@ def pad(op_name, attrs, inputs): new_attrs['pad_width'] = translation_utils._pad_sequence_fix(new_attrs.get('pad_width')) return 'pad', new_attrs, inputs -def matrix_multiplication(op_name, attrs, inputs): +def matrix_multiplication(attrs, inputs, cls): """Performs general matrix multiplication""" return 'linalg_gemm2', attrs, inputs -def batch_norm(op_name, attrs, inputs): +def batch_norm(attrs, inputs, cls): """Batch normalization.""" new_attrs = translation_utils._fix_attribute_names(attrs, {'epsilon' : 'eps'}) new_attrs = translation_utils._remove_attributes(new_attrs, @@ -179,7 +169,8 @@ def batch_norm(op_name, attrs, inputs): new_attrs = translation_utils._add_extra_attributes(new_attrs, {'cudnn_off': 1}) return 'BatchNorm', new_attrs, inputs -def leaky_relu(op_name, attrs, inputs): + +def leaky_relu(attrs, inputs, cls): """Leaky Relu function""" if 'alpha' in attrs: new_attrs = translation_utils._fix_attribute_names(attrs, {'alpha' : 'slope'}) @@ -187,7 +178,7 @@ def leaky_relu(op_name, attrs, inputs): new_attrs = translation_utils._add_extra_attributes(attrs, {'slope': 0.01}) return 'LeakyReLU', new_attrs, inputs -def _elu(op_name, attrs, inputs): +def _elu(attrs, inputs, cls): """Elu function""" if 'alpha' in attrs: new_attrs = translation_utils._fix_attribute_names(attrs, {'alpha' : 'slope'}) @@ -196,34 +187,65 @@ def _elu(op_name, attrs, inputs): new_attrs = translation_utils._add_extra_attributes(new_attrs, {'act_type': 'elu'}) return 'LeakyReLU', new_attrs, inputs -def _prelu(op_name, attrs, inputs): +def _prelu(attrs, inputs, cls): """PRelu function""" new_attrs = translation_utils._add_extra_attributes(attrs, {'act_type': 'prelu'}) return 'LeakyReLU', new_attrs, inputs -def softmax(op_name, attrs, inputs): +def softmax(attrs, inputs, cls): """Softmax function.""" if 'axis' not in attrs: attrs = translation_utils._add_extra_attributes(attrs, {'axis': 1}) return 'softmax', attrs, inputs + +def conv(attrs, inputs, cls): + """Compute N-D convolution on (N+2)-D input.""" + new_attrs = translation_utils._fix_attribute_names(attrs, {'kernel_shape' : 'kernel', + 'strides' : 'stride', + 'pads': 'pad', + 'dilations': 'dilate', + 'group': 'num_group'}) + new_attrs = translation_utils._add_extra_attributes(new_attrs, {'num_group' : 1}) + new_attrs = translation_utils._fix_bias('Convolution', new_attrs, len(inputs)) + + new_attrs = translation_utils._fix_channels('Convolution', new_attrs, inputs, cls) + + return 'Convolution', new_attrs, inputs + + +def deconv(attrs, inputs, cls): + """Compute N-D convolution on (N+2)-D input.""" + new_attrs = translation_utils._fix_attribute_names(attrs, {'kernel_shape' : 'kernel', + 'strides' : 'stride', + 'pads': 'pad', + 'dilations': 'dilate', + 'group': 'num_group'}) + new_attrs = translation_utils._add_extra_attributes(new_attrs, {'num_group' : 1}) + new_attrs = translation_utils._fix_bias('Deconvolution', new_attrs, len(inputs)) + + new_attrs = translation_utils._fix_channels('Deconvolution', new_attrs, inputs, cls) + + return 'Convolution', new_attrs, inputs + + # Changing shape and type. -def reshape(op_name, attrs, inputs): +def reshape(attrs, inputs, cls): """Reshape the given array by the shape attribute.""" return 'reshape', attrs, inputs -def cast(op_name, attrs, inputs): +def cast(attrs, inputs, cls): """ Cast input to a given dtype""" new_attrs = translation_utils._fix_attribute_names(attrs, {'to' : 'dtype'}) return 'cast', new_attrs, inputs -def split(op_name, attrs, inputs): +def split(attrs, inputs, cls): """Splits an array along a particular axis into multiple sub-arrays.""" new_attrs = translation_utils._fix_attribute_names(attrs, {'split' : 'num_outputs'}) return 'split', new_attrs, inputs -def _slice(op_name, attrs, inputs): +def _slice(attrs, inputs, cls): """Returns a slice of the input tensor along multiple axes.""" new_attrs = translation_utils._fix_attribute_names(attrs, {'axes' : 'axis', @@ -240,13 +262,13 @@ def _slice(op_name, attrs, inputs): slice_op = symbol.slice_axis(slice_op, axis=axis, begin=begin[i], end=end[i]) return slice_op, new_attrs, inputs -def transpose(op_name, attrs, inputs): +def transpose(attrs, inputs, cls): """Transpose the input array.""" new_attrs = translation_utils._fix_attribute_names(attrs, {'perm' : 'axes'}) return 'transpose', new_attrs, inputs -def squeeze(op_name, attrs, inputs): +def squeeze(attrs, inputs, cls): """Remove single-dimensional entries from the shape of a tensor.""" # MXNet doesnt have a squeeze operator. # Using "split" to perform similar operation. @@ -259,15 +281,15 @@ def squeeze(op_name, attrs, inputs): return mxnet_op, new_attrs, inputs #Powers -def reciprocal(op_name, attrs, inputs): +def reciprocal(attrs, inputs, cls): """Returns the reciprocal of the argument, element-wise.""" return 'reciprocal', attrs, inputs -def squareroot(op_name, attrs, inputs): +def squareroot(attrs, inputs, cls): """Returns element-wise square-root value of the input.""" return 'sqrt', attrs, inputs -def power(op_name, attrs, inputs): +def power(attrs, inputs, cls): """Returns element-wise result of base element raised to powers from exp element.""" new_attrs = translation_utils._fix_attribute_names(attrs, {'exponent':'exp'}) if 'broadcast' in attrs and attrs['broadcast'] == 1: @@ -275,41 +297,41 @@ def power(op_name, attrs, inputs): return 'broadcast_power', new_attrs, inputs return 'pow', new_attrs, inputs -def exponent(op_name, attrs, inputs): +def exponent(attrs, inputs, cls): """Elementwise exponent of input array.""" return 'exp', attrs, inputs -def _log(op_name, attrs, inputs): +def _log(attrs, inputs, cls): """Elementwise log of input array.""" return 'log', attrs, inputs # Reduce Functions -def reduce_max(op_name, attrs, inputs): +def reduce_max(attrs, inputs, cls): """Reduce the array along a given axis by maximum value""" new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':'axis'}) return 'max', new_attrs, inputs -def reduce_mean(op_name, attrs, inputs): +def reduce_mean(attrs, inputs, cls): """Reduce the array along a given axis by mean value""" new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':'axis'}) return 'mean', new_attrs, inputs -def reduce_min(op_name, attrs, inputs): +def reduce_min(attrs, inputs, cls): """Reduce the array along a given axis by mean value""" new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':'axis'}) return 'min', new_attrs, inputs -def reduce_sum(op_name, attrs, inputs): +def reduce_sum(attrs, inputs, cls): """Reduce the array along a given axis by mean value""" new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':'axis'}) return 'sum', new_attrs, inputs -def reduce_prod(op_name, attrs, inputs): +def reduce_prod(attrs, inputs, cls): """Reduce the array along a given axis by mean value""" new_attrs = translation_utils._fix_attribute_names(attrs, {'axes':'axis'}) return 'prod', new_attrs, inputs -def avg_pooling(op_name, attrs, inputs): +def avg_pooling(attrs, inputs, cls): """ Average pooling""" new_attrs = translation_utils._fix_attribute_names(attrs, {'kernel_shape': 'kernel', @@ -320,7 +342,7 @@ def avg_pooling(op_name, attrs, inputs): {'pool_type': 'avg', 'pooling_convention': 'valid' }) - new_op = translation_utils._fix_pooling(op_name, inputs, new_attrs) + new_op = translation_utils._fix_pooling(inputs, new_attrs) return new_op, new_attrs, inputs def argmax(op_name, attrs, inputs): diff --git a/python/mxnet/contrib/onnx/_import/translation_utils.py b/python/mxnet/contrib/onnx/_import/translation_utils.py index 7eb61cfc8a24..3fd1417f2ace 100644 --- a/python/mxnet/contrib/onnx/_import/translation_utils.py +++ b/python/mxnet/contrib/onnx/_import/translation_utils.py @@ -20,6 +20,7 @@ # pylint: disable= from __future__ import absolute_import as _abs from .... import symbol +from .... import ndarray as nd def _fix_attribute_names(attrs, change_map): """ @@ -62,7 +63,8 @@ def _add_extra_attributes(attrs, extra_attr_map): :return: new_attr """ for attr in extra_attr_map: - attrs[attr] = extra_attr_map[attr] + if attr not in attrs: + attrs[attr] = extra_attr_map[attr] return attrs @@ -94,3 +96,56 @@ def _fix_pooling(op_name, inputs, new_attr): new_pooling_op = symbol.Pooling(new_pad_op, pool_type=pool_type, stride=stride, kernel=kernel) return new_pooling_op + +def _fix_bias(op, attrs, num_inputs): + """A workaround for 'use_bias' attribute since onnx don't provide this attribute, + we have to check the number of inputs to decide it.""" + if num_inputs == 3: + attrs['no_bias'] = False + elif num_inputs == 2: + attrs['no_bias'] = True + else: + raise ValueError("Unexpected number of inputs for: {}".format(op)) + return attrs + +def _fix_bias_shape(op_name, inputs, attrs, cls): + """A workaround to reshape bias term to (1, num_channel).""" + if (int(len(cls._params)) > 0): + assert len(list(inputs)) == 2 + bias_name = cls._renames.get(inputs[1], inputs[1]) + bias = cls._params[bias_name.name] + assert len(bias.shape) == 1 + + op = symbol.reshape(inputs[1], shape=(1,-1,1,1)) + if op_name == 'broadcast_add': + op = symbol.broadcast_add(op, inputs[0]) + elif op_name == 'broadcast_mul': + op = symbol.broadcast_mul(op, inputs[0]) + else: + op = op_name + return op + + +def _fix_channels(op, attrs, inputs, cls): + """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. + """ + weight_name = inputs[1].name + if not weight_name in cls._params: + raise ValueError("Unable to get channels/units attr from onnx graph.") + else: + wshape = cls._params[weight_name].shape + assert len(wshape) >= 2, "Weights shape is invalid: {}".format(wshape) + + if op == 'FullyConnected': + attrs['num_hidden'] = wshape[0] + else: + if op == 'Convolution': + # Weight shape for Conv and FC: (M x C x kH x kW) : M is number of + # feature maps/hidden and C is number of channels + attrs['num_filter'] = wshape[0] + elif op == 'Deconvolution': + # Weight shape for DeConv : (C x M x kH x kW) : M is number of + # feature maps/filters and C is number of channels + attrs['num_filter'] = wshape[1] + return attrs \ No newline at end of file diff --git a/tests/integrationtests/onnx_backend_test.py b/tests/integrationtests/onnx_backend_test.py index c0978219ac28..23101dd51b58 100644 --- a/tests/integrationtests/onnx_backend_test.py +++ b/tests/integrationtests/onnx_backend_test.py @@ -64,6 +64,7 @@ 'test_leakyrelu*', 'test_elu*', #'test_softmax*', + 'test_Conv2d*', #'test_batch_norm', #Changing shape and type. 'test_reshape_*', From 51189a8ab2438fd6e167f41beaf206d04be9a7dc Mon Sep 17 00:00:00 2001 From: spidyDev Date: Thu, 8 Mar 2018 23:22:49 -0800 Subject: [PATCH 19/35] lint fix --- .../contrib/onnx/_import/op_translations.py | 14 +++++--- .../contrib/onnx/_import/translation_utils.py | 33 +++++++++---------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index 7bf19a8713bd..7c2f8d0d3da5 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -42,8 +42,8 @@ def add(attrs, inputs, cls): """Adding two tensors""" new_attr = {} if 'broadcast' in attrs and attrs['broadcast'] == 1: - op = translation_utils._fix_bias_shape('broadcast_add', inputs, attrs, cls) - return op, new_attr, inputs + op_value = translation_utils._fix_bias_shape('broadcast_add', inputs, cls) + return op_value, new_attr, inputs return 'elemwise_add', new_attr, inputs def subtract(attrs, inputs, cls): @@ -58,8 +58,8 @@ def multiply(attrs, inputs, cls): """Multiply two tensors""" new_attr = {} if 'broadcast' in attrs and attrs['broadcast'] == 1: - op = translation_utils._fix_bias_shape('broadcast_mul', inputs, attrs, cls) - return op, new_attr, inputs + op_value = translation_utils._fix_bias_shape('broadcast_mul', inputs, cls) + return op_value, new_attr, inputs return 'elemwise_mul', new_attr, inputs def divide(attrs, inputs, cls): @@ -169,6 +169,7 @@ def batch_norm(attrs, inputs, cls): new_attrs = translation_utils._add_extra_attributes(new_attrs, {'cudnn_off': 1}) return 'BatchNorm', new_attrs, inputs +<<<<<<< HEAD def leaky_relu(attrs, inputs, cls): """Leaky Relu function""" @@ -199,6 +200,8 @@ def softmax(attrs, inputs, cls): return 'softmax', attrs, inputs +======= +>>>>>>> e6db2ca0... lint fix def conv(attrs, inputs, cls): """Compute N-D convolution on (N+2)-D input.""" new_attrs = translation_utils._fix_attribute_names(attrs, {'kernel_shape' : 'kernel', @@ -342,7 +345,8 @@ def avg_pooling(attrs, inputs, cls): {'pool_type': 'avg', 'pooling_convention': 'valid' }) - new_op = translation_utils._fix_pooling(inputs, new_attrs) + new_op = translation_utils._fix_pooling('avg', inputs, new_attrs) + return new_op, new_attrs, inputs def argmax(op_name, attrs, inputs): diff --git a/python/mxnet/contrib/onnx/_import/translation_utils.py b/python/mxnet/contrib/onnx/_import/translation_utils.py index 3fd1417f2ace..03bb67312916 100644 --- a/python/mxnet/contrib/onnx/_import/translation_utils.py +++ b/python/mxnet/contrib/onnx/_import/translation_utils.py @@ -20,7 +20,7 @@ # pylint: disable= from __future__ import absolute_import as _abs from .... import symbol -from .... import ndarray as nd + def _fix_attribute_names(attrs, change_map): """ @@ -84,10 +84,9 @@ def _pad_sequence_fix(attr, kernel_dim=None): return new_attr -def _fix_pooling(op_name, inputs, new_attr): +def _fix_pooling(pool_type, inputs, new_attr): """onnx pooling operator supports asymmetrical padding Adding pad operator before pooling in mxnet to work with onnx""" - pool_type = 'avg' if op_name == 'AveragePool' else 'max' stride = new_attr.get('stride') kernel = new_attr.get('kernel') padding = new_attr.get('pad') @@ -97,7 +96,7 @@ def _fix_pooling(op_name, inputs, new_attr): stride=stride, kernel=kernel) return new_pooling_op -def _fix_bias(op, attrs, num_inputs): +def _fix_bias(op_name, attrs, num_inputs): """A workaround for 'use_bias' attribute since onnx don't provide this attribute, we have to check the number of inputs to decide it.""" if num_inputs == 3: @@ -105,28 +104,28 @@ def _fix_bias(op, attrs, num_inputs): elif num_inputs == 2: attrs['no_bias'] = True else: - raise ValueError("Unexpected number of inputs for: {}".format(op)) + raise ValueError("Unexpected number of inputs for: {}".format(op_name)) return attrs -def _fix_bias_shape(op_name, inputs, attrs, cls): +def _fix_bias_shape(op_name, inputs, cls): """A workaround to reshape bias term to (1, num_channel).""" - if (int(len(cls._params)) > 0): + if int(len(cls._params)) > 0: assert len(list(inputs)) == 2 bias_name = cls._renames.get(inputs[1], inputs[1]) bias = cls._params[bias_name.name] assert len(bias.shape) == 1 - op = symbol.reshape(inputs[1], shape=(1,-1,1,1)) + op_sym = symbol.reshape(inputs[1], shape=(1, -1, 1, 1)) if op_name == 'broadcast_add': - op = symbol.broadcast_add(op, inputs[0]) + op_sym = symbol.broadcast_add(op_sym, inputs[0]) elif op_name == 'broadcast_mul': - op = symbol.broadcast_mul(op, inputs[0]) + op_sym = symbol.broadcast_mul(op_sym, inputs[0]) else: - op = op_name - return op + op_sym = op_name + return op_sym -def _fix_channels(op, attrs, inputs, cls): +def _fix_channels(op_name, attrs, inputs, cls): """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. """ @@ -137,15 +136,15 @@ def _fix_channels(op, attrs, inputs, cls): wshape = cls._params[weight_name].shape assert len(wshape) >= 2, "Weights shape is invalid: {}".format(wshape) - if op == 'FullyConnected': + if op_name == 'FullyConnected': attrs['num_hidden'] = wshape[0] else: - if op == 'Convolution': + if op_name == 'Convolution': # Weight shape for Conv and FC: (M x C x kH x kW) : M is number of # feature maps/hidden and C is number of channels attrs['num_filter'] = wshape[0] - elif op == 'Deconvolution': + elif op_name == 'Deconvolution': # Weight shape for DeConv : (C x M x kH x kW) : M is number of # feature maps/filters and C is number of channels attrs['num_filter'] = wshape[1] - return attrs \ No newline at end of file + return attrs From 69bf6f8d544e9f87322bdbcd605ce1a255df7966 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Thu, 8 Mar 2018 23:40:48 -0800 Subject: [PATCH 20/35] Rebase fix --- python/mxnet/contrib/onnx/_import/op_translations.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index 7c2f8d0d3da5..70ae12e162ea 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -129,7 +129,7 @@ def ceil(attrs, inputs, cls): """ Calculate ceil value for input """ return 'ceil', attrs, inputs -def floor(attrs, inputs): +def floor(attrs, inputs, cls): """ Calculate floor value for input """ return 'floor', attrs, inputs @@ -169,7 +169,6 @@ def batch_norm(attrs, inputs, cls): new_attrs = translation_utils._add_extra_attributes(new_attrs, {'cudnn_off': 1}) return 'BatchNorm', new_attrs, inputs -<<<<<<< HEAD def leaky_relu(attrs, inputs, cls): """Leaky Relu function""" @@ -200,8 +199,6 @@ def softmax(attrs, inputs, cls): return 'softmax', attrs, inputs -======= ->>>>>>> e6db2ca0... lint fix def conv(attrs, inputs, cls): """Compute N-D convolution on (N+2)-D input.""" new_attrs = translation_utils._fix_attribute_names(attrs, {'kernel_shape' : 'kernel', From ee0393a9d8728643d86e1275d6bf9845998d7b46 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Fri, 9 Mar 2018 00:02:52 -0800 Subject: [PATCH 21/35] Added Maxpool operator --- .../mxnet/contrib/onnx/_import/import_helper.py | 3 ++- .../contrib/onnx/_import/op_translations.py | 17 +++++++++++++---- tests/integrationtests/onnx_backend_test.py | 1 + 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index 307403470b5d..f6d825b7f8b7 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -28,7 +28,7 @@ from .op_translations import reshape, cast, split, _slice, transpose, squeeze from .op_translations import reciprocal, squareroot, power, exponent, _log from .op_translations import reduce_max, reduce_mean, reduce_min, reduce_sum -from .op_translations import reduce_prod, avg_pooling +from .op_translations import reduce_prod, avg_pooling, max_pooling from .op_translations import argmax, argmin, maximum, minimum # convert_map defines maps of name to converter functor(callable) @@ -87,6 +87,7 @@ 'ReduceSum' : reduce_sum, 'ReduceProd' : reduce_prod, 'AveragePool' : avg_pooling, + 'MaxPool' : max_pooling, # Sorting and Searching 'ArgMax' : argmax, 'ArgMin' : argmin, diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index 70ae12e162ea..67672ce8e8f2 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -346,9 +346,18 @@ def avg_pooling(attrs, inputs, cls): return new_op, new_attrs, inputs -def argmax(op_name, attrs, inputs): - return 'argmax', attrs, inputs +def max_pooling(attrs, inputs, cls): + """ Average pooling""" + new_attrs = translation_utils._fix_attribute_names(attrs, + {'kernel_shape': 'kernel', + 'strides': 'stride', + 'pads': 'pad', + }) + new_attrs = translation_utils._add_extra_attributes(new_attrs, + {'pool_type': 'avg', + 'pooling_convention': 'valid' + }) + new_op = translation_utils._fix_pooling('max', inputs, new_attrs) -def argmin(op_name, attrs, inputs): - return 'argmin', attrs, inputs + return new_op, new_attrs, inputs diff --git a/tests/integrationtests/onnx_backend_test.py b/tests/integrationtests/onnx_backend_test.py index 23101dd51b58..35881e0bd169 100644 --- a/tests/integrationtests/onnx_backend_test.py +++ b/tests/integrationtests/onnx_backend_test.py @@ -69,6 +69,7 @@ #Changing shape and type. 'test_reshape_*', 'test_AvgPool2D*', + #'test_MaxPool2D*', #'test_cast', #'test_split', 'test_slice$', From 0ebd5b08d6ed0d727e73616b95a6bdf52e78d54d Mon Sep 17 00:00:00 2001 From: spidyDev Date: Fri, 9 Mar 2018 00:23:58 -0800 Subject: [PATCH 22/35] Adding FullyConnected operator --- python/mxnet/contrib/onnx/_import/import_helper.py | 3 ++- python/mxnet/contrib/onnx/_import/op_translations.py | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index f6d825b7f8b7..d593c19e5d2e 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -23,7 +23,7 @@ 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 +from .op_translations import leaky_relu, _elu, _prelu, softmax, fully_connected from .op_translations import sigmoid, pad, relu, matrix_multiplication, batch_norm, conv, deconv from .op_translations import reshape, cast, split, _slice, transpose, squeeze from .op_translations import reciprocal, squareroot, power, exponent, _log @@ -67,6 +67,7 @@ 'Elu' : _elu, 'PRelu' : _prelu, 'Softmax' : softmax, + 'FC' : fully_connected, # Changing shape and type. 'Reshape' : reshape, 'Cast' : cast, diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index 67672ce8e8f2..c743da47b4bb 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -229,6 +229,15 @@ def deconv(attrs, inputs, cls): return 'Convolution', new_attrs, inputs +def fully_connected(attrs, inputs, cls): + new_attrs = translation_utils._remove_attributes({attrs, ['axis']}) + + new_attrs = translation_utils._fix_bias('FullyConnected', new_attrs, len(inputs)) + + new_attrs = translation_utils._fix_channels('FullyConnected', new_attrs, inputs, cls) + + return 'FullyConnected', new_attrs, inputs + # Changing shape and type. def reshape(attrs, inputs, cls): """Reshape the given array by the shape attribute.""" From 40cbe11fab3af196d4d83e188a0a56c2199b5ee6 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Fri, 9 Mar 2018 00:54:25 -0800 Subject: [PATCH 23/35] Adding operator- GlobalPooling - max and avg Minor lint fixes. --- .../contrib/onnx/_import/import_helper.py | 3 +++ .../contrib/onnx/_import/op_translations.py | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index d593c19e5d2e..c2f97fdeac66 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -24,6 +24,7 @@ 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 from .op_translations import sigmoid, pad, relu, matrix_multiplication, batch_norm, conv, deconv from .op_translations import reshape, cast, split, _slice, transpose, squeeze from .op_translations import reciprocal, squareroot, power, exponent, _log @@ -68,6 +69,8 @@ 'PRelu' : _prelu, 'Softmax' : softmax, 'FC' : fully_connected, + 'GlobalAveragePool' : global_avgpooling, + 'GlobalMaxPool' : global_maxpooling, # Changing shape and type. 'Reshape' : reshape, 'Cast' : cast, diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index c743da47b4bb..5bc295f326f8 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -230,7 +230,8 @@ def deconv(attrs, inputs, cls): def fully_connected(attrs, inputs, cls): - new_attrs = translation_utils._remove_attributes({attrs, ['axis']}) + """Applies a linear transformation: Y=XWT+b.""" + new_attrs = translation_utils._remove_attributes(attrs, ['axis']) new_attrs = translation_utils._fix_bias('FullyConnected', new_attrs, len(inputs)) @@ -238,6 +239,22 @@ def fully_connected(attrs, inputs, cls): return 'FullyConnected', new_attrs, inputs + +def global_maxpooling(attrs, inputs, cls): + """Performs max pooling on the input.""" + new_attrs = translation_utils._add_extra_attributes(attrs, {'global_pool': True, + 'kernel': (1, 1), + 'pool_type': 'max'}) + return 'pooling', new_attrs, inputs + + +def global_avgpooling(attrs, inputs, cls): + """Performs avg pooling on the input.""" + new_attrs = translation_utils._add_extra_attributes(attrs, {'global_pool': True, + 'kernel': (1, 1), + 'pool_type': 'avg'}) + return 'pooling', new_attrs, inputs + # Changing shape and type. def reshape(attrs, inputs, cls): """Reshape the given array by the shape attribute.""" From 71517b55df47a4ddc3f03784250816a1800dc087 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Fri, 9 Mar 2018 01:21:19 -0800 Subject: [PATCH 24/35] Adding operator - Gemm --- .../mxnet/contrib/onnx/_import/import_helper.py | 3 ++- .../contrib/onnx/_import/op_translations.py | 9 +++++++++ .../contrib/onnx/_import/translation_utils.py | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index c2f97fdeac66..c36890f638f0 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -24,7 +24,7 @@ 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 +from .op_translations import global_avgpooling, global_maxpooling, linalg_gemm from .op_translations import sigmoid, pad, relu, matrix_multiplication, batch_norm, conv, deconv from .op_translations import reshape, cast, split, _slice, transpose, squeeze from .op_translations import reciprocal, squareroot, power, exponent, _log @@ -71,6 +71,7 @@ 'FC' : fully_connected, 'GlobalAveragePool' : global_avgpooling, 'GlobalMaxPool' : global_maxpooling, + 'Gemm' : linalg_gemm, # Changing shape and type. 'Reshape' : reshape, 'Cast' : cast, diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index 5bc295f326f8..62cfd00a8302 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -255,6 +255,15 @@ def global_avgpooling(attrs, inputs, cls): 'pool_type': 'avg'}) return 'pooling', new_attrs, inputs + +def linalg_gemm(attrs, inputs, cls): + """Performs general matrix multiplication and accumulation""" + new_attrs = translation_utils._fix_attribute_names(attrs, {'transA': 'transpose_a', + 'transB': 'transpose_b'}) + new_attrs = translation_utils._remove_attributes(new_attrs, ['broadcast']) + return translation_utils._fix_gemm('FullyConnected', inputs, new_attrs, cls) + + # Changing shape and type. def reshape(attrs, inputs, cls): """Reshape the given array by the shape attribute.""" diff --git a/python/mxnet/contrib/onnx/_import/translation_utils.py b/python/mxnet/contrib/onnx/_import/translation_utils.py index 03bb67312916..fdaefd6af346 100644 --- a/python/mxnet/contrib/onnx/_import/translation_utils.py +++ b/python/mxnet/contrib/onnx/_import/translation_utils.py @@ -148,3 +148,19 @@ def _fix_channels(op_name, attrs, inputs, cls): # feature maps/filters and C is number of channels attrs['num_filter'] = wshape[1] return attrs + + +def _fix_gemm(op_name, inputs, old_attr, cls): + """Using FullyConnected operator in place of linalg_gemm to perform same operation""" + op_sym = getattr(symbol, op_name, None) + alpha = float(old_attr.get('alpha', 1.0)) + beta = float(old_attr.get('beta', 1.0)) + trans_a = int(old_attr.get('transA', 0)) + trans_b = int(old_attr.get('transB', 0)) + if trans_a: + inputs[0] = symbol.transpose(inputs[0], axes=(1, 0)) + if not trans_b: + inputs[1] = symbol.transpose(inputs[1], axes=(1, 0)) + new_inputs = [alpha*inputs[0], inputs[1], beta*inputs[2]] + new_attr = {'num_hidden' : cls._params[inputs[2].name].shape[0]} + return op_sym, new_attr, new_inputs From 3dd7a2ea0f49b154c24b196488efd7edc033e8ca Mon Sep 17 00:00:00 2001 From: Acharya Date: Fri, 9 Mar 2018 06:16:27 -0800 Subject: [PATCH 25/35] Change test Path, LRN and Dropout operator. --- Jenkinsfile | 4 +- .../contrib/onnx/_import/import_helper.py | 4 ++ .../contrib/onnx/_import/op_translations.py | 14 ++++- tests/ci_build/install/ubuntu_install_onnx.sh | 4 +- .../onnx}/backend.py | 0 .../onnx}/backend_rep.py | 0 .../onnx}/onnx_backend_test.py | 59 +++++++++---------- .../onnx}/test_onnx.py | 2 +- 8 files changed, 51 insertions(+), 36 deletions(-) rename tests/{integrationtests => python-pytest/onnx}/backend.py (100%) rename tests/{integrationtests => python-pytest/onnx}/backend_rep.py (100%) rename tests/{integrationtests => python-pytest/onnx}/onnx_backend_test.py (80%) rename tests/{integrationtests => python-pytest/onnx}/test_onnx.py (98%) diff --git a/Jenkinsfile b/Jenkinsfile index e0d31eb50476..a7e13156f2aa 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -566,8 +566,8 @@ try { init_git() unpack_lib('cpu') timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} cpu --dockerbinary docker PYTHONPATH=./python/ pytest tests/integrationtests/onnx_backend_test.py" - sh "${docker_run} cpu --dockerbinary docker PYTHONPATH=./python/ pytest tests/integrationtests/test_onnx.py" + sh "${docker_run} cpu --dockerbinary docker PYTHONPATH=./python/ pytest tests/python-pytest/onnx/onnx_backend_test.py" + sh "${docker_run} cpu --dockerbinary docker PYTHONPATH=./python/ pytest tests/python-pytest/onnx/test_onnx.py" sh "${docker_run} cpu --dockerbinary docker PYTHONPATH=./python/ python example/onnx/test_super_resolution.py" } } diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index c36890f638f0..825ad02dd07b 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -26,6 +26,8 @@ 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, conv, deconv +from .op_translations import leaky_relu, _elu, _prelu, softmax, local_response_norm +from .op_translations import dropout from .op_translations import reshape, cast, split, _slice, transpose, squeeze from .op_translations import reciprocal, squareroot, power, exponent, _log from .op_translations import reduce_max, reduce_mean, reduce_min, reduce_sum @@ -72,6 +74,8 @@ 'GlobalAveragePool' : global_avgpooling, 'GlobalMaxPool' : global_maxpooling, 'Gemm' : linalg_gemm, + 'LRN' : local_response_norm, + 'Dropout' : dropout, # Changing shape and type. 'Reshape' : reshape, 'Cast' : cast, diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index 62cfd00a8302..de861115fbaa 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -198,7 +198,6 @@ def softmax(attrs, inputs, cls): attrs = translation_utils._add_extra_attributes(attrs, {'axis': 1}) return 'softmax', attrs, inputs - def conv(attrs, inputs, cls): """Compute N-D convolution on (N+2)-D input.""" new_attrs = translation_utils._fix_attribute_names(attrs, {'kernel_shape' : 'kernel', @@ -263,6 +262,19 @@ def linalg_gemm(attrs, inputs, cls): new_attrs = translation_utils._remove_attributes(new_attrs, ['broadcast']) return translation_utils._fix_gemm('FullyConnected', inputs, new_attrs, cls) +def local_response_norm(op_name, attrs, inputs): + """Local Response Normalization.""" + new_attrs = translation_utils._fix_attribute_names(attrs, + {'bias': 'knorm', + 'size' : 'nsize'}) + return 'LRN', attrs, inputs + +def dropout(op_name, attrs, inputs): + """Dropout Regularization.""" + new_attrs = translation_utils._fix_attribute_names(attrs, + {'ratio': 'p'}) + new_attrs = translation_utils._remove_attributes(new_attrs, ['is_test']) + return 'Dropout', new_attrs, inputs # Changing shape and type. def reshape(attrs, inputs, cls): diff --git a/tests/ci_build/install/ubuntu_install_onnx.sh b/tests/ci_build/install/ubuntu_install_onnx.sh index 4468857fae8b..d5c38c3e5587 100755 --- a/tests/ci_build/install/ubuntu_install_onnx.sh +++ b/tests/ci_build/install/ubuntu_install_onnx.sh @@ -22,5 +22,5 @@ set -x # install libraries for onnx's python package on ubuntu apt-get install -y libprotobuf-dev protobuf-compiler -pip2 install pytest==3.4.0 pytest-cov==2.5.1 protobuf==3.0.0 onnx==1.0.1 Pillow==5.0.0 -pip3 install pytest==3.4.0 pytest-cov==2.5.1 protobuf==3.0.0 onnx==1.0.1 Pillow==5.0.0 +pip2 install pytest==3.4.0 pytest-cov==2.5.1 protobuf==3.0.0 onnx==1.0.1 Pillow==5.0.0 tabulate==0.7.5 +pip3 install pytest==3.4.0 pytest-cov==2.5.1 protobuf==3.0.0 onnx==1.0.1 Pillow==5.0.0 tabulate==0.7.5 diff --git a/tests/integrationtests/backend.py b/tests/python-pytest/onnx/backend.py similarity index 100% rename from tests/integrationtests/backend.py rename to tests/python-pytest/onnx/backend.py diff --git a/tests/integrationtests/backend_rep.py b/tests/python-pytest/onnx/backend_rep.py similarity index 100% rename from tests/integrationtests/backend_rep.py rename to tests/python-pytest/onnx/backend_rep.py diff --git a/tests/integrationtests/onnx_backend_test.py b/tests/python-pytest/onnx/onnx_backend_test.py similarity index 80% rename from tests/integrationtests/onnx_backend_test.py rename to tests/python-pytest/onnx/onnx_backend_test.py index 35881e0bd169..0631f84247a9 100644 --- a/tests/integrationtests/onnx_backend_test.py +++ b/tests/python-pytest/onnx/onnx_backend_test.py @@ -40,60 +40,59 @@ #'test_random_uniform', #'test_random_normal', #Arithmetic Operators - 'test_add*', - 'test_sub*', - 'test_mul*', - 'test_div*', - 'test_neg*', - 'test_abs*', - 'test_sum*', + 'test_add', + 'test_sub', + 'test_mul', + 'test_div', + 'test_neg', + 'test_abs', + 'test_sum', #Hyperbolic functions - 'test_tanh*', + 'test_tanh', #Rounding - 'test_ceil*', - 'test_floor*', + 'test_ceil', + 'test_floor', ## Joining and spliting - # 'test_concat*', ---Failing test + #'test_concat.*', #---Failing test #Basic neural network functions - 'test_sigmoid*', + 'test_sigmoid', + 'test_relu', 'test_constant_pad', 'test_edge_pad', 'test_reflect_pad', - 'test_relu', - 'test_matmul*', - 'test_leakyrelu*', - 'test_elu*', + 'test_matmul', + 'test_leakyrelu', + 'test_elu', #'test_softmax*', 'test_Conv2d*', #'test_batch_norm', #Changing shape and type. - 'test_reshape_*', + 'test_reshape_', 'test_AvgPool2D*', #'test_MaxPool2D*', #'test_cast', #'test_split', - 'test_slice$', + 'test_slice_cpu', 'test_default_axes', #make PR against onnx to fix the test name(grep-able) 'test_slice_neg', #'test_slice_start_out_of_bounds', #'test_slice_end_out_of_bounds', #'test_transpose*', - 'test_squeeze$', + 'test_squeeze_', #Powers - 'test_reciprocal*', - 'test_sqrt*', + 'test_reciprocal', + 'test_sqrt', 'test_pow_example', - 'test_pow$', - 'test_pow_bcast$', - 'test_log$', - 'test_log_example', - 'test_exp*', + 'test_pow_cpu', + 'test_pow_bcast_cpu', #'test_pow_bcast_axis0', + 'test_log_', + 'test_exp', # Sorting and Searching - 'test_argmax*', - 'test_argmin*', - 'test_max*', - 'test_min*' + 'test_argmax', + 'test_argmin', + 'test_max', + 'test_min' ] for op_test in IMPLEMENTED_OPERATORS: diff --git a/tests/integrationtests/test_onnx.py b/tests/python-pytest/onnx/test_onnx.py similarity index 98% rename from tests/integrationtests/test_onnx.py rename to tests/python-pytest/onnx/test_onnx.py index 83115c186ba9..9b347b863ef8 100644 --- a/tests/integrationtests/test_onnx.py +++ b/tests/python-pytest/onnx/test_onnx.py @@ -26,7 +26,7 @@ from onnx import helper import backend as mxnet_backend CURR_PATH = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) -sys.path.insert(0, os.path.join(CURR_PATH, '../python/unittest')) +sys.path.insert(0, os.path.join(CURR_PATH, '../../python/unittest')) from common import with_seed @with_seed() From 11be77b7f1772c0f4d5debac9d6d05185f39aa40 Mon Sep 17 00:00:00 2001 From: Acharya Date: Fri, 9 Mar 2018 09:03:56 -0800 Subject: [PATCH 26/35] Add asserts for the super_res example. Signed-off-by: Acharya --- example/onnx/test_super_resolution.py | 28 +++++++++++++++++++ .../contrib/onnx/_import/op_translations.py | 2 +- .../contrib/onnx/_import/translation_utils.py | 2 +- tests/python-pytest/onnx/backend.py | 3 -- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/example/onnx/test_super_resolution.py b/example/onnx/test_super_resolution.py index 354bdeaaab08..a9da69d20693 100644 --- a/example/onnx/test_super_resolution.py +++ b/example/onnx/test_super_resolution.py @@ -42,6 +42,34 @@ def import_onnx(): sym, params = onnx_mxnet.import_model('super_resolution.onnx') assert sym is not None assert params is not None + + inputs = sym.list_inputs() + assert len(inputs) == 9 + for i, input_param in enumerate(['param_7', 'param_5', 'param_3', 'param_1', + 'input_0', 'param_0', 'param_2', 'param_4', 'param_6']): + assert inputs[i] == input_param + + assert len(sym.list_outputs()) == 1 + assert sym.list_outputs()[0] == 'reshape5_output' + + assert len(sym.list_attr()) == 1 + assert sym.list_attr()['shape'] == '(1L, 1L, 672L, 672L)' + + attrs_keys = sym.attr_dict().keys() + assert len(attrs_keys) == 19 + for i, key_item in enumerate(['reshape4', 'param_5', 'param_4', 'param_7', + 'param_6', 'param_1', 'param_0', 'param_3', + 'param_2', 'reshape2', 'reshape3', 'reshape0', + 'reshape1', 'convolution2', 'convolution3', + 'convolution0', 'convolution1', 'reshape5', + 'transpose0']): + assert attrs_keys[i] == key_item + + param_keys = params.keys() + assert len(param_keys) == 8 + for i, param_item in enumerate(['param_5', 'param_4', 'param_7', 'param_6', + 'param_1', 'param_0', 'param_3', 'param_2']): + assert param_keys[i] == param_item return sym, params def get_test_image(): diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index de861115fbaa..7fa6504b6a3c 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -267,7 +267,7 @@ def local_response_norm(op_name, attrs, inputs): new_attrs = translation_utils._fix_attribute_names(attrs, {'bias': 'knorm', 'size' : 'nsize'}) - return 'LRN', attrs, inputs + return 'LRN', new_attrs, inputs def dropout(op_name, attrs, inputs): """Dropout Regularization.""" diff --git a/python/mxnet/contrib/onnx/_import/translation_utils.py b/python/mxnet/contrib/onnx/_import/translation_utils.py index fdaefd6af346..8e143763bbb9 100644 --- a/python/mxnet/contrib/onnx/_import/translation_utils.py +++ b/python/mxnet/contrib/onnx/_import/translation_utils.py @@ -17,7 +17,7 @@ # coding: utf-8 """Utilities used for translating operators from Onnx to Mxnet.""" -# pylint: disable= +# pylint: disable=protected-access from __future__ import absolute_import as _abs from .... import symbol diff --git a/tests/python-pytest/onnx/backend.py b/tests/python-pytest/onnx/backend.py index 40ad2425b63e..3b99563bccf3 100644 --- a/tests/python-pytest/onnx/backend.py +++ b/tests/python-pytest/onnx/backend.py @@ -32,7 +32,6 @@ # MXNetBackend class will take an ONNX model with inputs, perform a computation, # and then return the output. - class MXNetBackend(Backend): """MXNet backend for ONNX""" @@ -50,8 +49,6 @@ def make_graph(node, inputs): # Creating an initializer for Weight params. # Assumes that weight params is named as 'W'. - # TODO: Handle multiple weight params. - # TODO: Add for "bias" if needed if node.input[index] == 'W': dim = inputs[index].shape param_tensor = helper.make_tensor( From d98de71358bb4745152abef43edcbce21a126289 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Fri, 9 Mar 2018 12:16:50 -0800 Subject: [PATCH 27/35] Fixing conv test failures. Removed redundant code --- python/mxnet/contrib/onnx/_import/translation_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/mxnet/contrib/onnx/_import/translation_utils.py b/python/mxnet/contrib/onnx/_import/translation_utils.py index 8e143763bbb9..2877e8d24db0 100644 --- a/python/mxnet/contrib/onnx/_import/translation_utils.py +++ b/python/mxnet/contrib/onnx/_import/translation_utils.py @@ -111,9 +111,9 @@ def _fix_bias_shape(op_name, inputs, cls): """A workaround to reshape bias term to (1, num_channel).""" if int(len(cls._params)) > 0: assert len(list(inputs)) == 2 - bias_name = cls._renames.get(inputs[1], inputs[1]) - bias = cls._params[bias_name.name] - assert len(bias.shape) == 1 + # bias_name = cls._renames.get(inputs[1].name, inputs[1].name) + # bias = cls._params[bias_name] + # assert len(bias.shape) == 1 op_sym = symbol.reshape(inputs[1], shape=(1, -1, 1, 1)) if op_name == 'broadcast_add': From 4ef7c3687a51a21c32809b372f6fdd1b963f80a5 Mon Sep 17 00:00:00 2001 From: Acharya Date: Fri, 9 Mar 2018 20:45:43 -0800 Subject: [PATCH 28/35] Update Jenkins job. Signed-off-by: Acharya --- Jenkinsfile | 6 ++---- ci/docker/Dockerfile.build.ubuntu_cpu | 2 ++ ci/docker/install/amzn_linux_testdeps.sh | 2 +- .../docker/install/ubuntu_onnx.sh | 9 ++++++-- ci/docker/runtime_functions.sh | 8 +++++++ docker/install/python.sh | 2 +- .../contrib/onnx/_import/import_helper.py | 5 ++--- .../mxnet/contrib/onnx/_import/import_onnx.py | 5 ++++- tests/ci_build/Dockerfile.cpu | 14 ------------- tests/python-pytest/onnx/onnx_backend_test.py | 21 ++++++++++++++----- .../onnx/{test_onnx.py => onnx_test.py} | 0 11 files changed, 43 insertions(+), 31 deletions(-) rename tests/ci_build/install/ubuntu_install_onnx.sh => ci/docker/install/ubuntu_onnx.sh (74%) delete mode 100644 tests/ci_build/Dockerfile.cpu rename tests/python-pytest/onnx/{test_onnx.py => onnx_test.py} (100%) diff --git a/Jenkinsfile b/Jenkinsfile index a7e13156f2aa..3c4994fdcf1d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -560,15 +560,13 @@ try { } stage('Integration Test') { - parallel 'Onnx CPU': { + parallel 'Onnx CPU': { node('mxnetlinux-cpu') { ws('workspace/it-onnx-cpu') { init_git() unpack_lib('cpu') timeout(time: max_time, unit: 'MINUTES') { - sh "${docker_run} cpu --dockerbinary docker PYTHONPATH=./python/ pytest tests/python-pytest/onnx/onnx_backend_test.py" - sh "${docker_run} cpu --dockerbinary docker PYTHONPATH=./python/ pytest tests/python-pytest/onnx/test_onnx.py" - sh "${docker_run} cpu --dockerbinary docker PYTHONPATH=./python/ python example/onnx/test_super_resolution.py" + sh "ci/build.py --docker --build --platform ubuntu_cpu /work/runtime_functions.sh integrationtest_ubuntu_cpu_onnx" } } } diff --git a/ci/docker/Dockerfile.build.ubuntu_cpu b/ci/docker/Dockerfile.build.ubuntu_cpu index f86c2f2e724b..41088ba98e1f 100755 --- a/ci/docker/Dockerfile.build.ubuntu_cpu +++ b/ci/docker/Dockerfile.build.ubuntu_cpu @@ -46,6 +46,8 @@ COPY install/ubuntu_docs.sh /work/ RUN /work/ubuntu_docs.sh COPY install/ubuntu_adduser.sh /work/ RUN /work/ubuntu_adduser.sh +COPY install/ubuntu_onnx.sh /work/ +RUN /work/ubuntu_onnx.sh COPY runtime_functions.sh /work/ diff --git a/ci/docker/install/amzn_linux_testdeps.sh b/ci/docker/install/amzn_linux_testdeps.sh index 25afecc2055e..f5c49d9e37bf 100755 --- a/ci/docker/install/amzn_linux_testdeps.sh +++ b/ci/docker/install/amzn_linux_testdeps.sh @@ -24,4 +24,4 @@ set -ex pip install cpplint 'pylint==1.4.4' 'astroid==1.3.6' pip3 install nose ln -s -f /opt/bin/nosetests /usr/local/bin/nosetests3 -ln -s -f /opt/bin/nosetests-3.4 /usr/local/bin/nosetests-3.4 +ln -s -f /opt/bin/nosetests-3.4 /usr/local/bin/nosetests-3.4 \ No newline at end of file diff --git a/tests/ci_build/install/ubuntu_install_onnx.sh b/ci/docker/install/ubuntu_onnx.sh similarity index 74% rename from tests/ci_build/install/ubuntu_install_onnx.sh rename to ci/docker/install/ubuntu_onnx.sh index d5c38c3e5587..d6889bd0e5e5 100755 --- a/tests/ci_build/install/ubuntu_install_onnx.sh +++ b/ci/docker/install/ubuntu_onnx.sh @@ -22,5 +22,10 @@ set -x # install libraries for onnx's python package on ubuntu apt-get install -y libprotobuf-dev protobuf-compiler -pip2 install pytest==3.4.0 pytest-cov==2.5.1 protobuf==3.0.0 onnx==1.0.1 Pillow==5.0.0 tabulate==0.7.5 -pip3 install pytest==3.4.0 pytest-cov==2.5.1 protobuf==3.0.0 onnx==1.0.1 Pillow==5.0.0 tabulate==0.7.5 +pip2 install pytest==3.4.0 pytest-cov==2.5.1 protobuf==3.0.0 Pillow==5.0.0 tabulate==0.7.5 +pip3 install pytest==3.4.0 pytest-cov==2.5.1 protobuf==3.0.0 Pillow==5.0.0 tabulate==0.7.5 + +git clone --recursive https://github.com/onnx/onnx.git +cd onnx +git checkout 7e205b66190f4376c64741ba7705dc23e9fbf225 +python setup.py install \ No newline at end of file diff --git a/ci/docker/runtime_functions.sh b/ci/docker/runtime_functions.sh index 89ea44fa1ef5..91fe626d5b37 100755 --- a/ci/docker/runtime_functions.sh +++ b/ci/docker/runtime_functions.sh @@ -386,6 +386,14 @@ unittest_centos7_gpu() { python3.6 -m "nose" --with-timer --verbose tests/python/gpu } +integrationtest_ubuntu_cpu_onnx() { + set -ex + export PYTHONPATH=./python/ + pytest tests/python-pytest/onnx/onnx_backend_test.py + pytest tests/python-pytest/onnx/onnx_test.py + python example/onnx/test_super_resolution.py +} + integrationtest_ubuntu_gpu_python() { set -ex export PYTHONPATH=./python/ diff --git a/docker/install/python.sh b/docker/install/python.sh index 5fe37b32f584..ba71246babbf 100755 --- a/docker/install/python.sh +++ b/docker/install/python.sh @@ -25,4 +25,4 @@ apt-get update && apt-get install -y python-dev python3-dev cd /tmp && wget https://bootstrap.pypa.io/get-pip.py && python3 get-pip.py && python2 get-pip.py pip2 install nose pylint numpy nose-timer requests Pillow -pip3 install nose pylint numpy nose-timer requests Pillow \ No newline at end of file +pip3 install nose pylint numpy nose-timer requests Pillow diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index 825ad02dd07b..0584e572f025 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -25,9 +25,8 @@ 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, conv, deconv -from .op_translations import leaky_relu, _elu, _prelu, softmax, local_response_norm -from .op_translations import dropout +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 from .op_translations import reciprocal, squareroot, power, exponent, _log from .op_translations import reduce_max, reduce_mean, reduce_min, reduce_sum diff --git a/python/mxnet/contrib/onnx/_import/import_onnx.py b/python/mxnet/contrib/onnx/_import/import_onnx.py index 00e3c4a2e356..56181c777be4 100644 --- a/python/mxnet/contrib/onnx/_import/import_onnx.py +++ b/python/mxnet/contrib/onnx/_import/import_onnx.py @@ -39,7 +39,7 @@ def _convert_operator(self, node_name, op_name, attrs, inputs): """Convert from onnx operator to mxnet operator. The converter must specify conversions explicitly for incompatible name, and apply handlers to operator attributes. - + Parameters ---------- :param node_name : str @@ -150,6 +150,9 @@ def _parse_attr(self, attr_proto): for f in ['f', 'i', 's']: if a.HasField(f): attrs[a.name] = getattr(a, f) + # Needed for supporting python version > 3.5 + if isinstance(attrs[a.name], bytes): + attrs[a.name] = attrs[a.name].decode(encoding='utf-8') for f in ['floats', 'ints', 'strings']: if list(getattr(a, f)): assert a.name not in attrs, "Only one type of attr is allowed" diff --git a/tests/ci_build/Dockerfile.cpu b/tests/ci_build/Dockerfile.cpu deleted file mode 100644 index ea636ed48462..000000000000 --- a/tests/ci_build/Dockerfile.cpu +++ /dev/null @@ -1,14 +0,0 @@ -FROM ubuntu:16.04 - -COPY install/ubuntu_install_core.sh /install/ -RUN /install/ubuntu_install_core.sh -COPY install/ubuntu_install_python.sh /install/ -RUN /install/ubuntu_install_python.sh -COPY install/ubuntu_install_onnx.sh /install/ -RUN /install/ubuntu_install_onnx.sh -COPY install/ubuntu_install_scala.sh /install/ -RUN /install/ubuntu_install_scala.sh -COPY install/ubuntu_install_r.sh /install/ -RUN /install/ubuntu_install_r.sh -COPY install/ubuntu_install_perl.sh /install/ -RUN /install/ubuntu_install_perl.sh diff --git a/tests/python-pytest/onnx/onnx_backend_test.py b/tests/python-pytest/onnx/onnx_backend_test.py index 0631f84247a9..28e2aaefcdd4 100644 --- a/tests/python-pytest/onnx/onnx_backend_test.py +++ b/tests/python-pytest/onnx/onnx_backend_test.py @@ -39,6 +39,7 @@ #'test_constant*', # Identity Function #'test_random_uniform', #'test_random_normal', + #Arithmetic Operators 'test_add', 'test_sub', @@ -47,28 +48,36 @@ 'test_neg', 'test_abs', 'test_sum', + #Hyperbolic functions 'test_tanh', + #Rounding 'test_ceil', 'test_floor', + ## Joining and spliting #'test_concat.*', #---Failing test + #Basic neural network functions 'test_sigmoid', 'test_relu', - 'test_constant_pad', - 'test_edge_pad', - 'test_reflect_pad', + #'test_constant_pad', + #'test_edge_pad', + #'test_reflect_pad', 'test_matmul', 'test_leakyrelu', 'test_elu', #'test_softmax*', - 'test_Conv2d*', + 'test_conv', + 'test_basic_conv', + #'test_globalmaxpool', + #'test_globalaveragepool', #'test_batch_norm', + #Changing shape and type. 'test_reshape_', - 'test_AvgPool2D*', + #'test_AvgPool2D*', #'test_MaxPool2D*', #'test_cast', #'test_split', @@ -79,6 +88,7 @@ #'test_slice_end_out_of_bounds', #'test_transpose*', 'test_squeeze_', + #Powers 'test_reciprocal', 'test_sqrt', @@ -88,6 +98,7 @@ #'test_pow_bcast_axis0', 'test_log_', 'test_exp', + # Sorting and Searching 'test_argmax', 'test_argmin', diff --git a/tests/python-pytest/onnx/test_onnx.py b/tests/python-pytest/onnx/onnx_test.py similarity index 100% rename from tests/python-pytest/onnx/test_onnx.py rename to tests/python-pytest/onnx/onnx_test.py From e96b41b207b7e2fc7a2317280374a996db56b019 Mon Sep 17 00:00:00 2001 From: spidyDev Date: Sat, 10 Mar 2018 10:23:36 -0800 Subject: [PATCH 29/35] Nits: Removing commented out code --- python/mxnet/contrib/onnx/_import/translation_utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/python/mxnet/contrib/onnx/_import/translation_utils.py b/python/mxnet/contrib/onnx/_import/translation_utils.py index 2877e8d24db0..0fdef647b50b 100644 --- a/python/mxnet/contrib/onnx/_import/translation_utils.py +++ b/python/mxnet/contrib/onnx/_import/translation_utils.py @@ -111,9 +111,6 @@ def _fix_bias_shape(op_name, inputs, cls): """A workaround to reshape bias term to (1, num_channel).""" if int(len(cls._params)) > 0: assert len(list(inputs)) == 2 - # bias_name = cls._renames.get(inputs[1].name, inputs[1].name) - # bias = cls._params[bias_name] - # assert len(bias.shape) == 1 op_sym = symbol.reshape(inputs[1], shape=(1, -1, 1, 1)) if op_name == 'broadcast_add': From f979d6cc53ded7c4150c68e89b48ac98110fba02 Mon Sep 17 00:00:00 2001 From: Acharya Date: Sat, 10 Mar 2018 15:00:11 -0800 Subject: [PATCH 30/35] Rebase after Docker PR --- dmlc-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dmlc-core b/dmlc-core index a1fd6834c0cd..282b98663f59 160000 --- a/dmlc-core +++ b/dmlc-core @@ -1 +1 @@ -Subproject commit a1fd6834c0cd3fd2cc586deec2dc24194924cada +Subproject commit 282b98663f59df6b26f906580af610dea3046f22 From 1d024902a04f0c1027d473b2435acbd36535527c Mon Sep 17 00:00:00 2001 From: Acharya Date: Mon, 12 Mar 2018 14:00:35 -0700 Subject: [PATCH 31/35] Fetch test files by version number. Verify the high resolution example. Signed-off-by: Acharya --- Jenkinsfile | 2 +- ci/docker/Dockerfile.build.ubuntu_cpu | 4 +- ci/docker/runtime_functions.sh | 2 +- ...uper_resolution.py => super_resolution.py} | 43 ++------------ .../contrib/onnx/_import/import_helper.py | 3 +- .../contrib/onnx/_import/op_translations.py | 3 +- python/mxnet/test_utils.py | 10 +++- tests/python-pytest/onnx/onnx_test.py | 57 ++++++++++++++++++- 8 files changed, 76 insertions(+), 48 deletions(-) rename example/onnx/{test_super_resolution.py => super_resolution.py} (66%) diff --git a/Jenkinsfile b/Jenkinsfile index e2b3874e6674..f84976b31a2e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -560,7 +560,7 @@ try { } stage('Integration Test') { - parallel 'Onnx CPU': { + parallel 'Onnx CPU': { node('mxnetlinux-cpu') { ws('workspace/it-onnx-cpu') { init_git() diff --git a/ci/docker/Dockerfile.build.ubuntu_cpu b/ci/docker/Dockerfile.build.ubuntu_cpu index 41088ba98e1f..d652a0d89c58 100755 --- a/ci/docker/Dockerfile.build.ubuntu_cpu +++ b/ci/docker/Dockerfile.build.ubuntu_cpu @@ -42,12 +42,12 @@ COPY install/ubuntu_mklml.sh /work/ RUN /work/ubuntu_mklml.sh COPY install/ubuntu_caffe.sh /work/ RUN /work/ubuntu_caffe.sh +COPY install/ubuntu_onnx.sh /work/ +RUN /work/ubuntu_onnx.sh COPY install/ubuntu_docs.sh /work/ RUN /work/ubuntu_docs.sh COPY install/ubuntu_adduser.sh /work/ RUN /work/ubuntu_adduser.sh -COPY install/ubuntu_onnx.sh /work/ -RUN /work/ubuntu_onnx.sh COPY runtime_functions.sh /work/ diff --git a/ci/docker/runtime_functions.sh b/ci/docker/runtime_functions.sh index e7f499174137..59a25fa4c1c5 100755 --- a/ci/docker/runtime_functions.sh +++ b/ci/docker/runtime_functions.sh @@ -389,7 +389,7 @@ unittest_centos7_gpu() { integrationtest_ubuntu_cpu_onnx() { set -ex export PYTHONPATH=./python/ - python example/onnx/test_super_resolution.py + python example/onnx/super_resolution.py pytest tests/python-pytest/onnx/onnx_backend_test.py pytest tests/python-pytest/onnx/onnx_test.py } diff --git a/example/onnx/test_super_resolution.py b/example/onnx/super_resolution.py similarity index 66% rename from example/onnx/test_super_resolution.py rename to example/onnx/super_resolution.py index 0404aed93699..4e18a50f8247 100644 --- a/example/onnx/test_super_resolution.py +++ b/example/onnx/super_resolution.py @@ -31,47 +31,14 @@ LOGGER = logging.getLogger() LOGGER.setLevel(logging.INFO) -def download_onnx_model(): - """Download the onnx model""" - model_url = 'https://s3.amazonaws.com/onnx-mxnet/examples/super_resolution.onnx' - download(model_url, 'super_resolution.onnx') - def import_onnx(): """Import the onnx model into mxnet""" + model_url = 'https://s3.amazonaws.com/onnx-mxnet/examples/super_resolution.onnx' + download(model_url, 'super_resolution.onnx', version_tag = '"7348c879d16c42bc77e24e270f663524"') + LOGGER.info("Converting onnx format to mxnet's symbol and params...") sym, params = onnx_mxnet.import_model('super_resolution.onnx') LOGGER.info("Successfully Converted onnx format to mxnet's symbol and params...") - assert sym is not None - assert params is not None - - inputs = sym.list_inputs() - assert len(inputs) == 9 - for i, input_param in enumerate(['param_7', 'param_5', 'param_3', 'param_1', - 'input_0', 'param_0', 'param_2', 'param_4', 'param_6']): - assert inputs[i] == input_param - - assert len(sym.list_outputs()) == 1 - assert sym.list_outputs()[0] == 'reshape5_output' - - assert len(sym.list_attr()) == 1 - assert sym.list_attr()['shape'] == '(1L, 1L, 672L, 672L)' - - attrs_keys = sym.attr_dict().keys() - assert len(attrs_keys) == 19 - for i, key_item in enumerate(['reshape4', 'param_5', 'param_4', 'param_7', - 'param_6', 'param_1', 'param_0', 'param_3', - 'param_2', 'reshape2', 'reshape3', 'reshape0', - 'reshape1', 'convolution2', 'convolution3', - 'convolution0', 'convolution1', 'reshape5', - 'transpose0']): - assert attrs_keys[i] == key_item - - param_keys = params.keys() - assert len(param_keys) == 8 - for i, param_item in enumerate(['param_5', 'param_4', 'param_7', 'param_6', - 'param_1', 'param_0', 'param_3', 'param_2']): - assert param_keys[i] == param_item - LOGGER.info("Asserted the result of the onnx model conversion") return sym, params def get_test_image(): @@ -79,7 +46,7 @@ def get_test_image(): # Load test image input_image_dim = 224 img_url = 'https://s3.amazonaws.com/onnx-mxnet/examples/super_res_input.jpg' - download(img_url, 'super_res_input.jpg') + download(img_url, 'super_res_input.jpg', version_tag = '"02c90a7248e51316b11f7f39dd1b226d"') img = Image.open('super_res_input.jpg').resize((input_image_dim, input_image_dim)) img_ycbcr = img.convert("YCbCr") img_y, img_cb, img_cr = img_ycbcr.split() @@ -109,7 +76,7 @@ def perform_inference((sym, params), (input_img, img_cb, img_cr)): assert result_img.size == (output_img_dim, output_img_dim) LOGGER.info("Super Resolution example success.") result_img.save("super_res_output.jpg") + return result_img if __name__ == '__main__': - download_onnx_model() perform_inference(import_onnx(), get_test_image()) diff --git a/python/mxnet/contrib/onnx/_import/import_helper.py b/python/mxnet/contrib/onnx/_import/import_helper.py index 0584e572f025..80541ec35774 100644 --- a/python/mxnet/contrib/onnx/_import/import_helper.py +++ b/python/mxnet/contrib/onnx/_import/import_helper.py @@ -33,7 +33,8 @@ from .op_translations import reduce_prod, avg_pooling, max_pooling from .op_translations import argmax, argmin, maximum, minimum -# convert_map defines maps of name to converter functor(callable) +# convert_map defines maps of ONNX operator names to converter functor(callable) +# defined in the op_translations module. _convert_map = { # Generator Functions 'Constant' : identity, diff --git a/python/mxnet/contrib/onnx/_import/op_translations.py b/python/mxnet/contrib/onnx/_import/op_translations.py index 7fa6504b6a3c..a67c18199eb8 100644 --- a/python/mxnet/contrib/onnx/_import/op_translations.py +++ b/python/mxnet/contrib/onnx/_import/op_translations.py @@ -21,7 +21,8 @@ from . import translation_utils from .... import symbol -#Generator Functions +# Method definitions for the callable objects mapped in the import_helper module + def identity(attrs, inputs, cls): """Returns the identity function of the the input.""" return 'identity', attrs, inputs diff --git a/python/mxnet/test_utils.py b/python/mxnet/test_utils.py index 91459de9d2ad..02c86cf96ddb 100644 --- a/python/mxnet/test_utils.py +++ b/python/mxnet/test_utils.py @@ -1367,7 +1367,8 @@ def list_gpus(): pass return range(len([i for i in re.split('\n') if 'GPU' in i])) -def download(url, fname=None, dirname=None, overwrite=False): + +def download(url, fname=None, dirname=None, overwrite=False, version_tag=None): """Download an given URL Parameters @@ -1385,6 +1386,8 @@ def download(url, fname=None, dirname=None, overwrite=False): Default is false, which means skipping download if the local file exists. If true, then download the url to overwrite the local file if exists. + version_tag : str, optional + the version tag of the file. Returns ------- @@ -1407,12 +1410,15 @@ def download(url, fname=None, dirname=None, overwrite=False): if exc.errno != errno.EEXIST: raise OSError('failed to create ' + dirname) - if not overwrite and os.path.exists(fname): + if not overwrite and os.path.exists(fname) and not version_tag: logging.info("%s exists, skipping download", fname) return fname r = requests.get(url, stream=True) assert r.status_code == 200, "failed to open %s" % url + if version_tag and r.headers['ETag'] != version_tag: + logging.info("The version tag of the file does not match the expected version. " + + "Proceeding with the file download...") with open(fname, 'wb') as f: for chunk in r.iter_content(chunk_size=1024): if chunk: # filter out keep-alive new chunks diff --git a/tests/python-pytest/onnx/onnx_test.py b/tests/python-pytest/onnx/onnx_test.py index 9b347b863ef8..ea1058090150 100644 --- a/tests/python-pytest/onnx/onnx_test.py +++ b/tests/python-pytest/onnx/onnx_test.py @@ -15,12 +15,17 @@ # specific language governing permissions and limitations # under the License. -"""Tests for individual operators""" - +""" +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. +""" from __future__ import absolute_import import sys import os import unittest +import logging import numpy as np import numpy.testing as npt from onnx import helper @@ -29,6 +34,11 @@ sys.path.insert(0, os.path.join(CURR_PATH, '../../python/unittest')) from common import with_seed +# set up logger +logging.basicConfig() +LOGGER = logging.getLogger() +LOGGER.setLevel(logging.INFO) + @with_seed() def test_reduce_max(): """Test for ReduceMax operator""" @@ -82,5 +92,48 @@ def test_squeeze(): output = mxnet_backend.run_node(node_def, [input1])[0] npt.assert_almost_equal(output, np.squeeze(input1, axis=[1, 3])) +def test_super_resolution(): + """Test the super resolution example in the example/onnx folder""" + sys.path.insert(0, os.path.join(CURR_PATH, '../../../example/onnx/')) + import super_resolution + + sym, params = super_resolution.import_onnx() + assert sym is not None + assert params is not None + + inputs = sym.list_inputs() + assert len(inputs) == 9 + for i, input_param in enumerate(['param_7', 'param_5', 'param_3', 'param_1', + 'input_0', 'param_0', 'param_2', 'param_4', 'param_6']): + assert inputs[i] == input_param + + assert len(sym.list_outputs()) == 1 + assert sym.list_outputs()[0] == 'reshape5_output' + + assert len(sym.list_attr()) == 1 + assert sym.list_attr()['shape'] == '(1L, 1L, 672L, 672L)' + + attrs_keys = sym.attr_dict().keys() + assert len(attrs_keys) == 19 + for i, key_item in enumerate(['reshape4', 'param_5', 'param_4', 'param_7', + 'param_6', 'param_1', 'param_0', 'param_3', + 'param_2', 'reshape2', 'reshape3', 'reshape0', + 'reshape1', 'convolution2', 'convolution3', + 'convolution0', 'convolution1', 'reshape5', + 'transpose0']): + assert attrs_keys[i] == key_item + + param_keys = params.keys() + assert len(param_keys) == 8 + for i, param_item in enumerate(['param_5', 'param_4', 'param_7', 'param_6', + 'param_1', 'param_0', 'param_3', 'param_2']): + assert param_keys[i] == param_item + LOGGER.info("Asserted the result of the onnx model conversion") + + output_img_dim = 672 + result_img = super_resolution.perform_inference((sym, params), + super_resolution.get_test_image()) + assert result_img.size == (output_img_dim, output_img_dim) + if __name__ == '__main__': unittest.main() From b4b6f9a0ac521cd23af7fe5102bdd0cc90139f07 Mon Sep 17 00:00:00 2001 From: Acharya Date: Mon, 12 Mar 2018 17:04:44 -0700 Subject: [PATCH 32/35] Fix method arguments for Python3.5+ Signed-off-by: Acharya --- example/onnx/super_resolution.py | 10 ++++++---- tests/python-pytest/onnx/onnx_test.py | 13 ++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/example/onnx/super_resolution.py b/example/onnx/super_resolution.py index 4e18a50f8247..544f791293d4 100644 --- a/example/onnx/super_resolution.py +++ b/example/onnx/super_resolution.py @@ -34,7 +34,7 @@ def import_onnx(): """Import the onnx model into mxnet""" model_url = 'https://s3.amazonaws.com/onnx-mxnet/examples/super_resolution.onnx' - download(model_url, 'super_resolution.onnx', version_tag = '"7348c879d16c42bc77e24e270f663524"') + download(model_url, 'super_resolution.onnx', version_tag='"7348c879d16c42bc77e24e270f663524"') LOGGER.info("Converting onnx format to mxnet's symbol and params...") sym, params = onnx_mxnet.import_model('super_resolution.onnx') @@ -46,14 +46,14 @@ def get_test_image(): # Load test image input_image_dim = 224 img_url = 'https://s3.amazonaws.com/onnx-mxnet/examples/super_res_input.jpg' - download(img_url, 'super_res_input.jpg', version_tag = '"02c90a7248e51316b11f7f39dd1b226d"') + download(img_url, 'super_res_input.jpg', version_tag='"02c90a7248e51316b11f7f39dd1b226d"') img = Image.open('super_res_input.jpg').resize((input_image_dim, input_image_dim)) img_ycbcr = img.convert("YCbCr") img_y, img_cb, img_cr = img_ycbcr.split() input_image = np.array(img_y)[np.newaxis, np.newaxis, :, :] return input_image, img_cb, img_cr -def perform_inference((sym, params), (input_img, img_cb, img_cr)): +def perform_inference(sym, params, input_img, img_cb, img_cr): """Perform inference on image using mxnet""" # create module mod = mx.mod.Module(symbol=sym, data_names=['input_0'], label_names=None) @@ -79,4 +79,6 @@ def perform_inference((sym, params), (input_img, img_cb, img_cr)): return result_img if __name__ == '__main__': - perform_inference(import_onnx(), get_test_image()) + MX_SYM, MX_PARAM = import_onnx() + INPUT_IMG, IMG_CB, IMG_CR = get_test_image() + perform_inference(MX_SYM, MX_PARAM, INPUT_IMG, IMG_CB, IMG_CR) diff --git a/tests/python-pytest/onnx/onnx_test.py b/tests/python-pytest/onnx/onnx_test.py index ea1058090150..2693a19e97fe 100644 --- a/tests/python-pytest/onnx/onnx_test.py +++ b/tests/python-pytest/onnx/onnx_test.py @@ -110,9 +110,6 @@ def test_super_resolution(): assert len(sym.list_outputs()) == 1 assert sym.list_outputs()[0] == 'reshape5_output' - assert len(sym.list_attr()) == 1 - assert sym.list_attr()['shape'] == '(1L, 1L, 672L, 672L)' - attrs_keys = sym.attr_dict().keys() assert len(attrs_keys) == 19 for i, key_item in enumerate(['reshape4', 'param_5', 'param_4', 'param_7', @@ -121,18 +118,20 @@ def test_super_resolution(): 'reshape1', 'convolution2', 'convolution3', 'convolution0', 'convolution1', 'reshape5', 'transpose0']): - assert attrs_keys[i] == key_item + assert key_item in attrs_keys param_keys = params.keys() assert len(param_keys) == 8 for i, param_item in enumerate(['param_5', 'param_4', 'param_7', 'param_6', 'param_1', 'param_0', 'param_3', 'param_2']): - assert param_keys[i] == param_item + assert param_item in param_keys + LOGGER.info("Asserted the result of the onnx model conversion") output_img_dim = 672 - result_img = super_resolution.perform_inference((sym, params), - super_resolution.get_test_image()) + input_image, img_cb, img_cr = super_resolution.get_test_image() + result_img = super_resolution.perform_inference(sym, params, input_image, + img_cb, img_cr) assert result_img.size == (output_img_dim, output_img_dim) if __name__ == '__main__': From 612e2a6f8fa306cfe33c5d40dc62887137ba17ee Mon Sep 17 00:00:00 2001 From: Acharya Date: Tue, 13 Mar 2018 09:19:13 -0700 Subject: [PATCH 33/35] Remove logging configuration from test files. Signed-off-by: Acharya --- tests/python-pytest/onnx/onnx_test.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/python-pytest/onnx/onnx_test.py b/tests/python-pytest/onnx/onnx_test.py index 2693a19e97fe..75f5fe7258bd 100644 --- a/tests/python-pytest/onnx/onnx_test.py +++ b/tests/python-pytest/onnx/onnx_test.py @@ -34,11 +34,6 @@ sys.path.insert(0, os.path.join(CURR_PATH, '../../python/unittest')) from common import with_seed -# set up logger -logging.basicConfig() -LOGGER = logging.getLogger() -LOGGER.setLevel(logging.INFO) - @with_seed() def test_reduce_max(): """Test for ReduceMax operator""" @@ -92,7 +87,7 @@ def test_squeeze(): output = mxnet_backend.run_node(node_def, [input1])[0] npt.assert_almost_equal(output, np.squeeze(input1, axis=[1, 3])) -def test_super_resolution(): +def test_super_resolution_example(): """Test the super resolution example in the example/onnx folder""" sys.path.insert(0, os.path.join(CURR_PATH, '../../../example/onnx/')) import super_resolution @@ -126,7 +121,7 @@ def test_super_resolution(): 'param_1', 'param_0', 'param_3', 'param_2']): assert param_item in param_keys - LOGGER.info("Asserted the result of the onnx model conversion") + logging.getLogger().info("Asserted the result of the onnx model conversion") output_img_dim = 672 input_image, img_cb, img_cr = super_resolution.get_test_image() From a9b2f62bf6359a6b95b50872c611c9a72e22d811 Mon Sep 17 00:00:00 2001 From: Acharya Date: Tue, 13 Mar 2018 10:52:51 -0700 Subject: [PATCH 34/35] Verify result image in example by hash Signed-off-by: Acharya --- tests/python-pytest/onnx/onnx_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/python-pytest/onnx/onnx_test.py b/tests/python-pytest/onnx/onnx_test.py index 75f5fe7258bd..016490a4c4bf 100644 --- a/tests/python-pytest/onnx/onnx_test.py +++ b/tests/python-pytest/onnx/onnx_test.py @@ -26,6 +26,7 @@ import os import unittest import logging +import hashlib import numpy as np import numpy.testing as npt from onnx import helper @@ -121,12 +122,14 @@ def test_super_resolution_example(): 'param_1', 'param_0', 'param_3', 'param_2']): assert param_item in param_keys - logging.getLogger().info("Asserted the result of the onnx model conversion") + logging.info("Asserted the result of the onnx model conversion") output_img_dim = 672 input_image, img_cb, img_cr = super_resolution.get_test_image() result_img = super_resolution.perform_inference(sym, params, input_image, img_cb, img_cr) + + assert hashlib.md5(result_img.tobytes()).hexdigest() == '0d98393a49b1d9942106a2ed89d1e854' assert result_img.size == (output_img_dim, output_img_dim) if __name__ == '__main__': From b24baba3e252b23bea4c1166fa1581082b3f79a7 Mon Sep 17 00:00:00 2001 From: Acharya Date: Wed, 14 Mar 2018 10:36:04 -0700 Subject: [PATCH 35/35] Remove fetching test files by ETag. Will add it as a separate PR as per review comments. Signed-off-by: Acharya --- example/onnx/super_resolution.py | 4 ++-- .../mxnet/contrib/onnx/_import/import_model.py | 18 +++++++++++------- python/mxnet/test_utils.py | 10 ++-------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/example/onnx/super_resolution.py b/example/onnx/super_resolution.py index 544f791293d4..1392b77715cb 100644 --- a/example/onnx/super_resolution.py +++ b/example/onnx/super_resolution.py @@ -34,7 +34,7 @@ def import_onnx(): """Import the onnx model into mxnet""" model_url = 'https://s3.amazonaws.com/onnx-mxnet/examples/super_resolution.onnx' - download(model_url, 'super_resolution.onnx', version_tag='"7348c879d16c42bc77e24e270f663524"') + download(model_url, 'super_resolution.onnx') LOGGER.info("Converting onnx format to mxnet's symbol and params...") sym, params = onnx_mxnet.import_model('super_resolution.onnx') @@ -46,7 +46,7 @@ def get_test_image(): # Load test image input_image_dim = 224 img_url = 'https://s3.amazonaws.com/onnx-mxnet/examples/super_res_input.jpg' - download(img_url, 'super_res_input.jpg', version_tag='"02c90a7248e51316b11f7f39dd1b226d"') + download(img_url, 'super_res_input.jpg') img = Image.open('super_res_input.jpg').resize((input_image_dim, input_image_dim)) img_ycbcr = img.convert("YCbCr") img_y, img_cb, img_cr = img_ycbcr.split() diff --git a/python/mxnet/contrib/onnx/_import/import_model.py b/python/mxnet/contrib/onnx/_import/import_model.py index 747019c67db8..1df429b4690f 100644 --- a/python/mxnet/contrib/onnx/_import/import_model.py +++ b/python/mxnet/contrib/onnx/_import/import_model.py @@ -22,17 +22,21 @@ from .import_onnx import GraphProto def import_model(model_file): - """Imports the supplied ONNX model file into MXNet symbol and parameters. - :parameters model_file + """Imports the ONNX model file passed as a parameter into MXNet symbol and parameters. + + Parameters ---------- - model_file : ONNX model file name + model_file : str + ONNX model file name - :returns (sym, params) + Returns ------- - sym : mx.symbol - Compatible mxnet symbol + Mxnet symbol and parameter objects. + + sym : mxnet.symbol + Mxnet symbol params : dict of str to mx.ndarray - Dict of converted parameters stored in mx.ndarray format + Dict of converted parameters stored in mxnet.ndarray format """ graph = GraphProto() diff --git a/python/mxnet/test_utils.py b/python/mxnet/test_utils.py index 02c86cf96ddb..91459de9d2ad 100644 --- a/python/mxnet/test_utils.py +++ b/python/mxnet/test_utils.py @@ -1367,8 +1367,7 @@ def list_gpus(): pass return range(len([i for i in re.split('\n') if 'GPU' in i])) - -def download(url, fname=None, dirname=None, overwrite=False, version_tag=None): +def download(url, fname=None, dirname=None, overwrite=False): """Download an given URL Parameters @@ -1386,8 +1385,6 @@ def download(url, fname=None, dirname=None, overwrite=False, version_tag=None): Default is false, which means skipping download if the local file exists. If true, then download the url to overwrite the local file if exists. - version_tag : str, optional - the version tag of the file. Returns ------- @@ -1410,15 +1407,12 @@ def download(url, fname=None, dirname=None, overwrite=False, version_tag=None): if exc.errno != errno.EEXIST: raise OSError('failed to create ' + dirname) - if not overwrite and os.path.exists(fname) and not version_tag: + if not overwrite and os.path.exists(fname): logging.info("%s exists, skipping download", fname) return fname r = requests.get(url, stream=True) assert r.status_code == 200, "failed to open %s" % url - if version_tag and r.headers['ETag'] != version_tag: - logging.info("The version tag of the file does not match the expected version. " - + "Proceeding with the file download...") with open(fname, 'wb') as f: for chunk in r.iter_content(chunk_size=1024): if chunk: # filter out keep-alive new chunks