From bb219c06db8cbfdb904cc23f9bd1661536545fd5 Mon Sep 17 00:00:00 2001 From: daivik Date: Sun, 11 Feb 2018 17:33:15 +0000 Subject: [PATCH 01/19] Add tests for mlpack_hmm_train CLI binding --- src/mlpack/methods/hmm/hmm.hpp | 2 + src/mlpack/methods/hmm/hmm_impl.hpp | 9 + src/mlpack/methods/hmm/hmm_model.hpp | 33 +++ src/mlpack/tests/CMakeLists.txt | 1 + .../tests/data/corrupt-observations-1.txt | 4 + .../tests/data/corrupt-observations-2.txt | 2 + src/mlpack/tests/data/hmm_train_lab.csv | 3 + src/mlpack/tests/data/hmm_train_obs.csv | 3 + src/mlpack/tests/data/lab1.csv | 4 + src/mlpack/tests/data/lab2.csv | 3 + src/mlpack/tests/data/lab3.csv | 4 + src/mlpack/tests/data/labels.txt | 3 + src/mlpack/tests/data/obs1.csv | 4 + src/mlpack/tests/data/obs2.csv | 3 + src/mlpack/tests/data/obs3.csv | 4 + src/mlpack/tests/data/observations.txt | 3 + .../tests/main_tests/hmm_training_tests.cpp | 207 ++++++++++++++++++ 17 files changed, 292 insertions(+) create mode 100644 src/mlpack/tests/data/corrupt-observations-1.txt create mode 100644 src/mlpack/tests/data/corrupt-observations-2.txt create mode 100644 src/mlpack/tests/data/hmm_train_lab.csv create mode 100644 src/mlpack/tests/data/hmm_train_obs.csv create mode 100644 src/mlpack/tests/data/lab1.csv create mode 100644 src/mlpack/tests/data/lab2.csv create mode 100644 src/mlpack/tests/data/lab3.csv create mode 100644 src/mlpack/tests/data/labels.txt create mode 100644 src/mlpack/tests/data/obs1.csv create mode 100644 src/mlpack/tests/data/obs2.csv create mode 100644 src/mlpack/tests/data/obs3.csv create mode 100644 src/mlpack/tests/data/observations.txt create mode 100644 src/mlpack/tests/main_tests/hmm_training_tests.cpp diff --git a/src/mlpack/methods/hmm/hmm.hpp b/src/mlpack/methods/hmm/hmm.hpp index 0e902896f0..b4189fb09a 100644 --- a/src/mlpack/methods/hmm/hmm.hpp +++ b/src/mlpack/methods/hmm/hmm.hpp @@ -328,6 +328,8 @@ class HMM template void serialize(Archive& ar, const unsigned int version); + bool ApproximatelyEqual(const HMM& other, double tolerance)const; + protected: // Helper functions. /** diff --git a/src/mlpack/methods/hmm/hmm_impl.hpp b/src/mlpack/methods/hmm/hmm_impl.hpp index 3d202990db..19132c94da 100644 --- a/src/mlpack/methods/hmm/hmm_impl.hpp +++ b/src/mlpack/methods/hmm/hmm_impl.hpp @@ -603,6 +603,15 @@ void HMM::serialize(Archive& ar, const unsigned int /* version */) ar & BOOST_SERIALIZATION_NVP(emission); } +template +bool HMM::ApproximatelyEqual(const HMM& other, double tolerance) const +{ + bool dimensionalityEqual = (dimensionality == other.dimensionality); + bool transitionEqual = approx_equal(transition, other.transition, "absdiff", tolerance); + bool initialEqual = approx_equal(initial, other.initial, "absdiff", tolerance); + return dimensionalityEqual && transitionEqual && initialEqual; +} + } // namespace hmm } // namespace mlpack diff --git a/src/mlpack/methods/hmm/hmm_model.hpp b/src/mlpack/methods/hmm/hmm_model.hpp index 17e66bd1f6..07a3cdcd8f 100644 --- a/src/mlpack/methods/hmm/hmm_model.hpp +++ b/src/mlpack/methods/hmm/hmm_model.hpp @@ -115,6 +115,39 @@ class HMMModel return *this; } + bool ApproximatelyEqual(const HMMModel& other, double tolerance) const + { + bool typeEqual = (type == other.type); + bool hmmEqual = false; + bool emissionEqual = true; + if (typeEqual) + { + if (type == HMMType::DiscreteHMM) + { + hmmEqual = discreteHMM->ApproximatelyEqual(*(other.discreteHMM), tolerance); // (*discreteHMM == *(other.discreteHMM)); + std::vector emission = discreteHMM->Emission(); + std::vector otherEmission = other.discreteHMM->Emission(); + if (emission.size() == otherEmission.size()) + { + for(size_t i=0; iApproximatelyEqual(*(other.gaussianHMM), tolerance); // (*gaussianHMM == *(other.gaussianHMM)); + if (type == HMMType::GaussianMixtureModelHMM) + hmmEqual = gmmHMM->ApproximatelyEqual(*(other.gmmHMM), tolerance); // (*gmmHMM == *(other.gmmHMM)); + } + return typeEqual && hmmEqual && emissionEqual; + } + //! Clean memory. ~HMMModel() { diff --git a/src/mlpack/tests/CMakeLists.txt b/src/mlpack/tests/CMakeLists.txt index a6ecd61d52..8af61fe6bd 100644 --- a/src/mlpack/tests/CMakeLists.txt +++ b/src/mlpack/tests/CMakeLists.txt @@ -138,6 +138,7 @@ add_executable(mlpack_test main_tests/random_forest_test.cpp main_tests/softmax_regression_test.cpp main_tests/sparse_coding_test.cpp + main_tests/hmm_training_tests.cpp ) # Link dependencies of test executable. diff --git a/src/mlpack/tests/data/corrupt-observations-1.txt b/src/mlpack/tests/data/corrupt-observations-1.txt new file mode 100644 index 0000000000..1963450398 --- /dev/null +++ b/src/mlpack/tests/data/corrupt-observations-1.txt @@ -0,0 +1,4 @@ +obs1.csv +obs2.csv +obs3.csv +obs4.csv diff --git a/src/mlpack/tests/data/corrupt-observations-2.txt b/src/mlpack/tests/data/corrupt-observations-2.txt new file mode 100644 index 0000000000..7724635b71 --- /dev/null +++ b/src/mlpack/tests/data/corrupt-observations-2.txt @@ -0,0 +1,2 @@ +obs1.csv +obs2.csv diff --git a/src/mlpack/tests/data/hmm_train_lab.csv b/src/mlpack/tests/data/hmm_train_lab.csv new file mode 100644 index 0000000000..bb5ee5c21e --- /dev/null +++ b/src/mlpack/tests/data/hmm_train_lab.csv @@ -0,0 +1,3 @@ +0 +0 +1 diff --git a/src/mlpack/tests/data/hmm_train_obs.csv b/src/mlpack/tests/data/hmm_train_obs.csv new file mode 100644 index 0000000000..4539bbf2d2 --- /dev/null +++ b/src/mlpack/tests/data/hmm_train_obs.csv @@ -0,0 +1,3 @@ +0 +1 +2 diff --git a/src/mlpack/tests/data/lab1.csv b/src/mlpack/tests/data/lab1.csv new file mode 100644 index 0000000000..0463db2671 --- /dev/null +++ b/src/mlpack/tests/data/lab1.csv @@ -0,0 +1,4 @@ +0 +0 +1 +1 diff --git a/src/mlpack/tests/data/lab2.csv b/src/mlpack/tests/data/lab2.csv new file mode 100644 index 0000000000..e8183f05f5 --- /dev/null +++ b/src/mlpack/tests/data/lab2.csv @@ -0,0 +1,3 @@ +1 +1 +1 diff --git a/src/mlpack/tests/data/lab3.csv b/src/mlpack/tests/data/lab3.csv new file mode 100644 index 0000000000..d9ff83f194 --- /dev/null +++ b/src/mlpack/tests/data/lab3.csv @@ -0,0 +1,4 @@ +1 +1 +0 +0 diff --git a/src/mlpack/tests/data/labels.txt b/src/mlpack/tests/data/labels.txt new file mode 100644 index 0000000000..5e154aeba3 --- /dev/null +++ b/src/mlpack/tests/data/labels.txt @@ -0,0 +1,3 @@ +lab1.csv +lab2.csv +lab3.csv diff --git a/src/mlpack/tests/data/obs1.csv b/src/mlpack/tests/data/obs1.csv new file mode 100644 index 0000000000..bc856dafab --- /dev/null +++ b/src/mlpack/tests/data/obs1.csv @@ -0,0 +1,4 @@ +0 +1 +2 +3 diff --git a/src/mlpack/tests/data/obs2.csv b/src/mlpack/tests/data/obs2.csv new file mode 100644 index 0000000000..565bab6081 --- /dev/null +++ b/src/mlpack/tests/data/obs2.csv @@ -0,0 +1,3 @@ +0 +2 +1 diff --git a/src/mlpack/tests/data/obs3.csv b/src/mlpack/tests/data/obs3.csv new file mode 100644 index 0000000000..4cc9dc3e9e --- /dev/null +++ b/src/mlpack/tests/data/obs3.csv @@ -0,0 +1,4 @@ +3 +2 +1 +0 diff --git a/src/mlpack/tests/data/observations.txt b/src/mlpack/tests/data/observations.txt new file mode 100644 index 0000000000..41e61d137e --- /dev/null +++ b/src/mlpack/tests/data/observations.txt @@ -0,0 +1,3 @@ +obs1.csv +obs2.csv +obs3.csv diff --git a/src/mlpack/tests/main_tests/hmm_training_tests.cpp b/src/mlpack/tests/main_tests/hmm_training_tests.cpp new file mode 100644 index 0000000000..62e6b89a59 --- /dev/null +++ b/src/mlpack/tests/main_tests/hmm_training_tests.cpp @@ -0,0 +1,207 @@ +/** + * @file hmm_training_tests.cpp + * @author Daivik Nema + * + * Test mlpackMain() of hmm_train_main.cpp. + */ +#include +#include + +#define BINDING_TYPE BINDING_TYPE_TEST +static const std::string testName = "HMMTrain"; + +#include +#include +#include "test_helper.hpp" +#include +#include + +#include +#include "../test_tools.hpp" + +#include +#include + +using namespace mlpack; + +struct HMMTrainMainTestFixture +{ + public: + HMMTrainMainTestFixture() + { + // Cache in the options for this program. + CLI::RestoreSettings(testName); + } + + ~HMMTrainMainTestFixture() + { + // Clear the settings. + bindings::tests::CleanMemory(); + CLI::ClearSettings(); + } +}; + +BOOST_FIXTURE_TEST_SUITE(HMMTrainMainTest, HMMTrainMainTestFixture); + +inline void fileExists(std::string fileName) +{ + ifstream ifp(fileName); + if(!ifp.good()) + BOOST_FAIL("Bad stream " + fileName); + ifp.close(); +} + +// Make sure that the number of states cannot be negative +BOOST_AUTO_TEST_CASE(HMMTrainStatesTest) +{ + std::string inputFileName = "hmm_train_obs.csv"; + int states = -3; // Invalid! + std::string hmmType = "discrete"; + + fileExists(inputFileName); + SetInputParam("input_file", std::move(inputFileName)); + SetInputParam("states", states); + SetInputParam("type", std::move(hmmType)); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +// Make sure that tolerance is non negative +BOOST_AUTO_TEST_CASE(HMMTrainToleranceNonNegative) +{ + std::string inputFileName = "hmm_train_obs.csv"; + int states = 3; + std::string hmmType = "gaussian"; + double tol = - 100; // Invalid + + fileExists(inputFileName); + SetInputParam("input_file", std::move(inputFileName)); + SetInputParam("states", states); + SetInputParam("type", std::move(hmmType)); + SetInputParam("tolerance", tol); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +// Make sure an error is thrown if type is something other than "discrete", "gaussian" or "gmm" +BOOST_AUTO_TEST_CASE(HMMTrainTypeTest) +{ + std::string inputFileName = "hmm_train_obs.csv"; + int states = 3; + std::string hmmType = "some-not-supported-possibly-non-type"; + + fileExists(inputFileName); + SetInputParam("input_file", std::move(inputFileName)); + SetInputParam("states", states); + SetInputParam("type", std::move(hmmType)); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +// Make sure that the number of gaussians cannot be less than 0 +BOOST_AUTO_TEST_CASE(HMMTrainGaussianTest) +{ + std::string inputFileName = "hmm_train_obs.csv"; + int states = 3; + std::string hmmType = "gmm"; + int gaussians = -2; + + fileExists(inputFileName); + SetInputParam("input_file", std::move(inputFileName)); + SetInputParam("states", states); + SetInputParam("type", std::move(hmmType)); + SetInputParam("gaussians", gaussians); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +// Make sure that model reuse is possible and work properly +BOOST_AUTO_TEST_CASE(HMMTrainReuseModelTest) +{ + std::string inputObsFileName = "hmm_train_obs.csv"; + std::string inputLabFileName = "hmm_train_lab.csv"; + std::string hmmType = "discrete"; + int states = 3; + + // First make sure that the size of the training seq, and training labels is same + arma::mat trainObs, trainLab; + data::Load(inputObsFileName, trainObs); + data::Load(inputLabFileName, trainLab); + BOOST_REQUIRE_EQUAL(trainObs.n_rows, trainLab.n_rows); + // Also require that all observation have same dimensions + // pass for now + + SetInputParam("input_file", std::move(inputObsFileName)); + SetInputParam("labels_file", std::move(inputLabFileName)); + SetInputParam("type", std::move(hmmType)); + SetInputParam("states", states); + + mlpackMain(); + + HMMModel * ph1 = CLI::GetParam("output_model"); + HMMModel h1 = *(CLI::GetParam("output_model")); + + SetInputParam("input_model", std::move(ph1)); + + CLI::GetSingleton().Parameters()["type"].wasPassed = false; + CLI::GetSingleton().Parameters()["states"].wasPassed = false; + + mlpackMain(); + + HMMModel h2 = *(CLI::GetParam("output_model")); + + BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1)); + BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-01)); + BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-02)); + BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-03)); + BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-04)); + BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-05)); +} + +// Test batch mode +BOOST_AUTO_TEST_CASE(HMMTrainBatchModeTest) +{ + std::string observationsFileName = "observations.txt"; + std::string labelsFileName = "labels.txt"; + std::string hmmType = "discrete"; + int states = 2; + + SetInputParam("input_file", std::move(observationsFileName)); + SetInputParam("labels_file", std::move(labelsFileName)); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; + + SetInputParam("states", states); + SetInputParam("type", std::move(hmmType)); + SetInputParam("batch", (bool)true); + + mlpackMain(); + + // Now pass an observations file with extra non-existent filenames + observationsFileName = "corrupt-observations-1.txt"; + SetInputParam("input_file", std::move(observationsFileName)); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; + + // Now a mismatch etween #observation files and #label files + observationsFileName = "corrupt-observations-2.txt"; + SetInputParam("input_file", std::move(observationsFileName)); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +BOOST_AUTO_TEST_SUITE_END(); From b0d9ccf4fb27e78b32ab91288f982206bddbd96d Mon Sep 17 00:00:00 2001 From: daivik Date: Sun, 11 Feb 2018 18:22:07 +0000 Subject: [PATCH 02/19] Fix Style issues --- src/mlpack/methods/hmm/hmm.hpp | 3 ++- src/mlpack/methods/hmm/hmm_impl.hpp | 9 ++++--- src/mlpack/methods/hmm/hmm_model.hpp | 27 ++++++++++++------- .../tests/main_tests/hmm_training_tests.cpp | 16 ++++++----- 4 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/mlpack/methods/hmm/hmm.hpp b/src/mlpack/methods/hmm/hmm.hpp index b4189fb09a..d1e395c0b8 100644 --- a/src/mlpack/methods/hmm/hmm.hpp +++ b/src/mlpack/methods/hmm/hmm.hpp @@ -328,7 +328,8 @@ class HMM template void serialize(Archive& ar, const unsigned int version); - bool ApproximatelyEqual(const HMM& other, double tolerance)const; + bool ApproximatelyEqual(const HMM& other, + double tolerance) const; protected: // Helper functions. diff --git a/src/mlpack/methods/hmm/hmm_impl.hpp b/src/mlpack/methods/hmm/hmm_impl.hpp index 19132c94da..96912f76df 100644 --- a/src/mlpack/methods/hmm/hmm_impl.hpp +++ b/src/mlpack/methods/hmm/hmm_impl.hpp @@ -604,11 +604,14 @@ void HMM::serialize(Archive& ar, const unsigned int /* version */) } template -bool HMM::ApproximatelyEqual(const HMM& other, double tolerance) const +bool HMM::ApproximatelyEqual(const HMM& other, + double tolerance) const { bool dimensionalityEqual = (dimensionality == other.dimensionality); - bool transitionEqual = approx_equal(transition, other.transition, "absdiff", tolerance); - bool initialEqual = approx_equal(initial, other.initial, "absdiff", tolerance); + bool transitionEqual = approx_equal(transition, other.transition, + "absdiff", tolerance); + bool initialEqual = approx_equal(initial, other.initial, + "absdiff", tolerance); return dimensionalityEqual && transitionEqual && initialEqual; } diff --git a/src/mlpack/methods/hmm/hmm_model.hpp b/src/mlpack/methods/hmm/hmm_model.hpp index 07a3cdcd8f..f2f408462c 100644 --- a/src/mlpack/methods/hmm/hmm_model.hpp +++ b/src/mlpack/methods/hmm/hmm_model.hpp @@ -124,26 +124,35 @@ class HMMModel { if (type == HMMType::DiscreteHMM) { - hmmEqual = discreteHMM->ApproximatelyEqual(*(other.discreteHMM), tolerance); // (*discreteHMM == *(other.discreteHMM)); - std::vector emission = discreteHMM->Emission(); - std::vector otherEmission = other.discreteHMM->Emission(); + hmmEqual = discreteHMM->ApproximatelyEqual(*(other.discreteHMM), + tolerance); + std::vector emission = + discreteHMM->Emission(); + std::vector otherEmission = + other.discreteHMM->Emission(); if (emission.size() == otherEmission.size()) { - for(size_t i=0; iApproximatelyEqual(*(other.gaussianHMM), tolerance); // (*gaussianHMM == *(other.gaussianHMM)); + hmmEqual = gaussianHMM->ApproximatelyEqual(*(other.gaussianHMM), + tolerance); if (type == HMMType::GaussianMixtureModelHMM) - hmmEqual = gmmHMM->ApproximatelyEqual(*(other.gmmHMM), tolerance); // (*gmmHMM == *(other.gmmHMM)); + hmmEqual = gmmHMM->ApproximatelyEqual(*(other.gmmHMM), tolerance); } return typeEqual && hmmEqual && emissionEqual; } diff --git a/src/mlpack/tests/main_tests/hmm_training_tests.cpp b/src/mlpack/tests/main_tests/hmm_training_tests.cpp index 62e6b89a59..e79aa07570 100644 --- a/src/mlpack/tests/main_tests/hmm_training_tests.cpp +++ b/src/mlpack/tests/main_tests/hmm_training_tests.cpp @@ -46,7 +46,7 @@ BOOST_FIXTURE_TEST_SUITE(HMMTrainMainTest, HMMTrainMainTestFixture); inline void fileExists(std::string fileName) { ifstream ifp(fileName); - if(!ifp.good()) + if (!ifp.good()) BOOST_FAIL("Bad stream " + fileName); ifp.close(); } @@ -87,7 +87,8 @@ BOOST_AUTO_TEST_CASE(HMMTrainToleranceNonNegative) Log::Fatal.ignoreInput = false; } -// Make sure an error is thrown if type is something other than "discrete", "gaussian" or "gmm" +// Make sure an error is thrown if type is something other than +// "discrete", "gaussian" or "gmm" BOOST_AUTO_TEST_CASE(HMMTrainTypeTest) { std::string inputFileName = "hmm_train_obs.csv"; @@ -131,13 +132,14 @@ BOOST_AUTO_TEST_CASE(HMMTrainReuseModelTest) std::string hmmType = "discrete"; int states = 3; - // First make sure that the size of the training seq, and training labels is same + fileExists(inputObsFileName); + fileExists(inputLabFileName); + // Make sure that the size of the + // training seq, and training labels is same arma::mat trainObs, trainLab; data::Load(inputObsFileName, trainObs); data::Load(inputLabFileName, trainLab); BOOST_REQUIRE_EQUAL(trainObs.n_rows, trainLab.n_rows); - // Also require that all observation have same dimensions - // pass for now SetInputParam("input_file", std::move(inputObsFileName)); SetInputParam("labels_file", std::move(inputLabFileName)); @@ -183,7 +185,7 @@ BOOST_AUTO_TEST_CASE(HMMTrainBatchModeTest) SetInputParam("states", states); SetInputParam("type", std::move(hmmType)); - SetInputParam("batch", (bool)true); + SetInputParam("batch", (bool) true); mlpackMain(); @@ -195,7 +197,7 @@ BOOST_AUTO_TEST_CASE(HMMTrainBatchModeTest) BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); Log::Fatal.ignoreInput = false; - // Now a mismatch etween #observation files and #label files + // Now a mismatch between #observation files and #label files observationsFileName = "corrupt-observations-2.txt"; SetInputParam("input_file", std::move(observationsFileName)); From ac74fcd678899cfc1fb57636b4b0f26fc7797a9e Mon Sep 17 00:00:00 2001 From: daivik Date: Sun, 11 Feb 2018 19:03:23 +0000 Subject: [PATCH 03/19] Add check for tolerance >= 0 in hmm_train_main.cpp --- src/mlpack/methods/hmm/hmm_train_main.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mlpack/methods/hmm/hmm_train_main.cpp b/src/mlpack/methods/hmm/hmm_train_main.cpp index 06cf01db0e..9357e8e01c 100644 --- a/src/mlpack/methods/hmm/hmm_train_main.cpp +++ b/src/mlpack/methods/hmm/hmm_train_main.cpp @@ -379,6 +379,12 @@ static void mlpackMain() "unknown HMM type"); } + if (CLI::HasParam("tolerance")) + { + RequireParamValue("tolerance", [](int x) { return x >= 0; }, true, + "tolerance must be non-negative"); + } + // Load the input data. vector trainSeq; if (batch) From 01791d0e557f10b8102d75a6190eec0b906c6f2b Mon Sep 17 00:00:00 2001 From: daivik Date: Sun, 11 Feb 2018 17:33:15 +0000 Subject: [PATCH 04/19] Add tests for mlpack_hmm_train CLI binding --- src/mlpack/methods/hmm/hmm.hpp | 2 + src/mlpack/methods/hmm/hmm_impl.hpp | 9 + src/mlpack/methods/hmm/hmm_model.hpp | 33 +++ src/mlpack/tests/CMakeLists.txt | 1 + .../tests/data/corrupt-observations-1.txt | 4 + .../tests/data/corrupt-observations-2.txt | 2 + src/mlpack/tests/data/hmm_train_lab.csv | 3 + src/mlpack/tests/data/hmm_train_obs.csv | 3 + src/mlpack/tests/data/lab1.csv | 4 + src/mlpack/tests/data/lab2.csv | 3 + src/mlpack/tests/data/lab3.csv | 4 + src/mlpack/tests/data/labels.txt | 3 + src/mlpack/tests/data/obs1.csv | 4 + src/mlpack/tests/data/obs2.csv | 3 + src/mlpack/tests/data/obs3.csv | 4 + src/mlpack/tests/data/observations.txt | 3 + .../tests/main_tests/hmm_training_tests.cpp | 207 ++++++++++++++++++ 17 files changed, 292 insertions(+) create mode 100644 src/mlpack/tests/data/corrupt-observations-1.txt create mode 100644 src/mlpack/tests/data/corrupt-observations-2.txt create mode 100644 src/mlpack/tests/data/hmm_train_lab.csv create mode 100644 src/mlpack/tests/data/hmm_train_obs.csv create mode 100644 src/mlpack/tests/data/lab1.csv create mode 100644 src/mlpack/tests/data/lab2.csv create mode 100644 src/mlpack/tests/data/lab3.csv create mode 100644 src/mlpack/tests/data/labels.txt create mode 100644 src/mlpack/tests/data/obs1.csv create mode 100644 src/mlpack/tests/data/obs2.csv create mode 100644 src/mlpack/tests/data/obs3.csv create mode 100644 src/mlpack/tests/data/observations.txt create mode 100644 src/mlpack/tests/main_tests/hmm_training_tests.cpp diff --git a/src/mlpack/methods/hmm/hmm.hpp b/src/mlpack/methods/hmm/hmm.hpp index 0e902896f0..b4189fb09a 100644 --- a/src/mlpack/methods/hmm/hmm.hpp +++ b/src/mlpack/methods/hmm/hmm.hpp @@ -328,6 +328,8 @@ class HMM template void serialize(Archive& ar, const unsigned int version); + bool ApproximatelyEqual(const HMM& other, double tolerance)const; + protected: // Helper functions. /** diff --git a/src/mlpack/methods/hmm/hmm_impl.hpp b/src/mlpack/methods/hmm/hmm_impl.hpp index 3d202990db..19132c94da 100644 --- a/src/mlpack/methods/hmm/hmm_impl.hpp +++ b/src/mlpack/methods/hmm/hmm_impl.hpp @@ -603,6 +603,15 @@ void HMM::serialize(Archive& ar, const unsigned int /* version */) ar & BOOST_SERIALIZATION_NVP(emission); } +template +bool HMM::ApproximatelyEqual(const HMM& other, double tolerance) const +{ + bool dimensionalityEqual = (dimensionality == other.dimensionality); + bool transitionEqual = approx_equal(transition, other.transition, "absdiff", tolerance); + bool initialEqual = approx_equal(initial, other.initial, "absdiff", tolerance); + return dimensionalityEqual && transitionEqual && initialEqual; +} + } // namespace hmm } // namespace mlpack diff --git a/src/mlpack/methods/hmm/hmm_model.hpp b/src/mlpack/methods/hmm/hmm_model.hpp index 17e66bd1f6..07a3cdcd8f 100644 --- a/src/mlpack/methods/hmm/hmm_model.hpp +++ b/src/mlpack/methods/hmm/hmm_model.hpp @@ -115,6 +115,39 @@ class HMMModel return *this; } + bool ApproximatelyEqual(const HMMModel& other, double tolerance) const + { + bool typeEqual = (type == other.type); + bool hmmEqual = false; + bool emissionEqual = true; + if (typeEqual) + { + if (type == HMMType::DiscreteHMM) + { + hmmEqual = discreteHMM->ApproximatelyEqual(*(other.discreteHMM), tolerance); // (*discreteHMM == *(other.discreteHMM)); + std::vector emission = discreteHMM->Emission(); + std::vector otherEmission = other.discreteHMM->Emission(); + if (emission.size() == otherEmission.size()) + { + for(size_t i=0; iApproximatelyEqual(*(other.gaussianHMM), tolerance); // (*gaussianHMM == *(other.gaussianHMM)); + if (type == HMMType::GaussianMixtureModelHMM) + hmmEqual = gmmHMM->ApproximatelyEqual(*(other.gmmHMM), tolerance); // (*gmmHMM == *(other.gmmHMM)); + } + return typeEqual && hmmEqual && emissionEqual; + } + //! Clean memory. ~HMMModel() { diff --git a/src/mlpack/tests/CMakeLists.txt b/src/mlpack/tests/CMakeLists.txt index 69910edfa8..86251f6e69 100644 --- a/src/mlpack/tests/CMakeLists.txt +++ b/src/mlpack/tests/CMakeLists.txt @@ -144,6 +144,7 @@ add_executable(mlpack_test main_tests/softmax_regression_test.cpp main_tests/sparse_coding_test.cpp main_tests/hoeffding_tree_test.cpp + main_tests/hmm_training_tests.cpp ) # Link dependencies of test executable. diff --git a/src/mlpack/tests/data/corrupt-observations-1.txt b/src/mlpack/tests/data/corrupt-observations-1.txt new file mode 100644 index 0000000000..1963450398 --- /dev/null +++ b/src/mlpack/tests/data/corrupt-observations-1.txt @@ -0,0 +1,4 @@ +obs1.csv +obs2.csv +obs3.csv +obs4.csv diff --git a/src/mlpack/tests/data/corrupt-observations-2.txt b/src/mlpack/tests/data/corrupt-observations-2.txt new file mode 100644 index 0000000000..7724635b71 --- /dev/null +++ b/src/mlpack/tests/data/corrupt-observations-2.txt @@ -0,0 +1,2 @@ +obs1.csv +obs2.csv diff --git a/src/mlpack/tests/data/hmm_train_lab.csv b/src/mlpack/tests/data/hmm_train_lab.csv new file mode 100644 index 0000000000..bb5ee5c21e --- /dev/null +++ b/src/mlpack/tests/data/hmm_train_lab.csv @@ -0,0 +1,3 @@ +0 +0 +1 diff --git a/src/mlpack/tests/data/hmm_train_obs.csv b/src/mlpack/tests/data/hmm_train_obs.csv new file mode 100644 index 0000000000..4539bbf2d2 --- /dev/null +++ b/src/mlpack/tests/data/hmm_train_obs.csv @@ -0,0 +1,3 @@ +0 +1 +2 diff --git a/src/mlpack/tests/data/lab1.csv b/src/mlpack/tests/data/lab1.csv new file mode 100644 index 0000000000..0463db2671 --- /dev/null +++ b/src/mlpack/tests/data/lab1.csv @@ -0,0 +1,4 @@ +0 +0 +1 +1 diff --git a/src/mlpack/tests/data/lab2.csv b/src/mlpack/tests/data/lab2.csv new file mode 100644 index 0000000000..e8183f05f5 --- /dev/null +++ b/src/mlpack/tests/data/lab2.csv @@ -0,0 +1,3 @@ +1 +1 +1 diff --git a/src/mlpack/tests/data/lab3.csv b/src/mlpack/tests/data/lab3.csv new file mode 100644 index 0000000000..d9ff83f194 --- /dev/null +++ b/src/mlpack/tests/data/lab3.csv @@ -0,0 +1,4 @@ +1 +1 +0 +0 diff --git a/src/mlpack/tests/data/labels.txt b/src/mlpack/tests/data/labels.txt new file mode 100644 index 0000000000..5e154aeba3 --- /dev/null +++ b/src/mlpack/tests/data/labels.txt @@ -0,0 +1,3 @@ +lab1.csv +lab2.csv +lab3.csv diff --git a/src/mlpack/tests/data/obs1.csv b/src/mlpack/tests/data/obs1.csv new file mode 100644 index 0000000000..bc856dafab --- /dev/null +++ b/src/mlpack/tests/data/obs1.csv @@ -0,0 +1,4 @@ +0 +1 +2 +3 diff --git a/src/mlpack/tests/data/obs2.csv b/src/mlpack/tests/data/obs2.csv new file mode 100644 index 0000000000..565bab6081 --- /dev/null +++ b/src/mlpack/tests/data/obs2.csv @@ -0,0 +1,3 @@ +0 +2 +1 diff --git a/src/mlpack/tests/data/obs3.csv b/src/mlpack/tests/data/obs3.csv new file mode 100644 index 0000000000..4cc9dc3e9e --- /dev/null +++ b/src/mlpack/tests/data/obs3.csv @@ -0,0 +1,4 @@ +3 +2 +1 +0 diff --git a/src/mlpack/tests/data/observations.txt b/src/mlpack/tests/data/observations.txt new file mode 100644 index 0000000000..41e61d137e --- /dev/null +++ b/src/mlpack/tests/data/observations.txt @@ -0,0 +1,3 @@ +obs1.csv +obs2.csv +obs3.csv diff --git a/src/mlpack/tests/main_tests/hmm_training_tests.cpp b/src/mlpack/tests/main_tests/hmm_training_tests.cpp new file mode 100644 index 0000000000..62e6b89a59 --- /dev/null +++ b/src/mlpack/tests/main_tests/hmm_training_tests.cpp @@ -0,0 +1,207 @@ +/** + * @file hmm_training_tests.cpp + * @author Daivik Nema + * + * Test mlpackMain() of hmm_train_main.cpp. + */ +#include +#include + +#define BINDING_TYPE BINDING_TYPE_TEST +static const std::string testName = "HMMTrain"; + +#include +#include +#include "test_helper.hpp" +#include +#include + +#include +#include "../test_tools.hpp" + +#include +#include + +using namespace mlpack; + +struct HMMTrainMainTestFixture +{ + public: + HMMTrainMainTestFixture() + { + // Cache in the options for this program. + CLI::RestoreSettings(testName); + } + + ~HMMTrainMainTestFixture() + { + // Clear the settings. + bindings::tests::CleanMemory(); + CLI::ClearSettings(); + } +}; + +BOOST_FIXTURE_TEST_SUITE(HMMTrainMainTest, HMMTrainMainTestFixture); + +inline void fileExists(std::string fileName) +{ + ifstream ifp(fileName); + if(!ifp.good()) + BOOST_FAIL("Bad stream " + fileName); + ifp.close(); +} + +// Make sure that the number of states cannot be negative +BOOST_AUTO_TEST_CASE(HMMTrainStatesTest) +{ + std::string inputFileName = "hmm_train_obs.csv"; + int states = -3; // Invalid! + std::string hmmType = "discrete"; + + fileExists(inputFileName); + SetInputParam("input_file", std::move(inputFileName)); + SetInputParam("states", states); + SetInputParam("type", std::move(hmmType)); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +// Make sure that tolerance is non negative +BOOST_AUTO_TEST_CASE(HMMTrainToleranceNonNegative) +{ + std::string inputFileName = "hmm_train_obs.csv"; + int states = 3; + std::string hmmType = "gaussian"; + double tol = - 100; // Invalid + + fileExists(inputFileName); + SetInputParam("input_file", std::move(inputFileName)); + SetInputParam("states", states); + SetInputParam("type", std::move(hmmType)); + SetInputParam("tolerance", tol); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +// Make sure an error is thrown if type is something other than "discrete", "gaussian" or "gmm" +BOOST_AUTO_TEST_CASE(HMMTrainTypeTest) +{ + std::string inputFileName = "hmm_train_obs.csv"; + int states = 3; + std::string hmmType = "some-not-supported-possibly-non-type"; + + fileExists(inputFileName); + SetInputParam("input_file", std::move(inputFileName)); + SetInputParam("states", states); + SetInputParam("type", std::move(hmmType)); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +// Make sure that the number of gaussians cannot be less than 0 +BOOST_AUTO_TEST_CASE(HMMTrainGaussianTest) +{ + std::string inputFileName = "hmm_train_obs.csv"; + int states = 3; + std::string hmmType = "gmm"; + int gaussians = -2; + + fileExists(inputFileName); + SetInputParam("input_file", std::move(inputFileName)); + SetInputParam("states", states); + SetInputParam("type", std::move(hmmType)); + SetInputParam("gaussians", gaussians); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +// Make sure that model reuse is possible and work properly +BOOST_AUTO_TEST_CASE(HMMTrainReuseModelTest) +{ + std::string inputObsFileName = "hmm_train_obs.csv"; + std::string inputLabFileName = "hmm_train_lab.csv"; + std::string hmmType = "discrete"; + int states = 3; + + // First make sure that the size of the training seq, and training labels is same + arma::mat trainObs, trainLab; + data::Load(inputObsFileName, trainObs); + data::Load(inputLabFileName, trainLab); + BOOST_REQUIRE_EQUAL(trainObs.n_rows, trainLab.n_rows); + // Also require that all observation have same dimensions + // pass for now + + SetInputParam("input_file", std::move(inputObsFileName)); + SetInputParam("labels_file", std::move(inputLabFileName)); + SetInputParam("type", std::move(hmmType)); + SetInputParam("states", states); + + mlpackMain(); + + HMMModel * ph1 = CLI::GetParam("output_model"); + HMMModel h1 = *(CLI::GetParam("output_model")); + + SetInputParam("input_model", std::move(ph1)); + + CLI::GetSingleton().Parameters()["type"].wasPassed = false; + CLI::GetSingleton().Parameters()["states"].wasPassed = false; + + mlpackMain(); + + HMMModel h2 = *(CLI::GetParam("output_model")); + + BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1)); + BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-01)); + BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-02)); + BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-03)); + BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-04)); + BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-05)); +} + +// Test batch mode +BOOST_AUTO_TEST_CASE(HMMTrainBatchModeTest) +{ + std::string observationsFileName = "observations.txt"; + std::string labelsFileName = "labels.txt"; + std::string hmmType = "discrete"; + int states = 2; + + SetInputParam("input_file", std::move(observationsFileName)); + SetInputParam("labels_file", std::move(labelsFileName)); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; + + SetInputParam("states", states); + SetInputParam("type", std::move(hmmType)); + SetInputParam("batch", (bool)true); + + mlpackMain(); + + // Now pass an observations file with extra non-existent filenames + observationsFileName = "corrupt-observations-1.txt"; + SetInputParam("input_file", std::move(observationsFileName)); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; + + // Now a mismatch etween #observation files and #label files + observationsFileName = "corrupt-observations-2.txt"; + SetInputParam("input_file", std::move(observationsFileName)); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +BOOST_AUTO_TEST_SUITE_END(); From 94f4a7ce04ef8964e9ba3f85c9d200074d0c745f Mon Sep 17 00:00:00 2001 From: daivik Date: Sun, 11 Feb 2018 18:22:07 +0000 Subject: [PATCH 05/19] Fix Style issues --- src/mlpack/methods/hmm/hmm.hpp | 3 ++- src/mlpack/methods/hmm/hmm_impl.hpp | 9 ++++--- src/mlpack/methods/hmm/hmm_model.hpp | 27 ++++++++++++------- .../tests/main_tests/hmm_training_tests.cpp | 16 ++++++----- 4 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/mlpack/methods/hmm/hmm.hpp b/src/mlpack/methods/hmm/hmm.hpp index b4189fb09a..d1e395c0b8 100644 --- a/src/mlpack/methods/hmm/hmm.hpp +++ b/src/mlpack/methods/hmm/hmm.hpp @@ -328,7 +328,8 @@ class HMM template void serialize(Archive& ar, const unsigned int version); - bool ApproximatelyEqual(const HMM& other, double tolerance)const; + bool ApproximatelyEqual(const HMM& other, + double tolerance) const; protected: // Helper functions. diff --git a/src/mlpack/methods/hmm/hmm_impl.hpp b/src/mlpack/methods/hmm/hmm_impl.hpp index 19132c94da..96912f76df 100644 --- a/src/mlpack/methods/hmm/hmm_impl.hpp +++ b/src/mlpack/methods/hmm/hmm_impl.hpp @@ -604,11 +604,14 @@ void HMM::serialize(Archive& ar, const unsigned int /* version */) } template -bool HMM::ApproximatelyEqual(const HMM& other, double tolerance) const +bool HMM::ApproximatelyEqual(const HMM& other, + double tolerance) const { bool dimensionalityEqual = (dimensionality == other.dimensionality); - bool transitionEqual = approx_equal(transition, other.transition, "absdiff", tolerance); - bool initialEqual = approx_equal(initial, other.initial, "absdiff", tolerance); + bool transitionEqual = approx_equal(transition, other.transition, + "absdiff", tolerance); + bool initialEqual = approx_equal(initial, other.initial, + "absdiff", tolerance); return dimensionalityEqual && transitionEqual && initialEqual; } diff --git a/src/mlpack/methods/hmm/hmm_model.hpp b/src/mlpack/methods/hmm/hmm_model.hpp index 07a3cdcd8f..f2f408462c 100644 --- a/src/mlpack/methods/hmm/hmm_model.hpp +++ b/src/mlpack/methods/hmm/hmm_model.hpp @@ -124,26 +124,35 @@ class HMMModel { if (type == HMMType::DiscreteHMM) { - hmmEqual = discreteHMM->ApproximatelyEqual(*(other.discreteHMM), tolerance); // (*discreteHMM == *(other.discreteHMM)); - std::vector emission = discreteHMM->Emission(); - std::vector otherEmission = other.discreteHMM->Emission(); + hmmEqual = discreteHMM->ApproximatelyEqual(*(other.discreteHMM), + tolerance); + std::vector emission = + discreteHMM->Emission(); + std::vector otherEmission = + other.discreteHMM->Emission(); if (emission.size() == otherEmission.size()) { - for(size_t i=0; iApproximatelyEqual(*(other.gaussianHMM), tolerance); // (*gaussianHMM == *(other.gaussianHMM)); + hmmEqual = gaussianHMM->ApproximatelyEqual(*(other.gaussianHMM), + tolerance); if (type == HMMType::GaussianMixtureModelHMM) - hmmEqual = gmmHMM->ApproximatelyEqual(*(other.gmmHMM), tolerance); // (*gmmHMM == *(other.gmmHMM)); + hmmEqual = gmmHMM->ApproximatelyEqual(*(other.gmmHMM), tolerance); } return typeEqual && hmmEqual && emissionEqual; } diff --git a/src/mlpack/tests/main_tests/hmm_training_tests.cpp b/src/mlpack/tests/main_tests/hmm_training_tests.cpp index 62e6b89a59..e79aa07570 100644 --- a/src/mlpack/tests/main_tests/hmm_training_tests.cpp +++ b/src/mlpack/tests/main_tests/hmm_training_tests.cpp @@ -46,7 +46,7 @@ BOOST_FIXTURE_TEST_SUITE(HMMTrainMainTest, HMMTrainMainTestFixture); inline void fileExists(std::string fileName) { ifstream ifp(fileName); - if(!ifp.good()) + if (!ifp.good()) BOOST_FAIL("Bad stream " + fileName); ifp.close(); } @@ -87,7 +87,8 @@ BOOST_AUTO_TEST_CASE(HMMTrainToleranceNonNegative) Log::Fatal.ignoreInput = false; } -// Make sure an error is thrown if type is something other than "discrete", "gaussian" or "gmm" +// Make sure an error is thrown if type is something other than +// "discrete", "gaussian" or "gmm" BOOST_AUTO_TEST_CASE(HMMTrainTypeTest) { std::string inputFileName = "hmm_train_obs.csv"; @@ -131,13 +132,14 @@ BOOST_AUTO_TEST_CASE(HMMTrainReuseModelTest) std::string hmmType = "discrete"; int states = 3; - // First make sure that the size of the training seq, and training labels is same + fileExists(inputObsFileName); + fileExists(inputLabFileName); + // Make sure that the size of the + // training seq, and training labels is same arma::mat trainObs, trainLab; data::Load(inputObsFileName, trainObs); data::Load(inputLabFileName, trainLab); BOOST_REQUIRE_EQUAL(trainObs.n_rows, trainLab.n_rows); - // Also require that all observation have same dimensions - // pass for now SetInputParam("input_file", std::move(inputObsFileName)); SetInputParam("labels_file", std::move(inputLabFileName)); @@ -183,7 +185,7 @@ BOOST_AUTO_TEST_CASE(HMMTrainBatchModeTest) SetInputParam("states", states); SetInputParam("type", std::move(hmmType)); - SetInputParam("batch", (bool)true); + SetInputParam("batch", (bool) true); mlpackMain(); @@ -195,7 +197,7 @@ BOOST_AUTO_TEST_CASE(HMMTrainBatchModeTest) BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); Log::Fatal.ignoreInput = false; - // Now a mismatch etween #observation files and #label files + // Now a mismatch between #observation files and #label files observationsFileName = "corrupt-observations-2.txt"; SetInputParam("input_file", std::move(observationsFileName)); From ece3e0dcce5f3099dd0b1ed5ae15f49924191fd8 Mon Sep 17 00:00:00 2001 From: daivik Date: Sun, 11 Feb 2018 19:03:23 +0000 Subject: [PATCH 06/19] Add check for tolerance >= 0 in hmm_train_main.cpp --- src/mlpack/methods/hmm/hmm_train_main.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/mlpack/methods/hmm/hmm_train_main.cpp b/src/mlpack/methods/hmm/hmm_train_main.cpp index 06cf01db0e..9357e8e01c 100644 --- a/src/mlpack/methods/hmm/hmm_train_main.cpp +++ b/src/mlpack/methods/hmm/hmm_train_main.cpp @@ -379,6 +379,12 @@ static void mlpackMain() "unknown HMM type"); } + if (CLI::HasParam("tolerance")) + { + RequireParamValue("tolerance", [](int x) { return x >= 0; }, true, + "tolerance must be non-negative"); + } + // Load the input data. vector trainSeq; if (batch) From cbe14aa8f7d8bc36ca44d02db68e1ade75a245be Mon Sep 17 00:00:00 2001 From: daivik Date: Tue, 13 Feb 2018 12:17:00 +0000 Subject: [PATCH 07/19] Minor changes --- src/mlpack/methods/hmm/hmm_train_main.cpp | 7 ++----- src/mlpack/tests/CMakeLists.txt | 1 + .../{hmm_training_tests.cpp => hmm_train_test.cpp} | 14 +++++++------- 3 files changed, 10 insertions(+), 12 deletions(-) rename src/mlpack/tests/main_tests/{hmm_training_tests.cpp => hmm_train_test.cpp} (96%) diff --git a/src/mlpack/methods/hmm/hmm_train_main.cpp b/src/mlpack/methods/hmm/hmm_train_main.cpp index 9357e8e01c..67eca0af21 100644 --- a/src/mlpack/methods/hmm/hmm_train_main.cpp +++ b/src/mlpack/methods/hmm/hmm_train_main.cpp @@ -379,11 +379,8 @@ static void mlpackMain() "unknown HMM type"); } - if (CLI::HasParam("tolerance")) - { - RequireParamValue("tolerance", [](int x) { return x >= 0; }, true, - "tolerance must be non-negative"); - } + RequireParamValue("tolerance", [](double x) { return x >= 0; }, true, + "tolerance must be non-negative"); // Load the input data. vector trainSeq; diff --git a/src/mlpack/tests/CMakeLists.txt b/src/mlpack/tests/CMakeLists.txt index 86251f6e69..597330d7c3 100644 --- a/src/mlpack/tests/CMakeLists.txt +++ b/src/mlpack/tests/CMakeLists.txt @@ -145,6 +145,7 @@ add_executable(mlpack_test main_tests/sparse_coding_test.cpp main_tests/hoeffding_tree_test.cpp main_tests/hmm_training_tests.cpp + main_tests/hmm_train_test.cpp ) # Link dependencies of test executable. diff --git a/src/mlpack/tests/main_tests/hmm_training_tests.cpp b/src/mlpack/tests/main_tests/hmm_train_test.cpp similarity index 96% rename from src/mlpack/tests/main_tests/hmm_training_tests.cpp rename to src/mlpack/tests/main_tests/hmm_train_test.cpp index e79aa07570..308234e8e5 100644 --- a/src/mlpack/tests/main_tests/hmm_training_tests.cpp +++ b/src/mlpack/tests/main_tests/hmm_train_test.cpp @@ -43,7 +43,7 @@ struct HMMTrainMainTestFixture BOOST_FIXTURE_TEST_SUITE(HMMTrainMainTest, HMMTrainMainTestFixture); -inline void fileExists(std::string fileName) +inline void FileExists(std::string fileName) { ifstream ifp(fileName); if (!ifp.good()) @@ -58,7 +58,7 @@ BOOST_AUTO_TEST_CASE(HMMTrainStatesTest) int states = -3; // Invalid! std::string hmmType = "discrete"; - fileExists(inputFileName); + FileExists(inputFileName); SetInputParam("input_file", std::move(inputFileName)); SetInputParam("states", states); SetInputParam("type", std::move(hmmType)); @@ -76,7 +76,7 @@ BOOST_AUTO_TEST_CASE(HMMTrainToleranceNonNegative) std::string hmmType = "gaussian"; double tol = - 100; // Invalid - fileExists(inputFileName); + FileExists(inputFileName); SetInputParam("input_file", std::move(inputFileName)); SetInputParam("states", states); SetInputParam("type", std::move(hmmType)); @@ -95,7 +95,7 @@ BOOST_AUTO_TEST_CASE(HMMTrainTypeTest) int states = 3; std::string hmmType = "some-not-supported-possibly-non-type"; - fileExists(inputFileName); + FileExists(inputFileName); SetInputParam("input_file", std::move(inputFileName)); SetInputParam("states", states); SetInputParam("type", std::move(hmmType)); @@ -113,7 +113,7 @@ BOOST_AUTO_TEST_CASE(HMMTrainGaussianTest) std::string hmmType = "gmm"; int gaussians = -2; - fileExists(inputFileName); + FileExists(inputFileName); SetInputParam("input_file", std::move(inputFileName)); SetInputParam("states", states); SetInputParam("type", std::move(hmmType)); @@ -132,8 +132,8 @@ BOOST_AUTO_TEST_CASE(HMMTrainReuseModelTest) std::string hmmType = "discrete"; int states = 3; - fileExists(inputObsFileName); - fileExists(inputLabFileName); + FileExists(inputObsFileName); + FileExists(inputLabFileName); // Make sure that the size of the // training seq, and training labels is same arma::mat trainObs, trainLab; From 14b5788832c657be6423739bb082948a6f43107b Mon Sep 17 00:00:00 2001 From: daivik Date: Wed, 14 Feb 2018 10:15:24 +0000 Subject: [PATCH 08/19] Add retraining tests, refactor ApproximatelyEqual --- src/mlpack/methods/hmm/hmm_impl.hpp | 12 - src/mlpack/methods/hmm/hmm_model.hpp | 48 +--- src/mlpack/tests/data/lab1_corrupt.csv | 4 + .../tests/main_tests/hmm_train_test.cpp | 211 +++++++++++++++++- 4 files changed, 213 insertions(+), 62 deletions(-) create mode 100644 src/mlpack/tests/data/lab1_corrupt.csv diff --git a/src/mlpack/methods/hmm/hmm_impl.hpp b/src/mlpack/methods/hmm/hmm_impl.hpp index 96912f76df..3d202990db 100644 --- a/src/mlpack/methods/hmm/hmm_impl.hpp +++ b/src/mlpack/methods/hmm/hmm_impl.hpp @@ -603,18 +603,6 @@ void HMM::serialize(Archive& ar, const unsigned int /* version */) ar & BOOST_SERIALIZATION_NVP(emission); } -template -bool HMM::ApproximatelyEqual(const HMM& other, - double tolerance) const -{ - bool dimensionalityEqual = (dimensionality == other.dimensionality); - bool transitionEqual = approx_equal(transition, other.transition, - "absdiff", tolerance); - bool initialEqual = approx_equal(initial, other.initial, - "absdiff", tolerance); - return dimensionalityEqual && transitionEqual && initialEqual; -} - } // namespace hmm } // namespace mlpack diff --git a/src/mlpack/methods/hmm/hmm_model.hpp b/src/mlpack/methods/hmm/hmm_model.hpp index f2f408462c..e26edf76db 100644 --- a/src/mlpack/methods/hmm/hmm_model.hpp +++ b/src/mlpack/methods/hmm/hmm_model.hpp @@ -115,48 +115,6 @@ class HMMModel return *this; } - bool ApproximatelyEqual(const HMMModel& other, double tolerance) const - { - bool typeEqual = (type == other.type); - bool hmmEqual = false; - bool emissionEqual = true; - if (typeEqual) - { - if (type == HMMType::DiscreteHMM) - { - hmmEqual = discreteHMM->ApproximatelyEqual(*(other.discreteHMM), - tolerance); - std::vector emission = - discreteHMM->Emission(); - std::vector otherEmission = - other.discreteHMM->Emission(); - if (emission.size() == otherEmission.size()) - { - for(size_t i = 0; i < emission.size(); i++) - { - if (emission[i].Dimensionality() != - otherEmission[i].Dimensionality()) - emissionEqual = false; - for (size_t dim = 0; dim < emission[i].Dimensionality(); dim++) - emissionEqual = emissionEqual && approx_equal( - emission[i].Probabilities(dim), - otherEmission[i].Probabilities(dim), - "absdiff", - tolerance); - } - } - else - emissionEqual = false; - } - if (type == HMMType::GaussianHMM) - hmmEqual = gaussianHMM->ApproximatelyEqual(*(other.gaussianHMM), - tolerance); - if (type == HMMType::GaussianMixtureModelHMM) - hmmEqual = gmmHMM->ApproximatelyEqual(*(other.gmmHMM), tolerance); - } - return typeEqual && hmmEqual && emissionEqual; - } - //! Clean memory. ~HMMModel() { @@ -206,6 +164,12 @@ class HMMModel else if (type == HMMType::GaussianMixtureModelHMM) ar & BOOST_SERIALIZATION_NVP(gmmHMM); } + + HMM* DiscreteHMM() { return discreteHMM; } + HMM* GaussianHMM() { return gaussianHMM; } + HMM* GMMHMM() { return gmmHMM; } + + HMMType Type() { return type; } }; } // namespace hmm diff --git a/src/mlpack/tests/data/lab1_corrupt.csv b/src/mlpack/tests/data/lab1_corrupt.csv new file mode 100644 index 0000000000..cbf0bcd892 --- /dev/null +++ b/src/mlpack/tests/data/lab1_corrupt.csv @@ -0,0 +1,4 @@ +0 +0 +1 +2 diff --git a/src/mlpack/tests/main_tests/hmm_train_test.cpp b/src/mlpack/tests/main_tests/hmm_train_test.cpp index 308234e8e5..b35149912d 100644 --- a/src/mlpack/tests/main_tests/hmm_train_test.cpp +++ b/src/mlpack/tests/main_tests/hmm_train_test.cpp @@ -51,6 +51,70 @@ inline void FileExists(std::string fileName) ifp.close(); } +inline bool ApproximatelyEqual(HMMModel& h1, + HMMModel& h2, + double tolerance) +{ + if (h1.Type() != h2.Type()) + return false; + HMMType hmmType = h1.Type(); + bool transitionEqual = false; + bool emissionEqual = false; + bool initialEqual = false; + if (hmmType == DiscreteHMM) + { + transitionEqual = approx_equal( + h1.DiscreteHMM()->Transition(), + h2.DiscreteHMM()->Transition(), + "absdiff", + tolerance + ); + initialEqual = approx_equal( + h1.DiscreteHMM()->Transition(), + h2.DiscreteHMM()->Transition(), + "absdiff", + tolerance + ); + // TODO + emissionEqual = true; + } + else if (hmmType == GaussianHMM) + { + transitionEqual = approx_equal( + h1.GaussianHMM()->Transition(), + h2.GaussianHMM()->Transition(), + "absdiff", + tolerance + ); + initialEqual = approx_equal( + h1.GaussianHMM()->Initial(), + h2.GaussianHMM()->Initial(), + "absdiff", + tolerance + ); + // TODO + emissionEqual = true; + } + else if (hmmType == GaussianMixtureModelHMM) + { + transitionEqual = approx_equal( + h1.GMMHMM()->Transition(), + h2.GMMHMM()->Transition(), + "absdiff", + tolerance + ); + initialEqual = approx_equal( + h1.GMMHMM()->Initial(), + h2.GMMHMM()->Initial(), + "absdiff", + tolerance + ); + // TODO + emissionEqual = true; + } + return emissionEqual && transitionEqual && initialEqual; +} + // Make sure that the number of states cannot be negative BOOST_AUTO_TEST_CASE(HMMTrainStatesTest) { @@ -148,10 +212,9 @@ BOOST_AUTO_TEST_CASE(HMMTrainReuseModelTest) mlpackMain(); - HMMModel * ph1 = CLI::GetParam("output_model"); HMMModel h1 = *(CLI::GetParam("output_model")); - SetInputParam("input_model", std::move(ph1)); + SetInputParam("input_model", CLI::GetParam("output_model")); CLI::GetSingleton().Parameters()["type"].wasPassed = false; CLI::GetSingleton().Parameters()["states"].wasPassed = false; @@ -160,12 +223,44 @@ BOOST_AUTO_TEST_CASE(HMMTrainReuseModelTest) HMMModel h2 = *(CLI::GetParam("output_model")); - BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1)); - BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-01)); - BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-02)); - BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-03)); - BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-04)); - BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-05)); + BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-01)); + BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-02)); + BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-03)); + BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-04)); + BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-05)); +} + +BOOST_AUTO_TEST_CASE(HMMTrainNoLabelsReuseModelTest) +{ + std::string inputObsFileName = "hmm_train_obs.csv"; + std::string hmmType = "discrete"; + int states = 3; + + FileExists(inputObsFileName); + SetInputParam("input_file", std::move(inputObsFileName)); + SetInputParam("states", states); + SetInputParam("type", std::move(hmmType)); + + // This call will train HMM using Baum-Welch training + mlpackMain(); + + HMMModel h1 = *(CLI::GetParam("output_model")); + + SetInputParam("input_model", CLI::GetParam("output_model")); + + CLI::GetSingleton().Parameters()["type"].wasPassed = false; + CLI::GetSingleton().Parameters()["states"].wasPassed = false; + + // Train again using Baum Welch + mlpackMain(); + + HMMModel h2 = *(CLI::GetParam("output_model")); + + BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-01)); + BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-02)); + BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-03)); + BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-04)); + BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-05)); } // Test batch mode @@ -206,4 +301,104 @@ BOOST_AUTO_TEST_CASE(HMMTrainBatchModeTest) Log::Fatal.ignoreInput = false; } +BOOST_AUTO_TEST_CASE(HMMTrainRetrainTest1) +{ + std::string inputObsFile1 = "obs1.csv"; + std::string type = "discrete"; + int states = 2; + + FileExists(inputObsFile1); + SetInputParam("input_file", std::move(inputObsFile1)); + SetInputParam("type", std::move(type)); + SetInputParam("states", states); + + mlpackMain(); + + HMMModel h1 = *(CLI::GetParam("output_model")); + + std::string inputObsFile2 = "obs3.csv"; + + CLI::GetSingleton().Parameters()["input_file"].wasPassed = false; + CLI::GetSingleton().Parameters()["type"].wasPassed = false; + CLI::GetSingleton().Parameters()["states"].wasPassed = false; + + FileExists(inputObsFile2); + SetInputParam("input_file", std::move(inputObsFile2)); + SetInputParam("input_model", CLI::GetParam("output_model")); + + mlpackMain(); + + HMMModel h2 = *(CLI::GetParam("output_model")); + + BOOST_REQUIRE(!ApproximatelyEqual(h1, h2, 1e-04)); +} + +// Attempt to retrain but increase states the second time round +BOOST_AUTO_TEST_CASE(HMMTrainRetrainTest2) +{ + // Provide no labels file + std::string inputObsFile1 = "obs1.csv"; + std::string type = "discrete"; + int states = 2; + + SetInputParam("input_file", std::move(inputObsFile1)); + SetInputParam("type", std::move(type)); + SetInputParam("states", states); + + mlpackMain(); + + HMMModel h1 = *(CLI::GetParam("output_model")); + + std::string inputObsFile2 = "obs3.csv"; + std::string inputLabFile2 = "lab1_corrupt.csv"; + + SetInputParam("input_file", std::move(inputObsFile2)); + // Provide a labels file with more states than initially specified + SetInputParam("labels_file", std::move(inputLabFile2)); + SetInputParam("input_model", CLI::GetParam("output_model")); + + CLI::GetSingleton().Parameters()["type"].wasPassed = false; + CLI::GetSingleton().Parameters()["states"].wasPassed = false; + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +// Attempt to retrain but change the emission distribution type +BOOST_AUTO_TEST_CASE(HMMTrainRetrainTest3) +{ + // Provide no labels file + std::string inputObsFile1 = "obs1.csv"; + std::string type = "discrete"; + int states = 2; + + SetInputParam("input_file", std::move(inputObsFile1)); + SetInputParam("type", std::move(type)); + SetInputParam("states", states); + + mlpackMain(); + + HMMModel h1 = *(CLI::GetParam("output_model")); + + std::string inputObsFile2 = "obs2.csv"; + std::string inputLabFile2 = "lab2.csv"; + type = "gaussian"; + + SetInputParam("input_file", std::move(inputObsFile2)); + // Provide a labels file with more states than initially specified + SetInputParam("labels_file", std::move(inputLabFile2)); + SetInputParam("type", std::move(type)); + SetInputParam("input_model", CLI::GetParam("output_model")); + + CLI::GetSingleton().Parameters()["type"].wasPassed = false; + CLI::GetSingleton().Parameters()["states"].wasPassed = false; + + HMMModel h2 = *(CLI::GetParam("output_model")); + + BOOST_REQUIRE(h1.Type() == DiscreteHMM); + BOOST_REQUIRE(h2.Type() == DiscreteHMM); + BOOST_REQUIRE(h2.Type() != GaussianHMM); +} + BOOST_AUTO_TEST_SUITE_END(); From 33f363a62d93652f112316df1c5d725a2c3b2f7c Mon Sep 17 00:00:00 2001 From: daivik Date: Wed, 14 Feb 2018 17:32:44 +0000 Subject: [PATCH 09/19] Add docs for accessors in hmm_model.hpp --- src/mlpack/methods/hmm/hmm_model.hpp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/mlpack/methods/hmm/hmm_model.hpp b/src/mlpack/methods/hmm/hmm_model.hpp index e26edf76db..d3693a9cad 100644 --- a/src/mlpack/methods/hmm/hmm_model.hpp +++ b/src/mlpack/methods/hmm/hmm_model.hpp @@ -165,11 +165,30 @@ class HMMModel ar & BOOST_SERIALIZATION_NVP(gmmHMM); } + // Accessor method for type of HMM + HMMType Type() { return type; } + + /** + * Accessor methods for discreteHMM, gaussianHMM and gmmHMM. + * Note that an instatiation of this class will only contain one type of HMM + * (as indicated by the "type" instance variable) - the other two pointers + * will be NULL. + * + * For instance, if the HMMModel object holds a discrete HMM, then: + * type --> DiscreteHMM + * gaussianHMM --> NULL + * gmmHMM --> NULL + * discreteHMM --> HMM object + * and hence, calls to GMMHMM() and GaussianHMM() will return NULL. Only the + * call to DiscreteHMM() will return a non NULL pointer. + * + * Hence, in practice, a user should be careful to first check the type of HMM + * (by calling the Type() accessor) and then perform subsequent actions, to + * avoid running into NullPointerExceptions. + */ HMM* DiscreteHMM() { return discreteHMM; } HMM* GaussianHMM() { return gaussianHMM; } HMM* GMMHMM() { return gmmHMM; } - - HMMType Type() { return type; } }; } // namespace hmm From e5b53b3756fed67054af3f3b1613bb51f86fe2cf Mon Sep 17 00:00:00 2001 From: daivik Date: Wed, 14 Feb 2018 18:23:40 +0000 Subject: [PATCH 10/19] Fix HMMTrainRetrainTest3 --- src/mlpack/tests/main_tests/hmm_train_test.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mlpack/tests/main_tests/hmm_train_test.cpp b/src/mlpack/tests/main_tests/hmm_train_test.cpp index b35149912d..889d6f891f 100644 --- a/src/mlpack/tests/main_tests/hmm_train_test.cpp +++ b/src/mlpack/tests/main_tests/hmm_train_test.cpp @@ -386,14 +386,16 @@ BOOST_AUTO_TEST_CASE(HMMTrainRetrainTest3) type = "gaussian"; SetInputParam("input_file", std::move(inputObsFile2)); - // Provide a labels file with more states than initially specified SetInputParam("labels_file", std::move(inputLabFile2)); SetInputParam("type", std::move(type)); SetInputParam("input_model", CLI::GetParam("output_model")); - CLI::GetSingleton().Parameters()["type"].wasPassed = false; CLI::GetSingleton().Parameters()["states"].wasPassed = false; + mlpackMain(); + // Note that when emission type is changed -- like in this test, a warning + // is printed stating that the new type is being ignored (no error is raised) + HMMModel h2 = *(CLI::GetParam("output_model")); BOOST_REQUIRE(h1.Type() == DiscreteHMM); From 0cd46e0cd86efbaf22b2f26bc3d853adae058fd5 Mon Sep 17 00:00:00 2001 From: daivik Date: Fri, 16 Feb 2018 14:13:37 +0000 Subject: [PATCH 11/19] Remove calls to approx_equal with CheckMatrices --- .../tests/main_tests/hmm_train_test.cpp | 80 ++++++++++--------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/src/mlpack/tests/main_tests/hmm_train_test.cpp b/src/mlpack/tests/main_tests/hmm_train_test.cpp index 889d6f891f..703e9bc1f1 100644 --- a/src/mlpack/tests/main_tests/hmm_train_test.cpp +++ b/src/mlpack/tests/main_tests/hmm_train_test.cpp @@ -51,68 +51,73 @@ inline void FileExists(std::string fileName) ifp.close(); } -inline bool ApproximatelyEqual(HMMModel& h1, +inline void CheckMatricesDiffer(arma::mat& a, arma::mat& b, double tolerance) +{ + bool dimsEqual = (a.n_rows == b.n_rows) + && (a.n_cols == b.n_cols) + && (a.n_elem == b.n_elem); + bool valsEqual = true; + if (dimsEqual) + { + for (size_t i=0; iTransition(), h2.DiscreteHMM()->Transition(), - "absdiff", tolerance ); - initialEqual = approx_equal( + CheckMatrices( h1.DiscreteHMM()->Transition(), h2.DiscreteHMM()->Transition(), - "absdiff", tolerance ); - // TODO - emissionEqual = true; + // TODO: Check if emission dists are equal } else if (hmmType == GaussianHMM) { - transitionEqual = approx_equal( + CheckMatrices( h1.GaussianHMM()->Transition(), h2.GaussianHMM()->Transition(), - "absdiff", tolerance ); - initialEqual = approx_equal( + CheckMatrices( h1.GaussianHMM()->Initial(), h2.GaussianHMM()->Initial(), - "absdiff", tolerance ); - // TODO - emissionEqual = true; + // TODO: Check if emission dists are equal } else if (hmmType == GaussianMixtureModelHMM) { - transitionEqual = approx_equal( + CheckMatrices( h1.GMMHMM()->Transition(), h2.GMMHMM()->Transition(), - "absdiff", tolerance ); - initialEqual = approx_equal( + CheckMatrices( h1.GMMHMM()->Initial(), h2.GMMHMM()->Initial(), - "absdiff", tolerance ); - // TODO - emissionEqual = true; + // TODO: Check if emission dists are equal } - return emissionEqual && transitionEqual && initialEqual; } // Make sure that the number of states cannot be negative @@ -223,11 +228,11 @@ BOOST_AUTO_TEST_CASE(HMMTrainReuseModelTest) HMMModel h2 = *(CLI::GetParam("output_model")); - BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-01)); - BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-02)); - BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-03)); - BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-04)); - BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-05)); + ApproximatelyEqual(h1, h2, 1e-01); + ApproximatelyEqual(h1, h2, 1e-02); + ApproximatelyEqual(h1, h2, 1e-03); + ApproximatelyEqual(h1, h2, 1e-04); + ApproximatelyEqual(h1, h2, 1e-05); } BOOST_AUTO_TEST_CASE(HMMTrainNoLabelsReuseModelTest) @@ -256,11 +261,11 @@ BOOST_AUTO_TEST_CASE(HMMTrainNoLabelsReuseModelTest) HMMModel h2 = *(CLI::GetParam("output_model")); - BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-01)); - BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-02)); - BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-03)); - BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-04)); - BOOST_REQUIRE(ApproximatelyEqual(h1, h2, 1e-05)); + ApproximatelyEqual(h1, h2, 1e-01); + ApproximatelyEqual(h1, h2, 1e-02); + ApproximatelyEqual(h1, h2, 1e-03); + ApproximatelyEqual(h1, h2, 1e-04); + ApproximatelyEqual(h1, h2, 1e-05); } // Test batch mode @@ -330,7 +335,10 @@ BOOST_AUTO_TEST_CASE(HMMTrainRetrainTest1) HMMModel h2 = *(CLI::GetParam("output_model")); - BOOST_REQUIRE(!ApproximatelyEqual(h1, h2, 1e-04)); + BOOST_REQUIRE(h1.Type() == h2.Type()); + // Since we know that type of HMMs is discrete + CheckMatricesDiffer(h1.DiscreteHMM()->Transition(), + h2.DiscreteHMM()->Transition(), 1e-04); } // Attempt to retrain but increase states the second time round From d7a708ef258a22ec19ed94d03e817db083e842a4 Mon Sep 17 00:00:00 2001 From: daivik Date: Fri, 16 Feb 2018 21:56:36 +0000 Subject: [PATCH 12/19] Remove ApproximatelyEqual() signature from hmm.hpp --- src/mlpack/methods/hmm/hmm.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/mlpack/methods/hmm/hmm.hpp b/src/mlpack/methods/hmm/hmm.hpp index d1e395c0b8..0e902896f0 100644 --- a/src/mlpack/methods/hmm/hmm.hpp +++ b/src/mlpack/methods/hmm/hmm.hpp @@ -328,9 +328,6 @@ class HMM template void serialize(Archive& ar, const unsigned int version); - bool ApproximatelyEqual(const HMM& other, - double tolerance) const; - protected: // Helper functions. /** From b38bb537776d77b89c47cbc75ef42937c6310f33 Mon Sep 17 00:00:00 2001 From: daivik Date: Fri, 16 Feb 2018 21:57:28 +0000 Subject: [PATCH 13/19] Completed TODOs in ApproximatelyEqual() --- .../tests/main_tests/hmm_train_test.cpp | 99 ++++++++++++++++++- 1 file changed, 95 insertions(+), 4 deletions(-) diff --git a/src/mlpack/tests/main_tests/hmm_train_test.cpp b/src/mlpack/tests/main_tests/hmm_train_test.cpp index 703e9bc1f1..2280522305 100644 --- a/src/mlpack/tests/main_tests/hmm_train_test.cpp +++ b/src/mlpack/tests/main_tests/hmm_train_test.cpp @@ -88,7 +88,21 @@ inline void ApproximatelyEqual(HMMModel& h1, h2.DiscreteHMM()->Transition(), tolerance ); - // TODO: Check if emission dists are equal + + // Check if emission dists are equal + std::vector d1 = + h1.DiscreteHMM()->Emission(); + std::vector d2 = + h2.DiscreteHMM()->Emission(); + + BOOST_REQUIRE_EQUAL(d1.size(), d2.size()); + + size_t states = d1.size(); + for (size_t i = 0; i < states; i++) + for (size_t j = 0; j < d1[i].Dimensionality(); j++) + CheckMatrices(d1[i].Probabilities(j), + d2[i].Probabilities(j), + tolerance); } else if (hmmType == GaussianHMM) { @@ -102,7 +116,29 @@ inline void ApproximatelyEqual(HMMModel& h1, h2.GaussianHMM()->Initial(), tolerance ); - // TODO: Check if emission dists are equal + // Check if emission dists are equal + // No easy way to do this, but here's how we'll go: + // 1. Sample a number (for now, 100) of points from a uniform random dist + // 2. Evaluate and compare Probability() of both dists at each of the points + std::vector d1 = + h1.GaussianHMM()->Emission(); + std::vector d2 = + h2.GaussianHMM()->Emission(); + + BOOST_REQUIRE_EQUAL(d1.size(), d2.size()); + + size_t states = d1.size(); + for (size_t i=0; i < states; i++) + { + size_t nPoints = 100; + for (size_t j=0; j < nPoints; j++) + { + arma::vec obs = randu(h1.GaussianHMM()->Dimensionality()); + double p1 = d1[i].Probability(obs); + double p2 = d2[i].Probability(obs); + BOOST_REQUIRE_SMALL(std::abs(p1 - p2), tolerance); + } + } } else if (hmmType == GaussianMixtureModelHMM) { @@ -116,7 +152,25 @@ inline void ApproximatelyEqual(HMMModel& h1, h2.GMMHMM()->Initial(), tolerance ); - // TODO: Check if emission dists are equal + // Check if emission dists are equal + // Similar to checking if two Gaussian emissions are equal + std::vector d1 = h1.GMMHMM()->Emission(); + std::vector d2 = h2.GMMHMM()->Emission(); + + BOOST_REQUIRE_EQUAL(d1.size(), d2.size()); + + size_t states = d1.size(); + for (size_t i=0; i < states; i++) + { + size_t nPoints = 100; + for (size_t j=0; j < nPoints; j++) + { + arma::vec obs = randu(h1.GMMHMM()->Dimensionality()); + double p1 = d1[i].Probability(obs); + double p2 = d2[i].Probability(obs); + BOOST_REQUIRE_SMALL(std::abs(p1 - p2), tolerance); + } + } } } @@ -194,7 +248,7 @@ BOOST_AUTO_TEST_CASE(HMMTrainGaussianTest) } // Make sure that model reuse is possible and work properly -BOOST_AUTO_TEST_CASE(HMMTrainReuseModelTest) +BOOST_AUTO_TEST_CASE(HMMTrainReuseDiscreteModelTest) { std::string inputObsFileName = "hmm_train_obs.csv"; std::string inputLabFileName = "hmm_train_lab.csv"; @@ -235,6 +289,43 @@ BOOST_AUTO_TEST_CASE(HMMTrainReuseModelTest) ApproximatelyEqual(h1, h2, 1e-05); } +// Make sure that model reuse is possible and work properly +BOOST_AUTO_TEST_CASE(HMMTrainReuseGaussianModelTest) +{ + std::string inputObsFileName = "hmm_train_obs.csv"; + std::string hmmType = "gaussian"; + int states = 3; + + FileExists(inputObsFileName); + // Make sure that the size of the + // training seq, and training labels is same + arma::mat trainObs; + data::Load(inputObsFileName, trainObs); + + SetInputParam("input_file", std::move(inputObsFileName)); + SetInputParam("type", std::move(hmmType)); + SetInputParam("states", states); + + mlpackMain(); + + HMMModel h1 = *(CLI::GetParam("output_model")); + + SetInputParam("input_model", CLI::GetParam("output_model")); + + CLI::GetSingleton().Parameters()["type"].wasPassed = false; + CLI::GetSingleton().Parameters()["states"].wasPassed = false; + + mlpackMain(); + + HMMModel h2 = *(CLI::GetParam("output_model")); + + ApproximatelyEqual(h1, h2, 1e-01); + ApproximatelyEqual(h1, h2, 1e-02); + ApproximatelyEqual(h1, h2, 1e-03); + ApproximatelyEqual(h1, h2, 1e-04); + ApproximatelyEqual(h1, h2, 1e-05); +} + BOOST_AUTO_TEST_CASE(HMMTrainNoLabelsReuseModelTest) { std::string inputObsFileName = "hmm_train_obs.csv"; From 7708c6194f4a2d9c93bd5ede739de444eddd4f9c Mon Sep 17 00:00:00 2001 From: daivik Date: Tue, 20 Feb 2018 16:10:00 +0000 Subject: [PATCH 14/19] Fix comparison of emissions for GMMs and Gaussians --- .../tests/main_tests/hmm_train_test.cpp | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/mlpack/tests/main_tests/hmm_train_test.cpp b/src/mlpack/tests/main_tests/hmm_train_test.cpp index 2280522305..5e38cec988 100644 --- a/src/mlpack/tests/main_tests/hmm_train_test.cpp +++ b/src/mlpack/tests/main_tests/hmm_train_test.cpp @@ -116,10 +116,7 @@ inline void ApproximatelyEqual(HMMModel& h1, h2.GaussianHMM()->Initial(), tolerance ); - // Check if emission dists are equal - // No easy way to do this, but here's how we'll go: - // 1. Sample a number (for now, 100) of points from a uniform random dist - // 2. Evaluate and compare Probability() of both dists at each of the points + // Check if emission dists are equal by comparing the mean and coviariance std::vector d1 = h1.GaussianHMM()->Emission(); std::vector d2 = @@ -130,14 +127,8 @@ inline void ApproximatelyEqual(HMMModel& h1, size_t states = d1.size(); for (size_t i=0; i < states; i++) { - size_t nPoints = 100; - for (size_t j=0; j < nPoints; j++) - { - arma::vec obs = randu(h1.GaussianHMM()->Dimensionality()); - double p1 = d1[i].Probability(obs); - double p2 = d2[i].Probability(obs); - BOOST_REQUIRE_SMALL(std::abs(p1 - p2), tolerance); - } + CheckMatrices(d1[i].Mean(), d2[i].Mean(), tolerance); + CheckMatrices(d1[i].Covariance(), d2[i].Covariance(), tolerance); } } else if (hmmType == GaussianMixtureModelHMM) @@ -153,7 +144,6 @@ inline void ApproximatelyEqual(HMMModel& h1, tolerance ); // Check if emission dists are equal - // Similar to checking if two Gaussian emissions are equal std::vector d1 = h1.GMMHMM()->Emission(); std::vector d2 = h2.GMMHMM()->Emission(); @@ -162,14 +152,18 @@ inline void ApproximatelyEqual(HMMModel& h1, size_t states = d1.size(); for (size_t i=0; i < states; i++) { - size_t nPoints = 100; - for (size_t j=0; j < nPoints; j++) + BOOST_REQUIRE_EQUAL(d1[i].Gaussians(), d2[i].Gaussians()); + size_t gaussians = d1[i].Gaussians(); + for (size_t j=0; j(h1.GMMHMM()->Dimensionality()); - double p1 = d1[i].Probability(obs); - double p2 = d2[i].Probability(obs); - BOOST_REQUIRE_SMALL(std::abs(p1 - p2), tolerance); + CheckMatrices(d1[i].Component(j).Mean(), + d2[i].Component(j).Mean(), + tolerance); + CheckMatrices(d1[i].Component(j).Covariance(), + d2[i].Component(j).Covariance(), + tolerance); } + CheckMatrices(d1[i].Weights(), d2[i].Weights(), tolerance); } } } From 237092e903f4bd89e37d788955b984f4b6a649bb Mon Sep 17 00:00:00 2001 From: daivik Date: Sat, 24 Feb 2018 17:20:17 +0000 Subject: [PATCH 15/19] lots of git confusion --- src/mlpack/tests/CMakeLists.txt | 2 - .../tests/main_tests/hmm_training_tests.cpp | 209 ------------------ 2 files changed, 211 deletions(-) delete mode 100644 src/mlpack/tests/main_tests/hmm_training_tests.cpp diff --git a/src/mlpack/tests/CMakeLists.txt b/src/mlpack/tests/CMakeLists.txt index 9362b0722e..250acc5664 100644 --- a/src/mlpack/tests/CMakeLists.txt +++ b/src/mlpack/tests/CMakeLists.txt @@ -143,9 +143,7 @@ add_executable(mlpack_test main_tests/random_forest_test.cpp main_tests/softmax_regression_test.cpp main_tests/sparse_coding_test.cpp - main_tests/hmm_training_tests.cpp main_tests/hoeffding_tree_test.cpp - main_tests/hmm_training_tests.cpp main_tests/hmm_train_test.cpp ) diff --git a/src/mlpack/tests/main_tests/hmm_training_tests.cpp b/src/mlpack/tests/main_tests/hmm_training_tests.cpp deleted file mode 100644 index e79aa07570..0000000000 --- a/src/mlpack/tests/main_tests/hmm_training_tests.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/** - * @file hmm_training_tests.cpp - * @author Daivik Nema - * - * Test mlpackMain() of hmm_train_main.cpp. - */ -#include -#include - -#define BINDING_TYPE BINDING_TYPE_TEST -static const std::string testName = "HMMTrain"; - -#include -#include -#include "test_helper.hpp" -#include -#include - -#include -#include "../test_tools.hpp" - -#include -#include - -using namespace mlpack; - -struct HMMTrainMainTestFixture -{ - public: - HMMTrainMainTestFixture() - { - // Cache in the options for this program. - CLI::RestoreSettings(testName); - } - - ~HMMTrainMainTestFixture() - { - // Clear the settings. - bindings::tests::CleanMemory(); - CLI::ClearSettings(); - } -}; - -BOOST_FIXTURE_TEST_SUITE(HMMTrainMainTest, HMMTrainMainTestFixture); - -inline void fileExists(std::string fileName) -{ - ifstream ifp(fileName); - if (!ifp.good()) - BOOST_FAIL("Bad stream " + fileName); - ifp.close(); -} - -// Make sure that the number of states cannot be negative -BOOST_AUTO_TEST_CASE(HMMTrainStatesTest) -{ - std::string inputFileName = "hmm_train_obs.csv"; - int states = -3; // Invalid! - std::string hmmType = "discrete"; - - fileExists(inputFileName); - SetInputParam("input_file", std::move(inputFileName)); - SetInputParam("states", states); - SetInputParam("type", std::move(hmmType)); - - Log::Fatal.ignoreInput = true; - BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); - Log::Fatal.ignoreInput = false; -} - -// Make sure that tolerance is non negative -BOOST_AUTO_TEST_CASE(HMMTrainToleranceNonNegative) -{ - std::string inputFileName = "hmm_train_obs.csv"; - int states = 3; - std::string hmmType = "gaussian"; - double tol = - 100; // Invalid - - fileExists(inputFileName); - SetInputParam("input_file", std::move(inputFileName)); - SetInputParam("states", states); - SetInputParam("type", std::move(hmmType)); - SetInputParam("tolerance", tol); - - Log::Fatal.ignoreInput = true; - BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); - Log::Fatal.ignoreInput = false; -} - -// Make sure an error is thrown if type is something other than -// "discrete", "gaussian" or "gmm" -BOOST_AUTO_TEST_CASE(HMMTrainTypeTest) -{ - std::string inputFileName = "hmm_train_obs.csv"; - int states = 3; - std::string hmmType = "some-not-supported-possibly-non-type"; - - fileExists(inputFileName); - SetInputParam("input_file", std::move(inputFileName)); - SetInputParam("states", states); - SetInputParam("type", std::move(hmmType)); - - Log::Fatal.ignoreInput = true; - BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); - Log::Fatal.ignoreInput = false; -} - -// Make sure that the number of gaussians cannot be less than 0 -BOOST_AUTO_TEST_CASE(HMMTrainGaussianTest) -{ - std::string inputFileName = "hmm_train_obs.csv"; - int states = 3; - std::string hmmType = "gmm"; - int gaussians = -2; - - fileExists(inputFileName); - SetInputParam("input_file", std::move(inputFileName)); - SetInputParam("states", states); - SetInputParam("type", std::move(hmmType)); - SetInputParam("gaussians", gaussians); - - Log::Fatal.ignoreInput = true; - BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); - Log::Fatal.ignoreInput = false; -} - -// Make sure that model reuse is possible and work properly -BOOST_AUTO_TEST_CASE(HMMTrainReuseModelTest) -{ - std::string inputObsFileName = "hmm_train_obs.csv"; - std::string inputLabFileName = "hmm_train_lab.csv"; - std::string hmmType = "discrete"; - int states = 3; - - fileExists(inputObsFileName); - fileExists(inputLabFileName); - // Make sure that the size of the - // training seq, and training labels is same - arma::mat trainObs, trainLab; - data::Load(inputObsFileName, trainObs); - data::Load(inputLabFileName, trainLab); - BOOST_REQUIRE_EQUAL(trainObs.n_rows, trainLab.n_rows); - - SetInputParam("input_file", std::move(inputObsFileName)); - SetInputParam("labels_file", std::move(inputLabFileName)); - SetInputParam("type", std::move(hmmType)); - SetInputParam("states", states); - - mlpackMain(); - - HMMModel * ph1 = CLI::GetParam("output_model"); - HMMModel h1 = *(CLI::GetParam("output_model")); - - SetInputParam("input_model", std::move(ph1)); - - CLI::GetSingleton().Parameters()["type"].wasPassed = false; - CLI::GetSingleton().Parameters()["states"].wasPassed = false; - - mlpackMain(); - - HMMModel h2 = *(CLI::GetParam("output_model")); - - BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1)); - BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-01)); - BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-02)); - BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-03)); - BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-04)); - BOOST_REQUIRE(h1.ApproximatelyEqual(h2, 1e-05)); -} - -// Test batch mode -BOOST_AUTO_TEST_CASE(HMMTrainBatchModeTest) -{ - std::string observationsFileName = "observations.txt"; - std::string labelsFileName = "labels.txt"; - std::string hmmType = "discrete"; - int states = 2; - - SetInputParam("input_file", std::move(observationsFileName)); - SetInputParam("labels_file", std::move(labelsFileName)); - - Log::Fatal.ignoreInput = true; - BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); - Log::Fatal.ignoreInput = false; - - SetInputParam("states", states); - SetInputParam("type", std::move(hmmType)); - SetInputParam("batch", (bool) true); - - mlpackMain(); - - // Now pass an observations file with extra non-existent filenames - observationsFileName = "corrupt-observations-1.txt"; - SetInputParam("input_file", std::move(observationsFileName)); - - Log::Fatal.ignoreInput = true; - BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); - Log::Fatal.ignoreInput = false; - - // Now a mismatch between #observation files and #label files - observationsFileName = "corrupt-observations-2.txt"; - SetInputParam("input_file", std::move(observationsFileName)); - - Log::Fatal.ignoreInput = true; - BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); - Log::Fatal.ignoreInput = false; -} - -BOOST_AUTO_TEST_SUITE_END(); From c4d59c6420e8c1fc5b680e8cd9fe344dd8a3dcf0 Mon Sep 17 00:00:00 2001 From: daivik Date: Sun, 25 Feb 2018 17:30:52 +0000 Subject: [PATCH 16/19] Fix failing tests --- src/mlpack/methods/hmm/hmm_impl.hpp | 6 ++ src/mlpack/tests/data/obs4.csv | 9 +++ .../tests/main_tests/hmm_train_test.cpp | 70 ++++++++----------- 3 files changed, 46 insertions(+), 39 deletions(-) create mode 100644 src/mlpack/tests/data/obs4.csv diff --git a/src/mlpack/methods/hmm/hmm_impl.hpp b/src/mlpack/methods/hmm/hmm_impl.hpp index 96912f76df..fbb6ad9334 100644 --- a/src/mlpack/methods/hmm/hmm_impl.hpp +++ b/src/mlpack/methods/hmm/hmm_impl.hpp @@ -169,6 +169,12 @@ void HMM::Train(const std::vector& dataSeq) } } + if (std::abs(oldLoglik - loglik) < tolerance) + { + Log::Debug << "Converged after " << iter << " iterations." << std::endl; + break; + } + // Normalize the new initial probabilities. if (dataSeq.size() > 1) initial = newInitial / dataSeq.size(); diff --git a/src/mlpack/tests/data/obs4.csv b/src/mlpack/tests/data/obs4.csv new file mode 100644 index 0000000000..68e029a20c --- /dev/null +++ b/src/mlpack/tests/data/obs4.csv @@ -0,0 +1,9 @@ +3 +2 +1 +0 +3 +3 +1 +2 +0 diff --git a/src/mlpack/tests/main_tests/hmm_train_test.cpp b/src/mlpack/tests/main_tests/hmm_train_test.cpp index 5e38cec988..c2e855434b 100644 --- a/src/mlpack/tests/main_tests/hmm_train_test.cpp +++ b/src/mlpack/tests/main_tests/hmm_train_test.cpp @@ -72,20 +72,20 @@ inline void CheckMatricesDiffer(arma::mat& a, arma::mat& b, double tolerance) inline void ApproximatelyEqual(HMMModel& h1, HMMModel& h2, - double tolerance) + double tolerance=1.0) { BOOST_REQUIRE(h1.Type() == h2.Type()); HMMType hmmType = h1.Type(); if (hmmType == DiscreteHMM) { CheckMatrices( - h1.DiscreteHMM()->Transition(), - h2.DiscreteHMM()->Transition(), + h1.DiscreteHMM()->Transition()*100, + h2.DiscreteHMM()->Transition()*100, tolerance ); CheckMatrices( - h1.DiscreteHMM()->Transition(), - h2.DiscreteHMM()->Transition(), + h1.DiscreteHMM()->Transition()*100, + h2.DiscreteHMM()->Transition()*100, tolerance ); @@ -100,20 +100,20 @@ inline void ApproximatelyEqual(HMMModel& h1, size_t states = d1.size(); for (size_t i = 0; i < states; i++) for (size_t j = 0; j < d1[i].Dimensionality(); j++) - CheckMatrices(d1[i].Probabilities(j), - d2[i].Probabilities(j), + CheckMatrices(d1[i].Probabilities(j)*100, + d2[i].Probabilities(j)*100, tolerance); } else if (hmmType == GaussianHMM) { CheckMatrices( - h1.GaussianHMM()->Transition(), - h2.GaussianHMM()->Transition(), + h1.GaussianHMM()->Transition()*100, + h2.GaussianHMM()->Transition()*100, tolerance ); CheckMatrices( - h1.GaussianHMM()->Initial(), - h2.GaussianHMM()->Initial(), + h1.GaussianHMM()->Initial()*100, + h2.GaussianHMM()->Initial()*100, tolerance ); // Check if emission dists are equal by comparing the mean and coviariance @@ -127,20 +127,20 @@ inline void ApproximatelyEqual(HMMModel& h1, size_t states = d1.size(); for (size_t i=0; i < states; i++) { - CheckMatrices(d1[i].Mean(), d2[i].Mean(), tolerance); - CheckMatrices(d1[i].Covariance(), d2[i].Covariance(), tolerance); + CheckMatrices(d1[i].Mean()*100, d2[i].Mean()*100, tolerance); + CheckMatrices(d1[i].Covariance()*100, d2[i].Covariance()*100, tolerance); } } else if (hmmType == GaussianMixtureModelHMM) { CheckMatrices( - h1.GMMHMM()->Transition(), - h2.GMMHMM()->Transition(), + h1.GMMHMM()->Transition()*100, + h2.GMMHMM()->Transition()*100, tolerance ); CheckMatrices( - h1.GMMHMM()->Initial(), - h2.GMMHMM()->Initial(), + h1.GMMHMM()->Initial()*100, + h2.GMMHMM()->Initial()*100, tolerance ); // Check if emission dists are equal @@ -156,14 +156,14 @@ inline void ApproximatelyEqual(HMMModel& h1, size_t gaussians = d1[i].Gaussians(); for (size_t j=0; j("output_model")); - ApproximatelyEqual(h1, h2, 1e-01); - ApproximatelyEqual(h1, h2, 1e-02); - ApproximatelyEqual(h1, h2, 1e-03); - ApproximatelyEqual(h1, h2, 1e-04); - ApproximatelyEqual(h1, h2, 1e-05); + ApproximatelyEqual(h1, h2); } // Make sure that model reuse is possible and work properly @@ -313,11 +309,7 @@ BOOST_AUTO_TEST_CASE(HMMTrainReuseGaussianModelTest) HMMModel h2 = *(CLI::GetParam("output_model")); - ApproximatelyEqual(h1, h2, 1e-01); - ApproximatelyEqual(h1, h2, 1e-02); - ApproximatelyEqual(h1, h2, 1e-03); - ApproximatelyEqual(h1, h2, 1e-04); - ApproximatelyEqual(h1, h2, 1e-05); + ApproximatelyEqual(h1, h2); } BOOST_AUTO_TEST_CASE(HMMTrainNoLabelsReuseModelTest) @@ -325,11 +317,13 @@ BOOST_AUTO_TEST_CASE(HMMTrainNoLabelsReuseModelTest) std::string inputObsFileName = "hmm_train_obs.csv"; std::string hmmType = "discrete"; int states = 3; + int seed = 0; FileExists(inputObsFileName); SetInputParam("input_file", std::move(inputObsFileName)); SetInputParam("states", states); SetInputParam("type", std::move(hmmType)); + SetInputParam("seed", seed); // This call will train HMM using Baum-Welch training mlpackMain(); @@ -346,11 +340,7 @@ BOOST_AUTO_TEST_CASE(HMMTrainNoLabelsReuseModelTest) HMMModel h2 = *(CLI::GetParam("output_model")); - ApproximatelyEqual(h1, h2, 1e-01); - ApproximatelyEqual(h1, h2, 1e-02); - ApproximatelyEqual(h1, h2, 1e-03); - ApproximatelyEqual(h1, h2, 1e-04); - ApproximatelyEqual(h1, h2, 1e-05); + ApproximatelyEqual(h1, h2); } // Test batch mode @@ -396,17 +386,19 @@ BOOST_AUTO_TEST_CASE(HMMTrainRetrainTest1) std::string inputObsFile1 = "obs1.csv"; std::string type = "discrete"; int states = 2; + int seed = 0; FileExists(inputObsFile1); SetInputParam("input_file", std::move(inputObsFile1)); SetInputParam("type", std::move(type)); SetInputParam("states", states); + SetInputParam("seed", seed); mlpackMain(); HMMModel h1 = *(CLI::GetParam("output_model")); - std::string inputObsFile2 = "obs3.csv"; + std::string inputObsFile2 = "obs4.csv"; CLI::GetSingleton().Parameters()["input_file"].wasPassed = false; CLI::GetSingleton().Parameters()["type"].wasPassed = false; @@ -423,7 +415,7 @@ BOOST_AUTO_TEST_CASE(HMMTrainRetrainTest1) BOOST_REQUIRE(h1.Type() == h2.Type()); // Since we know that type of HMMs is discrete CheckMatricesDiffer(h1.DiscreteHMM()->Transition(), - h2.DiscreteHMM()->Transition(), 1e-04); + h2.DiscreteHMM()->Transition(), 1e-50); } // Attempt to retrain but increase states the second time round From 3e759ee702a13de714161fda196cadb799f9b696 Mon Sep 17 00:00:00 2001 From: daivik Date: Sun, 25 Feb 2018 19:09:29 +0000 Subject: [PATCH 17/19] Removed old changes --- src/mlpack/methods/hmm/hmm.hpp | 3 -- src/mlpack/methods/hmm/hmm_impl.hpp | 12 -------- src/mlpack/methods/hmm/hmm_model.hpp | 42 ---------------------------- 3 files changed, 57 deletions(-) diff --git a/src/mlpack/methods/hmm/hmm.hpp b/src/mlpack/methods/hmm/hmm.hpp index d1e395c0b8..0e902896f0 100644 --- a/src/mlpack/methods/hmm/hmm.hpp +++ b/src/mlpack/methods/hmm/hmm.hpp @@ -328,9 +328,6 @@ class HMM template void serialize(Archive& ar, const unsigned int version); - bool ApproximatelyEqual(const HMM& other, - double tolerance) const; - protected: // Helper functions. /** diff --git a/src/mlpack/methods/hmm/hmm_impl.hpp b/src/mlpack/methods/hmm/hmm_impl.hpp index fbb6ad9334..62bc9a2930 100644 --- a/src/mlpack/methods/hmm/hmm_impl.hpp +++ b/src/mlpack/methods/hmm/hmm_impl.hpp @@ -609,18 +609,6 @@ void HMM::serialize(Archive& ar, const unsigned int /* version */) ar & BOOST_SERIALIZATION_NVP(emission); } -template -bool HMM::ApproximatelyEqual(const HMM& other, - double tolerance) const -{ - bool dimensionalityEqual = (dimensionality == other.dimensionality); - bool transitionEqual = approx_equal(transition, other.transition, - "absdiff", tolerance); - bool initialEqual = approx_equal(initial, other.initial, - "absdiff", tolerance); - return dimensionalityEqual && transitionEqual && initialEqual; -} - } // namespace hmm } // namespace mlpack diff --git a/src/mlpack/methods/hmm/hmm_model.hpp b/src/mlpack/methods/hmm/hmm_model.hpp index 50b52bdd5f..d3693a9cad 100644 --- a/src/mlpack/methods/hmm/hmm_model.hpp +++ b/src/mlpack/methods/hmm/hmm_model.hpp @@ -115,48 +115,6 @@ class HMMModel return *this; } - bool ApproximatelyEqual(const HMMModel& other, double tolerance) const - { - bool typeEqual = (type == other.type); - bool hmmEqual = false; - bool emissionEqual = true; - if (typeEqual) - { - if (type == HMMType::DiscreteHMM) - { - hmmEqual = discreteHMM->ApproximatelyEqual(*(other.discreteHMM), - tolerance); - std::vector emission = - discreteHMM->Emission(); - std::vector otherEmission = - other.discreteHMM->Emission(); - if (emission.size() == otherEmission.size()) - { - for(size_t i = 0; i < emission.size(); i++) - { - if (emission[i].Dimensionality() != - otherEmission[i].Dimensionality()) - emissionEqual = false; - for (size_t dim = 0; dim < emission[i].Dimensionality(); dim++) - emissionEqual = emissionEqual && approx_equal( - emission[i].Probabilities(dim), - otherEmission[i].Probabilities(dim), - "absdiff", - tolerance); - } - } - else - emissionEqual = false; - } - if (type == HMMType::GaussianHMM) - hmmEqual = gaussianHMM->ApproximatelyEqual(*(other.gaussianHMM), - tolerance); - if (type == HMMType::GaussianMixtureModelHMM) - hmmEqual = gmmHMM->ApproximatelyEqual(*(other.gmmHMM), tolerance); - } - return typeEqual && hmmEqual && emissionEqual; - } - //! Clean memory. ~HMMModel() { From a74470912e9138f7929d8c37b9b320e6fe16e293 Mon Sep 17 00:00:00 2001 From: daivik Date: Mon, 26 Feb 2018 20:34:48 +0000 Subject: [PATCH 18/19] Minor fixes. Style issues. --- src/mlpack/methods/hmm/hmm_impl.hpp | 10 ++------ src/mlpack/methods/hmm/hmm_model.hpp | 2 +- .../tests/main_tests/hmm_train_test.cpp | 24 +++++++------------ 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/src/mlpack/methods/hmm/hmm_impl.hpp b/src/mlpack/methods/hmm/hmm_impl.hpp index 62bc9a2930..5fc711f687 100644 --- a/src/mlpack/methods/hmm/hmm_impl.hpp +++ b/src/mlpack/methods/hmm/hmm_impl.hpp @@ -175,6 +175,8 @@ void HMM::Train(const std::vector& dataSeq) break; } + oldLoglik = loglik; + // Normalize the new initial probabilities. if (dataSeq.size() > 1) initial = newInitial / dataSeq.size(); @@ -203,14 +205,6 @@ void HMM::Train(const std::vector& dataSeq) Log::Debug << "Iteration " << iter << ": log-likelihood " << loglik << "." << std::endl; - - if (std::abs(oldLoglik - loglik) < tolerance) - { - Log::Debug << "Converged after " << iter << " iterations." << std::endl; - break; - } - - oldLoglik = loglik; } } diff --git a/src/mlpack/methods/hmm/hmm_model.hpp b/src/mlpack/methods/hmm/hmm_model.hpp index d3693a9cad..05e7208257 100644 --- a/src/mlpack/methods/hmm/hmm_model.hpp +++ b/src/mlpack/methods/hmm/hmm_model.hpp @@ -184,7 +184,7 @@ class HMMModel * * Hence, in practice, a user should be careful to first check the type of HMM * (by calling the Type() accessor) and then perform subsequent actions, to - * avoid running into NullPointerExceptions. + * avoid null pointer dereferences. */ HMM* DiscreteHMM() { return discreteHMM; } HMM* GaussianHMM() { return gaussianHMM; } diff --git a/src/mlpack/tests/main_tests/hmm_train_test.cpp b/src/mlpack/tests/main_tests/hmm_train_test.cpp index c2e855434b..40471ebad1 100644 --- a/src/mlpack/tests/main_tests/hmm_train_test.cpp +++ b/src/mlpack/tests/main_tests/hmm_train_test.cpp @@ -72,7 +72,7 @@ inline void CheckMatricesDiffer(arma::mat& a, arma::mat& b, double tolerance) inline void ApproximatelyEqual(HMMModel& h1, HMMModel& h2, - double tolerance=1.0) + double tolerance = 1.0) { BOOST_REQUIRE(h1.Type() == h2.Type()); HMMType hmmType = h1.Type(); @@ -81,13 +81,11 @@ inline void ApproximatelyEqual(HMMModel& h1, CheckMatrices( h1.DiscreteHMM()->Transition()*100, h2.DiscreteHMM()->Transition()*100, - tolerance - ); + tolerance); CheckMatrices( h1.DiscreteHMM()->Transition()*100, h2.DiscreteHMM()->Transition()*100, - tolerance - ); + tolerance); // Check if emission dists are equal std::vector d1 = @@ -109,13 +107,11 @@ inline void ApproximatelyEqual(HMMModel& h1, CheckMatrices( h1.GaussianHMM()->Transition()*100, h2.GaussianHMM()->Transition()*100, - tolerance - ); + tolerance); CheckMatrices( h1.GaussianHMM()->Initial()*100, h2.GaussianHMM()->Initial()*100, - tolerance - ); + tolerance); // Check if emission dists are equal by comparing the mean and coviariance std::vector d1 = h1.GaussianHMM()->Emission(); @@ -136,13 +132,11 @@ inline void ApproximatelyEqual(HMMModel& h1, CheckMatrices( h1.GMMHMM()->Transition()*100, h2.GMMHMM()->Transition()*100, - tolerance - ); + tolerance); CheckMatrices( h1.GMMHMM()->Initial()*100, h2.GMMHMM()->Initial()*100, - tolerance - ); + tolerance); // Check if emission dists are equal std::vector d1 = h1.GMMHMM()->Emission(); std::vector d2 = h2.GMMHMM()->Emission(); @@ -251,13 +245,13 @@ BOOST_AUTO_TEST_CASE(HMMTrainReuseDiscreteModelTest) FileExists(inputObsFileName); FileExists(inputLabFileName); - // Make sure that the size of the + // Make sure that the size of the // training seq, and training labels is same arma::mat trainObs, trainLab; data::Load(inputObsFileName, trainObs); data::Load(inputLabFileName, trainLab); BOOST_REQUIRE_EQUAL(trainObs.n_rows, trainLab.n_rows); - + SetInputParam("input_file", std::move(inputObsFileName)); SetInputParam("labels_file", std::move(inputLabFileName)); SetInputParam("type", std::move(hmmType)); From 969a200e5df69ddc7573481764d0264dd8ef194a Mon Sep 17 00:00:00 2001 From: daivik Date: Thu, 1 Mar 2018 10:14:44 +0000 Subject: [PATCH 19/19] Should pass all checks now --- src/mlpack/methods/hmm/hmm_model.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mlpack/methods/hmm/hmm_model.hpp b/src/mlpack/methods/hmm/hmm_model.hpp index 05e7208257..f87c52d61c 100644 --- a/src/mlpack/methods/hmm/hmm_model.hpp +++ b/src/mlpack/methods/hmm/hmm_model.hpp @@ -94,6 +94,9 @@ class HMMModel //! Copy assignment operator. HMMModel& operator=(const HMMModel& other) { + if (this == &other) + return *this; + delete discreteHMM; delete gaussianHMM; delete gmmHMM;