Update Cast op to use precision of 8 when casting floating point numbers to strings (#1210)

* Update Cast op to use precision of 8 when casting floating point numbers to strings. This matches numpy precision.

Update unit tests to include non-trivial floats in the input.

Update onnx test infrastructure to document why the test cases are disabled
This commit is contained in:
Scott McKay 2019-10-03 07:58:42 +10:00 committed by GitHub
parent 9e975f64c3
commit 9f633c5bd9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 56 additions and 32 deletions

View file

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include <iomanip>
#include <sstream>
#include "core/common/common.h"
#include "core/framework/op_kernel.h"
@ -72,19 +72,27 @@ template <typename SrcType>
inline void CastToStringData(const Tensor* in, Tensor* out, const TensorShape& shape) {
const int64_t len = shape.Size();
ORT_ENFORCE(len > 0);
auto input_data = in->DataAsSpan<SrcType>();
auto output_data = out->MutableDataAsSpan<std::string>();
for (int i = 0; i < len; ++i) {
if (std::is_floating_point<SrcType>::value && std::isnan(in->Data<SrcType>()[i])) {
out->MutableData<std::string>()[i] = "NaN";
} else if (std::is_floating_point<SrcType>::value && std::isinf(in->Data<SrcType>()[i])) {
if (in->Data<SrcType>()[i] < std::numeric_limits<SrcType>::lowest()) {
out->MutableData<std::string>()[i] = "-INF";
if (std::is_floating_point<SrcType>::value && std::isnan(input_data[i])) {
output_data[i] = "NaN";
} else if (std::is_floating_point<SrcType>::value && std::isinf(input_data[i])) {
if (input_data[i] < std::numeric_limits<SrcType>::lowest()) {
output_data[i] = "-INF";
} else {
out->MutableData<std::string>()[i] = "INF";
output_data[i] = "INF";
}
} else {
std::ostringstream convert;
convert << in->Data<SrcType>()[i];
out->MutableData<std::string>()[i] = convert.str();
if (std::is_floating_point<SrcType>::value) {
// match numpy default behavior
convert << std::setprecision(8);
}
convert << input_data[i];
output_data[i] = convert.str();
}
}
}
@ -196,7 +204,6 @@ class Cast final : public OpKernel {
ONNX_NAMESPACE::TensorProto_DataType to_;
};
const std::vector<MLDataType> castOpTypeConstraints{
DataTypeImpl::GetTensorType<bool>(),
DataTypeImpl::GetTensorType<float>(),

View file

@ -374,8 +374,11 @@ int real_main(int argc, char* argv[], Ort::Env& env) {
{"constantofshape_int_zeros", "test data bug", {"onnx141","onnx150"}},
{"convtranspose_1d", "1d convtranspose not supported yet"},
{"convtranspose_3d", "3d convtranspose not supported yet"},
{"cast_STRING_to_FLOAT", "result differs"},
{"cast_FLOAT_to_STRING", "result differs"},
{"cast_STRING_to_FLOAT", "Linux CI has old ONNX python package with bad test data", {"onnx141"}},
// Numpy float to string has unexpected rounding for some results given numpy default precision is meant to be 8.
// "e.g. 0.296140194 -> '0.2961402' not '0.29614019'. ORT produces the latter with precision set to 8,
// which doesn't match the expected output that was generated with numpy.
{"cast_FLOAT_to_STRING", "Numpy float to string has unexpected rounding for some results."},
{"tf_nasnet_large", "disable temporarily"},
{"tf_nasnet_mobile", "disable temporarily"},
{"tf_pnasnet_large", "disable temporarily"},

View file

@ -46,7 +46,7 @@ TEST(TensorOpTest, ShapeTest2D) {
test.AddInput<float>("data", {2, 3}, std::vector<float>(6, 1.0f));
test.AddOutput<int64_t>("shape", {2}, {2, 3});
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider});//TensorRT: volume of dimensions is not consistent with weights size
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider}); //TensorRT: volume of dimensions is not consistent with weights size
}
TEST(TensorOpTest, ShapeTest3D) {
@ -54,7 +54,7 @@ TEST(TensorOpTest, ShapeTest3D) {
test.AddInput<float>("data", {2, 3, 4}, std::vector<float>(24, 1.0f));
test.AddOutput<int64_t>("shape", {3}, {2, 3, 4});
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider});//TensorRT: volume of dimensions is not consistent with weights size
test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kTensorrtExecutionProvider}); //TensorRT: volume of dimensions is not consistent with weights size
}
template <typename SrcType,
@ -264,8 +264,11 @@ TEST(TensorOpTest, CastFromFloat16) {
TEST(TensorOpTest, CastFromString) {
const std::vector<int64_t> shape{2, 2, 2};
std::initializer_list<std::string> string_data = {"-inf", "+INF", "2.0f", "3.0f", "4.0f", "5.0f", "NaN", "nan"};
const std::initializer_list<float> float_output = {-(std::numeric_limits<float>::infinity()), std::numeric_limits<float>::infinity(), 2.0f, 3.0f, 4.0f, 5.0f, NAN, NAN};
std::initializer_list<std::string> string_data = {"-inf", "+INF", "0.9767611f", "0.28280696f",
"-0.12019656f", "5.0f", "NaN", "nan"};
const std::initializer_list<float> float_output = {-(std::numeric_limits<float>::infinity()), std::numeric_limits<float>::infinity(),
0.9767611f, 0.28280696f,
-0.12019656f, 5.0f, NAN, NAN};
TestCastOp(string_data, float_output, shape, TensorProto::FLOAT);
std::initializer_list<std::string> int_16_string_data = {"0", "1", "2", "3", "4", "5", "-32768", "32767"};
@ -279,8 +282,13 @@ TEST(TensorOpTest, CastFromString) {
TEST(TensorOpTest, CastToString) {
const std::vector<int64_t> shape{2, 2, 2};
const std::initializer_list<float> float_input = {NAN, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, -std::numeric_limits<float>::infinity(), std::numeric_limits<float>::infinity()};
std::initializer_list<std::string> string_output = {"NaN", "1", "2", "3", "4", "5", "-INF", "INF"};
const std::initializer_list<float> float_input = {NAN, -1.f, 0.0391877927f, 0.296140194f, -0.120196559f, 5.0f,
-std::numeric_limits<float>::infinity(),
std::numeric_limits<float>::infinity()};
// float output precision is 8, so the expected output differs slightly from the input due to that
std::initializer_list<std::string> string_output = {"NaN", "-1", "0.039187793", "0.29614019",
"-0.12019656", "5", "-INF", "INF"};
TestCastOp(float_input, string_output, shape, TensorProto::STRING);
std::initializer_list<std::string> int_string_data = {"0", "1", "2", "3", "4", "5", "6", "7"};
@ -375,7 +383,6 @@ void MeanVarianceNormalizationFunctionAcrossChannels(std::vector<int64_t> axes)
}
TEST(TensorOpTest, MeanVarianceNormalizationCPUTest) {
// axes: {0, 1, 2, 3} for across_channels
MeanVarianceNormalizationFunctionAcrossChannels({0, 1, 2, 3});

View file

@ -36,7 +36,7 @@ class OrtBackendTest(onnx.backend.test.BackendTest):
# ORT first supported opset 7, so models with nodes that require versions prior to opset 7 are not supported
def tests_with_pre_opset7_dependencies_filters():
filters = ('^test_AvgPool1d_cpu.*',
filters = ['^test_AvgPool1d_cpu.*',
'^test_AvgPool1d_stride_cpu.*',
'^test_AvgPool2d_cpu.*',
'^test_AvgPool2d_stride_cpu.*',
@ -69,17 +69,24 @@ def tests_with_pre_opset7_dependencies_filters():
'^test_operator_mm_cpu.*',
'^test_operator_non_float_params_cpu.*',
'^test_operator_params_cpu.*',
'^test_operator_pow_cpu.*')
'^test_operator_pow_cpu.*']
return filters
def unsupported_usages_filters():
filters = ('^test_convtranspose_1d_cpu.*', # ConvTransponse supports 4-D only
'^test_convtranspose_3d_cpu.*')
filters = ['^test_convtranspose_1d_cpu.*', # ConvTransponse supports 4-D only
'^test_convtranspose_3d_cpu.*']
return filters
def other_tests_failing_permanently_filters():
# Numpy float to string has unexpected rounding for some results given numpy default precision is meant to be 8.
# e.g. 0.296140194 -> '0.2961402' not '0.29614019'. ORT produces the latter with precision set to 8, which
# doesn't match the expected output that was generated with numpy.
filters = ['^test_cast_FLOAT_to_STRING_cpu.*']
return filters
def create_backend_test(testname=None):
backend_test = OrtBackendTest(c2, __name__)
@ -91,8 +98,7 @@ def create_backend_test(testname=None):
backend_test.include(testname + '.*')
else:
# Tests that are failing temporarily and should be fixed
current_failing_tests = ('^test_cast_STRING_to_FLOAT_cpu.*',
'^test_cast_FLOAT_to_STRING_cpu.*',
current_failing_tests = [#'^test_cast_STRING_to_FLOAT_cpu.*', # old test data that is bad on Linux CI builds
'^test_qlinearconv_cpu.*',
'^test_gru_seq_length_cpu.*',
'^test_bitshift_right_uint16_cpu.*',
@ -147,8 +153,8 @@ def create_backend_test(testname=None):
'^test_onehot_*',
'^test_constant_pad_cpu.*',
'^test_edge_pad_cpu.*',
'^test_reflect_pad_cpu.*'
)
'^test_reflect_pad_cpu.*'
]
# Example of how to disable tests for a specific provider.
# if c2.supports_device('NGRAPH'):
@ -156,20 +162,21 @@ def create_backend_test(testname=None):
if c2.supports_device('NGRAPH'):
current_failing_tests = current_failing_tests + ('|^test_clip*',)
current_failing_tests = current_failing_tests + ('|^test_depthtospace_crd*',)
current_failing_tests = current_failing_tests + ('|^test_argmax_negative_axis*',)
current_failing_tests = current_failing_tests + ('|^test_argmax_negative_axis*',)
current_failing_tests = current_failing_tests + ('|^test_argmin_negative_axis*',)
current_failing_tests = current_failing_tests + ('|^test_hadmax_negative_axis*',)
current_failing_tests = current_failing_tests + ('|^test_gemm_default_no_bias_cpu.*',)
current_failing_tests = current_failing_tests + ('|^test_hadmax_negative_axis*',)
current_failing_tests = current_failing_tests + ('|^test_gemm_default_no_bias_cpu.*',)
if c2.supports_device('OPENVINO_GPU_FP32') or c2.supports_device('OPENVINO_GPU_FP16'):
current_failing_tests = current_failing_tests + ('^test_div_cpu*',)
filters = current_failing_tests + \
tests_with_pre_opset7_dependencies_filters() + \
unsupported_usages_filters()
unsupported_usages_filters() + \
other_tests_failing_permanently_filters()
backend_test.exclude('(' + '|'.join(filters) + ')')
print ('excluded tests:', filters)
print('excluded tests:', filters)
# import all test cases at global scope to make
# them visible to python.unittest.