* Remove unnnecessary ir_version checks in the MO * Cleaned up 'backend_attrs_v2' function * Small clean up from the 'TFCustomSubgraphCall' * Clean up the MO extractor attributes mapping * Renamed PreluOp to PReLU
265 lines
14 KiB
Python
265 lines
14 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.
|
|
"""
|
|
|
|
import logging as log
|
|
|
|
import numpy as np
|
|
|
|
from mo.front.common.partial_infer.utils import int64_array, float_array, mark_input_bins, assign_dims_to_weights, \
|
|
tf_window_op_pad_infer
|
|
from mo.front.extractor import spatial_getter
|
|
from mo.front.onnx.extractors.utils import get_backend_pad
|
|
from mo.graph.graph import Node, Graph
|
|
from mo.ops.op import Op, PermuteAttrs
|
|
from mo.utils.error import Error
|
|
|
|
|
|
class Convolution(Op):
|
|
op = 'Convolution'
|
|
|
|
def __init__(self, graph: Graph, attrs: dict):
|
|
super().__init__(graph, {
|
|
'type': self.op,
|
|
'op': self.op,
|
|
'version': 'opset1',
|
|
'infer': self.infer,
|
|
'multiplication_transparent': True,
|
|
'multiplication_transparent_ports': [(0, 0), (1, 0)],
|
|
'in_ports_count': 3,
|
|
'out_ports_count': 1,
|
|
}, attrs)
|
|
|
|
def backend_attrs(self):
|
|
def pad_attribute_helper(node: Node, pad_type: str='begin'):
|
|
assert pad_type in ['begin', 'end']
|
|
if not node.has_valid('pad'):
|
|
return None
|
|
pad = get_backend_pad(node.pad, node.spatial_dims, 0 if pad_type == 'begin' else 1)
|
|
if node.has_valid('auto_pad'):
|
|
pad = [0 for _ in pad]
|
|
return ','.join(map(str, pad))
|
|
|
|
return [
|
|
'auto_pad',
|
|
('strides', lambda node: ','.join(map(str, node['stride'][node.spatial_dims]))),
|
|
('dilations', lambda node: ','.join(map(str, node['dilation'][node.spatial_dims]))),
|
|
('pads_begin', lambda node: pad_attribute_helper(node, 'begin')),
|
|
('pads_end', lambda node: pad_attribute_helper(node, 'end')),
|
|
('output_padding', lambda node: ','.join(map(str, node.output_padding[node.spatial_dims])) \
|
|
if node.has_valid('output_padding') else None),
|
|
|
|
# for BinaryConvolution only
|
|
'pad_value',
|
|
'mode',
|
|
]
|
|
|
|
@staticmethod
|
|
def calc_convolution(input_spatial_shape, stride_spatial_shape, pad_spatial_shape, kernel_extent):
|
|
''' Calculates output shape for Convolution.
|
|
Verified to be applicable for both Caffe and ONNX.
|
|
'''
|
|
spatial_val_wo_stride = input_spatial_shape + pad_spatial_shape - kernel_extent
|
|
float_spatial_val_wo_stride = float_array(spatial_val_wo_stride)
|
|
return float_spatial_val_wo_stride / stride_spatial_shape + 1
|
|
|
|
@staticmethod
|
|
def calc_deconvolution(node, input_spatial_shape, pad_spatial_shape, kernel_extent):
|
|
''' Calculates output shape for Deconvolution.
|
|
Verified to be applicable for both Caffe and ONNX with explicitly defined pads.
|
|
If pads are not specified for ONNX operator, this function is not applicable.
|
|
'''
|
|
shape = node.stride[node.spatial_dims] * (input_spatial_shape - 1) + kernel_extent - pad_spatial_shape
|
|
return shape
|
|
|
|
@staticmethod
|
|
def infer(node: Node):
|
|
"""
|
|
Infers shape of convolution node as it is done in ONNX.
|
|
It is very similar to one that Caffe does, but slightly different.
|
|
We made a complete fork of this function because they are supposed to be
|
|
supported differently by different people.
|
|
Args:
|
|
node: graph convolution node
|
|
"""
|
|
input_shape = node.in_node(0).shape
|
|
if input_shape is None:
|
|
return
|
|
|
|
# bias_term cannot be deduced earlier for frameworks that represent
|
|
# convolution weights/biases as regular inputs; so the number of inputs
|
|
# is being checked here and restore correct value for bias_term to
|
|
# have the rest of the code unchanged. It will be used after we merge
|
|
# several infer functions for convolution in different FWs to a single one.
|
|
if not node.has_valid('bias_term'):
|
|
node['bias_term'] = len(node.in_nodes()) == 3
|
|
|
|
weights_index = node.weights_index if node.has_valid('weights_index') else 1
|
|
|
|
# Reshape weights kernel to original shape
|
|
# In case of caffe ot MXNet framework, values for weights has no structed shape like OIHW
|
|
# so we have to reshape weights to normal shape
|
|
# For this case, Convolution node should have attribute reshape_kernel = True
|
|
if node.has_valid('reshape_kernel') and node.reshape_kernel:
|
|
if not (node.has_valid('output') and node.has_valid('channel_dims') and node.has_valid(
|
|
'group') and node.has_valid('kernel_spatial')):
|
|
log.error('Cannot reshape kernel due to not all required attrs was set to {} node'.format(node.id))
|
|
return
|
|
# layout for Convolution weights is OIHW
|
|
kernel_shape = np.array([node.output, input_shape[node.channel_dims].item() / node.group,
|
|
*[node.kernel_spatial[i] for i in range(len(node.kernel_spatial))]], dtype=np.int64)
|
|
if node.type == 'Deconvolution': # layout for Deconvolution weights is IOHW
|
|
kernel_shape[[0, 1]] = kernel_shape[[1, 0]]
|
|
#node.input_feature_channel, node.output_feature_channel = node.output_feature_channel, node.input_feature_channel
|
|
|
|
if np.prod(kernel_shape) != np.prod(node.in_node(weights_index).value.shape):
|
|
log.error("Size of weights {} does not match kernel shape: {}\n".format(np.prod(node.in_node(weights_index).value.shape), kernel_shape) +
|
|
" Possible reason is wrong channel number in input shape\n")
|
|
raise Error("Cannot reshape weights to kernel shape")
|
|
|
|
node.in_node(weights_index).shape = np.array(kernel_shape)
|
|
node.in_node(weights_index).value = np.reshape(node.in_node(weights_index).value, kernel_shape)
|
|
node.reshape_kernel = False
|
|
|
|
# Pass weights shape to node attribute kernel_shape
|
|
kernel_shape = node.in_node(weights_index).shape
|
|
node['kernel_shape'] = kernel_shape
|
|
# Calculate kernel_spatial_idx and spatial_dims if it is not specified
|
|
# It is necessary for ONNX dut to convolution can be 1D/2D/3D
|
|
if not node.has_valid('kernel_spatial_idx'):
|
|
node['kernel_spatial_idx'] = np.delete([x for x in range(len(kernel_shape))], (node.input_feature_channel, node.output_feature_channel))
|
|
|
|
if not node.has_valid('spatial_dims'):
|
|
node['spatial_dims'] = np.delete([x for x in range(len(input_shape))], (node.channel_dims[0], node.batch_dims[0]))
|
|
|
|
node['kernel_spatial'] = kernel_shape[node.kernel_spatial_idx]
|
|
|
|
if not node.has_valid('output'):
|
|
# restore the number of output feature maps from the second argument that is weights
|
|
if node.type in ['Convolution', 'Deconvolution', 'DeformableConvolution', 'BinaryConvolution']:
|
|
node['output'] = kernel_shape[node.output_feature_channel]
|
|
else:
|
|
raise Error(
|
|
'Convolution infer function was called for a node {} with unsupported type {}',
|
|
node.soft_get('name'),
|
|
node.type
|
|
)
|
|
|
|
# Set default values for dilation, strides and pads if not set
|
|
if not node.has_valid('dilation'):
|
|
node['dilation'] = np.full([len(input_shape)], 1, dtype=np.int64)
|
|
if not node.has_valid('stride'):
|
|
node['stride'] = np.full([len(input_shape)], 1, dtype=np.int64)
|
|
if not node.has_valid('pad'):
|
|
node['pad'] = np.array([[0, 0]] * len(input_shape), dtype=np.int64)
|
|
node['pad_spatial_shape'] = node.pad[node.spatial_dims]
|
|
|
|
if not node.has_valid('output_padding'):
|
|
node['output_padding'] = np.full([len(input_shape)], 0, dtype=np.int64)
|
|
|
|
if node.has_valid('output_padding') and len(input_shape) > len(node['output_padding']):
|
|
output_padding = np.zeros(len(input_shape), dtype=np.int64)
|
|
for i in range(len(node['output_padding'])):
|
|
output_padding[i] = node['output_padding'][i]
|
|
node['output_padding'] = output_padding
|
|
|
|
input_spatial_shape = input_shape[node.spatial_dims]
|
|
stride_spatial_shape = node.stride[node.spatial_dims]
|
|
|
|
kernel_extent = node.dilation[node.spatial_dims] * (node.kernel_spatial - 1) + 1
|
|
# TensorFlow always has auto_pad attribute that can be either valid or same_upper
|
|
# In ONNX auto_pad attribute is deprecated but appears in some models (could be valid, same_upper or same_lower)
|
|
# Caffe do not use auto_pad attribute
|
|
if node.has_valid('auto_pad') and not node.has_valid('output_spatial_shape'):
|
|
node['pad_spatial_shape'], node['output_spatial_shape'] = tf_window_op_pad_infer(input_spatial_shape,
|
|
kernel_extent,
|
|
stride_spatial_shape,
|
|
node.auto_pad,
|
|
node.type == 'Deconvolution')
|
|
|
|
pad = np.zeros((len(input_shape), 2), dtype=np.int64)
|
|
pad[node.spatial_dims] = node.pad_spatial_shape
|
|
node.pad = pad
|
|
else:
|
|
pad_spatial_shape = np.add.reduce(node.pad_spatial_shape, axis=1)
|
|
if node.type in ('Convolution', 'BinaryConvolution'):
|
|
float_spatial = Convolution.calc_convolution(input_spatial_shape, stride_spatial_shape,
|
|
pad_spatial_shape,
|
|
kernel_extent)
|
|
node['output_spatial_shape'] = int64_array(float_spatial)
|
|
elif node.type == 'Deconvolution':
|
|
# In case of given output_spatial_shape we calculate pads spatial
|
|
if node.has_valid('output_spatial_shape'):
|
|
if node.has_valid('get_pad'):
|
|
node['pad'] = node.get_pad(node, input_shape, kernel_shape)
|
|
else:
|
|
log.debug('Can\'t calculate paddings due to missing lambda get_pad in {} node'.format(node.id))
|
|
return
|
|
else:
|
|
output_padding = node.output_padding[node.spatial_dims] if node.has_valid('output_padding') else None
|
|
if output_padding is not None and any(output_padding):
|
|
pad_spatial_shape -= output_padding
|
|
for dim in range(len(pad_spatial_shape)):
|
|
node.pad_spatial_shape[dim][1] -= pad_spatial_shape[dim]
|
|
|
|
float_spatial = Convolution.calc_deconvolution(node, input_spatial_shape, pad_spatial_shape,
|
|
kernel_extent)
|
|
node['output_spatial_shape'] = int64_array(float_spatial)
|
|
elif node.type == 'DeformableConvolution':
|
|
# get the output spatial shape from the second input with offsets
|
|
node['output_spatial_shape'] = int64_array([node.in_node(1).shape[2:4]])
|
|
else:
|
|
assert 'Unsupported layer type "{}"'.format(node.type)
|
|
|
|
|
|
# For cases when group attribute wasn't set in extractor we should specify get_group attribute
|
|
# this attribute should store lambda node: ... (check tf convolution extractor)
|
|
if node.has_valid('get_group'):
|
|
node['group'] = node.get_group(node)
|
|
output_shape = np.full_like(input_shape, -1, dtype=np.int64)
|
|
output_shape[node.batch_dims] = input_shape[node.batch_dims] # pylint: disable=unsupported-assignment-operation
|
|
output_shape[node.spatial_dims] = node.output_spatial_shape # pylint: disable=unsupported-assignment-operation
|
|
|
|
# For cases when output attribute wasn't set in extractor we should specify get_output_feature_dim attribute
|
|
# this attribute should store lambda node: ... (check tf convolution extractor)
|
|
if node.has_valid('get_output_feature_dim'):
|
|
node['output'] = node.get_output_feature_dim(node)
|
|
output_shape[node.channel_dims] = node.output # pylint: disable=unsupported-assignment-operation
|
|
node['output_shape'] = output_shape
|
|
|
|
for n in node.out_nodes():
|
|
node.out_node(n).shape = output_shape
|
|
|
|
mark_input_bins(node, start_port=1 if node.type != 'DeformableConvolution' else 2)
|
|
assign_dims_to_weights(node.in_node(weights_index), node.kernel_spatial_idx, node.input_feature_channel,
|
|
node.output_feature_channel, len(kernel_shape))
|
|
|
|
PermuteAttrs.create_permute_attrs(node, attrs=[('pad', 'input:0'),
|
|
('stride', 'input:0'),
|
|
('dilation', 'input:0'),
|
|
('output_shape', 'input:0'),
|
|
('batch_dims', 'input:0'),
|
|
('channel_dims', 'input:0'),
|
|
('spatial_dims', 'input:0'),
|
|
|
|
('kernel_shape', 'input:{}'.format(weights_index)),
|
|
('kernel_spatial_idx', 'input:{}'.format(weights_index)),
|
|
('input_feature_channel', 'input:{}'.format(weights_index)),
|
|
('output_feature_channel', 'input:{}'.format(weights_index)),
|
|
])
|
|
|
|
PermuteAttrs.set_permutation(node.in_node(weights_index), node,
|
|
node.get_weights_permute if node.has_valid('get_weights_permute') else None)
|