Add Features to ForecastingPivot Transformer for ONNX Export (#3608)

* checkin

* fix MSVC build error

* test changes

* split pivot output into multiple tensors

* add horizon tensor

* Support multiple types for non-pivot tensor

* limit horizon tensor type to int32_t as max_horizon type

* work around some conversion warnings for local machine

* support variadic shape for non-pivot input

* dropping all rows is an exception

* fix a bug

* fix the way that generates horizon tensor

* more tests added

* add TypeConstraint() in ONNX_OPERATOR_KERNEL_EX

* update Featurizerslibrary
This commit is contained in:
Ye Wang 2020-04-22 13:09:31 -07:00 committed by GitHub
parent 2c74766ad1
commit 70b554cc85
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 241 additions and 56 deletions

View file

@ -450,7 +450,7 @@
{
"component": {
"git": {
"commitHash": "7922489c1e7e7baf20c4b1557743d6c7ea72647d",
"commitHash": "f01d8beeac92b54bf93df8053a2acb6a8a10f03c",
"repositoryUrl": "https://github.com/microsoft/FeaturizersLibrary.git"
},
"type": "git"

@ -1 +1 @@
Subproject commit 7922489c1e7e7baf20c4b1557743d6c7ea72647d
Subproject commit f01d8beeac92b54bf93df8053a2acb6a8a10f03c

View file

@ -494,6 +494,7 @@ void RegisterForecastingPivotFeaturizerVer1(){
MS_FEATURIZERS_OPERATOR_SCHEMA(ForecastingPivotTransformer)
.SinceVersion(1)
.SetDomain(kMSFeaturizersDomain)
.Attr("num_pivot_columns", "The first num_pivot_columns input in Input1 are pivoted", AttributeProto::INT)
.Input(
0,
"State",
@ -504,32 +505,31 @@ void RegisterForecastingPivotFeaturizerVer1(){
"Inputs",
"Variadic number of Input containing tensors of different size",
"T",
ONNX_NAMESPACE::OpSchema::FormalParameterOption::Variadic)
ONNX_NAMESPACE::OpSchema::FormalParameterOption::Variadic,
false)
.Output(
0,
"Output",
"No information is available",
"T")
"T",
ONNX_NAMESPACE::OpSchema::FormalParameterOption::Variadic,
false)
.TypeConstraint(
"T0",
{"tensor(uint8)"},
"No information is available")
.TypeConstraint(
"T",
{"tensor(float)", "tensor(double)"},
{"tensor(int8)", "tensor(int16)", "tensor(int32)", "tensor(int64)", "tensor(uint8)", "tensor(uint16)", "tensor(uint32)", "tensor(uint64)",
"tensor(float)", "tensor(double)", "tensor(bool)", "tensor(string)"},
"No information is available")
.TypeAndShapeInferenceFunction(
[](ONNX_NAMESPACE::InferenceContext& ctx) {
auto input_elem_type = ctx.getInputType(1)->tensor_type().elem_type();
if (input_elem_type == ONNX_NAMESPACE::TensorProto_DataType_FLOAT) {
propagateElemTypeFromDtypeToOutput(ctx, ONNX_NAMESPACE::TensorProto_DataType_FLOAT, 0);
} else if (input_elem_type == ONNX_NAMESPACE::TensorProto_DataType_DOUBLE) {
propagateElemTypeFromDtypeToOutput(ctx, ONNX_NAMESPACE::TensorProto_DataType_DOUBLE, 0);
}
//The first num_pivot_columns inputs of Input(1) only support float & double
if (hasInputShape(ctx, 1)) {
const auto& input_shape = getInputShape(ctx, 1);
if (input_shape.dim_size() != 3) {
fail_shape_inference("Expecting Inputs to have 3 dimensions");
if (input_shape.dim_size() < 2) {
fail_shape_inference("Expecting Inputs to have more than 2 dimensions");
}
}
ONNX_NAMESPACE::TensorShapeProto shape;

View file

@ -13,9 +13,27 @@ namespace NS = Microsoft::Featurizer;
namespace onnxruntime {
namespace featurizers {
template <typename T>
struct CopyImputedColumnsImpl {
void operator()(const Tensor* input_tensor, Tensor* output_tensor_imputed,
const std::vector<int64_t>& row_idx_record, int64_t input_matrix_size, int num_output_rows) const {
const T* input_data(input_tensor->template Data<T>());
T* output_data_imputed = output_tensor_imputed->MutableData<T>();
for (int imputed_output_row_idx = 0; imputed_output_row_idx < num_output_rows; imputed_output_row_idx++) {
output_data_imputed = std::copy(input_data + row_idx_record[imputed_output_row_idx] * input_matrix_size,
input_data + (row_idx_record[imputed_output_row_idx] + 1) * input_matrix_size,
output_data_imputed);
}
}
};
template <typename T>
struct ForecastingPivotTransformerImpl {
void operator()(OpKernelContext* ctx) const {
void operator()(OpKernelContext* ctx, int64_t num_pivot_columns) const {
ORT_ENFORCE(num_pivot_columns > 0, "num_pivot_columns > 0, otherwise there will be no input to pivot");
using MatrixT = NS::RowMajMatrix<typename NS::Traits<T>::nullable_type>;
using InputType = std::vector<Eigen::Map<const MatrixT>>;
using OutputType = std::vector<T>;
@ -33,21 +51,26 @@ struct ForecastingPivotTransformerImpl {
//Get the output for whole rows is inevitable because there is conceptually no way to determine the shape of output for each row
std::vector<OutputType> output;
std::vector<int64_t> row_idx_record;
int64_t row_idx = 0;
std::function<void(OutputType const & value)> callback_fn;
callback_fn = [&output](OutputType const & value) -> void {
callback_fn = [&output, &row_idx_record, &row_idx](OutputType const & value) -> void {
output.emplace_back(value);
row_idx_record.push_back(row_idx);
};
// Transform
const int input_node_0_count = ctx->NumVariadicInputs(0);
const int input_node_1_count = ctx->NumVariadicInputs(1);
InputType input;
input.reserve(input_node_1_count);
input.reserve(num_pivot_columns);
std::unordered_map<int, std::tuple<const T*,int64_t, int64_t>> dataPtrMap;
for (int64_t row_idx = 0; row_idx < row_num; ++row_idx) {
std::vector<uint32_t> horizon_output_helper;
for (row_idx = 0; row_idx < row_num; ++row_idx) {
//Prepare Input and Output
input.clear();
for (int index = input_node_0_count; index < input_node_0_count + input_node_1_count; ++index) {
for (int index = input_node_0_count; index < input_node_0_count + num_pivot_columns; ++index) {
if (row_idx == 0) {
//Get the Input
const auto* input_tensor(ctx->Input<Tensor>(index));
@ -66,34 +89,102 @@ struct ForecastingPivotTransformerImpl {
input.push_back(typename InputType::value_type(input_data, input_dim_1, input_dim_2));
//Increment data pointer
input_data += input_dim_1 * input_dim_2;
std::get<0>(inputTuple) = input_data;
}
// Get the horizon vector from input, since num_pivot_columns > 0. So input is not null
const size_t matrix_cols_num = input[0].cols();
for (size_t col_idx = 0; col_idx < matrix_cols_num; col_idx++) {
bool has_nan = false;
for (int input_matrix_id = 0; input_matrix_id < num_pivot_columns; input_matrix_id++) {
const size_t matrix_rows_num = input[input_matrix_id].rows();
auto matrix = input[input_matrix_id];
for (int row_id = 0; row_id < static_cast<int>(matrix_rows_num); row_id++) {
if (std::isnan(matrix(row_id, col_idx))) {
has_nan = true;
break;
}
}
if (has_nan)
break;
}
if (!has_nan) {
horizon_output_helper.push_back(static_cast<uint32_t>(matrix_cols_num - col_idx));
}
}
//Execute
transformer.execute(std::make_tuple(input.begin(), input.end()), callback_fn);
}
transformer.flush(callback_fn);
// Prepare the Output
TensorShape output_shape({static_cast<int64_t>(output.size()), static_cast<int64_t>(output[0].size())});
Tensor* output_tensor(ctx->Output(0, output_shape));
T* output_data = output_tensor->MutableData<T>();
// Prepare the number of output rows
ORT_ENFORCE(!output.empty(), "All rows dropped is an exception");
int num_output_rows = static_cast<int>(output.size());
ORT_ENFORCE(static_cast<int>(row_idx_record.size()) == num_output_rows, "row_idx_record.size() == num_output_rows");
for (OutputType const & row : output)
output_data = std::copy(row.begin(), row.end(), output_data);
// Prepare the pivoted Output
int num_pivot_output_columns = 0;
if (!output.empty() && !output[0].empty()) {
num_pivot_output_columns = static_cast<int>(output[0].size());
for (int pivot_output_tensor_idx = 0; pivot_output_tensor_idx < num_pivot_output_columns; pivot_output_tensor_idx++) {
TensorShape output_shape({static_cast<int64_t>(num_output_rows), 1});
Tensor* output_tensor(ctx->Output(pivot_output_tensor_idx, output_shape));
T* output_data = output_tensor->MutableData<T>();
for (int pivot_output_row_idx = 0; pivot_output_row_idx < num_output_rows; pivot_output_row_idx++){
*output_data++ = output[pivot_output_row_idx][pivot_output_tensor_idx];
}
}
}
// Prepare the non-pivot(imputed) Output
for (int imputed_output_count_idx = 0; imputed_output_count_idx < input_node_1_count - num_pivot_columns; imputed_output_count_idx++) {
int tensor_id = input_node_0_count + static_cast<int>(num_pivot_columns) + imputed_output_count_idx;
const auto* input_tensor(ctx->Input<Tensor>(tensor_id));
auto input_dims = input_tensor->Shape();
int64_t input_matrix_size = 1;
for (size_t dim_idx = 1; dim_idx < input_dims.NumDimensions(); dim_idx++)
input_matrix_size *= input_dims[dim_idx];
TensorShape output_shape_imputed({static_cast<int64_t>(num_output_rows), input_matrix_size});
Tensor* output_tensor_imputed(ctx->Output(imputed_output_count_idx + num_pivot_output_columns, output_shape_imputed));
const auto elem_type = input_tensor->GetElementType();
utils::MLTypeCallDispatcher<CopyImputedColumnsImpl,
int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t,
float, double, bool, std::string> t_disp(elem_type);
t_disp.Invoke(input_tensor, output_tensor_imputed, row_idx_record, input_matrix_size, num_output_rows);
}
// Prepare the horizon Output(uint32)
TensorShape output_shape_horizon({static_cast<int64_t>(num_output_rows), 1});
Tensor* output_tensor_horizon(ctx->Output(input_node_1_count + num_pivot_output_columns - static_cast<int>(num_pivot_columns), output_shape_horizon));
uint32_t* output_data_horizon = output_tensor_horizon->MutableData<uint32_t>();
std::copy(horizon_output_helper.begin(), horizon_output_helper.end(), output_data_horizon);
}
};
class ForecastingPivotTransformer final : public OpKernel {
public:
explicit ForecastingPivotTransformer(const OpKernelInfo& info) : OpKernel(info) {
explicit ForecastingPivotTransformer(const OpKernelInfo& info) :
OpKernel(info), _num_pivot_columns(info.GetAttrOrDefault("num_pivot_columns", static_cast<int64_t>(0))) {
}
Status Compute(OpKernelContext* ctx) const override {
utils::MLTypeCallDispatcher<ForecastingPivotTransformerImpl, float, double>
t_disp(ctx->Input<Tensor>(1)->GetElementType());
t_disp.Invoke(ctx);
t_disp.Invoke(ctx, _num_pivot_columns);
return Status::OK();
}
private:
const int64_t _num_pivot_columns;
};
ONNX_OPERATOR_KERNEL_EX(
@ -103,9 +194,18 @@ ONNX_OPERATOR_KERNEL_EX(
kCpuExecutionProvider,
KernelDefBuilder()
.TypeConstraint("T0", DataTypeImpl::GetTensorType<uint8_t>())
.TypeConstraint("T", {DataTypeImpl::GetTensorType<float>(),
DataTypeImpl::GetTensorType<double>()
}),
.TypeConstraint("T", {DataTypeImpl::GetTensorType<int8_t>(),
DataTypeImpl::GetTensorType<uint8_t>(),
DataTypeImpl::GetTensorType<int16_t>(),
DataTypeImpl::GetTensorType<uint16_t>(),
DataTypeImpl::GetTensorType<int32_t>(),
DataTypeImpl::GetTensorType<uint32_t>(),
DataTypeImpl::GetTensorType<int64_t>(),
DataTypeImpl::GetTensorType<uint64_t>(),
DataTypeImpl::GetTensorType<float>(),
DataTypeImpl::GetTensorType<double>(),
DataTypeImpl::GetTensorType<bool>(),
DataTypeImpl::GetTensorType<std::string>()}),
ForecastingPivotTransformer);
} // namespace featurizers

View file

@ -29,50 +29,135 @@ std::vector<uint8_t> GetStream() {
} // namespace
TEST(FeaturizersTests, ForecastingPivotTransformer_2_Inputs) {
auto stream = GetStream<float>();
auto stream = GetStream<double>();
auto dim = static_cast<int64_t>(stream.size());
OpTester test("ForecastingPivotTransformer", 1, onnxruntime::kMSFeaturizersDomain);
test.AddAttribute<int64_t>("num_pivot_columns", static_cast<int64_t>(2));
test.AddInput<uint8_t>("State", {dim}, stream);
test.AddInput<double>("Input_1", {2, 3, 4}, {1, 6, 3, 9,
2, 4, 5, 8,
NS::Traits<float>::CreateNullValue(), NS::Traits<float>::CreateNullValue(), 7, 10,
1, 6, 3, 9,
2, 4, 5, 8,
NS::Traits<float>::CreateNullValue(), NS::Traits<float>::CreateNullValue(), 7, 10});
test.AddInput<double>("Input_2", {2, 2, 4}, {2, NS::Traits<float>::CreateNullValue(), 5, 6,
2, NS::Traits<float>::CreateNullValue(), 3, 4,
2, NS::Traits<float>::CreateNullValue(), 5, 6,
2, NS::Traits<float>::CreateNullValue(), 3, 4});
test.AddOutput<double>("Output", {4, 5}, {3, 5, 7, 5, 3,
9, 8, 10, 6, 4,
3, 5, 7, 5, 3,
9, 8, 10, 6, 4});
NS::Traits<double>::CreateNullValue(), NS::Traits<double>::CreateNullValue(), 7, 10,
1, 6, 9, 3,
2, 4, 8, 5,
NS::Traits<double>::CreateNullValue(), NS::Traits<double>::CreateNullValue(), 10, 7});
test.AddInput<double>("Input_2", {2, 2, 4}, {2, NS::Traits<double>::CreateNullValue(), 5, 6,
2, NS::Traits<double>::CreateNullValue(), 3, 4,
2, NS::Traits<double>::CreateNullValue(), 5, 6,
2, NS::Traits<double>::CreateNullValue(), 3, 4});
test.AddOutput<double>("Output_1", {4, 1}, {3, 9, 9, 3});
test.AddOutput<double>("Output_2", {4, 1}, {5, 8, 8, 5});
test.AddOutput<double>("Output_3", {4, 1}, {7, 10, 10, 7});
test.AddOutput<double>("Output_4", {4, 1}, {5, 6, 5, 6});
test.AddOutput<double>("Output_5", {4, 1}, {3, 4, 3, 4});
//horizon output
test.AddOutput<uint32_t>("Output_6", {4, 1}, {2, 1, 2, 1});
test.Run();
}
TEST(FeaturizersTests, ForecastingPivotTransformer_3_Inputs) {
auto stream = GetStream<float>();
TEST(FeaturizersTests, ForecastingPivotTransformer_4_Inputs) {
auto stream = GetStream<double>();
auto dim = static_cast<int64_t>(stream.size());
OpTester test("ForecastingPivotTransformer", 1, onnxruntime::kMSFeaturizersDomain);
test.AddAttribute<int64_t>("num_pivot_columns", static_cast<int64_t>(2));
test.AddInput<uint8_t>("State", {dim}, stream);
test.AddInput<double>("Input_1", {2, 3, 4}, {1, 6, 3, 9,
2, 4, 5, 8,
NS::Traits<float>::CreateNullValue(), NS::Traits<float>::CreateNullValue(), 7, 10,
NS::Traits<double>::CreateNullValue(), NS::Traits<double>::CreateNullValue(), 7, 10,
1, 6, 3, 9,
2, 4, 5, 8,
NS::Traits<float>::CreateNullValue(), NS::Traits<float>::CreateNullValue(), 7, 10});
test.AddInput<double>("Input_2", {2, 2, 4}, {2, NS::Traits<float>::CreateNullValue(), 5, 6,
2, NS::Traits<float>::CreateNullValue(), 3, 4,
2, NS::Traits<float>::CreateNullValue(), 5, 6,
2, NS::Traits<float>::CreateNullValue(), 3, 4});
test.AddInput<double>("Input_3", {2, 1, 4}, {0, 0, 0, 0,
0, 0, 0, 0});
test.AddOutput<double>("Output", {4, 6}, {3, 5, 7, 5, 3, 0,
9, 8, 10, 6, 4, 0,
3, 5, 7, 5, 3, 0,
9, 8, 10, 6, 4, 0});
NS::Traits<double>::CreateNullValue(), NS::Traits<double>::CreateNullValue(), 7, 10});
test.AddInput<double>("Input_2", {2, 2, 4}, {2, NS::Traits<double>::CreateNullValue(), 5, 6,
2, NS::Traits<double>::CreateNullValue(), 3, 4,
2, NS::Traits<double>::CreateNullValue(), 5, 6,
2, NS::Traits<double>::CreateNullValue(), 3, 4});
test.AddInput<std::string>("Input_3", {2, 1, 4}, {"7", "7", "7", "7",
"9", "9", "9", "9"});
test.AddInput<double>("Input_4", {2, 4}, {-7, -7, -7, -7,
-9, -9, -9, -9});
//pivot output
test.AddOutput<double>("Output_1", {4, 1}, {3, 9, 3, 9});
test.AddOutput<double>("Output_2", {4, 1}, {5, 8, 5, 8});
test.AddOutput<double>("Output_3", {4, 1}, {7, 10, 7, 10});
test.AddOutput<double>("Output_4", {4, 1}, {5, 6, 5, 6});
test.AddOutput<double>("Output_5", {4, 1}, {3, 4, 3, 4});
//non-pivot output
test.AddOutput<std::string>("Output_6", {4, 4}, {"7", "7", "7", "7",
"7", "7", "7", "7",
"9", "9", "9", "9",
"9", "9", "9", "9"});
test.AddOutput<double>("Output_7", {4, 4}, {-7, -7, -7, -7,
-7, -7, -7, -7,
-9, -9, -9, -9,
-9, -9, -9, -9});
//horizon output
test.AddOutput<uint32_t>("Output_8", {4, 1}, {2, 1, 2, 1});
test.Run();
}
TEST(FeaturizersTests, ForecastingPivotTransformer_1_Input_1) {
auto stream = GetStream<double>();
auto dim = static_cast<int64_t>(stream.size());
OpTester test("ForecastingPivotTransformer", 1, onnxruntime::kMSFeaturizersDomain);
test.AddAttribute<int64_t>("num_pivot_columns", static_cast<int64_t>(1));
test.AddInput<uint8_t>("State", {dim}, stream);
test.AddInput<double>("Input_1", {2, 1, 2}, {1, 6, 3, 9});
test.AddOutput<double>("Output_1", {4, 1}, {1, 6, 3, 9});
//horizon output
test.AddOutput<uint32_t>("Output_2", {4, 1}, {2, 1, 2, 1});
test.Run();
}
TEST(FeaturizersTests, ForecastingPivotTransformer_1_Input_2) {
auto stream = GetStream<double>();
auto dim = static_cast<int64_t>(stream.size());
OpTester test("ForecastingPivotTransformer", 1, onnxruntime::kMSFeaturizersDomain);
test.AddAttribute<int64_t>("num_pivot_columns", static_cast<int64_t>(1));
test.AddInput<uint8_t>("State", {dim}, stream);
test.AddInput<double>("Input_1", {2, 1, 2}, {1, NS::Traits<double>::CreateNullValue(),
3, 9});
test.AddOutput<double>("Output_1", {3, 1}, {1, 3, 9});
//horizon output
test.AddOutput<uint32_t>("Output_2", {3, 1}, {2, 2, 1});
test.Run();
}
TEST(FeaturizersTests, ForecastingPivotTransformer_1_Input_3) {
auto stream = GetStream<double>();
auto dim = static_cast<int64_t>(stream.size());
OpTester test("ForecastingPivotTransformer", 1, onnxruntime::kMSFeaturizersDomain);
test.AddAttribute<int64_t>("num_pivot_columns", static_cast<int64_t>(1));
test.AddInput<uint8_t>("State", {dim}, stream);
test.AddInput<double>("Input_1", {1, 3, 4}, {1, 4, 6, NS::Traits<double>::CreateNullValue(),
2, 5, NS::Traits<double>::CreateNullValue(), NS::Traits<double>::CreateNullValue(),
3, NS::Traits<double>::CreateNullValue(), NS::Traits<double>::CreateNullValue(), 7});
test.AddOutput<double>("Output_1", {1, 1}, {1});
test.AddOutput<double>("Output_2", {1, 1}, {2});
test.AddOutput<double>("Output_3", {1, 1}, {3});
//horizon output
test.AddOutput<uint32_t>("Output_4", {1, 1}, {4});
test.Run();
}
TEST(FeaturizersTests, ForecastingPivotTransformer_1_Input_Horizon) {
auto stream = GetStream<double>();
auto dim = static_cast<int64_t>(stream.size());
OpTester test("ForecastingPivotTransformer", 1, onnxruntime::kMSFeaturizersDomain);
test.AddAttribute<int64_t>("num_pivot_columns", static_cast<int64_t>(1));
test.AddInput<uint8_t>("State", {dim}, stream);
test.AddInput<double>("Input_1", {3, 1, 6}, {1, NS::Traits<double>::CreateNullValue(), 3, NS::Traits<double>::CreateNullValue(), 5, NS::Traits<double>::CreateNullValue(),
NS::Traits<double>::CreateNullValue(), 2, 3, NS::Traits<double>::CreateNullValue(), 5, 6,
1, 2, 3, NS::Traits<double>::CreateNullValue(), NS::Traits<double>::CreateNullValue(), NS::Traits<double>::CreateNullValue()});
test.AddOutput<double>("Output_1", {10, 1}, {1, 3, 5, 2, 3, 5, 6, 1, 2, 3});
//horizon output
test.AddOutput<uint32_t>("Output_2", {10, 1}, {6, 4, 2, 5, 4, 2, 1, 6, 5, 4});
test.Run();
}