Add support for ONNX RandomNormal and RandomNormalLike operators (#8024)

Co-authored-by: Tomasz Dołbniak <tomasz.dolbniak@intel.com>
Co-authored-by: Tomasz Jankowski <tomasz1.jankowski@intel.com>
This commit is contained in:
Michał Karzyński
2021-11-03 13:50:35 +01:00
committed by GitHub
parent fb5c9caa4d
commit 165ce3eeea
17 changed files with 403 additions and 30 deletions

View File

@@ -0,0 +1,36 @@
// Copyright (C) 2018-2021 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "utils/random_normal.hpp"
#include "exceptions.hpp"
#include "ngraph/shape.hpp"
#include "utils/common.hpp"
namespace ngraph {
namespace onnx_import {
namespace op {
namespace set_1 {
OutputVector random_normal(const Node& node) {
CHECK_VALID_NODE(node, node.has_attribute("shape"), "RandomNormal operator must specify a 'shape' attribute.");
const auto dtype =
node.get_attribute_value<int64_t>("dtype", static_cast<int64_t>(ONNX_NAMESPACE::TensorProto_DataType_FLOAT));
const auto target_type = common::get_ngraph_element_type(dtype);
const auto mean = node.get_attribute_value<float>("mean", 0.0f);
const auto scale = node.get_attribute_value<float>("scale", 1.0f);
const auto seed = node.get_attribute_value<float>("seed", 0);
const auto shape_dims = node.get_attribute_value<std::vector<int64_t>>("shape");
const auto shape = default_opset::Constant::create(element::i64, {shape_dims.size()}, shape_dims);
return detail::make_random_normal(shape, target_type, mean, scale, seed);
}
} // namespace set_1
} // namespace op
} // namespace onnx_import
} // namespace ngraph

View File

@@ -0,0 +1,20 @@
// Copyright (C) 2018-2021 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "ngraph/node.hpp"
#include "onnx_import/core/node.hpp"
namespace ngraph {
namespace onnx_import {
namespace op {
namespace set_1 {
OutputVector random_normal(const Node& node);
} // namespace set_1
} // namespace op
} // namespace onnx_import
} // namespace ngraph

View File

@@ -0,0 +1,37 @@
// Copyright (C) 2018-2021 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "ngraph/shape.hpp"
#include "op/random_uniform_like.hpp"
#include "utils/common.hpp"
#include "utils/random_normal.hpp"
namespace ngraph {
namespace onnx_import {
namespace op {
namespace set_1 {
OutputVector random_normal_like(const Node& node) {
const auto input = node.get_ng_inputs().at(0);
ngraph::element::Type target_type;
if (node.has_attribute("dtype")) {
const auto dtype = node.get_attribute_value<int64_t>("dtype");
target_type = common::get_ngraph_element_type(dtype);
} else {
target_type = input.get_element_type();
}
const auto shape = std::make_shared<default_opset::ShapeOf>(input);
const auto mean = node.get_attribute_value<float>("mean", 0.0f);
const auto scale = node.get_attribute_value<float>("scale", 1.0f);
const auto seed = node.get_attribute_value<float>("seed", 0.0f);
return detail::make_random_normal(shape, target_type, mean, scale, seed);
}
} // namespace set_1
} // namespace op
} // namespace onnx_import
} // namespace ngraph

View File

@@ -0,0 +1,20 @@
// Copyright (C) 2018-2021 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "ngraph/node.hpp"
#include "onnx_import/core/node.hpp"
namespace ngraph {
namespace onnx_import {
namespace op {
namespace set_1 {
OutputVector random_normal_like(const Node& node);
} // namespace set_1
} // namespace op
} // namespace onnx_import
} // namespace ngraph

View File

@@ -23,7 +23,7 @@ OutputVector random_uniform(const Node& node) {
node.get_attribute_value<int64_t>("dtype", static_cast<int64_t>(ONNX_NAMESPACE::TensorProto_DataType_FLOAT));
const auto high = node.get_attribute_value<float>("high", 1.0f);
const auto low = node.get_attribute_value<float>("low", 0.0f);
const auto seed = node.get_attribute_value<int64_t>("seed", 0);
const auto seed = node.get_attribute_value<float>("seed", 0.0f);
const auto shape = node.get_attribute_value<std::vector<int64_t>>("shape");
const auto target_shape_const = default_opset::Constant::create(ngraph::element::i64, Shape{shape.size()}, shape);
@@ -32,19 +32,17 @@ OutputVector random_uniform(const Node& node) {
const auto target_type = common::get_ngraph_element_type(dtype);
const uint64_t global_seed = 0;
const auto seed_uint64 = static_cast<uint64_t>(seed * 1000);
return {std::make_shared<ngraph::opset8::RandomUniform>(target_shape_const,
low_const,
high_const,
target_type,
global_seed,
seed)};
seed_uint64)};
}
} // namespace set_1
} // namespace op
} // namespace onnx_import
} // namespace ngraph

View File

@@ -18,7 +18,7 @@ namespace set_1 {
OutputVector random_uniform_like(const Node& node) {
OutputVector inputs{node.get_ng_inputs()};
auto input = inputs.at(0);
const auto input = inputs.at(0);
ngraph::element::Type target_type;
if (node.has_attribute("dtype")) {
@@ -32,25 +32,23 @@ OutputVector random_uniform_like(const Node& node) {
const auto high = node.get_attribute_value<float>("high", 1.0f);
const auto low = node.get_attribute_value<float>("low", 0.0f);
const auto seed = node.get_attribute_value<int64_t>("seed", 0);
const auto seed = node.get_attribute_value<float>("seed", 0.f);
const auto high_const = default_opset::Constant::create(ngraph::element::f32, Shape{1}, {high});
const auto low_const = default_opset::Constant::create(ngraph::element::f32, Shape{1}, {low});
const uint64_t global_seed = 0;
const auto seed_uint64 = static_cast<uint64_t>(seed * 1000);
return {std::make_shared<ngraph::opset8::RandomUniform>(target_shape,
low_const,
high_const,
target_type,
global_seed,
seed)};
seed_uint64)};
}
} // namespace set_1
} // namespace op
} // namespace onnx_import
} // namespace ngraph

View File

@@ -116,6 +116,8 @@
#include "op/qlinear_conv.hpp"
#include "op/qlinear_matmul.hpp"
#include "op/quantize_linear.hpp"
#include "op/random_normal.hpp"
#include "op/random_normal_like.hpp"
#include "op/random_uniform.hpp"
#include "op/random_uniform_like.hpp"
#include "op/range.hpp"
@@ -384,6 +386,8 @@ OperatorsBridge::OperatorsBridge() {
REGISTER_OPERATOR("QuantizeLinear", 1, quantize_linear);
REGISTER_OPERATOR("QuantizeLinear", 13, quantize_linear);
REGISTER_OPERATOR("Range", 1, range);
REGISTER_OPERATOR("RandomNormal", 1, random_normal);
REGISTER_OPERATOR("RandomNormalLike", 1, random_normal_like);
REGISTER_OPERATOR("RandomUniform", 1, random_uniform);
REGISTER_OPERATOR("RandomUniformLike", 1, random_uniform_like);
REGISTER_OPERATOR("Reciprocal", 1, reciprocal);

View File

@@ -0,0 +1,62 @@
// Copyright (C) 2018-2021 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "random_normal.hpp"
#include "default_opset.hpp"
#include "ngraph/opsets/opset8.hpp"
namespace ngraph {
namespace onnx_import {
namespace detail {
OutputVector make_random_normal(const Output<ngraph::Node>& shape,
element::Type target_type,
float mean,
float scale,
float seed) {
// We start by generating two random series from a uniform distribution
const uint64_t global_seed = 0;
// ONNX specifies the seed as a float, but OpenVINO uses uint64_t
const auto op_seed = static_cast<uint64_t>(seed * 1000);
// We need to use two op_seeds to make sure we get different results for two RandomUniform series
const uint64_t seed_1 = (op_seed == 0 ? rand() % 10000 : op_seed);
const uint64_t seed_2 = (op_seed == 0 ? rand() % 10000 : op_seed + 10000);
const auto min_val = default_opset::Constant::create(target_type, Shape{1}, {0});
const auto max_val = default_opset::Constant::create(target_type, Shape{1}, {1});
const auto uniform_1 =
std::make_shared<ngraph::opset8::RandomUniform>(shape, min_val, max_val, target_type, global_seed, seed_1);
const auto uniform_2 =
std::make_shared<ngraph::opset8::RandomUniform>(shape, min_val, max_val, target_type, global_seed, seed_2);
// Compute BoxMuller transform
// random_normal = scale * ng.sqrt(-2.0 * ng.log(uniform_1)) * ng.cos(2.0 * np.pi * uniform_2) + mean
const auto pi = default_opset::Constant::create(target_type, Shape{1}, {3.141592653589793});
const auto minus_two = default_opset::Constant::create(target_type, Shape{1}, {-2.0});
const auto two = default_opset::Constant::create(target_type, Shape{1}, {2.0});
const auto log = std::make_shared<default_opset::Log>(uniform_1);
const auto multiply_minus_two_log = std::make_shared<default_opset::Multiply>(log, minus_two);
const auto sqrt = std::make_shared<default_opset::Sqrt>(multiply_minus_two_log);
const auto multiply_two_pi = std::make_shared<default_opset::Multiply>(uniform_2, pi);
const auto multiply_two_pi_uniform_2 = std::make_shared<default_opset::Multiply>(multiply_two_pi, uniform_2);
auto const cos = std::make_shared<default_opset::Cos>(multiply_two_pi_uniform_2);
auto const scale_const = default_opset::Constant::create(target_type, Shape{1}, {scale});
auto const mean_const = default_opset::Constant::create(target_type, Shape{1}, {mean});
auto const product =
std::make_shared<default_opset::Multiply>(scale_const, std::make_shared<default_opset::Multiply>(sqrt, cos));
auto const sum = std::make_shared<default_opset::Add>(product, mean_const);
return {sum};
}
} // namespace detail
} // namespace onnx_import
} // namespace ngraph

View File

@@ -0,0 +1,29 @@
// Copyright (C) 2018-2021 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include "ngraph/op/reshape.hpp"
#include "ngraph/output_vector.hpp"
namespace ngraph {
namespace onnx_import {
namespace detail {
/// \brief Creates a random normal tensor with the given shape and type.
/// \details Uses Box-Mueller algorithm to generate random numbers from a Gauassian distribution
/// \param shape Shape of the output tensor
/// \param type Type of the output tensor
/// \param mean Mean of the distribution
/// \param scale Standard deviation of the distribution
/// \param seed Seed for the random number generator
OutputVector make_random_normal(const Output<ngraph::Node>& shape,
element::Type type,
float mean,
float scale,
float seed);
} // namespace detail
} // namespace onnx_import
} // namespace ngraph

View File

@@ -0,0 +1,49 @@
ir_version: 3
producer_name: "nGraph ONNX Importer"
graph {
node {
output: "y"
op_type: "RandomNormal"
attribute {
name: "shape"
ints: 2
ints: 2
type: INTS
}
attribute {
name: "mean"
f: 50
type: FLOAT
}
attribute {
name: "scale"
f: 40
type: FLOAT
}
attribute {
name: "seed"
i: 100
type: INT
}
}
name: "test_model"
output {
name: "y"
type {
tensor_type {
elem_type: 1
shape {
dim {
dim_value: 2
}
dim {
dim_value: 2
}
}
}
}
}
}
opset_import {
version: 1
}

View File

@@ -0,0 +1,60 @@
ir_version: 3
producer_name: "nGraph ONNX Importer"
graph {
node {
input: "x"
output: "y"
op_type: "RandomNormalLike"
attribute {
name: "mean"
f: 50
type: FLOAT
}
attribute {
name: "scale"
f: 40
type: FLOAT
}
attribute {
name: "seed"
i: 100
type: INT
}
}
name: "test_model"
input {
name: "x"
type {
tensor_type {
elem_type: 1
shape {
dim {
dim_value: 2
}
dim {
dim_value: 2
}
}
}
}
}
output {
name: "y"
type {
tensor_type {
elem_type: 1
shape {
dim {
dim_value: 2
}
dim {
dim_value: 2
}
}
}
}
}
}
opset_import {
version: 1
}

View File

@@ -22,8 +22,8 @@ graph {
}
attribute {
name: "seed"
i: 100
type: INT
f: 100
type: FLOAT
}
}
name: "test_model"

View File

@@ -17,8 +17,8 @@ graph {
}
attribute {
name: "seed"
i: 100
type: INT
f: 100
type: FLOAT
}
}
name: "test_model"

View File

@@ -4170,9 +4170,7 @@ NGRAPH_TEST(${BACKEND_NAME}, onnx_model_random_uniform) {
onnx_import::import_onnx_model(file_util::path_join(SERIALIZED_ZOO, "onnx/random_uniform.onnx"));
auto test_case = test::TestCase<TestEngine>(function);
// These output values are unknown at this time as we don't have a reference implementation of random number
// generator
test_case.add_expected_output<ngraph::float16>(Shape{2, 2}, {41, 42, 43, 44});
test_case.add_expected_output<float>(Shape{2, 2}, {43.45518, 48.67585, 42.227386, 40.86294});
test_case.run();
}
@@ -4181,10 +4179,26 @@ NGRAPH_TEST(${BACKEND_NAME}, onnx_model_random_uniform_like) {
onnx_import::import_onnx_model(file_util::path_join(SERIALIZED_ZOO, "onnx/random_uniform_like.onnx"));
auto test_case = test::TestCase<TestEngine>(function);
test_case.add_expected_output<ngraph::float16>(Shape{2, 2}, {0, 0, 0, 0});
// These output values are unknown at this time as we don't have a reference implementation of random number
// generator
test_case.add_input<ngraph::float16>(Shape{2, 2}, {41, 42, 43, 44});
test_case.add_input<float>(Shape{2, 2}, {41, 42, 43, 44});
test_case.add_expected_output<float>(Shape{2, 2}, {43.45518, 48.67585, 42.227386, 40.86294});
test_case.run();
}
NGRAPH_TEST(${BACKEND_NAME}, onnx_model_random_normal) {
const auto function =
onnx_import::import_onnx_model(file_util::path_join(SERIALIZED_ZOO, "onnx/random_normal.onnx"));
auto test_case = test::TestCase<TestEngine>(function);
test_case.add_expected_output<float>(Shape{2, 2}, {13.459274, 41.75028, -19.311913, 131.79282});
test_case.run();
}
NGRAPH_TEST(${BACKEND_NAME}, onnx_model_random_normal_like) {
const auto function =
onnx_import::import_onnx_model(file_util::path_join(SERIALIZED_ZOO, "onnx/random_normal_like.onnx"));
auto test_case = test::TestCase<TestEngine>(function);
test_case.add_input<float>(Shape{2, 2}, {0, 0, 0, 0});
test_case.add_expected_output<float>(Shape{2, 2}, {13.459274, 41.75028, -19.311913, 131.79282});
test_case.run();
}

View File

@@ -24,9 +24,6 @@ IE_CPU.onnx_model_dequantize_linear_1d_zero_scale_int8
# C++ exception with description "Input data precision not supported. Expected float.
IE_CPU.onnx_model_dequantize_linear_1d_zero_scale_int8_4d
# No support yet for RandomUniform
onnx_model_random_uniform
onnx_model_random_uniform_like
# Result mismatch
onnx_model_shape

View File

@@ -125,7 +125,3 @@ onnx_model_deformable_conv_2d
# No support for unsigned types
INTERPRETER.zero_sized_negative
# No support yet for RandomUniform
INTERPRETER.onnx_model_random_uniform
INTERPRETER.onnx_model_random_uniform_like

View File

@@ -0,0 +1,53 @@
# Copyright (C) 2018-2021 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
import numpy as np
import onnx
import onnx.mapping
from tests.test_onnx.utils import run_node
def test_random_uniform():
low = 90.0
high = 100.0
node = onnx.helper.make_node(
"RandomUniform",
inputs=[],
outputs=["y"],
high=high,
low=low,
seed=10.0,
shape=(30, 30),
)
result = run_node(node, [])[0]
assert result.shape == (30, 30)
assert len(np.unique(result)) == 900
assert np.max(result) < high
assert np.min(result) > low
assert np.isclose(np.mean(result), np.mean(np.array([low, high])), rtol=0.001)
def test_random_normal():
mean = 100.0
scale = 10.0
node = onnx.helper.make_node(
"RandomNormal",
inputs=[],
outputs=["y"],
mean=mean,
scale=scale,
seed=10.0,
shape=(30, 30),
)
result = run_node(node, [])[0]
assert result.shape == (30, 30)
assert len(np.unique(result)) == 900
assert np.allclose(np.mean(result), mean, rtol=0.05)
assert np.allclose(np.std(result), scale, rtol=0.05)