diff --git a/src/mlpack/methods/gmm/gmm_train_main.cpp b/src/mlpack/methods/gmm/gmm_train_main.cpp index 8bc64cddcf..2185dcf1de 100644 --- a/src/mlpack/methods/gmm/gmm_train_main.cpp +++ b/src/mlpack/methods/gmm/gmm_train_main.cpp @@ -145,12 +145,18 @@ static void mlpackMain() "number of Gaussians must be positive"); const int gaussians = CLI::GetParam("gaussians"); + RequireParamValue("trials", [](int x) { return x > 0; }, true, + "trials must be greater than 0"); + ReportIgnoredParam({{ "diagonal_covariance", true }}, "no_force_positive"); RequireAtLeastOnePassed({ "output_model" }, false, "no model will be saved"); RequireParamValue("noise", [](double x) { return x >= 0.0; }, true, "variance of noise must be greater than or equal to 0"); + RequireParamValue("max_iterations", [](int x) { return x >= 0; }, true, + "max_iterations must be greater than or equal to 0"); + arma::mat dataPoints = std::move(CLI::GetParam("input")); // Do we need to add noise to the dataset? diff --git a/src/mlpack/tests/CMakeLists.txt b/src/mlpack/tests/CMakeLists.txt index 15056a6878..c6daa4457b 100644 --- a/src/mlpack/tests/CMakeLists.txt +++ b/src/mlpack/tests/CMakeLists.txt @@ -122,6 +122,9 @@ add_executable(mlpack_test main_tests/det_test.cpp main_tests/decision_tree_test.cpp main_tests/decision_stump_test.cpp + main_tests/gmm_generate_test.cpp + main_tests/gmm_probability_test.cpp + main_tests/gmm_train_test.cpp main_tests/fastmks_test.cpp main_tests/kde_test.cpp main_tests/linear_regression_test.cpp @@ -186,7 +189,7 @@ set(parallel_tests "SVDIncrementalTest;SVDBatchTest;" "LocalCoordinateCodingTest;FeedForwardNetworkTest;SparseAutoencoderTest;" "GMMTest;CFTest;ConvolutionalNetworkTest;HMMTest;LARSTest;" - "LogisticRegressionTest;" + "LogisticRegressionTest;GmmTrainMainTest;" "LinearSVMTest") # Add tests to the testing framework diff --git a/src/mlpack/tests/main_tests/gmm_generate_test.cpp b/src/mlpack/tests/main_tests/gmm_generate_test.cpp new file mode 100644 index 0000000000..0063c17aeb --- /dev/null +++ b/src/mlpack/tests/main_tests/gmm_generate_test.cpp @@ -0,0 +1,78 @@ +/** + * @file gmm_generate_test.cpp + * @author Yashwant Singh + * + * Test mlpackMain() of gmm_generate_main.cpp. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#define BINDING_TYPE BINDING_TYPE_TEST +static const std::string testName = "GmmGenerate"; + +#include +#include +#include + +#include "test_helper.hpp" +#include +#include "../test_tools.hpp" + +using namespace mlpack; + +struct GmmGenerateTestFixture +{ + public: + GmmGenerateTestFixture() + { + // Cache in the options for this program. + CLI::RestoreSettings(testName); + } + + ~GmmGenerateTestFixture() + { + // Clear the settings. + CLI::ClearSettings(); + } +}; + +BOOST_FIXTURE_TEST_SUITE(GmmGenerateMainTest, GmmGenerateTestFixture); + +// Checking that Samples must greater than 0. +BOOST_AUTO_TEST_CASE(GmmGenerateSamplesTest) +{ + arma::mat inputData(5, 10, arma::fill::randu); + + GMM gmm(1, 5); + gmm.Train(inputData, 5); + + SetInputParam("input_model", &gmm); + + Log::Fatal.ignoreInput = true; + SetInputParam("samples", 0); // Invalid + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +// Checking dimensionality of output. +BOOST_AUTO_TEST_CASE(GmmGenerateDimensionality) +{ + arma::mat inputData(5, 10, arma::fill::randu); + + GMM gmm(1, 5); + gmm.Train(inputData, 5); + SetInputParam("input_model", &gmm); + SetInputParam("samples", (int) 10); + + mlpackMain(); + + arma::mat output = std::move(CLI::GetParam("output")); + + BOOST_REQUIRE_EQUAL(output.n_rows, gmm.Dimensionality()); + BOOST_REQUIRE_EQUAL(output.n_cols, (int) 10); +} + +BOOST_AUTO_TEST_SUITE_END(); + diff --git a/src/mlpack/tests/main_tests/gmm_probability_test.cpp b/src/mlpack/tests/main_tests/gmm_probability_test.cpp new file mode 100644 index 0000000000..0282763aa4 --- /dev/null +++ b/src/mlpack/tests/main_tests/gmm_probability_test.cpp @@ -0,0 +1,72 @@ +/** + * @file gmm_probability_test.cpp + * @author Yashwant Singh + * + * Test mlpackMain() of gmm_probability_main.cpp. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ + +#define BINDING_TYPE BINDING_TYPE_TEST + +static const std::string testName = "GmmProbability"; + +#include +#include +#include + +#include "test_helper.hpp" + +#include + + +using namespace mlpack; + +struct GmmProbabilityTestFixture +{ + public: + GmmProbabilityTestFixture() + { + // Cache in the options for this program. + CLI::RestoreSettings(testName); + } + + ~GmmProbabilityTestFixture() + { + // Clear the settings. + CLI::ClearSettings(); + } +}; + +void ResetGmmProbabilitySetting() +{ + CLI::ClearSettings(); + CLI::RestoreSettings(testName); +} + +BOOST_FIXTURE_TEST_SUITE(GmmProbabilityMainTest, GmmProbabilityTestFixture); + +// Checking the input and output dimensionality. +BOOST_AUTO_TEST_CASE(GmmProbabilityDimensionality) +{ + arma::mat inputData(5, 10, arma::fill::randu); + + GMM gmm(1, 5); + gmm.Train(std::move(inputData), 5); + + arma::mat inputPoints(5, 5, arma::fill::randu); + + SetInputParam("input", std::move(inputPoints)); + SetInputParam("input_model", &gmm); + + mlpackMain(); + + BOOST_REQUIRE_EQUAL(CLI::GetParam("output").n_cols, 5); + BOOST_REQUIRE_EQUAL(CLI::GetParam("output").n_rows, 1); +} + +BOOST_AUTO_TEST_SUITE_END(); + diff --git a/src/mlpack/tests/main_tests/gmm_train_test.cpp b/src/mlpack/tests/main_tests/gmm_train_test.cpp new file mode 100644 index 0000000000..e049e8538e --- /dev/null +++ b/src/mlpack/tests/main_tests/gmm_train_test.cpp @@ -0,0 +1,490 @@ +/** + * @file gmm_train_test.cpp + * @author Yashwant Singh + * + * Test mlpackMain() of gmm_train_main.cpp. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ + +#include + +#define BINDING_TYPE BINDING_TYPE_TEST +static const std::string testName = "GmmTrain"; + +#include +#include +#include "test_helper.hpp" +#include + +#include +#include "../test_tools.hpp" + +using namespace mlpack; + +struct GmmTrainTestFixture +{ + public: + GmmTrainTestFixture() + { + // Cache in the options for this program. + CLI::RestoreSettings(testName); + } + + ~GmmTrainTestFixture() + { + // Clear the settings. + bindings::tests::CleanMemory(); + CLI::ClearSettings(); + } +}; + +void ResetGmmTrainSetting() +{ + CLI::ClearSettings(); + CLI::RestoreSettings(testName); +} + + +BOOST_FIXTURE_TEST_SUITE(GmmTrainMainTest, GmmTrainTestFixture); + +// To check if the gaussian is positive or not. +BOOST_AUTO_TEST_CASE(GmmTrainValidGaussianTest) +{ + arma::mat inputData(5, 10, arma::fill::randu); + + SetInputParam("input", std::move(inputData)); + SetInputParam("gaussians", 0); // Invalid + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +/** + * To check if the number of gaussians in the output model is same as + * that of input gaussian parameter or not. + **/ +BOOST_AUTO_TEST_CASE(GmmTrainOutputModelGaussianTest) +{ + arma::mat inputData(5, 10, arma::fill::randu); + + SetInputParam("input", std::move(inputData)); + SetInputParam("gaussians", (int) 2); + SetInputParam("trials", (int) 2); + + mlpackMain(); + + GMM* gmm = CLI::GetParam("output_model"); + BOOST_REQUIRE_EQUAL(gmm->Gaussians(), (int) 2); +} + +// Max iterations must be positive. +BOOST_AUTO_TEST_CASE(GmmTrainMaxIterationsTest) +{ + arma::mat inputData(5, 10, arma::fill::randu); + + SetInputParam("input", std::move(inputData)); + SetInputParam("gaussians", (int) 2); + SetInputParam("trials", (int) 1); + SetInputParam("max_iterations", (int)-1); // Invalid. + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +// Ensure that Trials must be greater than 0. +BOOST_AUTO_TEST_CASE(GmmTrainPositiveTrialsTest) +{ + arma::mat inputData(5, 10, arma::fill::randu); + + SetInputParam("input", std::move(inputData)); + SetInputParam("gaussians", (int) 2); + SetInputParam("trials", (int) 0); // Invalid. + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +// Checking that percentage is between 0 and 1. +BOOST_AUTO_TEST_CASE(RefinedStartPercentageTest) +{ + arma::mat inputData(5, 10, arma::fill::randu); + + SetInputParam("input", std::move(inputData)); + SetInputParam("refined_start", true); + + Log::Fatal.ignoreInput = true; + SetInputParam("percentage", (double) 2.0); // Invalid + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + + SetInputParam("percentage", (double) -1.0); // Invalid + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + + Log::Fatal.ignoreInput = false; +} + +// Samplings must be positive. +BOOST_AUTO_TEST_CASE(GmmTrainSamplings) +{ + arma::mat inputData(5, 10, arma::fill::randu); + + SetInputParam("input", std::move(inputData)); + SetInputParam("refined_start", true); + SetInputParam("samplings", (int) 0); // Invalid + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +// Number of gaussians in the model trained from input model. +BOOST_AUTO_TEST_CASE(GmmTrainNumberOfGaussian) +{ + arma::mat inputData(5, 10, arma::fill::randu); + + SetInputParam("input", inputData); + SetInputParam("gaussians", (int) 2); + + mlpackMain(); + + GMM* gmm = CLI::GetParam("output_model"); + + SetInputParam("input_model", gmm); + + CLI::GetSingleton().Parameters()["input"].wasPassed = false; + + SetInputParam("input", std::move(inputData)); + + mlpackMain(); + + GMM* gmm1 = CLI::GetParam("output_model"); + + BOOST_REQUIRE_EQUAL(gmm1->Gaussians(), (int) 2); +} + +// Making sure that enabling no_force_positive doesn't crash. +BOOST_AUTO_TEST_CASE(GmmTrainNoForcePositiveTest) +{ + arma::mat inputData(5, 10, arma::fill::randu); + + SetInputParam("input", inputData); + SetInputParam("gaussians", (int) 1); + SetInputParam("no_force_positive", true); + + mlpackMain(); + + GMM* gmm = CLI::GetParam("output_model"); + + SetInputParam("input_model", gmm); + + CLI::GetSingleton().Parameters()["input"].wasPassed = false; + + SetInputParam("input", std::move(inputData)); + + mlpackMain(); + + GMM* gmm1 = CLI::GetParam("output_model"); + + BOOST_REQUIRE_EQUAL(gmm1->Gaussians(), (int) 1); +} + +// Ensure that Noise affects the final result. +BOOST_AUTO_TEST_CASE(GmmTrainNoiseTest) +{ + arma::mat inputData; + if (!data::Load("data_3d_mixed.txt", inputData)) + BOOST_FAIL("Unable to load train dataset data_3d_mixed.txt!"); + + SetInputParam("input", inputData); + SetInputParam("gaussians", (int) 2); + SetInputParam("noise", (double) 0.0); + + size_t seed = std::time(NULL); + mlpack::math::randGen.seed((uint32_t) seed); + srand((unsigned int) seed); + arma::arma_rng::set_seed(seed); + + mlpackMain(); + + GMM* gmm = CLI::GetParam("output_model"); + + ResetGmmTrainSetting(); + + SetInputParam("input", std::move(inputData)); + SetInputParam("gaussians", (int) 2); + SetInputParam("noise", (double) 100.0); + + mlpack::math::randGen.seed((uint32_t) seed); + srand((unsigned int) seed); + arma::arma_rng::set_seed(seed); + + mlpackMain(); + + GMM* gmm1 = CLI::GetParam("output_model"); + + arma::uvec sortedIndices = sort_index(gmm->Weights()); + + for (size_t k = 0; k < sortedIndices.n_elem; k++) + { + BOOST_REQUIRE(arma::norm(gmm->Component(sortedIndices[k]).Mean() - + gmm1->Component(sortedIndices[k]).Mean()) > 1e-50 || + arma::norm(gmm->Component(sortedIndices[k]).Covariance() - + gmm1->Component(sortedIndices[k]).Covariance()) > 1e-50); + } +} + +// Ensure that Trials affects the final result. +BOOST_AUTO_TEST_CASE(GmmTrainTrialsTest) +{ + arma::mat inputData(5, 250, arma::fill::randu); + + SetInputParam("input", inputData); + SetInputParam("gaussians", (int) 3); + SetInputParam("trials", (int) 1); + SetInputParam("max_iterations", (int) 500); + + size_t seed = std::time(NULL); + mlpack::math::randGen.seed((uint32_t) seed); + srand((unsigned int) seed); + arma::arma_rng::set_seed(seed); + + mlpackMain(); + + GMM* gmm = CLI::GetParam("output_model"); + + ResetGmmTrainSetting(); + + SetInputParam("input", std::move(inputData)); + SetInputParam("gaussians", (int) 3); + SetInputParam("max_iterations", (int) 500); + SetInputParam("trials", (int) 500); + + mlpack::math::randGen.seed((uint32_t) seed); + srand((unsigned int) seed); + arma::arma_rng::set_seed(seed); + + mlpackMain(); + + GMM* gmm1 = CLI::GetParam("output_model"); + + arma::uvec sortedIndices = sort_index(gmm->Weights()); + + for (size_t k = 0; k < sortedIndices.n_elem; k++) + { + BOOST_REQUIRE(arma::norm(gmm->Component(sortedIndices[k]).Mean() - + gmm1->Component(sortedIndices[k]).Mean()) > 1e-50 || + arma::norm(gmm->Component(sortedIndices[k]).Covariance() - + gmm1->Component(sortedIndices[k]).Covariance()) > 1e-50); + } +} + +// Ensure that Percentage affects the final result when refined_start is true. +BOOST_AUTO_TEST_CASE(GmmTrainPercentageTest) +{ + arma::mat inputData; + if (!data::Load("data_3d_mixed.txt", inputData)) + BOOST_FAIL("Unable to load train dataset data_3d_mixed.txt!"); + + SetInputParam("input", inputData); + SetInputParam("gaussians", (int) 2); + SetInputParam("refined_start", true); + SetInputParam("percentage", (double) 0.01); + SetInputParam("samplings", (int) 1000); + + size_t seed = std::time(NULL); + mlpack::math::randGen.seed((uint32_t) seed); + srand((unsigned int) seed); + arma::arma_rng::set_seed(seed); + + mlpackMain(); + + GMM* gmm = CLI::GetParam("output_model"); + + ResetGmmTrainSetting(); + + SetInputParam("input", std::move(inputData)); + SetInputParam("gaussians", (int) 2); + SetInputParam("refined_start", true); + SetInputParam("percentage", (double) 0.45); + SetInputParam("samplings", (int) 1000); + + mlpack::math::randGen.seed((uint32_t) seed); + srand((unsigned int) seed); + arma::arma_rng::set_seed(seed); + + mlpackMain(); + + GMM* gmm1 = CLI::GetParam("output_model"); + + arma::uvec sortedIndices = sort_index(gmm->Weights()); + + for (size_t k = 0; k < sortedIndices.n_elem; k++) + { + BOOST_REQUIRE(arma::norm(gmm->Component(sortedIndices[k]).Mean() - + gmm1->Component(sortedIndices[k]).Mean()) > 1e-50 || + arma::norm(gmm->Component(sortedIndices[k]).Covariance() - + gmm1->Component(sortedIndices[k]).Covariance()) > 1e-50); + } +} + +// Ensure that Sampling affects the final result when refined_start is true. +BOOST_AUTO_TEST_CASE(GmmTrainSamplingsTest) +{ + arma::mat inputData; + if (!data::Load("data_3d_mixed.txt", inputData)) + BOOST_FAIL("Unable to load train dataset data_3d_mixed.txt!"); + + SetInputParam("input", inputData); + SetInputParam("gaussians", (int) 8); + SetInputParam("refined_start", true); + SetInputParam("trials", (int) 2); + SetInputParam("samplings", (int) 10); + + size_t seed = std::time(NULL); + mlpack::math::randGen.seed((uint32_t) seed); + srand((unsigned int) seed); + arma::arma_rng::set_seed(seed); + + mlpackMain(); + + GMM* gmm = CLI::GetParam("output_model"); + + ResetGmmTrainSetting(); + + SetInputParam("input", std::move(inputData)); + SetInputParam("gaussians", (int) 8); + SetInputParam("refined_start", true); + SetInputParam("trials", (int) 2); + SetInputParam("samplings", (int) 5000); + + mlpack::math::randGen.seed((uint32_t) seed); + srand((unsigned int) seed); + arma::arma_rng::set_seed(seed); + + mlpackMain(); + + GMM* gmm1 = CLI::GetParam("output_model"); + + arma::uvec sortedIndices = sort_index(gmm->Weights()); + + for (size_t k = 0; k < sortedIndices.n_elem; k++) + { + BOOST_REQUIRE(arma::norm(gmm->Component(sortedIndices[k]).Mean() - + gmm1->Component(sortedIndices[k]).Mean()) > 1e-50 || + arma::norm(gmm->Component(sortedIndices[k]).Covariance() - + gmm1->Component(sortedIndices[k]).Covariance()) > 1e-50); + } +} + +// Ensure that tolerance affects the final result. +BOOST_AUTO_TEST_CASE(GmmTrainToleranceTest) +{ + arma::mat inputData; + if (!data::Load("data_3d_mixed.txt", inputData)) + BOOST_FAIL("Unable to load train dataset data_3d_mixed.txt!"); + + SetInputParam("input", inputData); + SetInputParam("gaussians", (int) 2); + SetInputParam("tolerance", (double) 1e-8); + + size_t seed = std::time(NULL); + mlpack::math::randGen.seed((uint32_t) seed); + srand((unsigned int) seed); + arma::arma_rng::set_seed(seed); + + mlpackMain(); + + GMM* gmm = CLI::GetParam("output_model"); + + ResetGmmTrainSetting(); + + SetInputParam("input", std::move(inputData)); + SetInputParam("gaussians", (int) 2); + SetInputParam("tolerance", (double) 10); + + mlpack::math::randGen.seed((uint32_t) seed); + srand((unsigned int) seed); + arma::arma_rng::set_seed(seed); + + mlpackMain(); + + GMM* gmm1 = CLI::GetParam("output_model"); + + arma::uvec sortedIndices = sort_index(gmm->Weights()); + + for (size_t k = 0; k < sortedIndices.n_elem; k++) + { + BOOST_REQUIRE(arma::norm(gmm->Component(sortedIndices[k]).Mean() - + gmm1->Component(sortedIndices[k]).Mean()) > 1e-50 || + arma::norm(gmm->Component(sortedIndices[k]).Covariance() - + gmm1->Component(sortedIndices[k]).Covariance()) > 1e-50); + } +} + +// Ensure that saved model can be used again. +BOOST_AUTO_TEST_CASE(GmmTrainModelReuseTest) +{ + arma::mat inputData(5, 10, arma::fill::randu); + + SetInputParam("input", inputData); + SetInputParam("gaussians", (int) 2); + + mlpackMain(); + + GMM* gmm = CLI::GetParam("output_model"); + + SetInputParam("input_model", gmm); + + CLI::GetSingleton().Parameters()["input"].wasPassed = false; + + SetInputParam("input", inputData); + + mlpackMain(); + + GMM* gmm1 = CLI::GetParam("output_model"); + + SetInputParam("input_model", gmm1); + + CLI::GetSingleton().Parameters()["input"].wasPassed = false; + + SetInputParam("input", std::move(inputData)); + + mlpackMain(); + + GMM* gmm2 = CLI::GetParam("output_model"); + + BOOST_REQUIRE_EQUAL(gmm1, gmm2); +} + +// Ensure that Gmm's covariances are diagonal when diagonal_covariance is true. +BOOST_AUTO_TEST_CASE(GmmTrainDiagCovariance) +{ + arma::mat inputData(5, 10, arma::fill::randu); + + SetInputParam("input", std::move(inputData)); + SetInputParam("gaussians", (int) 2); + SetInputParam("diagonal_covariance", true); + + mlpackMain(); + + GMM* gmm = CLI::GetParam("output_model"); + + arma::uvec sortedIndices = sort_index(gmm->Weights()); + + for (size_t k = 0; k < sortedIndices.n_elem; k++) + { + arma::mat diagCov(gmm->Component(sortedIndices[k]).Covariance()); + for (size_t i = 0; i < diagCov.n_rows; i++) + for (size_t j = 0; j < diagCov.n_cols; j++) + if (i != j && diagCov(i, j) != (double) 0) + BOOST_FAIL("Covariance Are Not Diagonal"); + } +} + +BOOST_AUTO_TEST_SUITE_END();