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:
Maxim Vafin authored and GitHub committed 2021-02-02 17:45:56 +03:00
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
+13 -11
View File
@@ -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}),
+14 -4
View File
@@ -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))
+42 -38
View File
@@ -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)