Skip to content

Commit

Permalink
added type annotations, moved to more relevant place routines for sha…
Browse files Browse the repository at this point in the history
…pe calculation, and some other minor corrections
  • Loading branch information
pavel-esir committed Jul 31, 2020
1 parent 7773361 commit a3aa529
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 104 deletions.
2 changes: 1 addition & 1 deletion model-optimizer/extensions/front/caffe/slice_ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
limitations under the License.
"""

from mo.front.common.partial_infer.utils import int64_array
from mo.front.extractor import FrontExtractorOp
from mo.ops.slice import CaffeSlice
from mo.front.common.partial_infer.utils import int64_array


class SliceFrontExtractor(FrontExtractorOp):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from extensions.front.onnx.AttributedSliceToSlice import AttributedSliceToSliceReplacer
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_on_front
from mo.utils.unittest.graph import build_graph, regular_op_with_empty_data, result, const, connect


@generator
Expand Down Expand Up @@ -53,9 +53,9 @@ def test_attributed_slice_replacer(self, attributed_slice_attrs):

graph_ref = build_graph(nodes_attrs=nodes, edges=[
('input', 'slice'),
*connect_on_front('start', '1:slice'),
*connect_on_front('end', '2:slice'),
*connect_on_front('axis', '3:slice'),
*connect('start', '1:slice', on_front=True),
*connect('end', '2:slice', on_front=True),
*connect('axis', '3:slice', on_front=True),
('slice', 'output'),
], nodes_with_edges_only=True)

Expand Down
64 changes: 32 additions & 32 deletions model-optimizer/extensions/front/tf/TFSliceToSlice_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

from extensions.front.tf.TFSliceToSlice import TFSliceToSliceReplacer
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_on_front
from mo.utils.unittest.graph import build_graph, regular_op_with_empty_data, result, const, connect

nodes = {
**regular_op_with_empty_data('input', {'type': 'Parameter'}),
Expand All @@ -45,61 +45,61 @@ class SliceReplacerTest(unittest.TestCase):
def test_slice_replacer_begin_with_2_inputs(self):
graph = build_graph(nodes_attrs=nodes, edges=[
('input', 'tfslice'),
*connect_on_front('begin:0', '1:tfslice'),
*connect_on_front('begin:0', '0:john_doe'),
*connect_on_front('size:0', '2:tfslice'),
*connect_on_front('tfslice:0', 'output'),
*connect('begin:0', '1:tfslice', on_front=True),
*connect('begin:0', '0:john_doe', on_front=True),
*connect('size:0', '2:tfslice', on_front=True),
*connect('tfslice:0', 'output', on_front=True),
], nodes_with_edges_only=True)
graph.stage = 'front'

TFSliceToSliceReplacer().find_and_replace_pattern(graph)

graph_ref = build_graph(nodes_attrs=nodes, edges=[
*connect_on_front('input:0', 'slice'),
*connect_on_front('begin:0', 'slice:1'),
*connect_on_front('begin:0', 'john_doe:1'),
*connect('input:0', 'slice', on_front=True),
*connect('begin:0', 'slice:1', on_front=True),
*connect('begin:0', 'john_doe:1', on_front=True),

*connect_on_front('begin:0', 'end_const:0'),
*connect_on_front('size:0', 'end_const:1'),
*connect_on_front('size:0', 'equal:0'),
*connect('begin:0', 'end_const:0', on_front=True),
*connect('size:0', 'end_const:1', on_front=True),
*connect('size:0', 'equal:0', on_front=True),

*connect_on_front('int32_max:0', 'select:1'),
*connect_on_front('minus_one:0', 'equal:1'),
*connect('int32_max:0', 'select:1', on_front=True),
*connect('minus_one:0', 'equal:1', on_front=True),

*connect_on_front('equal:0', 'select:0'),
*connect('equal:0', 'select:0', on_front=True),

*connect_on_front('end_const:0', 'select:2'),
*connect_on_front('select:0', 'slice:2'),
*connect('end_const:0', 'select:2', on_front=True),
*connect('select:0', 'slice:2', on_front=True),

*connect_on_front('slice:0', 'output'),
*connect('slice:0', 'output', on_front=True),
], nodes_with_edges_only=True)

(flag, resp) = compare_graphs(graph, graph_ref, 'output', check_op_attrs=True)
self.assertTrue(flag, resp)

def test_slice_replacer(self):
graph = build_graph(nodes_attrs=nodes, edges=[
*connect_on_front('input:0', 'tfslice'),
*connect_on_front('begin:0', '1:tfslice'),
*connect_on_front('size:0', '2:tfslice'),
*connect_on_front('tfslice:0', 'output'),
*connect('input:0', 'tfslice', on_front=True),
*connect('begin:0', '1:tfslice', on_front=True),
*connect('size:0', '2:tfslice', on_front=True),
*connect('tfslice:0', 'output', on_front=True),
], nodes_with_edges_only=True)
graph.stage = 'front'

TFSliceToSliceReplacer().find_and_replace_pattern(graph)

graph_ref = build_graph(nodes_attrs=nodes, edges=[
*connect_on_front('input:0', 'slice'),
*connect_on_front('begin:0', '1:slice'),
*connect_on_front('begin:0', '0:end_const'),
*connect_on_front('size:0', '1:end_const'),
*connect_on_front('size:0', '0:equal'),
*connect_on_front('int32_max:0', '1:select'),
*connect_on_front('minus_one:0', '1:equal'),
*connect_on_front('equal:0', '0:select'),
*connect_on_front('end_const:0', '2:select'),
*connect_on_front('select:0', '2:slice'),
*connect_on_front('slice:0', 'output'),
*connect('input:0', 'slice', on_front=True),
*connect('begin:0', '1:slice', on_front=True),
*connect('begin:0', '0:end_const', on_front=True),
*connect('size:0', '1:end_const', on_front=True),
*connect('size:0', '0:equal', on_front=True),
*connect('int32_max:0', '1:select', on_front=True),
*connect('minus_one:0', '1:equal', on_front=True),
*connect('equal:0', '0:select', on_front=True),
*connect('end_const:0', '2:select', on_front=True),
*connect('select:0', '2:slice', on_front=True),
*connect('slice:0', 'output', on_front=True),
], nodes_with_edges_only=True)

(flag, resp) = compare_graphs(graph, graph_ref, 'output', check_op_attrs=True)
Expand Down
70 changes: 58 additions & 12 deletions model-optimizer/mo/ops/slice.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,22 @@
from mo.graph.graph import Node, Graph
from mo.ops.op import Op
from mo.utils.error import Error
from mo.utils.shape import get_shape_after_slice

"""
In each framework (or in different opsets of the same framework) Slice operation either has different semantic or
different parameters, or/and they specified differently in one case as attributes in other case as inputs. To
distinguish them and not confuse with OpenVINO Slice these operations were added. OpenVINO Slice operation is same as
ONNX Slice in opset >= 10. To unify all of them before middle phase these replacements take place on the front phase:
AttributedSlice, TFSlice -> Slice
CaffeSlice -> Split
MXSlice -> StridedSlice
Slicing operations have different semantic or different parameters/inputs in different frameworks. To distinguish them
and not confuse with OpenVINO Slice several Model Optimizer internal operations are introduced. OpenVINO Slice operation
behaves as ONNX Slice in opset >= 10. A number of transformations take place on the front phase to convert framework slicing
operations to OpenVINO operations from opset:
- AttributedSlice, TFSlice -> Slice
- CaffeSlice -> Split
- MXSlice -> StridedSlice
"""


class AttributedSlice(Op):
"""
AttributedSlice is used in old versions of ONNX models (opset version < 10). This operation is
used only for extracting. On the front phase is replaced with Slice.
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
Expand All @@ -57,7 +56,7 @@ class CaffeSlice(Op):
"""
Slice in Caffe is equivalent to Split operation in OpenVINO.
https://caffe.berkeleyvision.org/tutorial/layers/slice.html
After extracting operations on the front phase CaffeSlices are replaced with OpenVINO Splits operation.
The operation is replaced with the OpenVINO Split operation on the front phase.
"""
op = 'CaffeSlice'
enabled = False
Expand All @@ -80,7 +79,8 @@ class TFSlice(Op):
Slice operation in Tensorflow is different from Slice in ONNX, Caffe and MXNet. It has begin and size inputs while
OpenVINO Slice has start, end, step and axis parameters specified as inputs.
https://www.tensorflow.org/api_docs/python/tf/slice
On the front phase it is replaced with Slice operation. If size[i] == -1 is replaced to int32_max value for the end.
The operation is replaced with the OpenVINO Slice operation on the front phase.
If size[i] == -1 is replaced to int32_max value for the end.
"""
op = 'TFSlice'
enabled = False
Expand All @@ -99,6 +99,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.
"""
op = 'MXSlice'
enabled = False
Expand Down Expand Up @@ -165,3 +166,48 @@ def infer(node: Node):
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: int, slice_idx: int):
"""
Calculate shape of a tensor after slicing without actually creating the resulting tensor.
Is introduced to save memory.
"""
output_shape = np.zeros(len(input_shape), dtype=np.int32)
for i, s in enumerate(slice_idx):
start, end = normalize_slice_indices(input_shape[i], s.start, s.stop)
output_shape[i] = (end - start) / s.step
return output_shape


def convert_negative_indices(size: int, val: int):
"""
Converts negative indices of a tensors: e.g. if val == -1 then convert it to size
If val is -1 and we expect it to the start then returned val = size should be clipped by `clip_indices`
Note: returned value is not always positive and it's expected behaviour
"""
if val < 0:
return val + size + 1
else:
return val


def clip_indices(size: int, val: int):
# if slice starts and/or ends exceed indices bounds of a tensor this routine cuts them to size or 0
# Note: returned value is not always positive and it's expected behaviour
if val >= size:
return size
elif val < 0:
return 0
else:
return val


def normalize_slice_indices(size: int, start: int, end: int):
# convert slice_indices to format in which size of slice can calculated
start = convert_negative_indices(size, start)
end = convert_negative_indices(size, end)
start = clip_indices(size, start)
end = clip_indices(size, end)
return start, end

2 changes: 1 addition & 1 deletion model-optimizer/mo/ops/slice_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def convert_args(val, name=''):
if val is not None:
return valued_const_with_data(name, int64_array(val))
else:
return shaped_const_with_data(name, [0]) #fake shape
return shaped_const_with_data(name, [0]) #fake shape

starts = convert_args(starts, 'starts')
ends = convert_args(ends, 'ends')
Expand Down
45 changes: 3 additions & 42 deletions model-optimizer/mo/utils/shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,19 @@
See the License for the specific language governing permissions and
limitations under the License.
"""
import numpy as np

from extensions.ops.elementwise import Add
from extensions.ops.gather import Gather
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.graph.graph import Node, Graph
from mo.graph.graph import Node
from mo.graph.port import Port
from mo.ops.concat import Concat
from mo.ops.const import Const
from mo.ops.shape import Shape
from mo.ops.squeeze import Squeeze
import numpy as np


def get_canonical_axis_index_node(rank: Node, axis: int) -> Node:
Expand Down Expand Up @@ -221,43 +222,3 @@ def get_shape_and_rank_nodes_by_port(port: Port, return_as_a_scalar: bool = True
rank_1_d)
return shape, rank


def convert_negative_indices(size, val):
"""
Converts negative indices of a tensors: e.g. if val == -1 then convert it to size
Note: not necessary returns positive value and it's expected behaviour
"""
if val < 0:
return val + size + 1
else:
return val


def check_ind_boundaries(size, val):
"""if slice starts and/or ends exceed indices bounds of a tensor this routine cuts them to size or 0"""
if val >= size:
return size
elif val < 0:
return 0
else:
return val


def check_values(size, start, end):
start = convert_negative_indices(size, start)
end = convert_negative_indices(size, end)
start = check_ind_boundaries(size, start)
end = check_ind_boundaries(size, end)
return start, end


def get_shape_after_slice(input_shape, slice_idx):
"""
Calculate shape of a tensor after slicing without actually creating the resulting tensor.
Is introduced to save memory.
"""
output_shape = np.zeros(len(input_shape))
for i, s in enumerate(slice_idx):
start, end = check_values(input_shape[i], s.start, s.stop)
output_shape[i] = (end - start) / s.step
return output_shape
15 changes: 3 additions & 12 deletions model-optimizer/mo/utils/unittest/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,6 @@ def build_graph(nodes_attrs: dict, edges: list, update_attributes: dict = None,
# Add in_ports attribute
in_edges = node.in_edges()
for attr in in_edges.values():
# node.add_input_port(idx=attr['in'], skip_if_exist=True)
node.add_input_port(idx=attr['in'])

# Add out_ports attribute
Expand Down Expand Up @@ -331,7 +330,7 @@ def get_name_and_port(tensor_name):
return node_name, 0


def connect(first_tensor_name, second_tensor_name, skip_data=False):
def connect(first_tensor_name, second_tensor_name, skip_data=False, on_front=False):
# ports could be skipped -- then zero in/out ports would be used
# first_tensor_name = first_op_name:out_port
# second_tensor_name = in_port:second_op_name
Expand All @@ -341,6 +340,8 @@ def connect(first_tensor_name, second_tensor_name, skip_data=False):

if skip_data:
return [(first_op_name + '_d', second_op_name, {'in': in_port})]
if on_front:
return [(first_op_name, second_op_name, {'out': out_port, 'in': in_port})]
return [
(first_op_name, first_op_name + '_d', {'out': out_port}),
(first_op_name + '_d', second_op_name, {'in': in_port}),
Expand All @@ -350,13 +351,3 @@ def connect(first_tensor_name, second_tensor_name, skip_data=False):
def connect_data(first_tensor_name, second_tensor_name):
return connect(first_tensor_name, second_tensor_name, skip_data=True)


def connect_on_front(first_tensor_name, second_tensor_name, skip_data=False):
# ports could be skipped -- then zero in/out ports would be used
# first_tensor_name = first_op_name:out_port
# second_tensor_name = in_port:second_op_name

first_op_name, out_port = get_name_and_port(first_tensor_name)
second_op_name, in_port = get_name_and_port(second_tensor_name)

return [(first_op_name, second_op_name, {'out': out_port, 'in': in_port})]

0 comments on commit a3aa529

Please sign in to comment.