From 146b0c0be80b1e2ddbac400fc63eb625ea34a1d2 Mon Sep 17 00:00:00 2001 From: Katarzyna Mitrus Date: Fri, 13 Oct 2023 12:50:02 +0200 Subject: [PATCH] [Opset13][pyAPI] Python API Multinomial-13 (#20400) * Init Multinomial op python API * Add python tests for Multinomial op * Update num_samples input description --- .../src/openvino/runtime/opset13/__init__.py | 1 + .../src/openvino/runtime/opset13/ops.py | 41 ++++++++++ .../tests/test_graph/test_multinomial.py | 78 +++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 src/bindings/python/tests/test_graph/test_multinomial.py diff --git a/src/bindings/python/src/openvino/runtime/opset13/__init__.py b/src/bindings/python/src/openvino/runtime/opset13/__init__.py index 4ea991bf77b..9cdb7149569 100644 --- a/src/bindings/python/src/openvino/runtime/opset13/__init__.py +++ b/src/bindings/python/src/openvino/runtime/opset13/__init__.py @@ -106,6 +106,7 @@ from openvino.runtime.opset1.ops import minimum from openvino.runtime.opset4.ops import mish from openvino.runtime.opset1.ops import mod from openvino.runtime.opset9.ops import multiclass_nms +from openvino.runtime.opset13.ops import multinomial from openvino.runtime.opset1.ops import multiply from openvino.runtime.opset6.ops import mvn from openvino.runtime.opset1.ops import negative diff --git a/src/bindings/python/src/openvino/runtime/opset13/ops.py b/src/bindings/python/src/openvino/runtime/opset13/ops.py index f864d7fccca..fff95b33d23 100644 --- a/src/bindings/python/src/openvino/runtime/opset13/ops.py +++ b/src/bindings/python/src/openvino/runtime/opset13/ops.py @@ -110,6 +110,47 @@ def bitwise_xor( ) +@nameable_op +def multinomial( + probs: NodeInput, + num_samples: NodeInput, + convert_type: str, + with_replacement: bool, + log_probs: bool, + global_seed: int = 0, + op_seed: int = 0, +) -> Node: + """Return a node which generates a sequence of class indices sampled from the multinomial distribution. + + :param probs: Tensor with probabilities of floating-point type, and shape [class_size] or [batch_size, class_size]. + :param num_samples: Tensor (scalar or 1D) a single element of type i32 or i64, + specifying the number of samples to draw from the multinomial distribution. + :param convert_type: Specifies the output tensor type, possible values: 'i64', 'i32'. + :param with_replacement: Flag that specifies whether to sample with replacement. + :param log_probs: Flag that specifies whether *probs* should be treated as unnormalized log probabilities. + :param global_seed: Specifies global seed value. Required to be a positive integer or 0. + :param op_seed: Specifies operational seed value. Required to be a positive integer or 0. + + :return: The new node performing Multinomial operation. + """ + inputs = as_nodes(probs, num_samples) + + if global_seed < 0: + raise RuntimeError(f"global_seed should be positive or 0. Got: {global_seed}") + + if op_seed < 0: + raise RuntimeError(f"op_seed should be positive or 0. Got: {op_seed}") + + attributes = { + "convert_type": convert_type, + "with_replacement": with_replacement, + "log_probs": log_probs, + "global_seed": global_seed, + "op_seed": op_seed, + } + return _get_node_factory_opset13().create("Multinomial", inputs, attributes) + + @nameable_op def nms_rotated( boxes: NodeInput, diff --git a/src/bindings/python/tests/test_graph/test_multinomial.py b/src/bindings/python/tests/test_graph/test_multinomial.py new file mode 100644 index 00000000000..a1275837cc3 --- /dev/null +++ b/src/bindings/python/tests/test_graph/test_multinomial.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2018-2023 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import numpy as np +import pytest + +import openvino.runtime.opset13 as ops +from openvino.runtime import PartialShape, Dimension, Type + + +@pytest.mark.parametrize( + ("probs_shape", "num_samples_shape", "convert_type", "with_replacement", "log_probs", "global_seed", "op_seed", "expected_out_shape"), + [ + ([4, 16], [], "i32", False, True, 7461, 1546, PartialShape([4, -1])), + ([8], [1], "i64", True, False, 0, 0, PartialShape([-1])), + ], +) +def test_multinomial_param_inputs(probs_shape, num_samples_shape, convert_type, with_replacement, log_probs, global_seed, op_seed, expected_out_shape): + probs = ops.parameter(probs_shape, dtype=np.float32) + num_samples = ops.parameter(num_samples_shape, dtype=np.int32) + + op = ops.multinomial(probs, num_samples, + convert_type=convert_type, + with_replacement=with_replacement, + log_probs=log_probs, + global_seed=global_seed, + op_seed=op_seed) + assert op.get_output_size() == 1 + assert op.get_type_name() == "Multinomial" + assert op.get_output_element_type(0) == Type.i32 if convert_type == "i32" else Type.i64 + assert op.get_output_partial_shape(0) == expected_out_shape + + +@pytest.mark.parametrize( + ("probs_array", "num_samples_val", "convert_type", "with_replacement", "log_probs", "global_seed", "op_seed", "expected_out_shape"), + [ + (np.array([0.7, 0.3, 0.6, 0.5]), 3, "i32", False, True, 111, 222, PartialShape([3])), + (np.array([[0.7, 0.3], [0.6, 0.5]]), 2, "i64", True, False, 111, 222, PartialShape([2, 2])), + ], +) +def test_multinomial_const_inputs(probs_array, num_samples_val, convert_type, with_replacement, log_probs, global_seed, op_seed, expected_out_shape): + probs = ops.constant(probs_array, dtype=np.float32) + num_samples = ops.constant(num_samples_val, dtype=np.int32) + + op = ops.multinomial(probs, num_samples, + convert_type=convert_type, + with_replacement=with_replacement, + log_probs=log_probs, + global_seed=global_seed, + op_seed=op_seed) + + assert op.get_output_size() == 1 + assert op.get_type_name() == "Multinomial" + assert op.get_output_element_type(0) == Type.i32 if convert_type == "i32" else Type.i64 + assert op.get_output_partial_shape(0) == expected_out_shape + + +@pytest.mark.parametrize( + ("probs_shape", "num_samples_shape", "convert_type", "with_replacement", "log_probs", "expected_out_shape"), + [ + ([10], [1], "i32", True, True, PartialShape([-1])), + ([2, 16], [], "i64", False, False, PartialShape([2, -1])), + ], +) +def test_multinomial_default_attrs(probs_shape, num_samples_shape, convert_type, with_replacement, log_probs, expected_out_shape): + probs = ops.parameter(probs_shape, dtype=np.float32) + num_samples = ops.parameter(num_samples_shape, dtype=np.int32) + + op = ops.multinomial(probs, num_samples, + convert_type=convert_type, + with_replacement=with_replacement, + log_probs=log_probs) + + assert op.get_output_size() == 1 + assert op.get_type_name() == "Multinomial" + assert op.get_output_element_type(0) == Type.i32 if convert_type == "i32" else Type.i64 + assert op.get_output_partial_shape(0) == expected_out_shape