From f51c533ea8ac1522f813ca2bf2d1b1e34c12b1a7 Mon Sep 17 00:00:00 2001 From: Maxim Vafin Date: Fri, 5 Jun 2020 12:34:57 +0300 Subject: [PATCH] Add ReduceL2 decomposition (#733) * Add ReduceL2 decomposition * Add ReduceL2 transformation tests * Add const propagation unit test for ReduceL2 --- model-optimizer/automation/package_BOM.txt | 2 + .../extensions/front/ReduceL2Decomposition.py | 48 ++++++++++++++++ .../extensions/front/ReduceL2Decomposition_test.py | 63 +++++++++++++++++++++ .../extensions/front/onnx/reduce_l2_ext.py | 33 +++++++++++ model-optimizer/extensions/ops/ReduceOps.py | 7 +++ model-optimizer/extensions/ops/ReduceOps_test.py | 65 ++++++++++++++++++++++ 6 files changed, 218 insertions(+) create mode 100644 model-optimizer/extensions/front/ReduceL2Decomposition.py create mode 100644 model-optimizer/extensions/front/ReduceL2Decomposition_test.py create mode 100644 model-optimizer/extensions/front/onnx/reduce_l2_ext.py create mode 100644 model-optimizer/extensions/ops/ReduceOps_test.py diff --git a/model-optimizer/automation/package_BOM.txt b/model-optimizer/automation/package_BOM.txt index 9cf2e62..9697138 100644 --- a/model-optimizer/automation/package_BOM.txt +++ b/model-optimizer/automation/package_BOM.txt @@ -295,6 +295,7 @@ extensions/front/onnx/proposal_ext.py extensions/front/onnx/quantize_dequantize_linear.py extensions/front/onnx/quantize_ext.py extensions/front/onnx/range_ext.py +extensions/front/onnx/reduce_l2_ext.py extensions/front/onnx/reduce_max_ext.py extensions/front/onnx/reduce_mean_ext.py extensions/front/onnx/reduce_min_ext.py @@ -329,6 +330,7 @@ extensions/front/PowerToEltwises.py extensions/front/rank_decomposer.py extensions/front/reciprocal.py extensions/front/reduce_axis_normalizer.py +extensions/front/ReduceL2Decomposition.py extensions/front/reshape_dim_normalizer.py extensions/front/restore_ports.py extensions/front/scatter_normalizer.py diff --git a/model-optimizer/extensions/front/ReduceL2Decomposition.py b/model-optimizer/extensions/front/ReduceL2Decomposition.py new file mode 100644 index 0000000..11d91fa --- /dev/null +++ b/model-optimizer/extensions/front/ReduceL2Decomposition.py @@ -0,0 +1,48 @@ +""" + Copyright (C) 2020 Intel Corporation + + Licensed 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 extensions.front.reduce_axis_normalizer import ReduceAxisNormalizer +from extensions.ops.ReduceOps import ReduceSum +from extensions.ops.elementwise import Pow, Mul +from mo.front.common.partial_infer.utils import int64_array, float_array +from mo.front.common.replacement import FrontReplacementOp +from mo.front.tf.graph_utils import create_op_with_const_inputs +from mo.graph.graph import Graph, Node, rename_node + + +class ReduceL2Decomposition(FrontReplacementOp): + op = 'ReduceL2' + enabled = True + + def run_before(self): + return [ReduceAxisNormalizer] + + def replace_op(self, graph: Graph, node: Node): + node_name = node.soft_get('name', node.id) + + rename_node(node, node_name + '/TBR') + sqr_node = Mul(graph, {}).create_node() + reduce_sum_node = ReduceSum(graph, {'keep_dims': node.soft_get('keep_dims', 0), + 'axis': node.soft_get('axis', None)}).create_node() + sqrt_node = create_op_with_const_inputs(graph, Pow, {1: float_array(0.5)}) + rename_node(sqrt_node, node_name) + + # Connect nodes + node.in_port(0).get_connection().set_destination(sqr_node.in_port(0)) + sqr_node.in_port(0).get_connection().add_destination(sqr_node.in_port(1)) + sqr_node.out_port(0).connect(reduce_sum_node.in_port(0)) + reduce_sum_node.out_port(0).connect(sqrt_node.in_port(0)) + + return [sqrt_node.id] diff --git a/model-optimizer/extensions/front/ReduceL2Decomposition_test.py b/model-optimizer/extensions/front/ReduceL2Decomposition_test.py new file mode 100644 index 0000000..55b6666 --- /dev/null +++ b/model-optimizer/extensions/front/ReduceL2Decomposition_test.py @@ -0,0 +1,63 @@ +""" + Copyright (C) 2020 Intel Corporation + + Licensed 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. +""" + +import unittest + +import numpy as np + +from extensions.front.ReduceL2Decomposition import ReduceL2Decomposition +from mo.utils.ir_engine.compare_graphs import compare_graphs +from mo.utils.unittest.graph import build_graph, const + +nodes_attributes = { + 'input': {'shape': None, 'type': 'Parameter', 'kind': 'op', 'op': 'Parameter'}, + 'reduce_l2': {'type': None, 'kind': 'op', 'op': 'ReduceL2', 'axis': 0, 'name': 'my_reduce', 'keep_dims': 0}, + 'result': {'type': 'Result', 'value': None, 'kind': 'op', 'op': 'Result'}, + + # new layers + 'mul': {'type': 'Multiply', 'kind': 'op', 'op': 'Mul'}, + 'reduce_sum': {'type': 'ReduceSum', 'kind': 'op', 'op': 'ReduceSum', 'axis': 0, 'keep_dims': 0}, + 'pow': {'type': 'Power', 'kind': 'op', 'op': 'Pow'}, + **const('half', np.array(0.5, dtype=np.float32)), +} + + +class ReduceL2DecompositionTest(unittest.TestCase): + def test(self): + graph = build_graph(nodes_attributes, + [('input', 'reduce_l2', {'in': 0, 'out': 0}), + ('reduce_l2', 'result', {'in': 0, 'out': 0}), + ], + {}, nodes_with_edges_only=True) + + graph_ref = build_graph(nodes_attributes, + [('input', 'mul', {'in': 0, 'out': 0}), + ('input', 'mul', {'in': 1, 'out': 0}), + ('mul', 'reduce_sum', {'in': 0, 'out': 0}), + ('reduce_sum', 'pow', {'in': 0, 'out': 0}), + ('half', 'pow', {'in': 1, 'out': 0}), + ('pow', 'result', {'in': 0, 'out': 0}), + ], + {}, nodes_with_edges_only=True) + + graph.graph['layout'] = 'NCHW' + graph.stage = 'front' + + ReduceL2Decomposition().find_and_replace_pattern(graph) + + (flag, resp) = compare_graphs(graph, graph_ref, 'result', check_op_attrs=True) + self.assertTrue(flag, resp) + self.assertTrue(graph.node[graph.get_nodes_with_attributes(op='Pow')[0]]['name'] == 'my_reduce') diff --git a/model-optimizer/extensions/front/onnx/reduce_l2_ext.py b/model-optimizer/extensions/front/onnx/reduce_l2_ext.py new file mode 100644 index 0000000..7a7034f --- /dev/null +++ b/model-optimizer/extensions/front/onnx/reduce_l2_ext.py @@ -0,0 +1,33 @@ +""" + Copyright (C) 2020 Intel Corporation + + Licensed 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 extensions.front.reduce_axis_normalizer import ReduceAxisNormalizer +from extensions.ops.ReduceOps import ReduceL2 +from mo.front.common.partial_infer.utils import int64_array +from mo.front.extractor import FrontExtractorOp +from mo.front.onnx.extractors.utils import onnx_attr +from mo.graph.graph import Node + + +class ReduceL2FrontExtractor(FrontExtractorOp): + op = 'ReduceL2' + enabled = True + + @classmethod + def extract(cls, node: Node): + axis = onnx_attr(node, 'axes', 'ints', default=None, dst_type=lambda x: int64_array(x)) + keep_dims = onnx_attr(node, 'keepdims', 'i', default=True) + ReduceL2.update_node_stat(node, {'axis': axis, 'keep_dims': keep_dims}) + return cls.enabled diff --git a/model-optimizer/extensions/ops/ReduceOps.py b/model-optimizer/extensions/ops/ReduceOps.py index 7867ace..9ae3d06 100644 --- a/model-optimizer/extensions/ops/ReduceOps.py +++ b/model-optimizer/extensions/ops/ReduceOps.py @@ -24,6 +24,7 @@ from mo.ops.op import Op reduce_map = { 'ReduceSum': np.sum, 'ReduceProd': np.prod, + 'ReduceL2': lambda x, axis, keepdims: np.sqrt(np.sum(a=np.square(x), axis=axis, keepdims=keepdims)), 'ReduceMax': np.max, 'ReduceMin': np.min, 'ReduceMean': np.mean, @@ -137,6 +138,12 @@ class ReduceMean(ReduceOp): enabled = True +class ReduceL2(ReduceOp): + op = 'ReduceL2' + op_type = None + enabled = True + + class ReduceAnd(ReduceOp): op = 'ReduceAnd' op_type = 'ReduceLogicalAnd' diff --git a/model-optimizer/extensions/ops/ReduceOps_test.py b/model-optimizer/extensions/ops/ReduceOps_test.py new file mode 100644 index 0000000..97d7f05 --- /dev/null +++ b/model-optimizer/extensions/ops/ReduceOps_test.py @@ -0,0 +1,65 @@ +""" + Copyright (C) 2018-2020 Intel Corporation + + Licensed 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. +""" + +import unittest + +import numpy as np +from generator import generate, generator + +from extensions.ops.ReduceOps import reduce_infer +from mo.front.common.partial_infer.utils import int64_array +from mo.graph.graph import Node +from mo.utils.unittest.graph import build_graph, regular_op_with_shaped_data, result, connect, valued_const_with_data + +nodes_attributes = { + **regular_op_with_shaped_data('data', [1, 3, 224, 224], {'type': 'Parameter', 'value': None, + '_out_port_data_type': {0: np.float32}}), + **valued_const_with_data('axis', int64_array(0)), + **regular_op_with_shaped_data('reduce_l2', None, {'op': 'ReduceL2', 'type': None, 'name': 'my_reduce_l2'}), + **regular_op_with_shaped_data('identity', None, {'op': 'Identity', 'name': 'identity'}), + **result('output'), +} + + +@generator +class TestCumSum(unittest.TestCase): + @generate(*[ + ([3, 2, 2], [0], True), + ([3, 2, 2], [1], True), + ([3, 2, 2], [2], True), + ([3, 2, 2], [0], False), + ([3, 2, 2], [1], False), + ([3, 2, 2], [2], False), + ]) + def test_reduce_l2(self, shape, axes, keepdims): + data = np.reshape(np.arange(1, np.prod(shape) + 1, dtype=np.float32), shape) + reduced = np.sqrt(np.sum(a=np.square(data), axis=tuple(axes), keepdims=keepdims)) + axis = int64_array(axes) + graph = build_graph(nodes_attributes, + [*connect('data', '0:reduce_l2'), + *connect('axis', '1:reduce_l2'), + *connect('reduce_l2', '0:identity'), + ('identity', 'identity_d', {'out': 0}), + ('identity_d', 'output') + ], + {'data_d': {'value': data, 'shape': data.shape}, + 'axis_d': {'value': axis, 'shape': axis.shape}, + 'reduce_l2': {'keep_dims': keepdims}}, + nodes_with_edges_only=True) + + reduce_node = Node(graph, 'reduce_l2') + reduce_infer(reduce_node) + self.assertTrue(np.array_equal(reduce_node.out_port(0).data.get_value(), reduced)) -- 2.7.4