[TF FE] Implement translators for ExtractImagePatches and MatrixDiag (#12593)

* [TF FE] Implement translators for ExtractImagePatches and MatrixDiag

It allows to convert Inpaint model and infer it correctly

Signed-off-by: Kazantsev, Roman <roman.kazantsev@intel.com>

* Apply code-review feedback: correct comments, use set

Signed-off-by: Kazantsev, Roman <roman.kazantsev@intel.com>

* Apply suggestions from code review

Co-authored-by: Maxim Vafin <maxim.vafin@intel.com>

Signed-off-by: Kazantsev, Roman <roman.kazantsev@intel.com>
Co-authored-by: Maxim Vafin <maxim.vafin@intel.com>
This commit is contained in:
Roman Kazantsev 2022-08-19 11:44:41 +03:00 committed by GitHub
parent 6fd23416d4
commit 190d692c4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 184 additions and 8 deletions

View File

@ -0,0 +1,58 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "op_table.hpp"
#include "openvino/op/util/attr_types.hpp"
#include "openvino/opsets/opset8.hpp"
#include "utils.hpp"
using namespace std;
using namespace ov::opset8;
namespace ov {
namespace frontend {
namespace tensorflow {
namespace op {
OutputVector translate_extract_image_patches_op(const NodeContext& node) {
TENSORFLOW_OP_VALIDATION(node, node.get_input_size() >= 0, "ExtractImagePatches must have at least one input.");
auto images = node.get_input(0);
// retrieve attributes for ExtractImagePatches
auto tf_ksizes = node.get_attribute<std::vector<int64_t>>("ksizes");
auto tf_strides = node.get_attribute<std::vector<int64_t>>("strides");
auto tf_rates = node.get_attribute<std::vector<int64_t>>("rates");
auto tf_padding_type = node.get_attribute<std::string>("padding");
ov::op::PadType auto_pad = convert_tf_padding(node, tf_padding_type);
TENSORFLOW_OP_VALIDATION(node,
auto_pad == ov::op::PadType::SAME_UPPER || auto_pad == ov::op::PadType::VALID,
"Only SAME_UPPER and VALID padding modes are supported for ExtractImagePatches.");
// prepare attributes for OpenVINO ExtractImagePatches
Shape sizes(2);
Shape rates(2);
Strides strides(2);
// layout for this operation is always NHWC
bool is_nhwc = true;
convert_nhwc_to_hw(is_nhwc, tf_ksizes, sizes);
convert_nhwc_to_hw(is_nhwc, tf_strides, strides);
convert_nhwc_to_hw(is_nhwc, tf_rates, rates);
// prepare input to ExtractImagePatches
convert_nhwc_to_nchw(is_nhwc, images);
auto extract_image_patches = make_shared<ExtractImagePatches>(images, sizes, strides, rates, auto_pad);
// prepare output to return the original layout NHWC
auto extract_image_patches_output = extract_image_patches->output(0);
convert_nchw_to_nhwc(is_nhwc, extract_image_patches_output);
set_node_name(node.get_name(), extract_image_patches_output.get_node_shared_ptr());
return {extract_image_patches_output};
}
} // namespace op
} // namespace tensorflow
} // namespace frontend
} // namespace ov

View File

@ -0,0 +1,94 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "op_table.hpp"
#include "openvino/op/util/attr_types.hpp"
#include "openvino/opsets/opset8.hpp"
#include "utils.hpp"
using namespace std;
using namespace ov::opset8;
namespace ov {
namespace frontend {
namespace tensorflow {
namespace op {
OutputVector translate_matrix_diag_op(const NodeContext& node) {
// The translation of MatrixDiag to OpenVINO opset relies on padding of input tensor with zeros,
// reshape to a special form and cutting of unneeded padding part.
// Here is a basic idea described by an example,
// let us have a tensor [1, 2, 3] and generate padding tensor of zeros with a shape [3, 3].
// Concatenate input tensor with padding and get the following:
// [[1, 0, 0, 0]
// [2, 0, 0, 0]
// [3, 0, 0, 0]] of shape [3, 4]
// Reshape to tensor of a shape [12] equal to [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]
// Cut off last 3 elements and get [1, 0, 0, 0, 2, 0, 0, 0, 3] and reshape to [3, 3]
// This idea is generalized to higher rank tensors
TENSORFLOW_OP_VALIDATION(node, node.get_input_size() > 0, "MatrixDiag must have at least one input.");
// diagonal is the single input to MatrixDiag operation and has a shape [I, J, ..., M, N]
auto diagonal = node.get_input(0);
auto diagonal_type = diagonal.get_element_type();
// 1. unsqueeze to have at least three rank input of a shape [1, I, J, ..., M, N, 1]
// because dimensions [I, J, ..., M] can be absent
auto unsqueeze_axis = make_shared<Constant>(element::i64, Shape{2}, std::vector<int64_t>{0, -1});
auto unsqueeze_diag = make_shared<Unsqueeze>(diagonal, unsqueeze_axis);
// 2. compute a size of the last dimension of the diagonal input of a shape [I, J, ..., M, N],
// i.e. N that will be diagonalized
auto unsqueeze_diag_shape = make_shared<ShapeOf>(unsqueeze_diag);
auto last_dim = make_shared<StridedSlice>(unsqueeze_diag_shape,
make_shared<Constant>(element::i64, Shape{1}, std::vector<int64_t>{-2}),
make_shared<Constant>(element::i64, Shape{1}, std::vector<int64_t>{-1}),
make_shared<Constant>(element::i64, Shape{1}, std::vector<int64_t>{1}),
std::vector<int64_t>({0}),
std::vector<int64_t>({0}));
// 3. generate a tensor of zeros of a shape [1, I, J, ..., M, N, N]
auto diag_shape = make_shared<ShapeOf>(diagonal);
auto one_dim = make_shared<Constant>(last_dim->get_element_type(), Shape{1}, std::vector<int64_t>{1});
auto padding_shape = make_shared<Concat>(OutputVector({one_dim, diag_shape, last_dim}), 0);
auto padding =
make_shared<Broadcast>(make_shared<Constant>(diagonal_type, Shape{1}, std::vector<int64_t>{0}), padding_shape);
// 4. concatenate to get input tensor with zero padding of a shape [1, I, J, ..., M, N, N + 1]
auto zero_padded_diag = make_shared<Concat>(OutputVector({unsqueeze_diag, padding}), -1);
// reshape padded tensor to get a shape [I, J, ..., M, N * N + N]
// 4.1 retrieve a part of the shape value [1, I, J, ..., M]
auto new_shape_padded_diag1 =
make_shared<StridedSlice>(unsqueeze_diag_shape,
make_shared<Constant>(element::i64, Shape{1}, std::vector<int64_t>{0}),
make_shared<Constant>(element::i64, Shape{1}, std::vector<int64_t>{-2}),
make_shared<Constant>(element::i64, Shape{1}, std::vector<int64_t>{1}),
std::vector<int64_t>({0}),
std::vector<int64_t>({0}));
// 4.2 compute the last part of a shape that is [N * N + N]
auto last_dim_squared = make_shared<Multiply>(last_dim, last_dim);
auto new_shape_padded_diag2 = make_shared<Add>(last_dim_squared, last_dim);
// 4.3 compute a new shape and reshape padded diagonal
auto new_shape_padded_diag = make_shared<Concat>(OutputVector({new_shape_padded_diag1, new_shape_padded_diag2}), 0);
auto reshaped_padded_diag = make_shared<Reshape>(zero_padded_diag, new_shape_padded_diag, false);
// 5. cut off padding in the reshaped padded tensor to get a shape [1, I, J, ..., M, N * N]
auto cut_padded_diag = make_shared<Slice>(
reshaped_padded_diag,
make_shared<Constant>(last_dim_squared->get_element_type(), Shape{1}, std::vector<int64_t>{0}),
last_dim_squared,
make_shared<Constant>(last_dim_squared->get_element_type(), Shape{1}, std::vector<int64_t>{1}),
make_shared<Constant>(element::i64, Shape{1}, std::vector<int64_t>{-1}));
// 6. return the expected shape for the result [I, J, ..., M, N, N]
auto resulted_shape = make_shared<Concat>(OutputVector({diag_shape, last_dim}), 0);
auto resulted_diag = make_shared<Reshape>(cut_padded_diag, resulted_shape, false);
set_node_name(node.get_name(), resulted_diag);
return {resulted_diag};
}
} // namespace op
} // namespace tensorflow
} // namespace frontend
} // namespace ov

View File

@ -14,13 +14,25 @@ namespace tensorflow {
namespace op {
OutputVector translate_placeholder_op(const NodeContext& node) {
auto ng_et = node.get_attribute<ov::element::Type>("dtype");
auto ng_shape = node.get_attribute<ov::PartialShape>("shape", ov::PartialShape());
auto tf_dtype = node.get_attribute<ov::element::Type>("dtype");
auto tf_shape = node.get_attribute<ov::PartialShape>("shape", ov::PartialShape::dynamic());
auto res = std::make_shared<Parameter>(ng_et, ng_shape);
auto res = std::make_shared<Parameter>(tf_dtype, tf_shape);
set_node_name(node.get_name(), res);
return res->outputs();
}
OutputVector translate_placeholder_with_default_op(const NodeContext& node) {
// For parity with legacy frontend, it creates a constant node with the default value
// As a rule, PlaceholderWithDefault is mainly used for is_training variables in the model
TENSORFLOW_OP_VALIDATION(node,
node.get_input_size() > 0,
"PlaceholderWithDefault must have at least one input that is the default value.");
auto input = node.get_input(0);
set_out_name(node.get_name(), input);
return {input};
}
} // namespace op
} // namespace tensorflow
} // namespace frontend

View File

@ -41,6 +41,7 @@ OP_CONVERTER(translate_depth_to_space_op);
OP_CONVERTER(translate_depthwise_conv_2d_native_op);
OP_CONVERTER(translate_elu_op);
OP_CONVERTER(translate_expand_dims_op);
OP_CONVERTER(translate_extract_image_patches_op);
OP_CONVERTER(translate_fake_quant_op);
OP_CONVERTER(translate_fill_op);
OP_CONVERTER(translate_floor_div_op);
@ -58,10 +59,12 @@ OP_CONVERTER(translate_log_softmax_op);
OP_CONVERTER(translate_log_1p_op);
OP_CONVERTER(translate_lrn_op);
OP_CONVERTER(translate_mat_mul_op);
OP_CONVERTER(translate_matrix_diag_op);
OP_CONVERTER(translate_max_pool_op);
OP_CONVERTER(translate_non_max_suppression_op);
OP_CONVERTER(translate_pad_op);
OP_CONVERTER(translate_placeholder_op);
OP_CONVERTER(translate_placeholder_with_default_op);
OP_CONVERTER(translate_no_op);
OP_CONVERTER(translate_one_hot_op);
OP_CONVERTER(translate_pack_op);
@ -181,6 +184,7 @@ const std::map<std::string, CreatorFunction> get_supported_ops() {
{"DepthwiseConv2dNative", translate_depthwise_conv_2d_native_op},
{"Elu", translate_elu_op},
{"ExpandDims", translate_expand_dims_op},
{"ExtractImagePatches", translate_extract_image_patches_op},
{"FakeQuantWithMinMaxVars", translate_fake_quant_op},
{"Fill", translate_fill_op},
{"FloorDiv", translate_floor_div_op},
@ -200,6 +204,7 @@ const std::map<std::string, CreatorFunction> get_supported_ops() {
{"Log1p", translate_log_1p_op},
{"LRN", translate_lrn_op},
{"MatMul", translate_mat_mul_op},
{"MatrixDiag", translate_matrix_diag_op},
{"MaxPool", translate_max_pool_op},
{"MaxPoolV2", translate_max_pool_op},
{"MaxPool3D", translate_max_pool_op},
@ -215,6 +220,7 @@ const std::map<std::string, CreatorFunction> get_supported_ops() {
{"Pad", translate_pad_op},
{"PadV2", translate_pad_op},
{"Placeholder", translate_placeholder_op},
{"PlaceholderWithDefault", translate_placeholder_with_default_op},
{"PreventGradient", translate_identity_op},
{"Range", translate_range_op},
{"Rank", translate_rank_op},

View File

@ -34,13 +34,19 @@ void ov::frontend::tensorflow::set_out_name(const std::string& out_name, const o
ov::op::PadType ov::frontend::tensorflow::convert_tf_padding(const ov::frontend::tensorflow::NodeContext& node,
const std::string& tf_padding) {
std::set<std::string> supported_ops = {"Conv2D",
"Conv2DBackpropInput",
"Conv3D",
"Conv3DBackpropInputV2",
"MaxPool",
"MaxPoolV2",
"MaxPool3D",
"ExtractImagePatches"};
auto op_type = node.get_op_type();
TENSORFLOW_OP_VALIDATION(node,
op_type == "Conv2D" || op_type == "Conv2DBackpropInput" || op_type == "Conv3D" ||
op_type == "Conv3DBackpropInputV2" || op_type == "MaxPool" || op_type == "MaxPoolV2" ||
op_type == "MaxPool3D",
"The convert_conv_tf_padding routine supports only convolutional operations.");
supported_ops.count(op_type),
"Conversion of padding mode for " + op_type + " is not supported.");
TENSORFLOW_OP_VALIDATION(
node,
tf_padding == "VALID" || tf_padding == "SAME" || tf_padding == "EXPLICIT",
@ -57,7 +63,7 @@ ov::op::PadType ov::frontend::tensorflow::convert_tf_padding(const ov::frontend:
return ov::op::PadType::SAME_LOWER;
}
} else if (op_type == "Conv2D" || op_type == "Conv3D" || op_type == "MaxPool" || op_type == "MaxPoolV2" ||
op_type == "MaxPool3D") {
op_type == "MaxPool3D" || op_type == "ExtractImagePatches") {
if (tf_padding == "SAME") {
// According to the formulas for calculating auto_pad values of the
// Conv layer in the Operation specification,