Enable ReduceL1 and ReduceL2 operations (#1799)

* Initial version of ReduceL1, ReduceL2 and ReduceLp enabling in the MO

* Added operations ReduceL1 and ReduceL2 to nGraph

* Removed ReduceLp. Added ReduceL1 and ReduceL2

* Separated specification of ReduceLp into ReduceL1 and ReduceL2

* Updated ReduceL1 and ReduceL2 specification

* Fixed ReduceL1 and ReduceL2 type prop tests

* Implemented nGraph transformation to decompose ReduceL1 and ReduceL2. Disabled them for CPU and GPU plugins

* Updated supported framework layers

* Added unit tests for ReduceL1 and ReduceL2 reference implementation

* Fixed ReduceXXX operations reference implementation by adding support for a new parameter 'keep_dims'

* Fixed constant folding for v0::Any

* Added ReduceL1 and ReduceL2 to Python API

* Implemented ReduceL1 and ReduceL2 decomposition tests and fixed ReduceL2 decomposition

* Added specific creator for ReduceXXX operations instead of NodeBuilders

* Fixed conversion ReduceXXX to CNNLayer

* Fixed parser for ReduceLogicalXXX operations
This commit is contained in:
Evgeny Lazarev
2020-08-17 16:32:53 +03:00
committed by GitHub
parent 7c9815b4c1
commit 125a462400
73 changed files with 1530 additions and 719 deletions

View File

@@ -38,7 +38,7 @@ extensions/back/PackBinaryWeights.py
extensions/back/pass_separator.py
extensions/back/priorbox_mutation.py
extensions/back/ProposalMutation.py
extensions/back/ReduceToPooling.py
extensions/back/ReduceMerge.py
extensions/back/ReduceTransposeDimensions.py
extensions/back/remove_last_softmax_pattern.py
extensions/back/RemoveUselessConvert.py
@@ -291,12 +291,7 @@ extensions/front/onnx/quantize_ext.py
extensions/front/onnx/quantize_linear_ext.py
extensions/front/onnx/quantize_linear_resolver.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
extensions/front/onnx/reduce_prod_ext.py
extensions/front/onnx/reduce_sum_ext.py
extensions/front/onnx/reduce_ext.py
extensions/front/onnx/remove_filtering_boxes_by_size.py
extensions/front/onnx/resize_ext.py
extensions/front/onnx/resize_to_interpolate.py
@@ -327,7 +322,6 @@ 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

View File

@@ -18,7 +18,7 @@ from typing import Dict
import numpy as np
from extensions.back.FuseTransposesSequence import FuseTransposesSequence
from extensions.back.ReduceToPooling import ReduceMerge
from extensions.back.ReduceMerge import ReduceMerge
from extensions.ops.ReduceOps import reduce_map
from extensions.ops.gather import Gather
from mo.back.replacement import BackReplacementPattern

View File

@@ -1,48 +0,0 @@
"""
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]

View File

@@ -1,63 +0,0 @@
"""
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')

View File

@@ -0,0 +1,97 @@
"""
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.ops.ReduceOps import ReduceL1, ReduceL2, ReduceMax, ReduceMean, ReduceMin, ReduceProd, ReduceSum
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
def update_reduce_node_attrs_with(node: Node, c: callable):
axis = onnx_attr(node, 'axes', 'ints', default=None, dst_type=lambda x: int64_array(x))
keep_dims = onnx_attr(node, 'keepdims', 'i', default=True)
c.update_node_stat(node, {'axis': axis, 'keep_dims': keep_dims})
class ReduceL1Extractor(FrontExtractorOp):
op = 'ReduceL1'
enabled = True
@classmethod
def extract(cls, node: Node):
update_reduce_node_attrs_with(node, ReduceL1)
return cls.enabled
class ReduceL2Extractor(FrontExtractorOp):
op = 'ReduceL2'
enabled = True
@classmethod
def extract(cls, node: Node):
update_reduce_node_attrs_with(node, ReduceL2)
return cls.enabled
class ReduceMaxFrontExtractor(FrontExtractorOp):
op = 'ReduceMax'
enabled = True
@classmethod
def extract(cls, node: Node):
update_reduce_node_attrs_with(node, ReduceMax)
return cls.enabled
class ReduceMeanFrontExtractor(FrontExtractorOp):
op = 'ReduceMean'
enabled = True
@classmethod
def extract(cls, node: Node):
update_reduce_node_attrs_with(node, ReduceMean)
return cls.enabled
class ReduceMinFrontExtractor(FrontExtractorOp):
op = 'ReduceMin'
enabled = True
@classmethod
def extract(cls, node: Node):
update_reduce_node_attrs_with(node, ReduceMin)
return cls.enabled
class ReduceProdFrontExtractor(FrontExtractorOp):
op = 'ReduceProd'
enabled = True
@classmethod
def extract(cls, node: Node):
update_reduce_node_attrs_with(node, ReduceProd)
return cls.enabled
class ReduceSumFrontExtractor(FrontExtractorOp):
op = 'ReduceSum'
enabled = True
@classmethod
def extract(cls, node: Node):
update_reduce_node_attrs_with(node, ReduceSum)
return cls.enabled

View File

@@ -1,33 +0,0 @@
"""
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

View File

@@ -1,33 +0,0 @@
"""
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.
"""
from extensions.ops.ReduceOps import ReduceMax
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 ReduceMaxFrontExtractor(FrontExtractorOp):
op = 'ReduceMax'
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)
ReduceMax.update_node_stat(node, {'axis': axis, 'keep_dims': keep_dims})
return cls.enabled

View File

@@ -1,33 +0,0 @@
"""
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.
"""
from extensions.ops.ReduceOps import ReduceMean
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 ReduceMeanFrontExtractor(FrontExtractorOp):
op = 'ReduceMean'
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)
ReduceMean.update_node_stat(node, {'axis': axis, 'keep_dims': keep_dims})
return cls.enabled

View File

@@ -1,33 +0,0 @@
"""
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.
"""
from extensions.ops.ReduceOps import ReduceMin
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 ReduceMinFrontExtractor(FrontExtractorOp):
op = 'ReduceMin'
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)
ReduceMin.update_node_stat(node, {'axis': axis, 'keep_dims': keep_dims})
return cls.enabled

View File

@@ -1,33 +0,0 @@
"""
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.
"""
from extensions.ops.ReduceOps import ReduceProd
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 ReduceProdFrontExtractor(FrontExtractorOp):
op = 'ReduceProd'
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)
ReduceProd.update_node_stat(node, {'axis': axis, 'keep_dims': keep_dims})
return cls.enabled

View File

@@ -1,33 +0,0 @@
"""
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.
"""
from extensions.ops.ReduceOps import ReduceSum
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 ReduceSumFrontExtractor(FrontExtractorOp):
op = 'ReduceSum'
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)
ReduceSum.update_node_stat(node, {'axis': axis, 'keep_dims': keep_dims})
return cls.enabled

View File

@@ -46,16 +46,17 @@ class ReduceAxisNormalizer(FrontReplacementSubgraph):
node = match['reduce']
connected_in_ports = [port for port in node.in_ports().values() if not port.disconnected()]
if len(connected_in_ports) == 1:
node_name = node.soft_get('name', node.id)
# if the 'axis' is None then we still add a second input to the layer with a 1D array with 1 element equal
# to None. The infer function handles this case because the input shape is known at this stage only
if node.has('axis'):
const = Const(graph, {'value': node.axis}).create_node()
const = Const(graph, {'name': node_name + '/axis', 'value': node.axis}).create_node()
node.add_input_port(1, skip_if_exist=True)
const.out_port(0).connect(node.in_port(1))
del graph.node[node.id]['axis']
else:
# The default (if there is no 'axis') is to reduce over all the dimensions of the input tensor.
node_name = node.name
begin_of_range = Const(graph, dict(name=node_name + '/range_begin_', value=0)).create_node()
step = Const(graph, dict(name=node_name + '/range_step_', value=1)).create_node()

View File

@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
"""
from extensions.ops.ReduceOps import ReduceProd, ReduceAnd, ReduceMax, ReduceMean, ReduceSum
from extensions.ops.ReduceOps import ReduceProd, ReduceAnd, ReduceMax, ReduceMean, ReduceSum, ReduceL2
from mo.front.extractor import FrontExtractorOp
from mo.graph.graph import Node
@@ -67,3 +67,13 @@ class SumFrontExtractor(FrontExtractorOp):
def extract(cls, node: Node):
ReduceSum.update_node_stat(node, {'keep_dims': node.pb.attr["keep_dims"].b})
return cls.enabled
class EuclideanNormFrontExtractor(FrontExtractorOp):
op = 'EuclideanNorm'
enabled = True
@classmethod
def extract(cls, node: Node):
ReduceL2.update_node_stat(node, {'keep_dims': node.pb.attr["keep_dims"].b})
return cls.enabled

View File

@@ -24,6 +24,7 @@ from mo.ops.op import Op
reduce_map = {
'ReduceSum': np.sum,
'ReduceProd': np.prod,
'ReduceL1': lambda x, axis, keepdims: np.sum(a=np.absolute(x), axis=axis, keepdims=keepdims),
'ReduceL2': lambda x, axis, keepdims: np.sqrt(np.sum(a=np.square(x), axis=axis, keepdims=keepdims)),
'ReduceMax': np.max,
'ReduceMin': np.min,
@@ -86,12 +87,13 @@ class ReduceOp(Op):
enabled = False
op = None
op_type = None
version = 'opset1'
def __init__(self, graph: Graph, attrs: dict):
super().__init__(graph, {
'op': self.op,
'type': self.op_type,
'version': 'opset1',
'version': self.version,
'infer': reduce_infer,
'keep_dims': 0,
'in_ports_count': 2,
@@ -138,10 +140,15 @@ class ReduceMean(ReduceOp):
enabled = True
class ReduceL1(ReduceOp):
op = 'ReduceL1'
op_type = 'ReduceL1'
version = 'opset4'
class ReduceL2(ReduceOp):
op = 'ReduceL2'
op_type = None
enabled = True
op_type = 'ReduceL2'
version = 'opset4'
class ReduceAnd(ReduceOp):

View File

@@ -28,38 +28,42 @@ 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('reduce_lp', None, {'op': 'ReduceLp', 'type': None, 'name': 'my_reduce_lp'}),
**regular_op_with_shaped_data('identity', None, {'op': 'Identity', 'name': 'identity'}),
**result('output'),
}
@generator
class TestCumSum(unittest.TestCase):
class ReduceLpTest(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),
([3, 2, 2], [0], True, 1),
([3, 2, 2], [0], True, 2),
([3, 2, 2], [1], True, 2),
([3, 2, 2], [2], True, 2),
([3, 2, 2], [0], False, 1),
([3, 2, 2], [0], False, 2),
([3, 2, 2], [1], False, 2),
([3, 2, 2], [2], False, 2),
])
def test_reduce_l2(self, shape, axes, keepdims):
def test_reduce_lp(self, shape, axes, keepdims, p):
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))
reduced = np.power(np.sum(a=np.abs(np.power(data, p)), axis=tuple(axes), keepdims=keepdims), 1 / p)
axis = int64_array(axes)
p = int64_array(p)
graph = build_graph(nodes_attributes,
[*connect('data', '0:reduce_l2'),
*connect('axis', '1:reduce_l2'),
*connect('reduce_l2', '0:identity'),
[*connect('data', '0:reduce_lp'),
*connect('axis', '1:reduce_lp'),
*connect('reduce_lp', '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}},
'reduce_lp': {'keep_dims': keepdims}},
nodes_with_edges_only=True)
reduce_node = Node(graph, 'reduce_l2')
reduce_node = Node(graph, 'reduce_lp')
reduce_node.op = reduce_node.type = 'ReduceL' + str(p)
reduce_infer(reduce_node)
self.assertTrue(np.array_equal(reduce_node.out_port(0).data.get_value(), reduced))