diff --git a/src/frontends/tensorflow/docs/supported_ops.md b/src/frontends/tensorflow/docs/supported_ops.md index 3e8fa158c6c4a6..e4315fc5c4edff 100644 --- a/src/frontends/tensorflow/docs/supported_ops.md +++ b/src/frontends/tensorflow/docs/supported_ops.md @@ -139,7 +139,7 @@ A "supported operation" is one that TensorFlow Frontend can convert to the OpenV | BiasAdd | YES | | | BiasAddGrad | NO | | | BiasAddV1 | NO | | -| Bincount | NO | | +| Bincount | YES | | | Bitcast | NO | | | BitwiseAnd | YES | | | BitwiseOr | YES | | diff --git a/src/frontends/tensorflow/src/op_table.cpp b/src/frontends/tensorflow/src/op_table.cpp index e6568438919651..e3d8db8f0512ab 100644 --- a/src/frontends/tensorflow/src/op_table.cpp +++ b/src/frontends/tensorflow/src/op_table.cpp @@ -218,6 +218,7 @@ const std::map get_supported_ops() { {"BroadcastTo", CreatorFunction(translate_broadcast_to_op)}, {"Bucketize", CreatorFunction(translate_bucketize_op)}, {"BiasAdd", CreatorFunction(translate_bias_add_op)}, + {"Bincount", CreatorFunction(translate_bincount_op)}, {"Cast", CreatorFunction(translate_cast_op)}, {"CheckNumerics", CreatorFunction(translate_identity_op)}, {"CheckNumericsV2", CreatorFunction(translate_identity_op)}, diff --git a/src/frontends/tensorflow_common/include/common_op_table.hpp b/src/frontends/tensorflow_common/include/common_op_table.hpp index d573bd023fe4cd..905d437ec07f6e 100644 --- a/src/frontends/tensorflow_common/include/common_op_table.hpp +++ b/src/frontends/tensorflow_common/include/common_op_table.hpp @@ -42,6 +42,7 @@ OP_CONVERTER(translate_batch_mat_mul_op); OP_CONVERTER(translate_batch_mat_mul_with_type_op); OP_CONVERTER(translate_batch_to_space_nd_op); OP_CONVERTER(translate_bias_add_op); +OP_CONVERTER(translate_bincount_op); OP_CONVERTER(translate_broadcast_args_op); OP_CONVERTER(translate_broadcast_to_op); OP_CONVERTER(translate_bucketize_op); diff --git a/src/frontends/tensorflow_common/src/op/bincount.cpp b/src/frontends/tensorflow_common/src/op/bincount.cpp new file mode 100644 index 00000000000000..6cd263f145677e --- /dev/null +++ b/src/frontends/tensorflow_common/src/op/bincount.cpp @@ -0,0 +1,79 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include "common_op_table.hpp" +#include "openvino/core/shape.hpp" +#include "openvino/op/broadcast.hpp" +#include "openvino/op/convert.hpp" +#include "openvino/op/equal.hpp" +#include "openvino/op/less.hpp" +#include "openvino/op/multiply.hpp" +#include "openvino/op/range.hpp" +#include "openvino/op/reduce_sum.hpp" +#include "openvino/op/select.hpp" +#include "openvino/op/shape_of.hpp" +#include "openvino/op/unsqueeze.hpp" + +using namespace std; +using namespace ov; +using namespace ov::op; + +namespace ov { +namespace frontend { +namespace tensorflow { +namespace op { + +OutputVector translate_bincount_op(const NodeContext& node) { + default_op_checks(node, 3, {"Bincount"}); + auto arr = node.get_input(0); + auto size = node.get_input(1); + auto weights = node.get_input(2); + + auto scalar_shape = make_shared(element::i32, ov::Shape{0}, std::vector{}); + size = make_shared(size, scalar_shape, false); + + auto weights_type = weights.get_element_type(); + + if (weights.get_partial_shape() == ov::Shape{0}) { + auto arr_shape = make_shared(arr, element::i32); + weights = make_shared(weights_type, Shape{}, std::vector{1}); + weights = make_shared(weights, arr_shape); + } + + // implementation + auto start = make_shared(element::i32, Shape{}, std::vector{0}); + auto step = make_shared(element::i32, Shape{}, std::vector{1}); + auto range = make_shared(start, size, step, element::i32); + + // Reshape arr and weights to 1D tensors + auto const_flatten_shape = make_shared(element::i32, Shape{1}, std::vector{-1}); + auto arr_reshaped = make_shared(arr, const_flatten_shape, false); + auto weights_reshaped = make_shared(weights, const_flatten_shape, false); + + // Unsqueeze range to [size, 1] shape and unsqueeze arr and weights to shapes [1, num] + auto const_axis_zero = make_shared(element::i32, Shape{1}, vector({0})); + auto const_axis_one = make_shared(element::i32, Shape{1}, vector({1})); + auto unsqueeze_range = make_shared(range, const_axis_one); + auto unsqueeze_arr = make_shared(arr_reshaped, const_axis_zero); + auto unsqueeze_weights = make_shared(weights_reshaped, const_axis_zero); + + // Generate a mask [size, num] on range == arr + auto mask = make_shared(unsqueeze_range, unsqueeze_arr); + // Compute the weighted mask + auto mask_casted = make_shared(mask, weights_type); + + auto to_sum = make_shared(mask_casted, unsqueeze_weights); + auto reduce_axis = make_shared(element::i32, Shape{}, 1); + auto result = make_shared(to_sum, reduce_axis); + + set_node_name(node.get_name(), result); + + return {result}; +} +} // namespace op +} // namespace tensorflow +} // namespace frontend +} // namespace ov diff --git a/tests/layer_tests/tensorflow_tests/test_tf_Bincount.py b/tests/layer_tests/tensorflow_tests/test_tf_Bincount.py new file mode 100644 index 00000000000000..c521d0c391766a --- /dev/null +++ b/tests/layer_tests/tensorflow_tests/test_tf_Bincount.py @@ -0,0 +1,65 @@ +# Copyright (C) 2018-2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import platform + +import numpy as np +import pytest +import tensorflow as tf +from common.tf_layer_test_class import CommonTFLayerTest + +rng = np.random.default_rng() + +class TestBincount(CommonTFLayerTest): + def _prepare_input(self, inputs_info): + assert 'x:0' in inputs_info, "Test error: inputs_info must contain `x`" + x_shape = inputs_info['x:0'] + + inputs_data = {} + inputs_data['x:0'] = rng.integers(0, 8, x_shape).astype(np.int32) + + if 'w:0' in inputs_info: + w_shape = inputs_info['w:0'] + inputs_data['w:0'] = rng.uniform(-2.0, 2.0, w_shape).astype(self.weights_type) + + return inputs_data + + def create_bincount_net(self, input_shape, size, weights, weights_type): + tf.compat.v1.reset_default_graph() + # Create the graph and model + with tf.compat.v1.Session() as sess: + x = tf.compat.v1.placeholder(np.int32, input_shape, 'x') + s = tf.constant(size) + self.weights_type = weights_type + if weights is not None: + w = tf.compat.v1.placeholder(weights_type, input_shape, 'w') + else: + w = tf.constant([], dtype=weights_type) + + tf.raw_ops.Bincount(arr=x, size=s, weights=w) + tf.compat.v1.global_variables_initializer() + tf_net = sess.graph_def + + return tf_net, None + + test_data = [ + # with no weights + dict(input_shape=[], size=1, weights=None, weights_type=np.float32), + dict(input_shape=[2], size=2, weights=None, weights_type=np.float64), + dict(input_shape=[1,3], size=3, weights=None, weights_type=np.int32), + dict(input_shape=[3,1,4], size=4, weights=None, weights_type=np.int64), + + + # with weights + dict(input_shape=[], size=1, weights=True, weights_type=np.float32), + dict(input_shape=[2], size=2, weights=True, weights_type=np.float64), + dict(input_shape=[1,3], size=3, weights=True, weights_type=np.int32), + dict(input_shape=[3,1,4], size=4, weights=True, weights_type=np.int64), + ] + + @pytest.mark.parametrize("params", test_data) + @pytest.mark.precommit_tf_fe + @pytest.mark.nightly + def test_bincount(self, params, ie_device, precision, ir_version, temp_dir): + self._test(*self.create_bincount_net(**params), + ie_device, precision, ir_version, temp_dir=temp_dir) \ No newline at end of file