diff --git a/docs/MO_DG/prepare_model/Supported_Frameworks_Layers.md b/docs/MO_DG/prepare_model/Supported_Frameworks_Layers.md index 8be46067a6f..be1918ae621 100644 --- a/docs/MO_DG/prepare_model/Supported_Frameworks_Layers.md +++ b/docs/MO_DG/prepare_model/Supported_Frameworks_Layers.md @@ -130,7 +130,7 @@ Standard TensorFlow\* operations: | Asinh | No | | Atanh | No | | AvgPool | No | -| AvgPoolV2 | Supported only for constant kernel_size and strides inputs | +| AvgPoolV2 | Supported only for constant-foldable kernel_size and strides inputs | | BatchToSpaceND | No | | BiasAdd | No | | Bucketize | CPU only | @@ -182,7 +182,7 @@ Standard TensorFlow\* operations: | MatMul | No | | Max | No | | MaxPool | No | -| MaxPoolV2 | Supported only for constant kernel_size and strides inputs | +| MaxPoolV2 | Supported only for constant-foldable kernel_size and strides inputs | | Maximum | No | | Mean | No | | Merge | Supported only when it is fused to the TensorIterator layer | @@ -234,7 +234,7 @@ Standard TensorFlow\* operations: | Square| No | | Squeeze | The case when squeeze axis is not specified is not supported | | StopGradient | Not needed for shape inference | -| StridedSlice | Supported only for constant begin, end, and strides inputs | +| StridedSlice | Supported only for constant-foldable begin, end, and strides inputs | | Sub | No | | Sum | No | | Swish | No | diff --git a/model-optimizer/extensions/front/tf/TFSliceToSlice.py b/model-optimizer/extensions/front/tf/TFSliceToSlice.py index 62e59878452..4e4af4208d8 100644 --- a/model-optimizer/extensions/front/tf/TFSliceToSlice.py +++ b/model-optimizer/extensions/front/tf/TFSliceToSlice.py @@ -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,63 +19,50 @@ import numpy as np from extensions.ops.Cast import Cast from extensions.ops.elementwise import Add, Equal from extensions.ops.select import Select +from mo.front.common.partial_infer.utils import int64_array from mo.front.common.replacement import FrontReplacementOp +from mo.front.tf.graph_utils import create_op_with_const_inputs from mo.graph.graph import Graph, rename_nodes -from mo.ops.const import Const +from mo.ops.shape import Shape from mo.ops.slice import Slice class TFSliceToSliceReplacer(FrontReplacementOp): """ This transformation converts TFSlice to internal Slice operation. - In TFSlice size[i] == -1 means take all elements on axis i up to the end including(!) the last - In internal MO Slice (which is borrowed from ONNX) -1 means take all excluding(!) the last (shape[i] - 1). - Also TFSlice has 'sizes' on the second input while Slice has 'ends'. - This transformation was added to avoid multiple if statements in future transformations. + TFSlice has 'size' on the second input while Slice has 'ends', therefore we insert Add(begin, size). + size[i] == -1 is a magic number that means take the whole range along axis i up to the end. + To process the case when size[i] == -1 we insert subgraph with ShapeOf. """ op = 'TFSlice' enabled = True def replace_sub_graph(self, graph: Graph, match: dict): - node = match['op'] - slice_name = node.soft_get('name', node.id) + tf_slice_node = match['op'] + slice_name = tf_slice_node.soft_get('name', tf_slice_node.id) slice_node = Slice(graph).create_node() - rename_nodes([(node, slice_name + '/to_be_removed'), (slice_node, slice_name)]) + rename_nodes([(tf_slice_node, slice_name + '/to_be_removed'), (slice_node, slice_name)]) + ends_node = Add(graph, {'name': slice_name + '/ends'}).create_node() - eq_node = Equal(graph, {'name': slice_name + '/equal'}).create_node() - minus_one_node = Const(graph, {'name': slice_name + '/minus_one', 'value': np.array(-1)}).create_node() - int32_max_node = Const(graph, {'name': slice_name + '/int32_max', 'value': np.iinfo(np.int32).max}).create_node() - select_node = Select(graph, {'name': slice_name + '/select'}).create_node() + # reconnect input, begin, and size from TFSlice to the subgraph with Slice + tf_slice_node.in_port(0).get_connection().set_destination(slice_node.in_port(0)) + tf_slice_node.in_port(1).get_connection().set_destination(slice_node.in_port(1)) + tf_slice_node.in_port(2).get_connection().set_destination(ends_node.in_port(0)) + slice_node.in_port(1).get_connection().add_destination(ends_node.in_port(1)) - # node to convert sizes to ends - sum_node = Add(graph, {'name': slice_name + '/end_const'}).create_node() + max_ends = Shape(graph, {'name': slice_name + '/ShapeOf'}).create_node() + slice_node.in_port(0).get_connection().add_destination(max_ends.in_port(0)) - # reconnect input from tfslice to slice - node.in_port(0).get_source().connect(slice_node.in_port(0)) - node.in_port(0).disconnect() - # reconnect begin of tfslice to start of slice - node.in_port(1).get_source().connect(slice_node.in_port(1)) - node.in_port(1).disconnect() + # check if size[i] == -1, will be applied elementwisely: len(size) = len(begin) = input_rank + where_max_ends_is_needed = create_op_with_const_inputs(graph, Equal, {0: int64_array(-1)}, + {'name': slice_name + '/where_max_ends_is_needed'}) + ends_node.in_port(0).get_connection().add_destination(where_max_ends_is_needed.in_port(1)) + # select requires equal dtypes, need to convert ends to I64 + ends_casted_to_i64 = Cast(graph, {'name': slice_name + '/CastToI64', + 'dst_type': np.int64}).create_node([ends_node]) + # if size[i] == 1 then take max_ends values + correct_ends = Select(graph, {'name': slice_name + '/chosen_ends'}).create_node([where_max_ends_is_needed, + max_ends, ends_casted_to_i64]) + correct_ends.out_port(0).connect(slice_node.in_port(2)) - # (size -> ends) reconnect begins and sizes to sum to evaluate ends for Slice - # connects begins to slice - slice_node.in_port(1).get_source().connect(sum_node.in_port(0)) - node.in_port(2).get_source().connect(sum_node.in_port(1)) - node.in_port(2).disconnect() - - # if size[i] == -1 when take int32_max as end[i] - sum_node.in_port(1).get_source().connect(eq_node.in_port(0)) - minus_one_node.out_port(0).connect(eq_node.in_port(1)) - # from equal to 0 port of select - eq_node.out_port(0).connect(select_node.in_port(0)) - # from int32_max to 1 of select - int32_max_node.out_port(0).connect(select_node.in_port(1)) - # from sum to 2nd of select - sum_node.out_port(0).connect(select_node.in_port(2)) - # out of select to end (2nd of slice) - select_node.out_port(0).connect(slice_node.in_port(2)) - - cast = Cast(graph, dict(name=sum_node.name + '/CastToI64', dst_type=np.int64)).create_node() - select_node.in_port(2).get_connection().insert_node(cast) - - node.out_port(0).get_connection().set_source(slice_node.out_port(0)) + tf_slice_node.out_port(0).get_connection().set_source(slice_node.out_port(0)) diff --git a/model-optimizer/extensions/front/tf/TFSliceToSlice_test.py b/model-optimizer/extensions/front/tf/TFSliceToSlice_test.py index 2919a71bfc4..75a4523e133 100644 --- a/model-optimizer/extensions/front/tf/TFSliceToSlice_test.py +++ b/model-optimizer/extensions/front/tf/TFSliceToSlice_test.py @@ -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. @@ -32,7 +32,7 @@ nodes = { # nodes after replacement **const('minus_one', np.array(-1)), - **const('int32_max', np.array(np.iinfo(np.int32).max)), + **regular_op_with_empty_data('shapeof', {'op': 'ShapeOf', 'type': 'ShapeOf'}), **regular_op_with_empty_data('end_const', {'op': 'Add', 'type': 'Add'}), **regular_op_with_empty_data('equal', {'op': 'Equal', 'type': 'Equal'}), **regular_op_with_empty_data('select', {'op': 'Select', 'type': 'Select'}), @@ -57,6 +57,7 @@ class SliceReplacerTest(unittest.TestCase): graph_ref = build_graph(nodes_attrs=nodes, edges=[ *connect_front('input:0', 'slice'), + *connect_front('input:0', 'shapeof'), *connect_front('begin:0', 'slice:1'), *connect_front('begin:0', 'john_doe:1'), @@ -64,7 +65,7 @@ class SliceReplacerTest(unittest.TestCase): *connect_front('size:0', 'end_const:1'), *connect_front('size:0', 'equal:0'), - *connect_front('int32_max:0', 'select:1'), + *connect_front('shapeof:0', 'select:1'), *connect_front('minus_one:0', 'equal:1'), *connect_front('equal:0', 'select:0'), @@ -81,7 +82,7 @@ class SliceReplacerTest(unittest.TestCase): def test_slice_replacer(self): graph = build_graph(nodes_attrs=nodes, edges=[ - *connect_front('input:0', 'tfslice'), + *connect_front('input:0', '0:tfslice'), *connect_front('begin:0', '1:tfslice'), *connect_front('size:0', '2:tfslice'), *connect_front('tfslice:0', 'output'), @@ -92,11 +93,12 @@ class SliceReplacerTest(unittest.TestCase): graph_ref = build_graph(nodes_attrs=nodes, edges=[ *connect_front('input:0', 'slice'), + *connect_front('input:0', 'shapeof'), *connect_front('begin:0', '1:slice'), *connect_front('begin:0', '0:end_const'), *connect_front('size:0', '1:end_const'), *connect_front('size:0', '0:equal'), - *connect_front('int32_max:0', '1:select'), + *connect_front('shapeof:0', '1:select'), *connect_front('minus_one:0', '1:equal'), *connect_front('equal:0', '0:select'), *connect_front('end_const:0', '0:cast'), diff --git a/model-optimizer/mo/front/common/partial_infer/utils.py b/model-optimizer/mo/front/common/partial_infer/utils.py index ba692dea7ba..625e7c97bbb 100644 --- a/model-optimizer/mo/front/common/partial_infer/utils.py +++ b/model-optimizer/mo/front/common/partial_infer/utils.py @@ -20,16 +20,16 @@ from typing import Iterable, List, Union import numpy as np -def int64_array(l: Iterable): - return np.array(l, dtype=np.int64) +def int64_array(value: Union[Iterable[Union[float, int]], float, int]) -> np.ndarray: + return np.array(value, dtype=np.int64) -def float_array(l: list): - return np.array(l, dtype=np.float64) +def float_array(value: Union[Iterable[Union[float, int]], float, int]) -> np.ndarray: + return np.array(value, dtype=np.float64) -def float32_array(l: list): - return np.array(l, dtype=np.float32) +def float32_array(value: Union[Iterable[Union[float, int]], float, int]) -> np.ndarray: + return np.array(value, dtype=np.float32) def mark_input_bins(node, names=('weights', 'biases'), start_port: int = 1): diff --git a/model-optimizer/mo/ops/slice.py b/model-optimizer/mo/ops/slice.py index d9772bf43f4..e9baea5b404 100644 --- a/model-optimizer/mo/ops/slice.py +++ b/model-optimizer/mo/ops/slice.py @@ -34,7 +34,7 @@ A number of transformations take place on the front phase to convert framework s class AttributedSlice(Op): """ AttributedSlice is used in old versions of ONNX models (opset version < 10). - The operation is replaced with the OpenVINO Slice operation on the front phase. + Is replaced with internal Slice on the front phase. """ op = 'AttributedSlice' enabled = False @@ -53,7 +53,7 @@ class CaffeSlice(Op): """ Slice in Caffe is equivalent to Split operation in OpenVINO. https://caffe.berkeleyvision.org/tutorial/layers/slice.html - The operation is replaced with the OpenVINO Split operation on the front phase. + Is replaced with Split from opset on the front phase. """ op = 'CaffeSlice' enabled = False @@ -71,11 +71,10 @@ class CaffeSlice(Op): class TFSlice(Op): """ - Slice operation in Tensorflow is different from Slice in ONNX, Caffe and MXNet. It has begin and size inputs while - ONNX Slice and internal MO Slice has start, end, step and axis parameters specified as inputs. + TFSlice differs from Slice in ONNX, Caffe and MXNet. + TFSlice has 'begin' and 'size' inputs while Slice has 'start', 'end', 'step', and 'axis' inputs. https://www.tensorflow.org/api_docs/python/tf/slice - The operation is replaced with the internal Slice on the front phase. - If size[i] == -1 is replaced to int32_max value for the end. + Is replaced with internal Slice op on the front phase. """ op = 'TFSlice' enabled = False @@ -94,7 +93,7 @@ class MXSlice(Op): """ Slice operation in MXNet is different from ONNX, Caffe, Tensorflow. It has begin, end & step attributes https://mxnet.apache.org/versions/1.6/api/python/docs/api/symbol/op/index.html#mxnet.symbol.op.slice - The operation is replaced with the OpenVINO StridedSlice operation on the front phase. + Is replaced with the StridedSlice from opset on the front phase. """ op = 'MXSlice' enabled = False @@ -112,9 +111,9 @@ class MXSlice(Op): class Slice(Op): """ - Semantic of MO internal Slice operation is identical to Slice in ONNX opset >= 10. - It has starts, ends, steps and axes inputs. - The operation is internal (not present in the OpenVINO opset) and is replaced to StridedSlice. + Semantic of Slice is identical to Slice in ONNX opset >= 10. + It has 'starts', 'ends', 'steps', and 'axes' inputs. + SliceConverter replaces it with StridedSlice from opset. """ op = 'Slice' enabled = False