| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| /*! |
| * \file krprod_test.cc |
| * \brief Test Khatri-Rao product |
| * \author Jencir Lee |
| */ |
| #include <vector> |
| #include <random> |
| #include <cmath> |
| #include "gtest/gtest.h" |
| #include "operator/contrib/krprod.h" |
| |
| namespace mxnet { |
| namespace op { |
| |
| using namespace mshadow; |
| using namespace mshadow::expr; |
| using DType = double; |
| |
| #define EXPECT_DOUBLE_EQ_MATRIX(expected, actual) \ |
| { \ |
| for (int i = 0; i < static_cast<int>(actual.size(0)); ++i) \ |
| for (int j = 0; j < static_cast<int>(actual.size(1)); ++j) \ |
| EXPECT_LE(std::abs(actual[i][j] - expected[i][j]), 1e-10); \ |
| } |
| |
| TEST(row_wise_kronecker, OneInputMatrix) { |
| // Input matrices of shape (2, 4) which is also the expected result |
| DType mat[8]{1, 2, 3, 4, 5, 6, 7, 8}; |
| |
| // Make input tensors |
| std::vector<Tensor<cpu, 2, DType> > ts_arr; |
| ts_arr.emplace_back(mat, Shape2(2, 4), 4, nullptr); |
| |
| // Compute Khatri-Rao product |
| Tensor<cpu, 2, DType> result(Shape2(2, 4)); |
| AllocSpace(&result); |
| row_wise_kronecker(result, ts_arr); |
| |
| // Check against expected result |
| EXPECT_DOUBLE_EQ_MATRIX(ts_arr[0], result); |
| |
| FreeSpace(&result); |
| } |
| |
| TEST(row_wise_kronecker, TwoInputMatrices) { |
| // Input matrices of shape (2, 3) and (2, 4) |
| DType mat1[6]{1, 2, 3, 4, 5, 6}; |
| DType mat2[8]{1, 2, 3, 4, 5, 6, 7, 8}; |
| |
| // Expect result of shape (2, 12) |
| DType expected[24]{1, 2, 3, 4, 2, 4, 6, 8, 3, 6, 9, 12, |
| 20, 24, 28, 32, 25, 30, 35, 40, 30, 36, 42, 48}; |
| |
| // Make input tensors |
| std::vector<Tensor<cpu, 2, DType> > ts_arr; |
| ts_arr.emplace_back(mat1, Shape2(2, 3), 3, nullptr); |
| ts_arr.emplace_back(mat2, Shape2(2, 4), 4, nullptr); |
| |
| // Compute Khatri-Rao product |
| Tensor<cpu, 2, DType> result(Shape2(2, 12)); |
| AllocSpace(&result); |
| row_wise_kronecker(result, ts_arr); |
| |
| // Check against expected result |
| Tensor<cpu, 2, DType> ts_expected(expected, Shape2(2, 12), 12, nullptr); |
| EXPECT_DOUBLE_EQ_MATRIX(ts_expected, result); |
| |
| FreeSpace(&result); |
| } |
| |
| TEST(row_wise_kronecker, TwoInputMatrices2) { |
| // Input matrices of shape (2, 3) and (2, 1) |
| DType mat1[6]{1, 2, 3, 4, 5, 6}; |
| DType mat2[2]{1, 2}; |
| |
| // Expect result of shape (2, 3) |
| DType expected[6]{1, 2, 3, 8, 10, 12}; |
| |
| // Make input tensors |
| std::vector<Tensor<cpu, 2, DType> > ts_arr; |
| ts_arr.emplace_back(mat1, Shape2(2, 3), 3, nullptr); |
| ts_arr.emplace_back(mat2, Shape2(2, 1), 1, nullptr); |
| |
| // Compute Khatri-Rao product |
| Tensor<cpu, 2, DType> result(Shape2(2, 3)); |
| AllocSpace(&result); |
| row_wise_kronecker(result, ts_arr); |
| |
| // Check against expected result |
| Tensor<cpu, 2, DType> ts_expected(expected, Shape2(2, 3), 3, nullptr); |
| EXPECT_DOUBLE_EQ_MATRIX(ts_expected, result); |
| |
| FreeSpace(&result); |
| } |
| |
| TEST(row_wise_kronecker, ThreeInputMatrices) { |
| std::default_random_engine generator; |
| std::uniform_int_distribution<int> distribution(1, 6); |
| |
| Tensor<cpu, 2, DType> in1(Shape2(3, 4)), in2(Shape2(3, 2)), in3(Shape2(3, 3)), kr12(Shape2(3, 8)), |
| kr13(Shape2(3, 24)), result(Shape2(3, 24)); |
| AllocSpace(&in1); |
| AllocSpace(&in2); |
| AllocSpace(&in3); |
| AllocSpace(&kr12); |
| AllocSpace(&kr13); |
| AllocSpace(&result); |
| |
| std::vector<Tensor<cpu, 2, DType> > ts_arr{in1, in2, in3}; |
| for (auto& in : ts_arr) { |
| for (int i = 0; i < static_cast<int>(in.size(0)); ++i) |
| for (int j = 0; j < static_cast<int>(in.size(1)); ++j) |
| in[i][j] = distribution(generator); |
| } |
| |
| row_wise_kronecker(kr12, {in1, in2}); |
| row_wise_kronecker(kr13, {kr12, in3}); |
| row_wise_kronecker(result, ts_arr); |
| EXPECT_DOUBLE_EQ_MATRIX(kr13, result); |
| |
| for (auto& in : ts_arr) |
| FreeSpace(&in); |
| FreeSpace(&kr12); |
| FreeSpace(&kr13); |
| FreeSpace(&result); |
| } |
| |
| TEST(row_wise_kronecker, ThreeInputMatrices2) { |
| std::default_random_engine generator; |
| std::uniform_int_distribution<int> distribution(1, 6); |
| |
| Tensor<cpu, 2, DType> in1(Shape2(3, 4)), in2(Shape2(3, 1)), in3(Shape2(3, 3)), kr12(Shape2(3, 4)), |
| kr13(Shape2(3, 12)), result(Shape2(3, 12)); |
| AllocSpace(&in1); |
| AllocSpace(&in2); |
| AllocSpace(&in3); |
| AllocSpace(&kr12); |
| AllocSpace(&kr13); |
| AllocSpace(&result); |
| |
| std::vector<Tensor<cpu, 2, DType> > ts_arr{in1, in2, in3}; |
| for (auto& in : ts_arr) { |
| for (int i = 0; i < static_cast<int>(in.size(0)); ++i) |
| for (int j = 0; j < static_cast<int>(in.size(1)); ++j) |
| in[i][j] = distribution(generator); |
| } |
| |
| row_wise_kronecker(kr12, {in1, in2}); |
| row_wise_kronecker(kr13, {kr12, in3}); |
| row_wise_kronecker(result, ts_arr); |
| EXPECT_DOUBLE_EQ_MATRIX(kr13, result); |
| |
| for (auto& in : ts_arr) |
| FreeSpace(&in); |
| FreeSpace(&kr12); |
| FreeSpace(&kr13); |
| FreeSpace(&result); |
| } |
| |
| TEST(row_wise_kronecker, ThreeInputMatrices3) { |
| std::default_random_engine generator; |
| std::uniform_int_distribution<int> distribution(1, 6); |
| |
| Tensor<cpu, 2, DType> in1(Shape2(3, 1)), in2(Shape2(3, 4)), in3(Shape2(3, 3)), kr12(Shape2(3, 4)), |
| kr13(Shape2(3, 12)), result(Shape2(3, 12)); |
| AllocSpace(&in1); |
| AllocSpace(&in2); |
| AllocSpace(&in3); |
| AllocSpace(&kr12); |
| AllocSpace(&kr13); |
| AllocSpace(&result); |
| |
| std::vector<Tensor<cpu, 2, DType> > ts_arr{in1, in2, in3}; |
| for (auto& in : ts_arr) { |
| for (int i = 0; i < static_cast<int>(in.size(0)); ++i) |
| for (int j = 0; j < static_cast<int>(in.size(1)); ++j) |
| in[i][j] = distribution(generator); |
| } |
| |
| row_wise_kronecker(kr12, {in1, in2}); |
| row_wise_kronecker(kr13, {kr12, in3}); |
| row_wise_kronecker(result, ts_arr); |
| EXPECT_DOUBLE_EQ_MATRIX(kr13, result); |
| |
| for (auto& in : ts_arr) |
| FreeSpace(&in); |
| FreeSpace(&kr12); |
| FreeSpace(&kr13); |
| FreeSpace(&result); |
| } |
| |
| TEST(row_wise_kronecker, FourInputMatrices) { |
| std::default_random_engine generator; |
| std::uniform_int_distribution<int> distribution(1, 6); |
| |
| Tensor<cpu, 2, DType> in1(Shape2(3, 47)), in2(Shape2(3, 1)), in3(Shape2(3, 5)), |
| in4(Shape2(3, 2173)), kr12(Shape2(3, 47)), kr13(Shape2(3, 47 * 5)), |
| kr14(Shape2(3, 47 * 5 * 2173)), result(Shape2(3, 47 * 5 * 2173)); |
| AllocSpace(&in1); |
| AllocSpace(&in2); |
| AllocSpace(&in3); |
| AllocSpace(&in4); |
| AllocSpace(&kr12); |
| AllocSpace(&kr13); |
| AllocSpace(&kr14); |
| AllocSpace(&result); |
| |
| std::vector<Tensor<cpu, 2, DType> > ts_arr{in1, in2, in3, in4}; |
| for (auto& in : ts_arr) { |
| for (int i = 0; i < static_cast<int>(in.size(0)); ++i) |
| for (int j = 0; j < static_cast<int>(in.size(1)); ++j) |
| in[i][j] = distribution(generator); |
| } |
| |
| row_wise_kronecker(kr12, {in1, in2}); |
| row_wise_kronecker(kr13, {kr12, in3}); |
| row_wise_kronecker(kr14, {kr13, in4}); |
| row_wise_kronecker(result, ts_arr); |
| EXPECT_DOUBLE_EQ_MATRIX(kr14, result); |
| |
| for (auto& in : ts_arr) |
| FreeSpace(&in); |
| FreeSpace(&kr12); |
| FreeSpace(&kr13); |
| FreeSpace(&kr14); |
| FreeSpace(&result); |
| } |
| |
| #if MXNET_USE_LAPACK == 1 |
| TEST(khatri_rao, OneInputMatrix) { |
| // Input matrices of shape (2, 4) which is also the expected result |
| DType mat[8]{1, 2, 3, 4, 5, 6, 7, 8}; |
| |
| // Make input tensors |
| std::vector<Tensor<cpu, 2, DType> > ts_arr; |
| ts_arr.emplace_back(mat, Shape2(2, 4), 4, nullptr); |
| |
| // Compute Khatri-Rao product |
| Tensor<cpu, 2, DType> result(Shape2(2, 4)); |
| AllocSpace(&result); |
| khatri_rao(result, ts_arr); |
| |
| // Check against expected result |
| EXPECT_DOUBLE_EQ_MATRIX(ts_arr[0], result); |
| |
| FreeSpace(&result); |
| } |
| |
| TEST(khatri_rao, TwoInputMatrices) { |
| // Input matrices of shape (3, 2) and (4, 2) |
| DType mat1[6]{1, 4, 2, 5, 3, 6}; |
| DType mat2[8]{1, 5, 2, 6, 3, 7, 4, 8}; |
| |
| // Expect result of shape (12, 2) |
| DType expected[24]{1, 20, 2, 24, 3, 28, 4, 32, 2, 25, 4, 30, |
| 6, 35, 8, 40, 3, 30, 6, 36, 9, 42, 12, 48}; |
| |
| // Make input tensors |
| std::vector<Tensor<cpu, 2, DType> > ts_arr; |
| ts_arr.emplace_back(mat1, Shape2(3, 2), 2, nullptr); |
| ts_arr.emplace_back(mat2, Shape2(4, 2), 2, nullptr); |
| |
| // Compute Khatri-Rao product |
| Tensor<cpu, 2, DType> result(Shape2(12, 2)); |
| AllocSpace(&result); |
| khatri_rao(result, ts_arr); |
| |
| // Check against expected result |
| Tensor<cpu, 2, DType> ts_expected(expected, Shape2(12, 2), 2, nullptr); |
| EXPECT_DOUBLE_EQ_MATRIX(ts_expected, result); |
| |
| FreeSpace(&result); |
| } |
| |
| TEST(khatri_rao, ThreeInputMatrices) { |
| std::default_random_engine generator; |
| std::uniform_int_distribution<int> distribution(1, 6); |
| |
| Tensor<cpu, 2, DType> in1(Shape2(4, 3)), in2(Shape2(2, 3)), in3(Shape2(3, 3)), kr12(Shape2(8, 3)), |
| kr13(Shape2(24, 3)), result(Shape2(24, 3)); |
| AllocSpace(&in1); |
| AllocSpace(&in2); |
| AllocSpace(&in3); |
| AllocSpace(&kr12); |
| AllocSpace(&kr13); |
| AllocSpace(&result); |
| |
| std::vector<Tensor<cpu, 2, DType> > ts_arr{in1, in2, in3}; |
| for (auto& in : ts_arr) { |
| for (int i = 0; i < static_cast<int>(in.size(0)); ++i) |
| for (int j = 0; j < static_cast<int>(in.size(1)); ++j) |
| in[i][j] = distribution(generator); |
| } |
| |
| khatri_rao(kr12, {in1, in2}); |
| khatri_rao(kr13, {kr12, in3}); |
| khatri_rao(result, ts_arr); |
| EXPECT_DOUBLE_EQ_MATRIX(kr13, result); |
| |
| for (auto& in : ts_arr) |
| FreeSpace(&in); |
| FreeSpace(&kr12); |
| FreeSpace(&kr13); |
| FreeSpace(&result); |
| } |
| |
| TEST(inv_khatri_rao, OneInputMatrixTransposed) { |
| DType mat[8]{1, 2, 3, 4, 5, 6, 7, 8}; |
| |
| // Make input tensors |
| std::vector<Tensor<cpu, 2, DType> > ts_arr; |
| ts_arr.emplace_back(mat, Shape2(2, 4), 4, nullptr); |
| |
| // Compute inverse Khatri-Rao product |
| Tensor<cpu, 2, DType> inv_kr(Shape2(2, 4)); |
| AllocSpace(&inv_kr); |
| inv_khatri_rao(inv_kr, ts_arr, true); |
| |
| // Check against expected result |
| Tensor<cpu, 2, DType> actual_dot(Shape2(2, 4)); |
| AllocSpace(&actual_dot); |
| actual_dot = implicit_dot(implicit_dot(inv_kr, ts_arr[0].T()), inv_kr); |
| EXPECT_DOUBLE_EQ_MATRIX(inv_kr, actual_dot); |
| |
| FreeSpace(&inv_kr); |
| FreeSpace(&actual_dot); |
| } |
| |
| TEST(inv_khatri_rao, TwoInputMatrices) { |
| // Input matrices of shape (3, 2) and (4, 2) |
| DType mat1[6]{1, 4, 2, 5, 3, 6}; |
| DType mat2[8]{1, 5, 2, 6, 3, 7, 4, 8}; |
| |
| // Make input tensors |
| std::vector<Tensor<cpu, 2, DType> > ts_arr; |
| ts_arr.emplace_back(mat1, Shape2(3, 2), 2, nullptr); |
| ts_arr.emplace_back(mat2, Shape2(4, 2), 2, nullptr); |
| |
| // Compute inverse Khatri-Rao product |
| Tensor<cpu, 2, DType> inv_kr(Shape2(2, 12)), kr(Shape2(12, 2)); |
| AllocSpace(&inv_kr); |
| AllocSpace(&kr); |
| inv_khatri_rao(inv_kr, ts_arr, false); |
| khatri_rao(kr, ts_arr); |
| |
| // Check against expected result |
| Tensor<cpu, 2, DType> actual_dot(Shape2(2, 12)); |
| AllocSpace(&actual_dot); |
| actual_dot = implicit_dot(implicit_dot(inv_kr, kr), inv_kr); |
| EXPECT_DOUBLE_EQ_MATRIX(inv_kr, actual_dot); |
| |
| FreeSpace(&inv_kr); |
| FreeSpace(&kr); |
| FreeSpace(&actual_dot); |
| } |
| |
| TEST(inv_khatri_rao, TwoInputMatricesTransposed) { |
| // Transposed input matrices of shape (2, 3) and (2, 4) |
| DType mat1[6]{1, 2, 3, 4, 5, 6}; |
| DType mat2[8]{1, 2, 3, 4, 5, 6, 7, 8}; |
| |
| // Make input tensors |
| std::vector<Tensor<cpu, 2, DType> > ts_arr; |
| ts_arr.emplace_back(mat1, Shape2(2, 3), 3, nullptr); |
| ts_arr.emplace_back(mat2, Shape2(2, 4), 4, nullptr); |
| |
| // Compute invser Khatri-Rao product |
| Tensor<cpu, 2, DType> inv_kr(Shape2(2, 12)), kr_t(Shape2(2, 12)); |
| AllocSpace(&inv_kr); |
| AllocSpace(&kr_t); |
| inv_khatri_rao(inv_kr, ts_arr, true); |
| row_wise_kronecker(kr_t, ts_arr); |
| |
| // Check against expected result |
| Tensor<cpu, 2, DType> actual_dot(Shape2(2, 12)); |
| AllocSpace(&actual_dot); |
| actual_dot = implicit_dot(implicit_dot(inv_kr, kr_t.T()), inv_kr); |
| EXPECT_DOUBLE_EQ_MATRIX(inv_kr, actual_dot); |
| |
| FreeSpace(&inv_kr); |
| FreeSpace(&kr_t); |
| FreeSpace(&actual_dot); |
| } |
| |
| TEST(inv_khatri_rao, ThreeInputMatricesTranposed) { |
| // Randomly initialise the transposed input matrices |
| std::default_random_engine generator; |
| std::uniform_int_distribution<int> distribution(1, 6); |
| |
| Tensor<cpu, 2, DType> in1(Shape2(3, 4)), in2(Shape2(3, 2)), in3(Shape2(3, 3)); |
| AllocSpace(&in1); |
| AllocSpace(&in2); |
| AllocSpace(&in3); |
| |
| std::vector<Tensor<cpu, 2, DType> > ts_arr{in1, in2, in3}; |
| for (auto& in : ts_arr) { |
| for (int i = 0; i < static_cast<int>(in.size(0)); ++i) |
| for (int j = 0; j < static_cast<int>(in.size(1)); ++j) |
| in[i][j] = distribution(generator); |
| } |
| |
| // Compute inv_kr & kr |
| Tensor<cpu, 2, DType> inv_kr(Shape2(3, 24)), kr_t(Shape2(3, 24)); |
| AllocSpace(&inv_kr); |
| AllocSpace(&kr_t); |
| |
| inv_khatri_rao(inv_kr, ts_arr, true); |
| row_wise_kronecker(kr_t, ts_arr); |
| |
| // Check dot result |
| Tensor<cpu, 2, DType> actual_dot(Shape2(3, 24)); |
| AllocSpace(&actual_dot); |
| actual_dot = implicit_dot(implicit_dot(inv_kr, kr_t.T()), inv_kr); |
| EXPECT_DOUBLE_EQ_MATRIX(inv_kr, actual_dot); |
| |
| for (auto& in : ts_arr) |
| FreeSpace(&in); |
| FreeSpace(&inv_kr); |
| FreeSpace(&kr_t); |
| FreeSpace(&actual_dot); |
| } |
| #endif // MXNET_USE_LAPACK == 1 |
| } // namespace op |
| } // namespace mxnet |