[MO] Add transformation for single CTCGreedyDecoder operation (#7023)

* Add transformation for single CTCGreedyDecoder operation

* Fix style in op specification

* Update transformation logic

* refactor old tests and add tests for new transformation

* Move tf specific front transformations to tf folder

* Update transformation logic and comments

* Add run_after function and update comments

* Add output_sparse_format attribute to extractor

* Update transformation conditions and tests

* Fix incorrect comment

* Move sparse_to_dense_replacer to front/tf folder to fix problems with class registration

* Update import

* Update output ports handling in transformation

* Update test

* Fix BOM file

* Update pattern for ctcloss transformation

* Fix and refactor tests for ctcloss transform

* Update transformation conditions
This commit is contained in:
Anton Chetverikov authored and GitHub committed 2021-08-24 15:42:27 +03:00
1 parent 83e1359c9e
commit f8e90f0c56
12 files changed
+422 -311

No files matched your search

@@ -74,7 +74,8 @@ The main difference between [CTCGreedyDecoder](CTCGreedyDecoder_1.md) and CTCGre
**Example**
```xml
<layer type="CTCGreedyDecoderSeqLen" merge_repeated="true" classes_index_type="i64" sequence_length_type="i64">
<layer ... type="CTCGreedyDecoderSeqLen" version="opset6">
<data merge_repeated="true" classes_index_type="i64" sequence_length_type="i64"/>
<input>
<port id="0">
<dim>8</dim>
+3 -3
View File
@@ -133,8 +133,6 @@ extensions/front/caffe/split_to_identity.py
extensions/front/caffe/tanh.py
extensions/front/ChangePlaceholderTypes.py
extensions/front/create_tensor_nodes.py
extensions/front/CTCGreedyDecoderReplacement.py
extensions/front/CTCLossReplacement.py
extensions/front/disable_weights_quantize_value_propagation.py
extensions/front/div.py
extensions/front/DropoutWithRandomUniformReplacer.py
@@ -370,7 +368,6 @@ extensions/front/scatter_normalizer.py
extensions/front/SizeReplacer.py
extensions/front/softmax.py
extensions/front/softsign_replacer.py
extensions/front/sparse_to_dense_replacer.py
extensions/front/split_normalizer.py
extensions/front/SqueezeNormalize.py
extensions/front/sub.py
@@ -401,7 +398,9 @@ extensions/front/tf/CorrectRollAxes.py
extensions/front/tf/crop_and_resize_ext.py
extensions/front/tf/CropAndResizeReplacement.py
extensions/front/tf/CTCGreedyDecoder_ext.py
extensions/front/tf/CTCGreedyDecoderReplacement.py
extensions/front/tf/CTCLoss_ext.py
extensions/front/tf/CTCLossReplacement.py
extensions/front/tf/cumsum_ext.py
extensions/front/tf/deconv_ext.py
extensions/front/tf/depth_to_space.py
@@ -496,6 +495,7 @@ extensions/front/tf/sparse_fill_empty_rows_ext.py
extensions/front/tf/sparse_segment_mean_ext.py
extensions/front/tf/sparse_segment_sqrtn_ext.py
extensions/front/tf/sparse_segment_sum_ext.py
extensions/front/tf/sparse_to_dense_replacer.py
extensions/front/tf/split_ext.py
extensions/front/tf/ssd_support.json
extensions/front/tf/ssd_support_api_v1.14.json
@@ -1,91 +0,0 @@
# Copyright (C) 2018-2021 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
from extensions.ops.ctc_greedy_decoder_seq_len import CTCGreedyDecoderSeqLenOp
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_with_const_inputs
from mo.graph.graph import Graph, rename_nodes
def replace_ctc_greedy_decoder(graph: Graph, match: dict):
ctc_greedy_decoder_tf = match['decoder']
cast = match['cast']
sparse_to_dense = match['sparse_to_dense']
sparse_to_dense_name = sparse_to_dense.soft_get('name', sparse_to_dense.id)
ctc_greedy_decoder_tf_name = ctc_greedy_decoder_tf.soft_get('name', ctc_greedy_decoder_tf.id)
# for normalizing input chanel need to transpose input data from [T, N, C] to [N, T, C]
# which supported CTCGreedyDecoderSeqLen op.
ctc_data_permute = create_op_with_const_inputs(graph, Transpose, {1: int64_array([1, 0, 2])},
{'name': ctc_greedy_decoder_tf_name + '/ctc_data_permute'})
assert ctc_greedy_decoder_tf.has_valid('merge_repeated'), \
'The CTCGreedyDecoderSeqLen node "{}" misses "merge_repeated" attribute'.format(ctc_greedy_decoder_tf_name)
ctc_greedy_decoder_tf.in_port(0).get_source().connect(ctc_data_permute.in_port(0))
merge_repeated_tf = ctc_greedy_decoder_tf.merge_repeated
ctc_greedy_decoder = CTCGreedyDecoderSeqLenOp(graph, {'name': sparse_to_dense_name,
'merge_repeated': merge_repeated_tf}).create_node()
rename_nodes(
[(sparse_to_dense, sparse_to_dense_name + '/AbandonedName'), (ctc_greedy_decoder, sparse_to_dense_name)])
ctc_greedy_decoder.in_port(0).connect(ctc_data_permute.out_port(0))
ctc_greedy_decoder_tf.in_port(1).get_source().connect(ctc_greedy_decoder.in_port(1))
# set output of the new sub-graph as a source for SparseToDense consumer
sparse_to_dense.out_port(0).get_connection().set_source(ctc_greedy_decoder.out_port(0))
# remove no longer needed nodes
graph.remove_nodes_from([sparse_to_dense.id, cast.id, ctc_greedy_decoder_tf.id])
class CTCGreedyDecoderReplacement(FrontReplacementSubgraph):
"""
TensorFlow CTCGreedyDecoder produces output in a sparse tensor that is not supported by Inference Engine and
Inference Engine's CTCGreedyDecoderSeqLen has different output that is in a dense format. So this transformation
intents to replace TF CTCGreedyDecoder+SparseToDense where SparseToDense third input get from input parameter
to CTCGreedyDecoderSeqLen which compatible with IE.
"""
enabled = True
@staticmethod
def pattern(**kwargs):
return dict(
nodes=[('decoder', dict(op='CTCGreedyDecoderSeqLen')),
('cast', dict(op='Cast')),
('sparse_to_dense', dict(op='SparseToDense'))
],
edges=[('decoder', 'sparse_to_dense', {'out': 0}),
('decoder', 'cast', {'out': 1}),
('cast', 'sparse_to_dense', {'out': 0})]
)
def replace_sub_graph(self, graph: Graph, match: dict):
replace_ctc_greedy_decoder(graph, match)
class CTCGreedyDecoderWithSparseToDenseShapeReplacement(FrontReplacementSubgraph):
"""
TensorFlow CTCGreedyDecoder produces output in a sparse tensor that is not supported by Inference Engine and
Inference Engine's CTCGreedyDecoderSeqLen has different output that is in a dense format. So this transformation
intents to replace TF CTCGreedyDecoder+SparseToDense where SparseToDense third input get from CTCGreedyDecoder
second output to CTCGreedyDecoderSeqLen which compatible with IE.
"""
enabled = True
@staticmethod
def pattern(**kwargs):
return dict(
nodes=[('decoder', dict(op='CTCGreedyDecoderSeqLen')),
('cast', dict(op='Cast')),
('sparse_to_dense', dict(op='SparseToDense'))
],
edges=[('decoder', 'sparse_to_dense', {'out': 0}),
('decoder', 'cast', {'out': 1}),
('decoder', 'sparse_to_dense', {'out': 2}),
('cast', 'sparse_to_dense', {'out': 0})]
)
def replace_sub_graph(self, graph: Graph, match: dict):
replace_ctc_greedy_decoder(graph, match)
@@ -0,0 +1,155 @@
# Copyright (C) 2018-2021 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
import logging as log
from extensions.ops.ctc_greedy_decoder_seq_len import CTCGreedyDecoderSeqLenOp
from extensions.ops.transpose import Transpose
from mo.front.common.partial_infer.utils import int64_array
from mo.front.common.replacement import FrontReplacementSubgraph, FrontReplacementPattern
from mo.front.tf.graph_utils import create_op_with_const_inputs
from mo.graph.graph import Graph, rename_nodes
from mo.ops.result import Result
def replace_ctc_greedy_decoder(graph: Graph, match: dict):
ctc_greedy_decoder_tf = match['decoder']
cast = match['cast']
sparse_to_dense = match['sparse_to_dense']
sparse_to_dense_name = sparse_to_dense.soft_get('name', sparse_to_dense.id)
ctc_greedy_decoder_tf_name = ctc_greedy_decoder_tf.soft_get('name', ctc_greedy_decoder_tf.id)
# For normalizing input channel needs to transpose input data from [T, N, C] to [N, T, C]
# which supported CTCGreedyDecoderSeqLen op.
ctc_data_permute = create_op_with_const_inputs(graph, Transpose, {1: int64_array([1, 0, 2])},
{'name': ctc_greedy_decoder_tf_name + '/ctc_data_permute'})
assert ctc_greedy_decoder_tf.has_valid('merge_repeated'), \
'The CTCGreedyDecoderSeqLen node "{}" misses "merge_repeated" attribute'.format(ctc_greedy_decoder_tf_name)
ctc_greedy_decoder_tf.in_port(0).get_source().connect(ctc_data_permute.in_port(0))
merge_repeated_tf = ctc_greedy_decoder_tf.merge_repeated
ctc_greedy_decoder = CTCGreedyDecoderSeqLenOp(graph, {'name': sparse_to_dense_name,
'merge_repeated': merge_repeated_tf}).create_node()
rename_nodes(
[(sparse_to_dense, sparse_to_dense_name + '/AbandonedName'), (ctc_greedy_decoder, sparse_to_dense_name)])
ctc_greedy_decoder.in_port(0).connect(ctc_data_permute.out_port(0))
ctc_greedy_decoder_tf.in_port(1).get_source().connect(ctc_greedy_decoder.in_port(1))
# Set output of the new sub-graph as a source for SparseToDense consumer
sparse_to_dense.out_port(0).get_connection().set_source(ctc_greedy_decoder.out_port(0))
# Remove no longer needed nodes
graph.remove_nodes_from([sparse_to_dense.id, cast.id, ctc_greedy_decoder_tf.id])
class CTCGreedyDecoderReplacement(FrontReplacementSubgraph):
"""
TensorFlow CTCGreedyDecoder produces output in a sparse tensor that is not supported by Inference Engine, and
Inference Engine's CTCGreedyDecoderSeqLen has a different output that is in a dense format. So this transformation
intents to replace TF CTCGreedyDecoder+SparseToDense where SparseToDense third input get from input parameter
to CTCGreedyDecoderSeqLen which compatible with IE.
"""
enabled = True
@staticmethod
def pattern(**kwargs):
return dict(
nodes=[('decoder', dict(op='CTCGreedyDecoderSeqLen', output_sparse_format=True)),
('cast', dict(op='Cast')),
('sparse_to_dense', dict(op='SparseToDense'))
],
edges=[('decoder', 'sparse_to_dense', {'out': 0}),
('decoder', 'cast', {'out': 1}),
('cast', 'sparse_to_dense', {'out': 0})]
)
def replace_sub_graph(self, graph: Graph, match: dict):
replace_ctc_greedy_decoder(graph, match)
class CTCGreedyDecoderWithSparseToDenseShapeReplacement(FrontReplacementSubgraph):
"""
TensorFlow CTCGreedyDecoder produces output in a sparse tensor that is not supported by Inference Engine, and
Inference Engine's CTCGreedyDecoderSeqLen has a different output that is in a dense format. So this transformation
intents to replace TF CTCGreedyDecoder+SparseToDense where SparseToDense third input get from CTCGreedyDecoder
second output to CTCGreedyDecoderSeqLen which compatible with IE.
"""
enabled = True
@staticmethod
def pattern(**kwargs):
return dict(
nodes=[('decoder', dict(op='CTCGreedyDecoderSeqLen', output_sparse_format=True)),
('cast', dict(op='Cast')),
('sparse_to_dense', dict(op='SparseToDense'))
],
edges=[('decoder', 'sparse_to_dense', {'out': 0}),
('decoder', 'cast', {'out': 1}),
('decoder', 'sparse_to_dense', {'out': 2}),
('cast', 'sparse_to_dense', {'out': 0})]
)
def replace_sub_graph(self, graph: Graph, match: dict):
replace_ctc_greedy_decoder(graph, match)
class CTCGreedyDecoderSingleReplacement(FrontReplacementPattern):
"""
TensorFlow CTCGreedyDecoder produces output in a sparse tensor that is not supported by Inference Engine, and
Inference Engine's CTCGreedyDecoderSeqLen has a different output that is in a dense format. So this transformation
handles a single TF CTCGreedyDecoder and warns the user about another format of the output
"""
enabled = True
def run_after(self):
return [CTCGreedyDecoderReplacement, CTCGreedyDecoderWithSparseToDenseShapeReplacement]
def find_and_replace_pattern(self, graph: Graph):
for ctc_greedy_decoder_tf in graph.get_op_nodes(op='CTCGreedyDecoderSeqLen', output_sparse_format=True):
ctc_greedy_decoder_tf_name = ctc_greedy_decoder_tf.soft_get('name', ctc_greedy_decoder_tf.id)
# TF CTCGreedyDecoder have 4 output tensors. If any of them connected to not Result operation then
# transformation in not applicable
for port_num in ctc_greedy_decoder_tf.out_ports():
if not ctc_greedy_decoder_tf.out_port(port_num).disconnected()\
and ctc_greedy_decoder_tf.out_port(port_num).get_destination().node.soft_get('op') != 'Result':
return
# If the first and second output are not connected to Result operations -
# create Result operation and connect it to appropriate output
if ctc_greedy_decoder_tf.out_port(0).disconnected():
first_result = Result(graph,
{'name': ctc_greedy_decoder_tf_name + '/decoded_classes'}
).create_node()
ctc_greedy_decoder_tf.out_port(0).connect(first_result.in_port(0))
if ctc_greedy_decoder_tf.out_port(1).disconnected():
second_result = Result(graph,
{'name': ctc_greedy_decoder_tf_name + '/seq_lengths_output'}
).create_node()
ctc_greedy_decoder_tf.out_port(1).connect(second_result.in_port(0))
# For normalizing input channel needs to transpose input data from [T, N, C] to [N, T, C]
# which supported CTCGreedyDecoderSeqLen op.
log.warning('Found TF CTCGreedyDecoder operation at the end of network. '
'PLEASE NOTE, appropriate network output operation CTCGreedyDecoderSeqLen {} '
'will have dense format, not sparse format!'.format(ctc_greedy_decoder_tf_name))
ctc_data_permute = create_op_with_const_inputs(graph, Transpose, {1: int64_array([1, 0, 2])},
{'name': ctc_greedy_decoder_tf_name + '/ctc_data_permute'})
assert ctc_greedy_decoder_tf.has_valid('merge_repeated'), \
'The CTCGreedyDecoderSeqLen node "{}" misses "merge_repeated" attribute'.format(
ctc_greedy_decoder_tf_name)
ctc_greedy_decoder_tf.in_port(0).get_source().connect(ctc_data_permute.in_port(0))
ctc_greedy_decoder_tf.in_port(0).disconnect()
ctc_data_permute.out_port(0).connect(ctc_greedy_decoder_tf.in_port(0))
del ctc_greedy_decoder_tf['output_sparse_format']
for port_num in [2, 3]: # MO CTCGreedyDecoderSeqLen may have 2 outputs
if port_num in ctc_greedy_decoder_tf.out_ports():
if not ctc_greedy_decoder_tf.out_port(port_num).disconnected():
ctc_greedy_decoder_tf.out_port(port_num).disconnect()
@@ -13,6 +13,7 @@ class CTCCGreedyDecoderFrontExtractor(FrontExtractorOp):
def extract(cls, node):
attrs = {
'merge_repeated': bool(node.pb.attr['merge_repeated'].b),
'output_sparse_format': True, # Special argument for TF CTCGreedyDecoder replacement transformations
}
CTCGreedyDecoderSeqLenOp.update_node_stat(node, attrs)
return cls.enabled
@@ -20,14 +20,14 @@ class CTCLossReplacement(FrontReplacementSubgraph):
enabled = True
def run_before(self):
from extensions.front.CTCGreedyDecoderReplacement import CTCGreedyDecoderReplacement
from extensions.front.tf.CTCGreedyDecoderReplacement import CTCGreedyDecoderReplacement
return [CTCGreedyDecoderReplacement]
def pattern(self):
return dict(
nodes=[
('transpose', dict(op='Transpose')),
('ctc_greedy_decoder', dict(op='CTCGreedyDecoderSeqLen')),
('ctc_greedy_decoder', dict(op='CTCGreedyDecoderSeqLen', output_sparse_format=True)),
('cast', dict(op='Cast')),
('sparse_to_dense', dict(op='SparseToDense')),
('const', dict(op='Const')),
@@ -21,8 +21,8 @@ class SparseToDenseReplacer(FrontReplacementOp):
enabled = True
def run_after(self):
from extensions.front.CTCGreedyDecoderReplacement import CTCGreedyDecoderReplacement
from extensions.front.CTCLossReplacement import CTCLossReplacement
from extensions.front.tf.CTCGreedyDecoderReplacement import CTCGreedyDecoderReplacement
from extensions.front.tf.CTCLossReplacement import CTCLossReplacement
return [CTCGreedyDecoderReplacement, CTCLossReplacement]
def replace_op(self, graph: Graph, node: Node):
@@ -1,96 +0,0 @@
# Copyright (C) 2018-2021 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
import unittest
from extensions.front.CTCGreedyDecoderReplacement import CTCGreedyDecoderReplacement, CTCGreedyDecoderWithSparseToDenseShapeReplacement
from mo.front.common.partial_infer.utils import int64_array
from mo.utils.ir_engine.compare_graphs import compare_graphs
from unit_tests.utils.graph import build_graph, const
class CTCGreedyDecoderReplacementTests(unittest.TestCase):
def test1(self):
nodes_attributes = {
# nodes from original graph
'logits': {'type': 'Parameter', 'kind': 'op', 'op': 'Parameter'},
'seq_len': {'type': 'Parameter', 'kind': 'op', 'op': 'Parameter'},
'order_arr': {'kind': 'op', 'op': 'Const'},
'transpose': {'type': 'Transpose', 'kind': 'op', 'op': 'Transpose'},
'decoder': {'kind': 'op', 'op': 'CTCGreedyDecoderSeqLen', 'merge_repeated': True},
'cast': {'kind': 'op', 'op': 'Cast'},
'sparse_to_dense': {'kind': 'op', 'op': 'SparseToDense'},
'last': {'type': None, 'value': None, 'kind': 'op', 'op': 'Result'},
# new nodes
'new_decoder': {'kind': 'op', 'op': 'CTCGreedyDecoderSeqLen', 'use_mask_format': True},
**const('squeeze_axes', int64_array([2, 3])),
'squeeze_dec_seq': {'kind': 'op', 'op': 'Squeeze'},
'cast_to_int': {'kind': 'op', 'op': 'Cast'},
}
graph = build_graph(nodes_attributes,
[('logits', 'decoder', {'out': 0, 'in': 0}),
('seq_len', 'decoder', {'out': 0, 'in': 1}),
('decoder', 'sparse_to_dense', {'out': 0, 'in': 0}),
('decoder', 'sparse_to_dense', {'out': 2, 'in': 1}),
('decoder', 'cast', {'out': 1, 'in': 0}),
('cast', 'sparse_to_dense', {'out': 0}),
('sparse_to_dense', 'last', {'out': 0, 'in': 0}),
], nodes_with_edges_only=True)
graph.stage = 'front'
CTCGreedyDecoderWithSparseToDenseShapeReplacement().find_and_replace_pattern(graph)
graph_ref = build_graph(nodes_attributes,
[('logits', 'transpose', {'out': 0, 'in': 0}),
('order_arr', 'transpose', {'out': 0, 'in': 1}),
('transpose', 'decoder', {'out': 0, 'in': 0}),
('seq_len', 'decoder', {'out': 0, 'in': 1}),
('decoder', 'last', {'out': 0, 'in': 0}),
],
nodes_with_edges_only=True)
(flag, resp) = compare_graphs(graph, graph_ref, 'last', check_op_attrs=True)
self.assertTrue(flag, resp)
def test2(self):
nodes_attributes = {
# nodes from original graph
'logits': {'type': 'Parameter', 'kind': 'op', 'op': 'Parameter'},
'seq_len': {'type': 'Parameter', 'kind': 'op', 'op': 'Parameter'},
'order_arr': {'kind': 'op', 'op': 'Const'},
'transpose': {'type': 'Transpose', 'kind': 'op', 'op': 'Transpose'},
'decoder': {'kind': 'op', 'op': 'CTCGreedyDecoderSeqLen', 'merge_repeated': True},
'cast': {'kind': 'op', 'op': 'Cast'},
'sparse_to_dense': {'kind': 'op', 'op': 'SparseToDense'},
'last': {'type': None, 'value': None, 'kind': 'op', 'op': 'Result'},
# new nodes
'new_decoder': {'kind': 'op', 'op': 'CTCGreedyDecoderSeqLen', 'use_mask_format': True},
**const('squeeze_axes', int64_array([2, 3])),
'squeeze_dec_seq': {'kind': 'op', 'op': 'Squeeze'},
'cast_to_int': {'kind': 'op', 'op': 'Cast'},
}
graph = build_graph(nodes_attributes,
[('logits', 'decoder', {'out': 0, 'in': 0}),
('seq_len', 'decoder', {'out': 0, 'in': 1}),
('decoder', 'sparse_to_dense', {'out': 0, 'in': 0}),
('decoder', 'cast', {'out': 1, 'in': 0}),
('cast', 'sparse_to_dense', {'out': 0}),
('sparse_to_dense', 'last', {'out': 0, 'in': 0}),
], nodes_with_edges_only=True)
graph.stage = 'front'
CTCGreedyDecoderReplacement().find_and_replace_pattern(graph)
graph_ref = build_graph(nodes_attributes,
[('logits', 'transpose', {'out': 0, 'in': 0}),
('order_arr', 'transpose', {'out': 0, 'in': 1}),
('transpose', 'decoder', {'out': 0, 'in': 0}),
('seq_len', 'decoder', {'out': 0, 'in': 1}),
('decoder', 'last', {'out': 0, 'in': 0}),
],
nodes_with_edges_only=True)
(flag, resp) = compare_graphs(graph, graph_ref, 'last', check_op_attrs=True)
self.assertTrue(flag, resp)
@@ -1,115 +0,0 @@
# Copyright (C) 2018-2021 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
import numpy as np
import unittest
from argparse import Namespace
from extensions.front.CTCLossReplacement import CTCLossReplacement
from mo.front.common.partial_infer.utils import int64_array
from mo.utils.ir_engine.compare_graphs import compare_graphs
from unit_tests.utils.graph import build_graph, const
class CTCLossFrontReplacementTest(unittest.TestCase):
def test1(self):
nodes_attributes = {
'logits': {'shape': int64_array([2, 6, 100]), 'type': 'Parameter', 'kind': 'op', 'op': 'Parameter'},
'seq_mask': {'shape': int64_array([2]), 'data_type': np.int32, 'kind': 'op', 'op': 'Parameter'},
'transpose': {'kind': 'op', 'op': 'Transpose'},
'ctc_greedy_decoder': {'kind': 'op', 'op': 'CTCGreedyDecoderSeqLen', 'merge_repeated': True},
'cast': {'kind': 'op', 'op': 'Cast'},
'sparse_to_dense': {'kind': 'op', 'op': 'SparseToDense'},
'tf_ctc_loss': {'kind': 'op', 'op': 'CTCLoss', 'preprocess_collapse_repeated': False,
'ctc_merge_repeated': True, 'unique': False, 'logits_time_major': True},
'ctc_loss': {'kind': 'op', 'op': 'CTCLoss', 'preprocess_collapse_repeated': False,
'ctc_merge_repeated': True, 'unique': False},
**const('default_value', int64_array(-1)),
'last': {'type': None, 'value': None, 'kind': 'op', 'op': 'Result'},
'transpose2': {'kind': 'op', 'op': 'Transpose'},
**const('transpose2_axis', int64_array([1, 0, 2])),
}
graph = build_graph(nodes_attributes, [('logits', 'transpose', {'out': 0, 'in': 0}),
('transpose', 'ctc_greedy_decoder', {'out': 0, 'in': 0}),
('seq_mask', 'ctc_greedy_decoder', {'out': 0, 'in': 1}),
('transpose', 'tf_ctc_loss', {'out': 0, 'in': 0}),
('seq_mask', 'tf_ctc_loss', {'out': 0, 'in': 3}),
('ctc_greedy_decoder', 'sparse_to_dense', {'out': 0, 'in': 0}),
('ctc_greedy_decoder', 'sparse_to_dense', {'out': 2, 'in': 1}),
('ctc_greedy_decoder', 'sparse_to_dense', {'out': 1, 'in': 2}),
('default_value', 'sparse_to_dense', {'out': 0, 'in': 3}),
('ctc_greedy_decoder', 'cast', {'out': 1, 'in': 0}),
('ctc_greedy_decoder', 'tf_ctc_loss', {'out': 0, 'in': 1}),
('cast', 'tf_ctc_loss', {'out': 0, 'in': 2}),
('tf_ctc_loss', 'last', {'out': 0, 'in': 0})],
nodes_with_edges_only=True)
graph.graph['cmd_params'] = Namespace(data_type='FP32')
graph.stage = 'front'
CTCLossReplacement().find_and_replace_pattern(graph)
graph_ref = build_graph(nodes_attributes,
[('logits', 'transpose', {'out': 0, 'in': 0}),
('transpose', 'transpose2', {'out': 0, 'in': 0}),
('transpose2_axis', 'transpose2', {'out': 0, 'in': 1}),
('transpose2', 'ctc_greedy_decoder', {'out': 0, 'in': 0}),
('seq_mask', 'ctc_greedy_decoder', {'out': 0, 'in': 1}),
('transpose2', 'ctc_loss', {'out': 0, 'in': 0}),
('ctc_greedy_decoder', 'ctc_loss', {'out': 0, 'in': 2}),
('ctc_greedy_decoder', 'ctc_loss', {'out': 1, 'in': 3}),
('seq_mask', 'ctc_loss', {'out': 0, 'in': 1}),
('ctc_loss', 'last', {'out': 0, 'in': 0})],
nodes_with_edges_only=True)
(flag, resp) = compare_graphs(graph, graph_ref, 'last', check_op_attrs=True)
self.assertTrue(flag, resp)
def test2(self):
nodes_attributes = {
'logits': {'shape': int64_array([2, 6, 100]), 'type': 'Parameter', 'kind': 'op', 'op': 'Parameter'},
'seq_mask': {'shape': int64_array([2]), 'data_type': np.int32, 'kind': 'op', 'op': 'Parameter'},
'transpose': {'kind': 'op', 'op': 'Transpose'},
'ctc_greedy_decoder': {'kind': 'op', 'op': 'CTCGreedyDecoderSeqLen', 'merge_repeated': True},
'cast': {'kind': 'op', 'op': 'Cast'},
'sparse_to_dense': {'kind': 'op', 'op': 'SparseToDense'},
'tf_ctc_loss': {'kind': 'op', 'op': 'CTCLoss', 'preprocess_collapse_repeated': False,
'ctc_merge_repeated': True, 'unique': False, 'logits_time_major': False},
'ctc_loss': {'kind': 'op', 'op': 'CTCLoss', 'preprocess_collapse_repeated': False,
'ctc_merge_repeated': True, 'unique': False},
**const('default_value', int64_array(-1)),
'last': {'type': None, 'value': None, 'kind': 'op', 'op': 'Result'},
'transpose2': {'kind': 'op', 'op': 'Transpose'},
**const('transpose2_axis', int64_array([1, 0, 2])),
}
graph = build_graph(nodes_attributes, [('logits', 'transpose', {'out': 0, 'in': 0}),
('transpose', 'ctc_greedy_decoder', {'out': 0, 'in': 0}),
('seq_mask', 'ctc_greedy_decoder', {'out': 0, 'in': 1}),
('transpose', 'tf_ctc_loss', {'out': 0, 'in': 0}),
('seq_mask', 'tf_ctc_loss', {'out': 0, 'in': 3}),
('ctc_greedy_decoder', 'sparse_to_dense', {'out': 0, 'in': 0}),
('ctc_greedy_decoder', 'sparse_to_dense', {'out': 2, 'in': 1}),
('ctc_greedy_decoder', 'sparse_to_dense', {'out': 1, 'in': 2}),
('default_value', 'sparse_to_dense', {'out': 0, 'in': 3}),
('ctc_greedy_decoder', 'cast', {'out': 1, 'in': 0}),
('ctc_greedy_decoder', 'tf_ctc_loss', {'out': 0, 'in': 1}),
('cast', 'tf_ctc_loss', {'out': 0, 'in': 2}),
('tf_ctc_loss', 'last', {'out': 0, 'in': 0})],
nodes_with_edges_only=True)
graph.graph['cmd_params'] = Namespace(data_type='FP32')
graph.stage = 'front'
CTCLossReplacement().find_and_replace_pattern(graph)
graph_ref = build_graph(nodes_attributes,
[('logits', 'transpose', {'out': 0, 'in': 0}),
('transpose', 'transpose2', {'out': 0, 'in': 0}),
('transpose2_axis', 'transpose2', {'out': 0, 'in': 1}),
('transpose2', 'ctc_greedy_decoder', {'out': 0, 'in': 0}),
('seq_mask', 'ctc_greedy_decoder', {'out': 0, 'in': 1}),
('transpose', 'ctc_loss', {'out': 0, 'in': 0}),
('ctc_greedy_decoder', 'ctc_loss', {'out': 0, 'in': 2}),
('ctc_greedy_decoder', 'ctc_loss', {'out': 1, 'in': 3}),
('seq_mask', 'ctc_loss', {'out': 0, 'in': 1}),
('ctc_loss', 'last', {'out': 0, 'in': 0})],
nodes_with_edges_only=True)
(flag, resp) = compare_graphs(graph, graph_ref, 'last', check_op_attrs=True)
self.assertTrue(flag, resp)
@@ -0,0 +1,149 @@
# Copyright (C) 2018-2021 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
import unittest
from extensions.front.tf.CTCGreedyDecoderReplacement import CTCGreedyDecoderReplacement, \
CTCGreedyDecoderWithSparseToDenseShapeReplacement, CTCGreedyDecoderSingleReplacement
from mo.front.common.partial_infer.utils import int64_array
from mo.utils.ir_engine.compare_graphs import compare_graphs
from unit_tests.utils.graph import build_graph, const
class CTCGreedyDecoderReplacementTests(unittest.TestCase):
nodes_attributes = {
# nodes from original graph
'logits': {'type': 'Parameter', 'kind': 'op', 'op': 'Parameter'},
'seq_len': {'type': 'Parameter', 'kind': 'op', 'op': 'Parameter'},
'order_arr': {'kind': 'op', 'op': 'Const'},
'transpose': {'type': 'Transpose', 'kind': 'op', 'op': 'Transpose'},
'decoder': {'kind': 'op', 'op': 'CTCGreedyDecoderSeqLen', 'merge_repeated': True, 'output_sparse_format': True},
'cast': {'kind': 'op', 'op': 'Cast'},
'sparse_to_dense': {'kind': 'op', 'op': 'SparseToDense'},
'last': {'type': None, 'value': None, 'kind': 'op', 'op': 'Result'},
'last_1': {'type': None, 'value': None, 'kind': 'op', 'op': 'Result'},
# new nodes
'new_decoder': {'kind': 'op', 'op': 'CTCGreedyDecoderSeqLen', 'merge_repeated': True},
**const('squeeze_axes', int64_array([2, 3])),
'squeeze_dec_seq': {'kind': 'op', 'op': 'Squeeze'},
'cast_to_int': {'kind': 'op', 'op': 'Cast'},
'out_seq_len': {'type': None, 'value': None, 'kind': 'op', 'op': 'Result'},
}
def test_CTCGreedyDecoderWithSparseToDenseShape(self):
graph = build_graph(self.nodes_attributes,
[('logits', 'decoder', {'out': 0, 'in': 0}),
('seq_len', 'decoder', {'out': 0, 'in': 1}),
('decoder', 'sparse_to_dense', {'out': 0, 'in': 0}),
('decoder', 'sparse_to_dense', {'out': 2, 'in': 1}),
('decoder', 'cast', {'out': 1, 'in': 0}),
('cast', 'sparse_to_dense', {'out': 0}),
('sparse_to_dense', 'last', {'out': 0, 'in': 0}),
], nodes_with_edges_only=True)
graph.stage = 'front'
CTCGreedyDecoderWithSparseToDenseShapeReplacement().find_and_replace_pattern(graph)
graph_ref = build_graph(self.nodes_attributes,
[('logits', 'transpose', {'out': 0, 'in': 0}),
('order_arr', 'transpose', {'out': 0, 'in': 1}),
('transpose', 'new_decoder', {'out': 0, 'in': 0}),
('seq_len', 'new_decoder', {'out': 0, 'in': 1}),
('new_decoder', 'last', {'out': 0, 'in': 0}),
],
nodes_with_edges_only=True)
(flag, resp) = compare_graphs(graph, graph_ref, 'last', check_op_attrs=True)
self.assertTrue(flag, resp)
def test_CTCGreedyDecoderReplacement(self):
graph = build_graph(self.nodes_attributes,
[('logits', 'decoder', {'out': 0, 'in': 0}),
('seq_len', 'decoder', {'out': 0, 'in': 1}),
('decoder', 'sparse_to_dense', {'out': 0, 'in': 0}),
('decoder', 'cast', {'out': 1, 'in': 0}),
('cast', 'sparse_to_dense', {'out': 0}),
('sparse_to_dense', 'last', {'out': 0, 'in': 0}),
], nodes_with_edges_only=True)
graph.stage = 'front'
CTCGreedyDecoderReplacement().find_and_replace_pattern(graph)
graph_ref = build_graph(self.nodes_attributes,
[('logits', 'transpose', {'out': 0, 'in': 0}),
('order_arr', 'transpose', {'out': 0, 'in': 1}),
('transpose', 'new_decoder', {'out': 0, 'in': 0}),
('seq_len', 'new_decoder', {'out': 0, 'in': 1}),
('new_decoder', 'last', {'out': 0, 'in': 0}),
],
nodes_with_edges_only=True)
(flag, resp) = compare_graphs(graph, graph_ref, 'last', check_op_attrs=True)
self.assertTrue(flag, resp)
def test_CTCGreedyDecoderSingle(self):
graph = build_graph(self.nodes_attributes,
[('logits', 'decoder', {'out': 0, 'in': 0}),
('seq_len', 'decoder', {'out': 0, 'in': 1}),
('decoder', 'last', {'out': 0, 'in': 0}),
('decoder', 'last_1', {'out': 1, 'in': 0}),
], nodes_with_edges_only=True)
graph.stage = 'front'
CTCGreedyDecoderSingleReplacement().find_and_replace_pattern(graph)
graph_ref = build_graph(self.nodes_attributes,
[('logits', 'transpose', {'out': 0, 'in': 0}),
('order_arr', 'transpose', {'out': 0, 'in': 1}),
('transpose', 'new_decoder', {'out': 0, 'in': 0}),
('seq_len', 'new_decoder', {'out': 0, 'in': 1}),
('new_decoder', 'last', {'out': 0, 'in': 0}),
('new_decoder', 'last_1', {'out': 1, 'in': 0}),
],
nodes_with_edges_only=True)
(flag, resp) = compare_graphs(graph, graph_ref, 'last', check_op_attrs=True)
self.assertTrue(flag, resp)
def test_CTCGreedyDecoderSingle_negative(self):
edges = [('logits', 'decoder', {'out': 0, 'in': 0}),
('seq_len', 'decoder', {'out': 0, 'in': 1}),
('decoder', 'sparse_to_dense', {'out': 0, 'in': 0}),
('decoder', 'cast', {'out': 1, 'in': 0}),
('cast', 'sparse_to_dense', {'out': 0}),
('sparse_to_dense', 'last', {'out': 0, 'in': 0}),
]
graph = build_graph(self.nodes_attributes,
edges, nodes_with_edges_only=True)
graph.stage = 'front'
CTCGreedyDecoderSingleReplacement().find_and_replace_pattern(graph)
graph_ref = build_graph(self.nodes_attributes,
edges, nodes_with_edges_only=True)
(flag, resp) = compare_graphs(graph, graph_ref, 'last', check_op_attrs=True)
self.assertTrue(flag, resp)
def test_CTCGreedyDecoder_no_consequent_transforms(self):
graph = build_graph(self.nodes_attributes,
[('logits', 'decoder', {'out': 0, 'in': 0}),
('seq_len', 'decoder', {'out': 0, 'in': 1}),
('decoder', 'sparse_to_dense', {'out': 0, 'in': 0}),
('decoder', 'sparse_to_dense', {'out': 2, 'in': 1}),
('decoder', 'cast', {'out': 1, 'in': 0}),
('cast', 'sparse_to_dense', {'out': 0}),
('sparse_to_dense', 'last', {'out': 0, 'in': 0}),
], nodes_with_edges_only=True)
graph.stage = 'front'
CTCGreedyDecoderWithSparseToDenseShapeReplacement().find_and_replace_pattern(graph)
CTCGreedyDecoderSingleReplacement().find_and_replace_pattern(graph)
graph_ref = build_graph(self.nodes_attributes,
[('logits', 'transpose', {'out': 0, 'in': 0}),
('order_arr', 'transpose', {'out': 0, 'in': 1}),
('transpose', 'new_decoder', {'out': 0, 'in': 0}),
('seq_len', 'new_decoder', {'out': 0, 'in': 1}),
('new_decoder', 'last', {'out': 0, 'in': 0}),
],
nodes_with_edges_only=True)
(flag, resp) = compare_graphs(graph, graph_ref, 'last', check_op_attrs=True)
self.assertTrue(flag, resp)
@@ -0,0 +1,107 @@
# Copyright (C) 2018-2021 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
import numpy as np
import unittest
from argparse import Namespace
from extensions.front.tf.CTCLossReplacement import CTCLossReplacement
from mo.front.common.partial_infer.utils import int64_array
from mo.utils.ir_engine.compare_graphs import compare_graphs
from unit_tests.utils.graph import build_graph, const
class CTCLossFrontReplacementTest(unittest.TestCase):
nodes_attributes = {
'logits': {'shape': int64_array([2, 6, 100]), 'type': 'Parameter', 'kind': 'op', 'op': 'Parameter'},
'seq_mask': {'shape': int64_array([2]), 'data_type': np.int32, 'kind': 'op', 'op': 'Parameter'},
'transpose': {'kind': 'op', 'op': 'Transpose'},
'ctc_greedy_decoder': {'kind': 'op', 'op': 'CTCGreedyDecoderSeqLen', 'merge_repeated': True,
'output_sparse_format': True},
'cast': {'kind': 'op', 'op': 'Cast'},
'sparse_to_dense': {'kind': 'op', 'op': 'SparseToDense'},
'tf_ctc_loss_true_logits': {'kind': 'op', 'op': 'CTCLoss', 'preprocess_collapse_repeated': False,
'ctc_merge_repeated': True, 'unique': False, 'logits_time_major': True},
'tf_ctc_loss_false_logits': {'kind': 'op', 'op': 'CTCLoss', 'preprocess_collapse_repeated': False,
'ctc_merge_repeated': True, 'unique': False, 'logits_time_major': False},
'ctc_loss': {'kind': 'op', 'op': 'CTCLoss', 'preprocess_collapse_repeated': False,
'ctc_merge_repeated': True, 'unique': False},
**const('default_value', int64_array(-1)),
'last': {'type': None, 'value': None, 'kind': 'op', 'op': 'Result'},
'transpose2': {'kind': 'op', 'op': 'Transpose'},
**const('transpose2_axis', int64_array([1, 0, 2])),
'new_ctc_greedy_decoder': {'kind': 'op', 'op': 'CTCGreedyDecoderSeqLen', 'merge_repeated': True},
}
def CTCLossReplacement_test_true_logits(self):
graph = build_graph(self.nodes_attributes,
[('logits', 'transpose', {'out': 0, 'in': 0}),
('transpose', 'ctc_greedy_decoder', {'out': 0, 'in': 0}),
('seq_mask', 'ctc_greedy_decoder', {'out': 0, 'in': 1}),
('transpose', 'tf_ctc_loss_true_logits', {'out': 0, 'in': 0}),
('seq_mask', 'tf_ctc_loss_true_logits', {'out': 0, 'in': 3}),
('ctc_greedy_decoder', 'sparse_to_dense', {'out': 0, 'in': 0}),
('ctc_greedy_decoder', 'sparse_to_dense', {'out': 2, 'in': 1}),
('ctc_greedy_decoder', 'sparse_to_dense', {'out': 1, 'in': 2}),
('default_value', 'sparse_to_dense', {'out': 0, 'in': 3}),
('ctc_greedy_decoder', 'cast', {'out': 1, 'in': 0}),
('ctc_greedy_decoder', 'tf_ctc_loss_true_logits', {'out': 0, 'in': 1}),
('cast', 'tf_ctc_loss_true_logits', {'out': 0, 'in': 2}),
('tf_ctc_loss_true_logits', 'last', {'out': 0, 'in': 0})],
nodes_with_edges_only=True)
graph.graph['cmd_params'] = Namespace(data_type='FP32')
graph.stage = 'front'
CTCLossReplacement().find_and_replace_pattern(graph)
graph_ref = build_graph(self.nodes_attributes,
[('logits', 'transpose', {'out': 0, 'in': 0}),
('transpose', 'transpose2', {'out': 0, 'in': 0}),
('transpose2_axis', 'transpose2', {'out': 0, 'in': 1}),
('transpose2', 'new_ctc_greedy_decoder', {'out': 0, 'in': 0}),
('seq_mask', 'new_ctc_greedy_decoder', {'out': 0, 'in': 1}),
('transpose2', 'ctc_loss', {'out': 0, 'in': 0}),
('new_ctc_greedy_decoder', 'ctc_loss', {'out': 0, 'in': 2}),
('new_ctc_greedy_decoder', 'ctc_loss', {'out': 1, 'in': 3}),
('seq_mask', 'ctc_loss', {'out': 0, 'in': 1}),
('ctc_loss', 'last', {'out': 0, 'in': 0})],
nodes_with_edges_only=True)
(flag, resp) = compare_graphs(graph, graph_ref, 'last', check_op_attrs=True)
self.assertTrue(flag, resp)
def CTCLossReplacement_test_false_logits(self):
graph = build_graph(self.nodes_attributes,
[('logits', 'transpose', {'out': 0, 'in': 0}),
('transpose', 'ctc_greedy_decoder', {'out': 0, 'in': 0}),
('seq_mask', 'ctc_greedy_decoder', {'out': 0, 'in': 1}),
('transpose', 'tf_ctc_loss_false_logits', {'out': 0, 'in': 0}),
('seq_mask', 'tf_ctc_loss_false_logits', {'out': 0, 'in': 3}),
('ctc_greedy_decoder', 'sparse_to_dense', {'out': 0, 'in': 0}),
('ctc_greedy_decoder', 'sparse_to_dense', {'out': 2, 'in': 1}),
('ctc_greedy_decoder', 'sparse_to_dense', {'out': 1, 'in': 2}),
('default_value', 'sparse_to_dense', {'out': 0, 'in': 3}),
('ctc_greedy_decoder', 'cast', {'out': 1, 'in': 0}),
('ctc_greedy_decoder', 'tf_ctc_loss_false_logits', {'out': 0, 'in': 1}),
('cast', 'tf_ctc_loss_false_logits', {'out': 0, 'in': 2}),
('tf_ctc_loss_false_logits', 'last', {'out': 0, 'in': 0})],
nodes_with_edges_only=True)
graph.graph['cmd_params'] = Namespace(data_type='FP32')
graph.stage = 'front'
CTCLossReplacement().find_and_replace_pattern(graph)
graph_ref = build_graph(self.nodes_attributes,
[('logits', 'transpose', {'out': 0, 'in': 0}),
('transpose', 'transpose2', {'out': 0, 'in': 0}),
('transpose2_axis', 'transpose2', {'out': 0, 'in': 1}),
('transpose2', 'new_ctc_greedy_decoder', {'out': 0, 'in': 0}),
('seq_mask', 'new_ctc_greedy_decoder', {'out': 0, 'in': 1}),
('transpose', 'ctc_loss', {'out': 0, 'in': 0}),
('new_ctc_greedy_decoder', 'ctc_loss', {'out': 0, 'in': 2}),
('new_ctc_greedy_decoder', 'ctc_loss', {'out': 1, 'in': 3}),
('seq_mask', 'ctc_loss', {'out': 0, 'in': 1}),
('ctc_loss', 'last', {'out': 0, 'in': 0})],
nodes_with_edges_only=True)
(flag, resp) = compare_graphs(graph, graph_ref, 'last', check_op_attrs=True)
self.assertTrue(flag, resp)
@@ -3,7 +3,7 @@
import unittest
from extensions.front.sparse_to_dense_replacer import SparseToDenseReplacer
from extensions.front.tf.sparse_to_dense_replacer import SparseToDenseReplacer
from mo.front.common.partial_infer.utils import int64_array
from mo.utils.ir_engine.compare_graphs import compare_graphs
from unit_tests.utils.graph import build_graph, const