diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index b29dded095..79e41b787c 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -87,6 +87,7 @@ Copyright: Copyright 2017, Manish Kumar Copyright 2017, Haritha Sreedharan Nair Copyright 2017&2018, Sourabh Varshney + Copyright 2018, Nikhil Goel License: BSD-3-clause All rights reserved. diff --git a/src/mlpack/core.hpp b/src/mlpack/core.hpp index 902f5b02d1..918421a6e7 100644 --- a/src/mlpack/core.hpp +++ b/src/mlpack/core.hpp @@ -230,6 +230,7 @@ * - Manish Kumar * - Haritha Sreedharan Nair * - Sourabh Varshney + * - Nikhil Goel */ // First, include all of the prerequisites. diff --git a/src/mlpack/tests/CMakeLists.txt b/src/mlpack/tests/CMakeLists.txt index 34a5f3c099..a6ecd61d52 100644 --- a/src/mlpack/tests/CMakeLists.txt +++ b/src/mlpack/tests/CMakeLists.txt @@ -125,6 +125,7 @@ add_executable(mlpack_test vantage_point_tree_test.cpp main_tests/test_helper.hpp main_tests/emst_test.cpp + main_tests/adaboost_test.cpp main_tests/decision_tree_test.cpp main_tests/decision_stump_test.cpp main_tests/linear_regression_test.cpp diff --git a/src/mlpack/tests/main_tests/adaboost_test.cpp b/src/mlpack/tests/main_tests/adaboost_test.cpp new file mode 100644 index 0000000000..4d4a3e197a --- /dev/null +++ b/src/mlpack/tests/main_tests/adaboost_test.cpp @@ -0,0 +1,415 @@ +/** + * @file adaboost_test.cpp + * @author Nikhil Goel + * + * Test mlpackMain() of adaboost_main.cpp. + */ +#include + +#define BINDING_TYPE BINDING_TYPE_TEST +static const std::string testName = "AdaBoost"; + +#include +#include +#include "test_helper.hpp" +#include + +#include +#include "../test_tools.hpp" + +using namespace mlpack; + +struct AdaBoostTestFixture +{ + public: + AdaBoostTestFixture() + { + // Cache in the options for this program. + CLI::RestoreSettings(testName); + } + + ~AdaBoostTestFixture() + { + // Clear the settings. + bindings::tests::CleanMemory(); + CLI::ClearSettings(); + } +}; + +BOOST_FIXTURE_TEST_SUITE(AdaBoostMainTest, AdaBoostTestFixture); + +/** + * Check that number of output labels and number of input + * points are equal. + */ +BOOST_AUTO_TEST_CASE(AdaBoostOutputDimensionTest) +{ + arma::mat trainData; + if (!data::Load("vc2.csv", trainData)) + BOOST_FAIL("Unable to load train dataset vc2.csv!"); + + arma::Row labels; + if (!data::Load("vc2_labels.txt", labels)) + BOOST_FAIL("Unable to load label dataset vc2_labels.txt!"); + + arma::mat testData; + if (!data::Load("vc2_test.csv", testData)) + BOOST_FAIL("Unable to load test dataset vc2.csv!"); + + size_t testSize = testData.n_cols; + + SetInputParam("training", std::move(trainData)); + SetInputParam("labels", std::move(labels)); + + SetInputParam("test", std::move(testData)); + + mlpackMain(); + + // Check that number of predicted labels is equal to the input test points. + BOOST_REQUIRE_EQUAL(CLI::GetParam>("output").n_cols, + testSize); + BOOST_REQUIRE_EQUAL(CLI::GetParam>("output").n_rows, 1); +} + +/** + * Ensure that saved model can be used again. + */ +BOOST_AUTO_TEST_CASE(AdaBoostModelReuseTest) +{ + arma::mat trainData; + if (!data::Load("vc2.csv", trainData)) + BOOST_FAIL("Unable to load train dataset vc2.csv!"); + + arma::Row labels; + if (!data::Load("vc2_labels.txt", labels)) + BOOST_FAIL("Unable to load label dataset vc2_labels.txt!"); + + arma::mat testData; + if (!data::Load("vc2_test.csv", testData)) + BOOST_FAIL("Unable to load test dataset vc2.csv!"); + + SetInputParam("training", std::move(trainData)); + SetInputParam("labels", std::move(labels)); + + SetInputParam("test", testData); + + mlpackMain(); + + arma::Row output; + output = std::move(CLI::GetParam>("output")); + + CLI::GetSingleton().Parameters()["training"].wasPassed = false; + CLI::GetSingleton().Parameters()["labels"].wasPassed = false; + CLI::GetSingleton().Parameters()["test"].wasPassed = false; + + SetInputParam("test", std::move(testData)); + SetInputParam("input_model", + CLI::GetParam("output_model")); + + mlpackMain(); + + // Check that initial output and output using saved model are same. + CheckMatrices(output, CLI::GetParam>("output")); +} + +/** + * Test that iterations in adaboost is always non-negative. + */ +BOOST_AUTO_TEST_CASE(AdaBoostItrTest) +{ + arma::mat trainData; + if (!data::Load("trainSet.csv", trainData)) + BOOST_FAIL("Unable load train dataset trainSet.csv!"); + + SetInputParam("training", std::move(trainData)); + SetInputParam("iterations", (int) -1); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +/** + * Check that the last dimension of the training set is + * used as labels when labels are not passed specifically + * and results are same from both label and without label models. + */ +BOOST_AUTO_TEST_CASE(AdaBoostWithoutLabelTest) +{ + // Train adaboost without providing labels. + arma::mat trainData; + if (!data::Load("trainSet.csv", trainData)) + BOOST_FAIL("Unable to load train dataset trainSet.csv!"); + + // Give labels. + arma::Row labels(trainData.n_cols); + for (size_t i = 0; i < trainData.n_cols; ++i) + labels[i] = trainData(trainData.n_rows - 1, i); + + arma::mat testData; + if (!data::Load("testSet.csv", testData)) + BOOST_FAIL("Unable to load test dataset testSet.csv!"); + + // Delete the last row containing labels from test dataset. + testData.shed_row(testData.n_rows - 1); + + SetInputParam("training", trainData); + + SetInputParam("test", testData); + + mlpackMain(); + + CLI::GetSingleton().Parameters()["training"].wasPassed = false; + CLI::GetSingleton().Parameters()["test"].wasPassed = false; + + arma::Row output; + output = std::move(CLI::GetParam>("output")); + + bindings::tests::CleanMemory(); + + trainData.shed_row(trainData.n_rows - 1); + + // Now train Adaboost with labels provided. + SetInputParam("training", std::move(trainData)); + SetInputParam("test", std::move(testData)); + SetInputParam("labels", std::move(labels)); + + mlpackMain(); + + // Check that initial output and final output matrix are same. + CheckMatrices(output, CLI::GetParam>("output")); +} + +/** + * Testing that only one of training data or pre-trained model is passed. + */ +BOOST_AUTO_TEST_CASE(AdaBoostTrainingDataOrModelTest) +{ + arma::mat trainData; + if (!data::Load("trainSet.csv", trainData)) + BOOST_FAIL("Unable to load train dataset trainSet.csv!"); + + SetInputParam("training", std::move(trainData)); + + mlpackMain(); + + SetInputParam("input_model", + CLI::GetParam("output_model")); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +/** + * Weak learner should be either Decision Stump or Perceptron. + */ +BOOST_AUTO_TEST_CASE(AdaBoostWeakLearnerTest) +{ + arma::mat trainData; + if (!data::Load("trainSet.csv", trainData)) + BOOST_FAIL("Unable to load train dataset trainSet.csv!"); + + SetInputParam("training", std::move(trainData)); + SetInputParam("weak_learner", std::string("decision tree")); + + Log::Fatal.ignoreInput = true; + BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +/** + * Different Weak learner should give different outputs. + */ +BOOST_AUTO_TEST_CASE(AdaBoostDiffWeakLearnerOutputTest) +{ + arma::mat trainData; + if (!data::Load("vc2.csv", trainData)) + BOOST_FAIL("Unable to load train dataset vc2.csv!"); + + arma::Row labels; + if (!data::Load("vc2_labels.txt", labels)) + BOOST_FAIL("Unable to load label dataset vc2_labels.txt!"); + + arma::mat testData; + if (!data::Load("vc2_test.csv", testData)) + BOOST_FAIL("Unable to load test dataset vc2.csv!"); + + SetInputParam("training", trainData); + SetInputParam("labels", labels); + SetInputParam("test", testData); + + mlpackMain(); + + arma::Row output; + output = std::move(CLI::GetParam>("output")); + + bindings::tests::CleanMemory(); + + CLI::GetSingleton().Parameters()["training"].wasPassed = false; + CLI::GetSingleton().Parameters()["labels"].wasPassed = false; + CLI::GetSingleton().Parameters()["test"].wasPassed = false; + + SetInputParam("training", trainData); + SetInputParam("labels", labels); + SetInputParam("test", testData); + SetInputParam("weak_learner", std::string("perceptron")); + + mlpackMain(); + + arma::Row outputPerceptron; + outputPerceptron = std::move(CLI::GetParam>("output")); + + BOOST_REQUIRE_GT(arma::accu(output != outputPerceptron), 1); +} + +/** + * Accuracy increases as Number of Iterations increases. + * (Or converges and remains same) + */ +BOOST_AUTO_TEST_CASE(AdaBoostDiffItrTest) +{ + arma::mat trainData; + if (!data::Load("vc2.csv", trainData)) + BOOST_FAIL("Unable to load train dataset vc2.csv!"); + + arma::Row labels; + if (!data::Load("vc2_labels.txt", labels)) + BOOST_FAIL("Unable to load label dataset vc2_labels.txt!"); + + arma::mat testData; + if (!data::Load("vc2_test.csv", testData)) + BOOST_FAIL("Unable to load test dataset vc2.csv!"); + + arma::Row testLabels; + if (!data::Load("vc2_test_labels.txt", testLabels)) + BOOST_FAIL("Unable to load labels for vc2__test_labels.txt"); + + // Iterations = 1 + SetInputParam("training", trainData); + SetInputParam("labels", labels); + SetInputParam("weak_learner", std::string("perceptron")); + SetInputParam("iterations", (int) 1); + + mlpackMain(); + + // Calculate accuracy. + arma::Row output; + CLI::GetParam("output_model")->Classify(testData, + output); + + size_t correct = arma::accu(output == testLabels); + double accuracy1 = (double(correct) / double(testLabels.n_elem) * 100); + + bindings::tests::CleanMemory(); + + // Iterations = 10 + SetInputParam("training", trainData); + SetInputParam("labels", labels); + SetInputParam("weak_learner", std::string("perceptron")); + SetInputParam("iterations", (int) 10); + + mlpackMain(); + + // Calculate accuracy. + CLI::GetParam("output_model")->Classify(testData, + output); + + correct = arma::accu(output == testLabels); + double accuracy10 = (double(correct) / double(testLabels.n_elem) * 100); + + bindings::tests::CleanMemory(); + + // Iterations = 100 + SetInputParam("training", trainData); + SetInputParam("labels", labels); + SetInputParam("weak_learner", std::string("perceptron")); + SetInputParam("iterations", (int) 100); + + mlpackMain(); + + // Calculate accuracy. + CLI::GetParam("output_model")->Classify(testData, + output); + + correct = arma::accu(output == testLabels); + double accuracy100 = (double(correct) / double(testLabels.n_elem) * 100); + + BOOST_REQUIRE_LE(accuracy1, accuracy10); + BOOST_REQUIRE_LE(accuracy10, accuracy100); +} + +/** + * Accuracy increases as tolerance decreases. + * (Execution Time also increases) + */ +BOOST_AUTO_TEST_CASE(AdaBoostDiffTolTest) +{ + arma::mat trainData; + if (!data::Load("vc2.csv", trainData)) + BOOST_FAIL("Unable to load train dataset vc2.csv!"); + + arma::Row labels; + if (!data::Load("vc2_labels.txt", labels)) + BOOST_FAIL("Unable to load label dataset vc2_labels.txt!"); + + arma::mat testData; + if (!data::Load("vc2_test.csv", testData)) + BOOST_FAIL("Unable to load test dataset vc2.csv!"); + + arma::Row testLabels; + if (!data::Load("vc2_test_labels.txt", testLabels)) + BOOST_FAIL("Unable to load labels for vc2__test_labels.txt"); + + // tolerance = 0.001 + SetInputParam("training", trainData); + SetInputParam("labels", labels); + SetInputParam("tolerance", (double) 0.001); + + mlpackMain(); + + // Calculate accuracy. + arma::Row output; + CLI::GetParam("output_model")->Classify(testData, + output); + + size_t correct = arma::accu(output == testLabels); + double accuracy1 = (double(correct) / double(testLabels.n_elem) * 100); + + bindings::tests::CleanMemory(); + + // tolerance = 0.01 + SetInputParam("training", trainData); + SetInputParam("labels", labels); + SetInputParam("tolerance", (double) 0.01); + + mlpackMain(); + + // Calculate accuracy. + CLI::GetParam("output_model")->Classify(testData, + output); + + correct = arma::accu(output == testLabels); + double accuracy2 = (double(correct) / double(testLabels.n_elem) * 100); + + bindings::tests::CleanMemory(); + + // tolerance = 0.1 + SetInputParam("training", trainData); + SetInputParam("labels", labels); + SetInputParam("tolerance", (double) 0.1); + + mlpackMain(); + + // Calculate accuracy. + CLI::GetParam("output_model")->Classify(testData, + output); + + correct = arma::accu(output == testLabels); + double accuracy3 = (double(correct) / double(testLabels.n_elem) * 100); + + BOOST_REQUIRE_LE(accuracy1, accuracy2); + BOOST_REQUIRE_LE(accuracy2, accuracy3); +} + +BOOST_AUTO_TEST_SUITE_END();