Add MO support for MVN-6 (#3772)
* Add MO support for MVN-6 * Apply review feedback * Apply review feedback * Enable MVN-6 decomposition * Fix shape_infer issue * Fix MVN6 decomposition * Fix build * Fix tests * Small fix * Add MVN-6 reader test * Apply feedback * Apply review feedback * Fix onnx test failure
This commit is contained in:
1 parent
6a0bc80137
commit
0b3ff5edc2
22 files changed
+555
-138
No files matched your search
@@ -95,6 +95,7 @@ extensions/front/caffe/input_ext.py
|
||||
extensions/front/caffe/interp_ext.py
|
||||
extensions/front/caffe/lrn_ext.py
|
||||
extensions/front/caffe/mvn_ext.py
|
||||
extensions/front/caffe/MVNNormalizer.py
|
||||
extensions/front/caffe/normalize_ext.py
|
||||
extensions/front/caffe/permute_ext.py
|
||||
extensions/front/caffe/pooling_ext.py
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Copyright (C) 2020 Intel Corporation
|
||||
Copyright (C) 2017-2021 Intel Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
import logging as log
|
||||
|
||||
from mo.front.common.replacement import FrontReplacementSubgraph
|
||||
from mo.front.tf.graph_utils import create_op_with_const_inputs
|
||||
from mo.graph.graph import Graph, rename_nodes
|
||||
from extensions.ops.mvn import MVN
|
||||
|
||||
@@ -65,9 +66,9 @@ class LayerNorm(FrontReplacementSubgraph):
|
||||
if add_param.value.size == 1 and pow_param.value.size == 1 and add_param.value.item() <= 1e-05 \
|
||||
and pow_param.value.item() == 0.5 and match['pool0_param'].value == match['pool1_param'].value:
|
||||
log.debug('Found LayerNorm pattern after {} with name {}'.format(inp_port.node.op, inp_port.node.name))
|
||||
mvn = MVN(graph, {'eps': add_param.value.item(),
|
||||
'axes': match['pool1_param'].value,
|
||||
'normalize_variance': 1}).create_node()
|
||||
mvn = create_op_with_const_inputs(graph, MVN, {1: match['pool1_param'].value},
|
||||
{'eps': add_param.value.item(), 'normalize_variance': 1,
|
||||
'eps_mode': 'inside_sqrt'})
|
||||
div_name = match['div'].soft_get('name', match['div'].id)
|
||||
rename_nodes([(match['div'], div_name + '/to_be_removed'), (mvn, div_name)])
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Copyright (C) 2020 Intel Corporation
|
||||
Copyright (C) 2017-2021 Intel Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -17,7 +17,6 @@
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
from math import sqrt
|
||||
|
||||
from extensions.front.LayerNorm import LayerNorm
|
||||
from mo.utils.ir_engine.compare_graphs import compare_graphs
|
||||
@@ -41,7 +40,8 @@ nodes_attributes_mvn = {
|
||||
|
||||
nodes_attributes_ref = {
|
||||
'inp': {'kind': 'op', 'op': 'AnyOp'},
|
||||
'mvn': {'kind': 'op', 'op': 'MVN'},
|
||||
'mvn': {'kind': 'op', 'op': 'MVN', 'eps': 1e-6, 'normalize_variance': 1, 'eps_mode': 'inside_sqrt'},
|
||||
'mvn_param': {'kind': 'op', 'op': 'Const'},
|
||||
'out': {'kind': 'op', 'op': 'AnyOp'},
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ class TestMVNPatternReplacement(unittest.TestCase):
|
||||
nodes_with_edges_only=True)
|
||||
graph_ref = build_graph(nodes_attributes_ref,
|
||||
[('inp', 'mvn'),
|
||||
('mvn_param', 'mvn'),
|
||||
('mvn', 'out')],
|
||||
{}, nodes_with_edges_only=True)
|
||||
graph.stage = 'front'
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
Copyright (C) 2017-2021 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.caffe.MVNNormalizer import MVNCaffeToMVN
|
||||
from mo.utils.ir_engine.compare_graphs import compare_graphs
|
||||
from mo.utils.unittest.graph import build_graph, regular_op_with_empty_data, result, const, connect_front
|
||||
|
||||
nodes = {
|
||||
**regular_op_with_empty_data('input', {'type': 'Parameter'}),
|
||||
**regular_op_with_empty_data('mvn_caffe', {'op': 'MVNCaffe'}),
|
||||
**result(),
|
||||
|
||||
# nodes after replacement
|
||||
**const('start_1', np.array(1)),
|
||||
**const('start_2', np.array(2)),
|
||||
**const('step', np.array(1)),
|
||||
**regular_op_with_empty_data('rank', {'op': 'Rank', 'type': None}),
|
||||
**regular_op_with_empty_data('range', {'op': 'Range', 'type': None}),
|
||||
**regular_op_with_empty_data('mvn', {'op': 'MVN', 'type': None}),
|
||||
}
|
||||
|
||||
|
||||
class MVNNormalizerTest(unittest.TestCase):
|
||||
def test_mvn_normalizer(self):
|
||||
graph = build_graph(nodes, [('input', 'mvn_caffe'),
|
||||
('mvn_caffe', 'output')],
|
||||
{'mvn_caffe': {'across_channels': 0}},
|
||||
nodes_with_edges_only=True)
|
||||
graph.stage = 'front'
|
||||
|
||||
MVNCaffeToMVN().find_and_replace_pattern(graph)
|
||||
|
||||
graph_ref = build_graph(nodes, [('input', 'mvn', {'out': 0}),
|
||||
('input', 'rank', {'out': 0}),
|
||||
*connect_front('start_2', '0:range'),
|
||||
*connect_front('rank', '1:range'),
|
||||
*connect_front('step', '2:range'),
|
||||
*connect_front('range', '1:mvn'),
|
||||
('mvn', 'output')],
|
||||
nodes_with_edges_only=True)
|
||||
|
||||
(flag, resp) = compare_graphs(graph, graph_ref, 'output', check_op_attrs=True)
|
||||
self.assertTrue(flag, resp)
|
||||
|
||||
def test_mvn_normalizer_across_channels(self):
|
||||
graph = build_graph(nodes, [('input', 'mvn_caffe'),
|
||||
('mvn_caffe', 'output')],
|
||||
{'mvn_caffe': {'across_channels': 1}},
|
||||
nodes_with_edges_only=True)
|
||||
graph.stage = 'front'
|
||||
|
||||
MVNCaffeToMVN().find_and_replace_pattern(graph)
|
||||
|
||||
graph_ref = build_graph(nodes, [('input', 'mvn', {'out': 0}),
|
||||
('input', 'rank', {'out': 0}),
|
||||
*connect_front('start_1', '0:range'),
|
||||
*connect_front('rank', '1:range'),
|
||||
*connect_front('step', '2:range'),
|
||||
*connect_front('range', '1:mvn'),
|
||||
('mvn', 'output')],
|
||||
nodes_with_edges_only=True)
|
||||
|
||||
(flag, resp) = compare_graphs(graph, graph_ref, 'output', check_op_attrs=True)
|
||||
self.assertTrue(flag, resp)
|
||||
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
Copyright (C) 2017-2021 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.mvn import MVN
|
||||
from extensions.ops.range import Range
|
||||
from extensions.ops.rank import Rank
|
||||
from mo.front.common.partial_infer.utils import int64_array
|
||||
from mo.front.common.replacement import FrontReplacementPattern
|
||||
from mo.front.tf.graph_utils import create_op_with_const_inputs
|
||||
from mo.graph.graph import Graph, rename_nodes
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
class MVNCaffeToMVN(FrontReplacementPattern):
|
||||
"""
|
||||
Replace MVNCaffe operation with MVN
|
||||
"""
|
||||
enabled = True
|
||||
|
||||
def find_and_replace_pattern(self, graph: Graph):
|
||||
for node in graph.get_op_nodes(op='MVNCaffe'):
|
||||
node_name = node.soft_get('name', node.id)
|
||||
|
||||
start_axis = 2
|
||||
if node['across_channels'] == 1:
|
||||
start_axis = 1
|
||||
|
||||
rank = Rank(graph, {'name': node_name + '/Rank'}).create_node()
|
||||
|
||||
# create range of axes based on `start_axis` and rank of input
|
||||
rng = create_op_with_const_inputs(graph, Range, {0: int64_array(start_axis), 2: int64_array(1)},
|
||||
{'name': node_name + '/Range', 'output_type': np.int64})
|
||||
rng.in_port(1).connect(rank.out_port(0))
|
||||
|
||||
new_mvn = MVN(graph, {'eps': node.soft_get('eps', 1e-9), 'eps_mode': 'inside_sqrt',
|
||||
'normalize_variance': node.soft_get('normalize_variance', 1)}).create_node(
|
||||
[node.in_port(0).get_source().node, rng])
|
||||
new_mvn.in_port(0).get_connection().add_destination(rank.in_port(0))
|
||||
node.out_port(0).get_connection().set_source(new_mvn.out_port(0))
|
||||
rename_nodes([(node, node_name + '/tbd'), (new_mvn, node_name)])
|
||||
|
||||
graph.remove_node(node.id)
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Copyright (C) 2018-2020 Intel Corporation
|
||||
Copyright (C) 2018-2021 Intel Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -13,7 +13,7 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
from extensions.ops.mvn import MVN
|
||||
from extensions.ops.mvn import MVNCaffe
|
||||
from mo.front.caffe.collect_attributes import collect_attributes
|
||||
from mo.front.extractor import FrontExtractorOp
|
||||
|
||||
@@ -30,5 +30,5 @@ class MVNFrontExtractor(FrontExtractorOp):
|
||||
attrs = collect_attributes(param)
|
||||
|
||||
# update the attributes of the node
|
||||
MVN.update_node_stat(node, attrs)
|
||||
MVNCaffe.update_node_stat(node, attrs)
|
||||
return cls.enabled
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Copyright (C) 2018-2020 Intel Corporation
|
||||
Copyright (C) 2018-2021 Intel Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -16,8 +16,14 @@
|
||||
|
||||
from extensions.ops.elementwise import Add, Mul
|
||||
from extensions.ops.mvn import MVN
|
||||
from extensions.ops.range import Range
|
||||
from extensions.ops.rank import Rank
|
||||
from mo.front.common.partial_infer.utils import int64_array
|
||||
from mo.front.common.replacement import FrontReplacementOp
|
||||
from mo.graph.graph import Node, Graph
|
||||
from mo.front.tf.graph_utils import create_op_with_const_inputs
|
||||
from mo.graph.graph import Node, Graph, rename_nodes
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
class InstanceNormalization(FrontReplacementOp):
|
||||
@@ -29,18 +35,26 @@ class InstanceNormalization(FrontReplacementOp):
|
||||
enabled = True
|
||||
|
||||
def replace_op(self, graph: Graph, node: Node):
|
||||
name = node.soft_get('name', node.id)
|
||||
|
||||
# Add new nodes
|
||||
mvn = MVN(graph, {'eps': node.epsilon, 'name': node.name + '/Ins_Norm/MVN_', }).create_node()
|
||||
mul = Mul(graph, {'axis': 1, 'name': node.name + '/Ins_Norm/mul_'}).create_node()
|
||||
add = Add(graph, {'axis': 1, 'name': node.name + '/Ins_Norm/add_'}).create_node()
|
||||
|
||||
# Connect nodes
|
||||
# create range of axes for MVN based on `start_axis` and rank of input
|
||||
rank = Rank(graph, {'name': name + '/Rank'}).create_node()
|
||||
rng = create_op_with_const_inputs(graph, Range, {0: int64_array(2), 2: int64_array(1)},
|
||||
{'name': name + '/Range', 'output_type': np.int64})
|
||||
mvn = MVN(graph, {'eps': node.epsilon, 'eps_mode': 'inside_sqrt', 'normalize_variance': 1,
|
||||
'name': name + '/Ins_Norm/MVN_', }).create_node()
|
||||
node.in_port(0).get_connection().set_destination(mvn.in_port(0))
|
||||
rng.out_port(0).connect(mvn.in_port(1))
|
||||
mul = Mul(graph, {'axis': 1, 'name': name + '/Ins_Norm/mul_'}).create_node()
|
||||
mvn.out_port(0).connect(mul.in_port(0))
|
||||
node.in_port(1).get_connection().set_destination(mul.in_port(1))
|
||||
add = Add(graph, {'axis': 1, 'name': name + '/Ins_Norm/add_'}).create_node()
|
||||
mul.out_port(0).connect(add.in_port(0))
|
||||
node.in_port(2).get_connection().set_destination(add.in_port(1))
|
||||
|
||||
mvn.out_port(0).connect(mul.in_port(0))
|
||||
mul.out_port(0).connect(add.in_port(0))
|
||||
mvn.in_port(0).get_connection().add_destination(rank.in_port(0))
|
||||
rng.in_port(1).connect(rank.out_port(0))
|
||||
|
||||
rename_nodes([(node, name + '/TBD'), (add, name)])
|
||||
|
||||
return [add.id]
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Copyright (C) 2018-2020 Intel Corporation
|
||||
Copyright (C) 2018-2021 Intel Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -16,10 +16,8 @@
|
||||
|
||||
import unittest
|
||||
|
||||
import networkx as nx
|
||||
|
||||
from extensions.front.instance_normalization import InstanceNormalization
|
||||
from mo.middle.pattern_match import node_match
|
||||
from mo.utils.ir_engine.compare_graphs import compare_graphs
|
||||
from mo.utils.unittest.graph import build_graph
|
||||
|
||||
nodes_attributes = {
|
||||
@@ -27,15 +25,21 @@ nodes_attributes = {
|
||||
'scale': {'kind': 'op', 'op': 'AnyOp'},
|
||||
'B': {'kind': 'op', 'op': 'AnyOp'},
|
||||
'node': {'kind': 'op', 'op': 'InstanceNormalization', 'epsilon': None},
|
||||
'out': {'kind': 'op', 'op': 'AnyOp'},
|
||||
}
|
||||
|
||||
nodes_ref_attributes = {
|
||||
'input': {'op': 'AnyOp'},
|
||||
'scale': {'op': 'AnyOp'},
|
||||
'B': {'op': 'AnyOp'},
|
||||
'input': {'kind': 'op', 'op': 'AnyOp'},
|
||||
'scale': {'kind': 'op', 'op': 'AnyOp'},
|
||||
'B': {'kind': 'op', 'op': 'AnyOp'},
|
||||
'start': {'kind': 'op', 'op': 'Const'},
|
||||
'step': {'kind': 'op', 'op': 'Const'},
|
||||
'rank': {'kind': 'op', 'op': 'Rank'},
|
||||
'mvn_axes': {'kind': 'op', 'op': 'Range'},
|
||||
'mvn': {'kind': 'op', 'op': 'MVN', 'name': 'node/Ins_Norm/MVN_', 'eps': None},
|
||||
'mul': {'kind': 'op', 'op': 'Mul', 'name': 'node/Ins_Norm/mul_'},
|
||||
'add': {'kind': 'op', 'op': 'Add', 'name': 'node/Ins_Norm/add_'},
|
||||
'out': {'kind': 'op', 'op': 'AnyOp'},
|
||||
}
|
||||
|
||||
|
||||
@@ -45,18 +49,25 @@ class TestInstanceNormalization(unittest.TestCase):
|
||||
[('input', 'node'),
|
||||
('scale', 'node'),
|
||||
('B', 'node'),
|
||||
('node', 'out')
|
||||
],
|
||||
{'node': {'epsilon': 0.123},
|
||||
}, nodes_with_edges_only=True)
|
||||
|
||||
ref_graph = build_graph(nodes_ref_attributes,
|
||||
[('input', 'mvn'),
|
||||
graph_ref = build_graph(nodes_ref_attributes,
|
||||
[('input', 'mvn', {'out': 0}),
|
||||
('input', 'rank', {'out': 0}),
|
||||
('start', 'mvn_axes'),
|
||||
('rank', 'mvn_axes'),
|
||||
('step', 'mvn_axes'),
|
||||
('mvn_axes', 'mvn'),
|
||||
('mvn', 'mul'),
|
||||
('scale', 'mul'),
|
||||
('mul', 'add'),
|
||||
('B', 'add'),
|
||||
('add', 'out')
|
||||
],
|
||||
{'mvn': {'eps': 0.123},
|
||||
{'mvn': {'eps': 0.123, 'eps_mode': 'inside_sqrt', 'normalize_variance': 1},
|
||||
}, nodes_with_edges_only=True)
|
||||
|
||||
graph.stage = 'front'
|
||||
@@ -64,4 +75,41 @@ class TestInstanceNormalization(unittest.TestCase):
|
||||
tested_class = InstanceNormalization()
|
||||
tested_class.find_and_replace_pattern(graph)
|
||||
|
||||
self.assertTrue(nx.is_isomorphic(graph, ref_graph, node_match))
|
||||
(flag, resp) = compare_graphs(graph, graph_ref, 'out', check_op_attrs=False)
|
||||
self.assertTrue(flag, resp)
|
||||
|
||||
def test_instance_normalization_test_2(self):
|
||||
graph = build_graph(nodes_attributes,
|
||||
[('input', 'out', {'out': 0, 'in': 0}),
|
||||
('input', 'node', {'out': 1}),
|
||||
('scale', 'node'),
|
||||
('B', 'node'),
|
||||
('node', 'out', {'in': 1})
|
||||
],
|
||||
{'node': {'epsilon': 0.123},
|
||||
}, nodes_with_edges_only=True)
|
||||
|
||||
graph_ref = build_graph(nodes_ref_attributes,
|
||||
[('input', 'out', {'out': 0, 'in': 0}),
|
||||
('input', 'mvn', {'out': 1}),
|
||||
('input', 'rank', {'out': 1}),
|
||||
('start', 'mvn_axes'),
|
||||
('rank', 'mvn_axes'),
|
||||
('step', 'mvn_axes'),
|
||||
('mvn_axes', 'mvn'),
|
||||
('mvn', 'mul'),
|
||||
('scale', 'mul'),
|
||||
('mul', 'add'),
|
||||
('B', 'add'),
|
||||
('add', 'out', {'in': 1})
|
||||
],
|
||||
{'mvn': {'eps': 0.123, 'eps_mode': 'inside_sqrt', 'normalize_variance': 1},
|
||||
}, nodes_with_edges_only=True)
|
||||
|
||||
graph.stage = 'front'
|
||||
|
||||
tested_class = InstanceNormalization()
|
||||
tested_class.find_and_replace_pattern(graph)
|
||||
|
||||
(flag, resp) = compare_graphs(graph, graph_ref, 'out', check_op_attrs=False)
|
||||
self.assertTrue(flag, resp)
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Copyright (C) 2018-2020 Intel Corporation
|
||||
Copyright (C) 2018-2021 Intel Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -19,7 +19,7 @@ import numpy as np
|
||||
from extensions.ops.mvn import MVN
|
||||
from mo.front.extractor import FrontExtractorOp
|
||||
from mo.front.onnx.extractors.utils import onnx_attr
|
||||
from mo.utils.error import Error
|
||||
from mo.ops.const import Const
|
||||
|
||||
|
||||
class MeanVarianceNormalizationExtractor(FrontExtractorOp):
|
||||
@@ -33,18 +33,15 @@ class MeanVarianceNormalizationExtractor(FrontExtractorOp):
|
||||
default=np.array([0, 2, 3], dtype=np.int64),
|
||||
dst_type=lambda x: np.array(x, dtype=np.int64))
|
||||
|
||||
if 0 in axes:
|
||||
raise Error('Reduction over the batch dimension in node "{}" is not supported by the backend.'.format(name))
|
||||
# Dimension 4 (if it's present in the input tensor) should also be in the list of axes for reduction.
|
||||
# This case will be handled at the MVN Op side, because input shape is not available at that stage.
|
||||
for i in (2, 3):
|
||||
if i not in axes:
|
||||
raise Error('Reduction over spatial dimensions in node "{}" is obligatory for a backend.'.format(name))
|
||||
axes = Const(node.graph, {'value': axes, 'name': name + '/Axes'}).create_node()
|
||||
node.add_input_port(1, skip_if_exist=True)
|
||||
node.in_port(1).connect(axes.out_port(0))
|
||||
|
||||
attrs = {
|
||||
'eps': 1e-9,
|
||||
'normalize_variance': 1,
|
||||
'axes': axes
|
||||
'eps_mode': 'outside_sqrt'
|
||||
}
|
||||
|
||||
MVN.update_node_stat(node, attrs)
|
||||
return cls.enabled
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Copyright (C) 2017-2020 Intel Corporation
|
||||
Copyright (C) 2017-2021 Intel Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -59,7 +59,8 @@ class MVNReplacer(FrontReplacementSubgraph):
|
||||
mvn = MVN(graph, dict(
|
||||
name=fbn.name + '/MVN_',
|
||||
eps=fbn.eps,
|
||||
required_reduction_indices=[1, 2] if fbn.data_format == b'NHWC' else [2, 3]
|
||||
eps_mode='outside_sqrt',
|
||||
normalize_variance=1
|
||||
))
|
||||
mvn.attrs['old_infer'] = mvn.attrs['infer']
|
||||
mvn.attrs['infer'] = __class__.infer
|
||||
@@ -84,19 +85,20 @@ class MVNReplacer(FrontReplacementSubgraph):
|
||||
|
||||
@staticmethod
|
||||
def infer(node: Node):
|
||||
if not (node.in_node(1).has_valid('value') and node.in_node(2).has_valid('value')):
|
||||
axes_1_value = node.in_port(1).data.get_value()
|
||||
axes_2_value = node.in_port(2).data.get_value()
|
||||
if axes_1_value is None or axes_2_value is None:
|
||||
log.warning('Reduction indices for mean and variance for MVN node {} are not constants'.format(node.name))
|
||||
return
|
||||
|
||||
if not (all(node.in_node(1).value == node.required_reduction_indices) and
|
||||
all(node.in_node(2).value == node.required_reduction_indices)):
|
||||
log.warning('Reduction indices for mean {} and variance {} do not match required ones {}'.format(
|
||||
node.in_node(1).value,
|
||||
node.in_node(2).value,
|
||||
node.required_reduction_indices
|
||||
if not (all(axes_1_value == axes_2_value)):
|
||||
log.warning('Reduction indices for mean {} and variance {} do not match'.format(
|
||||
axes_1_value,
|
||||
axes_2_value
|
||||
))
|
||||
return
|
||||
|
||||
node.graph.remove_edge(node.in_node(2).id, node.id)
|
||||
node.graph.remove_edge(node.in_node(1).id, node.id)
|
||||
node.in_port(2).disconnect()
|
||||
node.old_infer(node)
|
||||
node.infer = node.old_infer
|
||||
del node['old_infer']
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Copyright (C) 2017-2020 Intel Corporation
|
||||
Copyright (C) 2017-2021 Intel Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -56,7 +56,8 @@ class MVNUnrolled(FrontReplacementSubgraph):
|
||||
def replace_sub_graph(graph: Graph, match: dict):
|
||||
mvn = MVN(graph, dict(
|
||||
name=match['truediv'].name + '/MVN_',
|
||||
required_reduction_indices=[1, 2] if graph.graph['layout'] == 'NHWC' else [2, 3]
|
||||
eps_mode='outside_sqrt',
|
||||
normalize_variance=1
|
||||
))
|
||||
mvn.attrs['old_infer'] = mvn.attrs['infer']
|
||||
mvn.attrs['infer'] = __class__.infer
|
||||
@@ -72,29 +73,33 @@ class MVNUnrolled(FrontReplacementSubgraph):
|
||||
|
||||
@staticmethod
|
||||
def infer(node: Node):
|
||||
if not (node.in_node(1).has_valid('value') and node.in_node(2).has_valid('value')):
|
||||
axes_1_value = node.in_port(1).data.get_value()
|
||||
axes_2_value = node.in_port(2).data.get_value()
|
||||
if axes_1_value is None or axes_2_value is None:
|
||||
log.warning('Reduction indices for mean and variance for MVN node {} are not constants'.format(node.name))
|
||||
return
|
||||
|
||||
if not (all(node.in_node(1).value == node.required_reduction_indices) and
|
||||
all(node.in_node(2).value == node.required_reduction_indices)):
|
||||
log.warning('Reduction indices for mean {} and variance {} do not match required ones {}'.format(
|
||||
node.in_node(1).value,
|
||||
node.in_node(2).value,
|
||||
node.required_reduction_indices
|
||||
if not (all(axes_1_value == axes_2_value)):
|
||||
log.warning('Reduction indices for mean {} and variance {} do not match'.format(
|
||||
axes_1_value,
|
||||
axes_2_value
|
||||
))
|
||||
return
|
||||
|
||||
if not (node.in_node(3).has_valid('value') and node.in_node(4).has_valid('value')):
|
||||
|
||||
power_value = node.in_port(3).data.get_value()
|
||||
eps_value = node.in_port(4).data.get_value()
|
||||
if power_value is None or eps_value is None:
|
||||
log.warning('Power or/and epsilon values for MVN node {} are not constants'.format(node.name))
|
||||
return
|
||||
|
||||
if node.in_node(3).value != 0.5:
|
||||
log.warning('Power for MVN node {} ({}) is not equal to 0.5'.format(node.name, node.in_node(3).value))
|
||||
if power_value != 0.5:
|
||||
log.warning('Power for MVN node {} ({}) is not equal to 0.5'.format(node.name, power_value))
|
||||
return
|
||||
|
||||
node['eps'] = node.in_node(4).value
|
||||
node['eps'] = eps_value
|
||||
|
||||
for i in range(1, 5):
|
||||
node.graph.remove_edge(node.in_node(i).id, node.id)
|
||||
for i in range(2, 5):
|
||||
node.in_port(i).disconnect()
|
||||
node.old_infer(node)
|
||||
node.infer = node.old_infer
|
||||
del node['old_infer']
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Copyright (C) 2018-2020 Intel Corporation
|
||||
Copyright (C) 2018-2021 Intel Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -25,7 +25,7 @@ from extensions.ops.mvn import MVN
|
||||
from extensions.ops.transpose import Transpose
|
||||
from mo.front.common.partial_infer.utils import int64_array
|
||||
from mo.front.common.replacement import FrontReplacementSubgraph
|
||||
from mo.front.tf.graph_utils import create_op_node_with_second_input
|
||||
from mo.front.tf.graph_utils import create_op_node_with_second_input, create_op_with_const_inputs
|
||||
from mo.graph.graph import Graph
|
||||
from mo.ops.reshape import Reshape
|
||||
from mo.ops.shape import Shape
|
||||
@@ -173,8 +173,10 @@ class TransposedMVNUnrolled(FrontReplacementSubgraph):
|
||||
variance = match['variance']
|
||||
eps_port_num = 0 if add.in_port(0).get_connection().get_source().node.id != variance.id else 1
|
||||
eps = add.in_port(eps_port_num).get_connection().get_source().node
|
||||
mvn_node = MVN(graph, dict(name=div_name + '/MVN/MVN_T_', required_reduction_indices=[1, 2, 3],
|
||||
eps=eps.value)).create_node()
|
||||
mvn_node = create_op_with_const_inputs(graph, MVN, {1: int64_array([1, 2, 3])},
|
||||
dict(name=div_name + '/MVN/MVN_T_',
|
||||
eps=eps.value, normalize_variance=1,
|
||||
eps_mode='inside_sqrt'))
|
||||
first_permute.out_port(0).connect(mvn_node.in_port(0))
|
||||
|
||||
second_permute = create_op_node_with_second_input(graph, Transpose, permute_order,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Copyright (C) 2018-2020 Intel Corporation
|
||||
Copyright (C) 2018-2021 Intel Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -17,8 +17,9 @@
|
||||
import numpy as np
|
||||
|
||||
from extensions.ops.mvn import MVN
|
||||
from extensions.ops.range import Range
|
||||
from mo.front.common.partial_infer.utils import int64_array
|
||||
from mo.front.tf.graph_utils import create_op_node_with_second_input
|
||||
from mo.front.tf.graph_utils import create_op_node_with_second_input, create_op_with_const_inputs
|
||||
from mo.graph.graph import Graph
|
||||
from mo.middle.replacement import MiddleReplacementPattern
|
||||
from mo.ops.const import Const
|
||||
@@ -45,20 +46,21 @@ class FusedBatchNormTraining(MiddleReplacementPattern):
|
||||
return dict(
|
||||
nodes=[
|
||||
('op', dict(kind='op', op=lambda op: op in ['FusedBatchNorm', 'FusedBatchNormV2', 'FusedBatchNormV3'],
|
||||
is_training=True))],
|
||||
is_training=True))],
|
||||
edges=[]
|
||||
)
|
||||
|
||||
def replace_pattern(self, graph: Graph, match: dict):
|
||||
node = match['op']
|
||||
node_name = node.soft_get('name', node.id)
|
||||
node.is_training = False
|
||||
|
||||
shape = node.in_port(1).data.get_shape()
|
||||
assert shape is not None, 'The shape of scale input of the BatchNorm node {} is not defined'.format(node.name)
|
||||
|
||||
bn_mean = Const(graph, {'name': node.name + '/mean', 'value': np.zeros(shape, dtype=np.float32),
|
||||
bn_mean = Const(graph, {'name': node_name + '/mean', 'value': np.zeros(shape, dtype=np.float32),
|
||||
'override_output_shape': True}).create_node()
|
||||
bn_std = Const(graph, {'name': node.name + '/std', 'value': np.ones(shape, dtype=np.float32),
|
||||
bn_std = Const(graph, {'name': node_name + '/std', 'value': np.ones(shape, dtype=np.float32),
|
||||
'override_output_shape': True}).create_node()
|
||||
node.in_port(3).get_connection().set_source(bn_mean.out_port(0))
|
||||
node.in_port(4).get_connection().set_source(bn_std.out_port(0))
|
||||
@@ -67,17 +69,22 @@ class FusedBatchNormTraining(MiddleReplacementPattern):
|
||||
original_shape = Shape(graph, {'name': node.in_port(0).get_source().node.soft_get('name')}).create_node()
|
||||
original_shape.in_port(0).connect(node.in_port(0).get_source())
|
||||
|
||||
mvn = MVN(graph, {'name': node.name + '/mvn_', 'eps': node.soft_get('eps', 1e-6),
|
||||
'override_output_shape': True}).create_node()
|
||||
input_rank = len(node.in_port(0).data.get_shape())
|
||||
rng = create_op_with_const_inputs(graph, Range,
|
||||
{0: int64_array(2), 1: int64_array(input_rank), 2: int64_array(1)},
|
||||
{'name': node_name + '/Range', 'output_type': np.int64})
|
||||
mvn = MVN(graph, {'name': node_name + '/mvn_', 'eps': node.soft_get('eps', 1e-6), 'eps_mode': 'outside_sqrt',
|
||||
'normalize_variance': 1, 'override_output_shape': True}).create_node()
|
||||
node.in_port(0).get_connection().insert_node(mvn)
|
||||
mvn.in_port(1).connect(rng.out_port(0))
|
||||
|
||||
reshape_4d = create_op_node_with_second_input(graph, Reshape, int64_array([1, -1, 0, 0]),
|
||||
{'override_output_shape': True,
|
||||
'name': node.soft_get('name') + '/fused_batch_and_channels'})
|
||||
'name': node_name + '/fused_batch_and_channels'})
|
||||
mvn.in_port(0).get_connection().insert_node(reshape_4d)
|
||||
|
||||
# restore original shape
|
||||
reshape_back = Reshape(graph, {'name': mvn.soft_get('name') + '/restore_shape',
|
||||
reshape_back = Reshape(graph, {'name': node_name + '/restore_shape',
|
||||
'override_output_shape': True}).create_node()
|
||||
reshape_back.in_port(1).connect(original_shape.out_port(0))
|
||||
mvn.out_port(0).get_connection().insert_node(reshape_back)
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Copyright (C) 2018-2020 Intel Corporation
|
||||
Copyright (C) 2018-2021 Intel Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -65,6 +65,15 @@ nodes_attributes = {
|
||||
'reshape_to_orig': {'type': 'Reshape', 'value': None, 'kind': 'op', 'op': 'Reshape'},
|
||||
'reshape_to_orig_data': {'value': None, 'shape': None, 'kind': 'data'},
|
||||
|
||||
'start': {'kind': 'op', 'op': 'Const'},
|
||||
'start_data': {'value': None, 'shape': None, 'kind': 'data'},
|
||||
'stop': {'kind': 'op', 'op': 'Const'},
|
||||
'stop_data': {'value': None, 'shape': None, 'kind': 'data'},
|
||||
'step': {'kind': 'op', 'op': 'Const'},
|
||||
'step_data': {'value': None, 'shape': None, 'kind': 'data'},
|
||||
'mvn_axes': {'kind': 'op', 'op': 'Range'},
|
||||
'mvn_axes_data': {'value': None, 'shape': None, 'kind': 'data'},
|
||||
|
||||
'mvn': {'type': 'MVN', 'value': None, 'kind': 'op', 'op': 'MVN', 'eps': 1e-3},
|
||||
'mvn_data': {'value': None, 'shape': None, 'kind': 'data'},
|
||||
|
||||
@@ -115,6 +124,14 @@ class FusedBatchNormTrainingTest(unittest.TestCase):
|
||||
('reshape_1_data', 'mvn', {'in': 0}),
|
||||
('mvn', 'mvn_data'),
|
||||
('mvn_data', 'reshape_to_orig', {'in': 0}),
|
||||
('start', 'start_data'),
|
||||
('start_data', 'mvn_axes'),
|
||||
('stop', 'stop_data'),
|
||||
('stop_data', 'mvn_axes'),
|
||||
('step', 'step_data'),
|
||||
('step_data', 'mvn_axes'),
|
||||
('mvn_axes', 'mvn_axes_data'),
|
||||
('mvn_axes_data', 'mvn'),
|
||||
('placeholder_data', 'shapeof', {'in': 0}),
|
||||
('shapeof', 'shapeof_data'),
|
||||
('shapeof_data', 'reshape_to_orig', {'in': 1}),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Copyright (C) 2018-2020 Intel Corporation
|
||||
Copyright (C) 2018-2021 Intel Corporation
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -21,7 +21,9 @@ import numpy as np
|
||||
from extensions.ops.Cast import Cast
|
||||
from extensions.ops.elementwise import Mul, Add
|
||||
from extensions.ops.mvn import MVN
|
||||
from extensions.ops.range import Range
|
||||
from mo.front.common.partial_infer.utils import int64_array
|
||||
from mo.front.tf.graph_utils import create_op_with_const_inputs
|
||||
from mo.graph.graph import Graph, Node
|
||||
from mo.middle.passes.convert_data_type import data_type_str_to_np
|
||||
from mo.middle.replacement import MiddleReplacementPattern
|
||||
@@ -29,7 +31,7 @@ from mo.ops.const import Const
|
||||
from mo.ops.reshape import Reshape
|
||||
from mo.ops.shape import Shape
|
||||
from mo.utils.shape import node_to_get_spatial_dimensions_value, node_to_get_features_dimension_value, \
|
||||
node_to_get_batch_value, new_shape_node_from_shape_nodes
|
||||
node_to_get_batch_value, new_shape_node_from_shape_nodes, get_shape_and_rank_nodes_by_port
|
||||
|
||||
|
||||
class GroupNormToMVN(MiddleReplacementPattern):
|
||||
@@ -115,11 +117,19 @@ class GroupNormToMVN(MiddleReplacementPattern):
|
||||
|
||||
# MVN
|
||||
mvn_node = MVN(graph, {'name': group_norm_node.name + '/MVN',
|
||||
'across_channels': 1,
|
||||
'normalize_variance': 1,
|
||||
'eps': group_norm_node.eps}).create_node()
|
||||
'eps': group_norm_node.eps,
|
||||
'eps_mode': 'inside_sqrt'}).create_node()
|
||||
mvn_node.in_port(0).connect(reshape_for_mvn_node.out_port(0))
|
||||
|
||||
# MVN axes
|
||||
_, rank = get_shape_and_rank_nodes_by_port(mvn_node.in_port(0), return_as_a_scalar=True)
|
||||
rng = create_op_with_const_inputs(graph, Range, {0: int64_array(1), 2: int64_array(1)},
|
||||
{'name': group_norm_node.name + '/Range', 'output_type': np.int64})
|
||||
mvn_node.in_port(1).connect(rng.out_port(0))
|
||||
rng.in_port(1).connect(rank.out_port(0))
|
||||
mvn_node.in_port(0).get_connection().add_destination(rank.in_port(0))
|
||||
|
||||
# reshape to the initial shape before multiplying with gamma and adding beta
|
||||
reshape_to_initial_shape_node = Reshape(graph, {}).create_node()
|
||||
reshape_to_initial_shape_node.in_port(0).connect(mvn_node.out_port(0))
|
||||
|
||||
@@ -18,6 +18,7 @@ from mo.front.common.layout import get_features_dim
|
||||
from mo.front.common.partial_infer.elemental import copy_shape_infer
|
||||
from mo.front.extractor import bool_to_str
|
||||
from mo.graph.graph import Graph
|
||||
from mo.graph.perm_inputs import PermuteInputs
|
||||
from mo.ops.op import Op
|
||||
from mo.utils.error import Error
|
||||
|
||||
@@ -29,55 +30,58 @@ class MVN(Op):
|
||||
def __init__(self, graph: Graph, attrs: dict):
|
||||
super().__init__(graph, {
|
||||
'kind': 'op',
|
||||
'type': __class__.op,
|
||||
'op': __class__.op,
|
||||
'version': 'opset2',
|
||||
'type': self.op,
|
||||
'op': self.op,
|
||||
'version': 'opset6',
|
||||
'eps': None,
|
||||
'across_channels': None,
|
||||
'normalize_variance': 1,
|
||||
'axes': None,
|
||||
'in_ports_count': 1,
|
||||
'normalize_variance': None,
|
||||
'eps_mode': None,
|
||||
'in_ports_count': 2,
|
||||
'out_ports_count': 1,
|
||||
'infer': __class__.infer
|
||||
'infer': self.infer
|
||||
}, attrs)
|
||||
|
||||
def supported_attrs(self):
|
||||
return ['eps', 'across_channels', 'normalize_variance', 'axes']
|
||||
return ['eps', 'eps_mode', 'normalize_variance']
|
||||
|
||||
def backend_attrs(self):
|
||||
return ['eps',
|
||||
('across_channels', lambda node: bool_to_str(node, 'across_channels')),
|
||||
('normalize_variance', lambda node: bool_to_str(node, 'normalize_variance'))]
|
||||
version = self.get_opset()
|
||||
if version == 'opset2':
|
||||
return ['eps',
|
||||
('across_channels', lambda node: bool_to_str(node, 'across_channels')),
|
||||
('normalize_variance', lambda node: bool_to_str(node, 'normalize_variance'))]
|
||||
elif version == 'opset6':
|
||||
return ['eps', 'eps_mode', ('normalize_variance', lambda node: bool_to_str(node, 'normalize_variance'))]
|
||||
else:
|
||||
raise Error('Unsupported MVN opset version "{}"'.format(version))
|
||||
|
||||
@staticmethod
|
||||
def infer(node: None):
|
||||
input_shape = node.in_node(0).shape
|
||||
name = node.soft_get('name', node.id)
|
||||
|
||||
if node.axes is not None and node.across_channels is not None:
|
||||
raise Error('Either axes or across_channels can be set for the MVN in node "{}".'.format(name))
|
||||
|
||||
if node.across_channels is None:
|
||||
if node.axes is not None:
|
||||
# normalizing (replacing -1 with actual index)
|
||||
axes_data_value = node.axes
|
||||
axes = [axes_data_value.item()] if axes_data_value.size == 1 else axes_data_value
|
||||
axes = [get_canonical_axis_index(input_shape, a) for a in axes]
|
||||
# deduce across_channels from the axes, e.g. if the first axis is included (assuming batch is zero axis)
|
||||
feature_dim = get_features_dim(node.graph.graph['layout'], len(input_shape)) \
|
||||
if (4 <= len(input_shape) <= 5) \
|
||||
else 1
|
||||
node.across_channels = int(feature_dim in axes)
|
||||
|
||||
if 0 in axes:
|
||||
raise Error('Reduction over the batch dimension in node "{}" '
|
||||
'is not supported by the backend.'.format(name))
|
||||
for i in range(2, len(input_shape)):
|
||||
if i not in axes:
|
||||
raise Error(
|
||||
'Reduction over spatial dimensions in node "{}" '
|
||||
'is obligatory for the backend.'.format(name))
|
||||
else:
|
||||
node.across_channels = 0 # default
|
||||
assert node.eps is not None, 'MVN required attribute `eps` unspecified for node {}'.format(name)
|
||||
assert node.eps_mode is not None, 'MVN required attribute `eps_mode` unspecified for node {}'.format(name)
|
||||
assert node.normalize_variance is not None, \
|
||||
'MVN required attribute `normalize_variance` unspecified for node {}'.format(name)
|
||||
|
||||
PermuteInputs().set_input_permutation(node.in_node(1), node, 'input:0', 'axis')
|
||||
copy_shape_infer(node)
|
||||
|
||||
|
||||
class MVNCaffe(Op):
|
||||
op = 'MVNCaffe'
|
||||
enabled = False
|
||||
|
||||
def __init__(self, graph: Graph, attrs: dict):
|
||||
super().__init__(graph, {
|
||||
'kind': 'op',
|
||||
'type': None,
|
||||
'op': self.op,
|
||||
'version': None,
|
||||
'eps': 1e-9,
|
||||
'normalize_variance': 1,
|
||||
'across_channels': 0,
|
||||
'in_ports_count': 1,
|
||||
'out_ports_count': 1,
|
||||
'infer': None
|
||||
}, attrs)
|
||||
Reference in new issue
Block a user