Files
openvino/model-optimizer/mo/ops/slice.py
T
Evgeny LazarevandEvgeny Lazarev cabf8d8534 ONNX Loop operation support (#2756)
* Generate TensorIterator without back edges from TensorFlow models

* Added a check in the MarkSubgraphsWithCorrectLayout to not fail when port is not connected

* Updated the 'protobuf2nx' to consume the graph protobuf message

* Cleanup TI from the IRv7 specific code

* Do not run some front transformations recursively

* Draft support for the ONNX Loop operation when 'cond' = True

* LoopToTI transformation changes

* Added draft of Loop operation and parser for ONNX Loop operation body

* Updated Loop body parser + added shape and type infer for the Loop operation

* Fixes for ONNX Loop operation parser

* Moved Loop parsing to Loop op extractor. Added generation of external edges for the Loop body ops

* Added support for ThresholdedRelu using decomposition

* Added support for Min ONNX operation

* Draft fixes for port_map generation for the Loop

* Rename transformation file and fix BOM

* Fixed shape inference for Loop scan outputs (axis is not None)

* Fixed shape inference for ONNX Loop operation

* Refactor checks in the TensorIteratorMerge transformation

* Code refactoring. Enabled commented transformations

* Documentation update for ONNX Loop, ThresholdedRelu and Min

* Fixed typo in the Loop front transformation where execution condition input is connected. Other refactorings

* Fixed in the Loop extractor

* Added printing 'internal_layer_id' attribute in the graph dumper

* Updated calculation of iterations number for the Loop

* Added missing code

* Fixed output port shapes and types generation for Loop operation

* Update function names and variable names in the Loop operation

* Fixed type inference for iteration count input

* Added removal of input/output ports of the Loop if they are not used

* Fixed renumbering Loop operations input/output ports to keep mandatory

* Fixed ThresholdedReluDecomposition transformation

* Updated MO IR Reader to know about Loop operation. But it is still not supported by the MO IR Reader

* Added unit test for Slice op shape infer (reverse the sequence of elements)

* Reverted changes in the ONNX loader function call to protobuf2nx

* Enable Reshape0DToSqueeze transformation recursively

* Refactored Loop operation support implementation

* Changed ThresholdedReluDecomposition to generate Const with shape [1] instead of scalar

* Code style and wording fixes

* Restored accidentally removed 'return' statement in the TI shape infer function

* Fixed comments

* Fixed comment

Co-authored-by: Evgeny Lazarev <elazarev.nnov@gmail.com>
2020-10-27 23:04:43 +03:00

177 lines
6.2 KiB
Python

"""
Copyright (C) 2018-2020 Intel Corporation
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from typing import List
import numpy as np
from mo.graph.graph import Node, Graph
from mo.ops.op import Op
from mo.utils.error import Error
"""
Slicing operations have different semantic or different parameters/inputs in different frameworks. To distinguish them
several internal operations are introduced. The internal MO Slice operation behaves same as Slice in ONNX opset >= 10.
A number of transformations take place on the front phase to convert framework slicing:
- AttributedSlice, TFSlice -> Slice
- CaffeSlice -> Split
- MXSlice -> StridedSlice
"""
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.
"""
op = 'AttributedSlice'
enabled = False
def __init__(self, graph: Graph, attrs: dict):
super().__init__(graph, {
'type': None,
'op': self.op,
'in_ports_count': 1,
'out_ports_count': 1,
'infer': None,
}, attrs)
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.
"""
op = 'CaffeSlice'
enabled = False
def __init__(self, graph: Graph, attrs: dict):
super().__init__(graph, {
'type': None,
'op': self.op,
'in_ports_count': 1,
'out_ports_count': 1,
'infer': None,
}, attrs)
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.
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.
"""
op = 'TFSlice'
enabled = False
def __init__(self, graph: Graph, attrs: dict):
super().__init__(graph, {
'type': None,
'op': self.op,
'in_ports_count': 3,
'out_ports_count': 1,
'infer': None,
}, attrs)
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.
"""
op = 'MXSlice'
enabled = False
def __init__(self, graph: Graph, attrs: dict):
super().__init__(graph, {
'kind': 'op',
'type': None,
'op': self.op,
'in_ports_count': 1,
'out_ports_count': 1,
'infer': None
}, attrs)
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.
"""
op = 'Slice'
enabled = False
def __init__(self, graph: Graph, attrs: dict = None):
super().__init__(graph, {
'type': None,
'op': 'Slice',
'in_ports_count': 5,
'out_ports_count': 1,
'infer': self.infer
}, attrs)
@staticmethod
def infer(node: Node):
input_value = node.in_port(0).data.get_value()
input_shape = node.in_port(0).data.get_shape()
starts = node.in_port(1).data.get_value()
ends = node.in_port(2).data.get_value()
if starts is None or ends is None:
raise Error('The non-constant start/end values for Slice operation "{}" are not supported'.format(node.name))
if node.is_in_port_connected(3):
axes = node.in_port(3).data.get_value()
if axes is None:
raise Error('The non-constant axes values for Slice operation "{}" is not supported'.format(node.name))
else:
axes = [x for x in range(len(starts))]
if node.is_in_port_connected(4):
steps = node.in_port(4).data.get_value()
if steps is None:
raise Error('The non-constant steps values for Slice operation "{}" is not supported'.format(node.name))
else:
steps = np.ones(len(starts), dtype=np.int64)
slice_idx = [slice(0, in_shape, 1) for in_shape in input_shape]
for i in range(len(axes)):
# Ranged for output value for specified axis
slice_idx[axes[i]] = slice(starts[i], ends[i], steps[i])
if input_value is None:
output_shape = get_shape_after_slice(input_shape, slice_idx)
if np.any(output_shape <= 0):
raise Error('Output shape: {} of node "{}" contains non-positive values'.format(output_shape, node.name))
node.out_port(0).data.set_shape(output_shape)
else:
node.out_port(0).data.set_value(input_value[tuple(slice_idx)])
def get_shape_after_slice(input_shape: np.ndarray, slice_idx: List[slice]) -> np.ndarray:
"""
Calculate shape of a tensor after slicing without actually creating the resulting tensor.
Is introduced to prevent potentially large memory consumption.
"""
output_shape = np.zeros(len(input_shape), dtype=np.int32)
for i, s in enumerate(slice_idx):
output_shape[i] = len(range(*s.indices(input_shape[i])))
return output_shape