Skip to content

Commit c208a88

Browse files
chux0519rkazants
andauthored
[TF FE] Support Bincount operation (openvinotoolkit#23418)
### Details: - implemented Bincount op - simple test cases ### Tickets: openvinotoolkit#22071 --------- Co-authored-by: Roman Kazantsev <roman.kazantsev@intel.com>
1 parent 217afee commit c208a88

File tree

5 files changed

+147
-1
lines changed

5 files changed

+147
-1
lines changed

src/frontends/tensorflow/docs/supported_ops.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ A "supported operation" is one that TensorFlow Frontend can convert to the OpenV
139139
| BiasAdd | YES | |
140140
| BiasAddGrad | NO | |
141141
| BiasAddV1 | NO | |
142-
| Bincount | NO | |
142+
| Bincount | YES | |
143143
| Bitcast | NO | |
144144
| BitwiseAnd | YES | |
145145
| BitwiseOr | YES | |

src/frontends/tensorflow/src/op_table.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ const std::map<std::string, CreatorFunction> get_supported_ops() {
218218
{"BroadcastTo", CreatorFunction(translate_broadcast_to_op)},
219219
{"Bucketize", CreatorFunction(translate_bucketize_op)},
220220
{"BiasAdd", CreatorFunction(translate_bias_add_op)},
221+
{"Bincount", CreatorFunction(translate_bincount_op)},
221222
{"Cast", CreatorFunction(translate_cast_op)},
222223
{"CheckNumerics", CreatorFunction(translate_identity_op)},
223224
{"CheckNumericsV2", CreatorFunction(translate_identity_op)},

src/frontends/tensorflow_common/include/common_op_table.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ OP_CONVERTER(translate_batch_mat_mul_op);
4242
OP_CONVERTER(translate_batch_mat_mul_with_type_op);
4343
OP_CONVERTER(translate_batch_to_space_nd_op);
4444
OP_CONVERTER(translate_bias_add_op);
45+
OP_CONVERTER(translate_bincount_op);
4546
OP_CONVERTER(translate_broadcast_args_op);
4647
OP_CONVERTER(translate_broadcast_to_op);
4748
OP_CONVERTER(translate_bucketize_op);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright (C) 2018-2024 Intel Corporation
2+
// SPDX-License-Identifier: Apache-2.0
3+
//
4+
5+
#include <memory>
6+
7+
#include "common_op_table.hpp"
8+
#include "openvino/core/shape.hpp"
9+
#include "openvino/op/broadcast.hpp"
10+
#include "openvino/op/convert.hpp"
11+
#include "openvino/op/equal.hpp"
12+
#include "openvino/op/less.hpp"
13+
#include "openvino/op/multiply.hpp"
14+
#include "openvino/op/range.hpp"
15+
#include "openvino/op/reduce_sum.hpp"
16+
#include "openvino/op/select.hpp"
17+
#include "openvino/op/shape_of.hpp"
18+
#include "openvino/op/unsqueeze.hpp"
19+
20+
using namespace std;
21+
using namespace ov;
22+
using namespace ov::op;
23+
24+
namespace ov {
25+
namespace frontend {
26+
namespace tensorflow {
27+
namespace op {
28+
29+
OutputVector translate_bincount_op(const NodeContext& node) {
30+
default_op_checks(node, 3, {"Bincount"});
31+
auto arr = node.get_input(0);
32+
auto size = node.get_input(1);
33+
auto weights = node.get_input(2);
34+
35+
auto scalar_shape = make_shared<v0::Constant>(element::i32, ov::Shape{0}, std::vector<int32_t>{});
36+
size = make_shared<v1::Reshape>(size, scalar_shape, false);
37+
38+
auto weights_type = weights.get_element_type();
39+
40+
if (weights.get_partial_shape() == ov::Shape{0}) {
41+
auto arr_shape = make_shared<v3::ShapeOf>(arr, element::i32);
42+
weights = make_shared<v0::Constant>(weights_type, Shape{}, std::vector<int>{1});
43+
weights = make_shared<v3::Broadcast>(weights, arr_shape);
44+
}
45+
46+
// implementation
47+
auto start = make_shared<v0::Constant>(element::i32, Shape{}, std::vector<int>{0});
48+
auto step = make_shared<v0::Constant>(element::i32, Shape{}, std::vector<int>{1});
49+
auto range = make_shared<v4::Range>(start, size, step, element::i32);
50+
51+
// Reshape arr and weights to 1D tensors
52+
auto const_flatten_shape = make_shared<v0::Constant>(element::i32, Shape{1}, std::vector<int32_t>{-1});
53+
auto arr_reshaped = make_shared<v1::Reshape>(arr, const_flatten_shape, false);
54+
auto weights_reshaped = make_shared<v1::Reshape>(weights, const_flatten_shape, false);
55+
56+
// Unsqueeze range to [size, 1] shape and unsqueeze arr and weights to shapes [1, num]
57+
auto const_axis_zero = make_shared<v0::Constant>(element::i32, Shape{1}, vector<int>({0}));
58+
auto const_axis_one = make_shared<v0::Constant>(element::i32, Shape{1}, vector<int>({1}));
59+
auto unsqueeze_range = make_shared<v0::Unsqueeze>(range, const_axis_one);
60+
auto unsqueeze_arr = make_shared<v0::Unsqueeze>(arr_reshaped, const_axis_zero);
61+
auto unsqueeze_weights = make_shared<v0::Unsqueeze>(weights_reshaped, const_axis_zero);
62+
63+
// Generate a mask [size, num] on range == arr
64+
auto mask = make_shared<v1::Equal>(unsqueeze_range, unsqueeze_arr);
65+
// Compute the weighted mask
66+
auto mask_casted = make_shared<v0::Convert>(mask, weights_type);
67+
68+
auto to_sum = make_shared<v1::Multiply>(mask_casted, unsqueeze_weights);
69+
auto reduce_axis = make_shared<v0::Constant>(element::i32, Shape{}, 1);
70+
auto result = make_shared<v1::ReduceSum>(to_sum, reduce_axis);
71+
72+
set_node_name(node.get_name(), result);
73+
74+
return {result};
75+
}
76+
} // namespace op
77+
} // namespace tensorflow
78+
} // namespace frontend
79+
} // namespace ov
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Copyright (C) 2018-2024 Intel Corporation
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import platform
5+
6+
import numpy as np
7+
import pytest
8+
import tensorflow as tf
9+
from common.tf_layer_test_class import CommonTFLayerTest
10+
11+
rng = np.random.default_rng()
12+
13+
class TestBincount(CommonTFLayerTest):
14+
def _prepare_input(self, inputs_info):
15+
assert 'x:0' in inputs_info, "Test error: inputs_info must contain `x`"
16+
x_shape = inputs_info['x:0']
17+
18+
inputs_data = {}
19+
inputs_data['x:0'] = rng.integers(0, 8, x_shape).astype(np.int32)
20+
21+
if 'w:0' in inputs_info:
22+
w_shape = inputs_info['w:0']
23+
inputs_data['w:0'] = rng.uniform(-2.0, 2.0, w_shape).astype(self.weights_type)
24+
25+
return inputs_data
26+
27+
def create_bincount_net(self, input_shape, size, weights, weights_type):
28+
tf.compat.v1.reset_default_graph()
29+
# Create the graph and model
30+
with tf.compat.v1.Session() as sess:
31+
x = tf.compat.v1.placeholder(np.int32, input_shape, 'x')
32+
s = tf.constant(size)
33+
self.weights_type = weights_type
34+
if weights is not None:
35+
w = tf.compat.v1.placeholder(weights_type, input_shape, 'w')
36+
else:
37+
w = tf.constant([], dtype=weights_type)
38+
39+
tf.raw_ops.Bincount(arr=x, size=s, weights=w)
40+
tf.compat.v1.global_variables_initializer()
41+
tf_net = sess.graph_def
42+
43+
return tf_net, None
44+
45+
test_data = [
46+
# with no weights
47+
dict(input_shape=[], size=1, weights=None, weights_type=np.float32),
48+
dict(input_shape=[2], size=2, weights=None, weights_type=np.float64),
49+
dict(input_shape=[1,3], size=3, weights=None, weights_type=np.int32),
50+
dict(input_shape=[3,1,4], size=4, weights=None, weights_type=np.int64),
51+
52+
53+
# with weights
54+
dict(input_shape=[], size=1, weights=True, weights_type=np.float32),
55+
dict(input_shape=[2], size=2, weights=True, weights_type=np.float64),
56+
dict(input_shape=[1,3], size=3, weights=True, weights_type=np.int32),
57+
dict(input_shape=[3,1,4], size=4, weights=True, weights_type=np.int64),
58+
]
59+
60+
@pytest.mark.parametrize("params", test_data)
61+
@pytest.mark.precommit_tf_fe
62+
@pytest.mark.nightly
63+
def test_bincount(self, params, ie_device, precision, ir_version, temp_dir):
64+
self._test(*self.create_bincount_net(**params),
65+
ie_device, precision, ir_version, temp_dir=temp_dir)

0 commit comments

Comments
 (0)