Files
mlpack/src/mlpack/tests/cv_test.cpp
T

781 lines
26 KiB
C++

/**
* @file tests/cv_test.cpp
*
* Unit tests for the cross-validation module.
*
* mlpack is free software; you may redistribute it and/or modify it under the
* terms of the 3-clause BSD license. You should have received a copy of the
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*/
#include <type_traits>
#include <mlpack/core/cv/meta_info_extractor.hpp>
#include <mlpack/core/cv/metrics/accuracy.hpp>
#include <mlpack/core/cv/metrics/f1.hpp>
#include <mlpack/core/cv/metrics/mse.hpp>
#include <mlpack/core/cv/metrics/precision.hpp>
#include <mlpack/core/cv/metrics/recall.hpp>
#include <mlpack/core/cv/metrics/r2_score.hpp>
#include <mlpack/core/cv/metrics/silhouette_score.hpp>
#include <mlpack/core/cv/simple_cv.hpp>
#include <mlpack/core/cv/k_fold_cv.hpp>
#include <mlpack/methods/ann/ffn.hpp>
#include <mlpack/methods/ann/init_rules/const_init.hpp>
#include <mlpack/methods/ann/layer/layer_types.hpp>
#include <mlpack/methods/ann/loss_functions/mean_squared_error.hpp>
#include <mlpack/methods/decision_tree/decision_tree.hpp>
#include <mlpack/methods/decision_tree/information_gain.hpp>
#include <mlpack/methods/hoeffding_trees/hoeffding_tree.hpp>
#include <mlpack/methods/lars/lars.hpp>
#include <mlpack/methods/linear_regression/linear_regression.hpp>
#include <mlpack/methods/logistic_regression/logistic_regression.hpp>
#include <mlpack/methods/naive_bayes/naive_bayes_classifier.hpp>
#include <mlpack/methods/softmax_regression/softmax_regression.hpp>
#include <mlpack/core/data/confusion_matrix.hpp>
#include <ensmallen.hpp>
#include "catch.hpp"
#include "mock_categorical_data.hpp"
using namespace mlpack;
using namespace mlpack::ann;
using namespace mlpack::cv;
using namespace mlpack::naive_bayes;
using namespace mlpack::regression;
using namespace mlpack::tree;
using namespace mlpack::data;
/**
* Test metrics for binary classification.
*/
TEST_CASE("BinaryClassificationMetricsTest", "[CVTest]")
{
// Using the same data for training and testing.
arma::mat data = arma::linspace<arma::rowvec>(1.0, 10.0, 10);
// Labels that will be considered as "ground truth".
arma::Row<size_t> labels("0 0 1 0 0 1 0 1 0 1");
// Labels that make the data linearly separable. These labels will be
// predicted in response to the data since we use them for training.
arma::Row<size_t> predictedLabels("0 0 0 0 0 1 1 1 1 1");
LogisticRegression<> lr(data, predictedLabels);
REQUIRE(Accuracy::Evaluate(lr, data, labels) == Approx(0.7).epsilon(1e-7));
REQUIRE(Precision<Binary>::Evaluate(lr, data, labels)
== Approx(0.6).epsilon(1e-7));
REQUIRE(Recall<Binary>::Evaluate(lr, data, labels)
== Approx(0.75).epsilon(1e-7));
double f1 = 2 * 0.6 * 0.75 / (0.6 + 0.75);
REQUIRE(F1<Binary>::Evaluate(lr, data, labels) == Approx(f1).epsilon(1e-7));
}
/**
* Test for confusion matrix.
*/
TEST_CASE("ConfusionMatrixTest", "[CVTest]")
{
// Labels that will be considered as "ground truth".
arma::Row<size_t> labels("0 0 1 0 0 1 0 1 0 1");
// Predicted labels.
arma::Row<size_t> predictedLabels("0 0 0 0 0 1 1 1 1 1");
// Confusion matrix.
arma::Mat<int> output;
data::ConfusionMatrix(predictedLabels, labels, output, 2);
REQUIRE(output(0, 0) == 4);
REQUIRE(output(0, 1) == 1);
REQUIRE(output(1, 0) == 2);
REQUIRE(output(1, 1) == 3);
}
/**
* Test metrics for multiclass classification.
*/
TEST_CASE("MulticlassClassificationMetricsTest", "[CVTest]")
{
// Using the same data for training and testing.
arma::mat data = arma::linspace<arma::rowvec>(1.0, 12.0, 12);
// Labels that will be considered as "ground truth".
arma::Row<size_t> labels("0 1 0 1 2 2 1 2 3 3 3 3");
// These labels should be predicted in response to the data since we use them
// for training.
arma::Row<size_t> predictedLabels("0 0 1 1 2 2 2 2 3 3 3 3");
size_t numClasses = 4;
NaiveBayesClassifier<> nb(data, predictedLabels, numClasses);
// Assert that the Naive Bayes model really predicts the labels above in
// response to the data.
REQUIRE(Accuracy::Evaluate(nb, data, predictedLabels)
== Approx(1.0).epsilon(1e-7));
double microaveragedPrecision = double(1 + 1 + 3 + 4) / 12;
REQUIRE(Precision<Micro>::Evaluate(nb, data, labels)
== Approx(microaveragedPrecision).epsilon(1e-7));
double microaveragedRecall = double(1 + 1 + 3 + 4) / 12;
REQUIRE(Recall<Micro>::Evaluate(nb, data, labels)
== Approx(microaveragedRecall).epsilon(1e-7));
double microaveragedF1 = 2 * microaveragedPrecision * microaveragedRecall /
(microaveragedPrecision + microaveragedRecall);
REQUIRE(F1<Micro>::Evaluate(nb, data, labels)
== Approx(microaveragedF1).epsilon(1e-7));
double macroaveragedPrecision = (0.5 + 0.5 + 0.75 + 1.0) / 4;
REQUIRE(Precision<Macro>::Evaluate(nb, data, labels)
== Approx(macroaveragedPrecision).epsilon(1e-7));
double macroaveragedRecall = (0.5 + 1.0 / 3 + 1.0 + 1.0) / 4;
REQUIRE(Recall<Macro>::Evaluate(nb, data, labels)
== Approx(macroaveragedRecall).epsilon(1e-7));
double macroaveragedF1 = (2 * 0.5 * 0.5 / (0.5 + 0.5) +
2 * 0.5 * (1.0 / 3) / (0.5 + (1.0 / 3)) + 2 * 0.75 * 1.0 / (0.75 + 1.0) +
2 * 1.0 * 1.0 / (1.0 + 1.0)) / 4;
REQUIRE(F1<Macro>::Evaluate(nb, data, labels)
== Approx(macroaveragedF1).epsilon(1e-7));
}
/**
* Test the mean squared error.
*/
TEST_CASE("MSETest", "[CVTest]")
{
// Making two points that define the linear function f(x) = x - 1
arma::mat trainingData("0 1");
arma::rowvec trainingResponses("-1 0");
LinearRegression lr(trainingData, trainingResponses);
// Making three responses that differ from the correct ones by 0, 1, and 2
// respectively
arma::mat data("2 3 4");
arma::rowvec responses("1 3 5");
double expectedMSE = (0 * 0 + 1 * 1 + 2 * 2) / 3.0;
REQUIRE(MSE::Evaluate(lr, data, responses)
== Approx(expectedMSE).epsilon(1e-7));
}
/**
* Test the R squared metric (R2 Score).
*/
TEST_CASE("R2ScoreTest", "[CVTest]")
{
// Making two points that define the linear function f(x) = x - 1.
arma::mat trainingData("0 1");
arma::rowvec trainingResponses("-1 0");
LinearRegression lr(trainingData, trainingResponses);
// Making five responses that are the output of regression function f(x)
// with some responses having a slight deviation of 0.005.
// Mean Responses = (1 + 2 + 3 + 6 + 8)/5 = 4.
arma::mat data("2 3 4 7 9");
arma::rowvec responses("1 2.005 3 6.005 8.005");
double expectedR2 = 0.99999779;
REQUIRE(R2Score<false>::Evaluate(lr, data, responses)
== Approx(expectedR2).epsilon(1e-7));
}
/**
* Test the Adjusted R squared metric.
*/
TEST_CASE("AdjR2ScoreTest", "[CVTest]")
{
// Making two variables that define the linear function is
// f(x1, x2) = x1 + x2.
arma::mat X;
X = { { 1, 2, 3, 4, 5, 6 },
{ 2, 3, 4, 5, 6, 7 } };
arma::rowvec Y;
Y = { 3, 5, 7, 9, 11, 13 };
LinearRegression lr(X, Y);
// Theoretically Adjusted R squared should be equal 1
double expAdjR2 = 1;
REQUIRE(std::abs(R2Score<true>::Evaluate(lr, X, Y) - expAdjR2)
<= 1e-7);
}
/**
* Test the mean squared error with matrix responses.
*/
TEST_CASE("MSEMatResponsesTest", "[CVTest]")
{
arma::mat data("1 2");
arma::mat trainingResponses("1 2; 3 4");
FFN<MeanSquaredError, ConstInitialization> ffn(MeanSquaredError(),
ConstInitialization(0));
ffn.Add<Linear>(2);
ens::RMSProp opt(0.2);
opt.BatchSize() = 1;
opt.Shuffle() = false;
ffn.Train(data, trainingResponses, opt);
// Making four responses that differ from the correct ones by 0, 1, 2 and 3
// respectively
arma::mat responses("1 3; 5 7");
double expectedMSE = (0 * 0 + 1 * 1 + 2 * 2 + 3 * 3) / 4.0;
REQUIRE(MSE::Evaluate(ffn, data, responses)
== Approx(expectedMSE).epsilon(1e-3));
}
template<typename Class,
typename ExpectedPT,
typename PassedMT = arma::mat,
typename PassedPT = arma::Row<size_t>>
void CheckPredictionsType()
{
using Extractor = MetaInfoExtractor<Class, PassedMT, PassedPT>;
using ActualPT = typename Extractor::PredictionsType;
static_assert(std::is_same<ExpectedPT, ActualPT>::value,
"Should be the same");
}
/**
* Test MetaInfoExtractor correctly recognizes the type of predictions for a
* given machine learning algorithm.
*/
TEST_CASE("PredictionsTypeTest", "[CVTest]")
{
CheckPredictionsType<LinearRegression, arma::rowvec>();
// CheckPredictionsType<FFN<>, arma::mat>();
CheckPredictionsType<LogisticRegression<>, arma::Row<size_t>>();
CheckPredictionsType<SoftmaxRegression, arma::Row<size_t>>();
CheckPredictionsType<HoeffdingTree<>, arma::Row<size_t>, arma::mat>();
CheckPredictionsType<HoeffdingTree<>, arma::Row<size_t>, arma::imat>();
CheckPredictionsType<DecisionTree<>, arma::Row<size_t>, arma::mat,
arma::Row<size_t>>();
CheckPredictionsType<DecisionTree<>, arma::Row<char>, arma::mat,
arma::Row<char>>();
}
/**
* Test MetaInfoExtractor correctly identifies whether a given machine learning
* algorithm supports weighted learning.
*/
TEST_CASE("SupportsWeightsTest", "[CVTest]")
{
static_assert(MetaInfoExtractor<LinearRegression>::SupportsWeights,
"Value should be true");
static_assert(MetaInfoExtractor<DecisionTree<>>::SupportsWeights,
"Value should be true");
static_assert(MetaInfoExtractor<DecisionTree<>, arma::mat, arma::urowvec,
arma::Row<float>>::SupportsWeights, "Value should be true");
static_assert(!MetaInfoExtractor<LARS>::SupportsWeights,
"Value should be false");
static_assert(!MetaInfoExtractor<LogisticRegression<>>::SupportsWeights,
"Value should be false");
}
template<typename Class,
typename ExpectedWT,
typename PassedMT = arma::mat,
typename PassedPT = arma::Row<size_t>,
typename PassedWT = arma::rowvec>
void CheckWeightsType()
{
using Extractor = MetaInfoExtractor<Class, PassedMT, PassedPT, PassedWT>;
using ActualWT = typename Extractor::WeightsType;
static_assert(std::is_same<ExpectedWT, ActualWT>::value,
"Should be the same");
}
/**
* Test MetaInfoExtractor correctly recognizes the type of weights for a given
* machine learning algorithm.
*/
TEST_CASE("WeightsTypeTest", "[CVTest]")
{
CheckWeightsType<LinearRegression, arma::rowvec>();
CheckWeightsType<DecisionTree<>, arma::rowvec>();
CheckWeightsType<DecisionTree<>, arma::Row<float>, arma::mat,
arma::Row<size_t>, arma::Row<float>>();
}
/**
* Test MetaInfoExtractor correctly identifies whether a given machine learning
* algorithm takes a data:DatasetInfo parameter.
*/
TEST_CASE("TakesDatasetInfoTest", "[CVTest]")
{
static_assert(MetaInfoExtractor<DecisionTree<>>::TakesDatasetInfo,
"Value should be true");
static_assert(!MetaInfoExtractor<LinearRegression>::TakesDatasetInfo,
"Value should be false");
static_assert(!MetaInfoExtractor<SoftmaxRegression>::TakesDatasetInfo,
"Value should be false");
}
/**
* Test MetaInfoExtractor correctly identifies whether a given machine learning
* algorithm takes the numClasses parameter.
*/
TEST_CASE("TakesNumClassesTest", "[CVTest]")
{
static_assert(MetaInfoExtractor<DecisionTree<>>::TakesNumClasses,
"Value should be true");
static_assert(MetaInfoExtractor<SoftmaxRegression>::TakesNumClasses,
"Value should be true");
static_assert(!MetaInfoExtractor<LinearRegression>::TakesNumClasses,
"Value should be false");
static_assert(!MetaInfoExtractor<LARS>::TakesNumClasses,
"Value should be false");
}
/**
* Test the simple cross-validation strategy implementation with the Accuracy
* metric.
*/
TEST_CASE("SimpleCVAccuracyTest", "[CVTest]")
{
// Using the first half of data for training and the rest for validation.
// The validation labels are 75% correct.
arma::mat data =
arma::mat("1 0; 2 0; 1 1; 2 1; 1 0; 2 0; 1 1; 2 1").t();
arma::Row<size_t> labels("0 0 1 1 0 1 1 1");
SimpleCV<LogisticRegression<>, Accuracy> cv(0.5, data, labels);
REQUIRE(cv.Evaluate() == Approx(0.75).epsilon(1e-7));
}
/**
* Test the simple cross-validation strategy implementation with the MSE metric.
*/
TEST_CASE("SimpleCVMSETest", "[CVTest]")
{
// Using the first two points for training and remaining three for validation.
// See the test MSETest for more explanation.
arma::mat data("0 1 2 3 4");
arma::rowvec responses("-1 0 1 3 5");
double expectedMSE = (0 * 0 + 1 * 1 + 2 * 2) / 3.0;
SimpleCV<LinearRegression, MSE> cv(0.6, data, responses);
REQUIRE(cv.Evaluate() == Approx(expectedMSE).epsilon(1e-7));
arma::mat noiseData("-1 -2 -3 -4 -5");
arma::rowvec noiseResponses("10 20 30 40 50");
arma::mat allData = arma::join_rows(noiseData, data);
arma::rowvec allResponces = arma::join_rows(noiseResponses, responses);
arma::rowvec weights = arma::join_rows(arma::zeros(noiseData.n_cols).t(),
arma::ones(data.n_cols).t());
SimpleCV<LinearRegression, MSE> weightedCV(0.3, allData, allResponces,
weights);
REQUIRE(weightedCV.Evaluate() == Approx(expectedMSE).epsilon(1e-7));
arma::rowvec weights2 = arma::join_rows(arma::zeros(noiseData.n_cols - 1).t(),
arma::ones(data.n_cols + 1).t());
SimpleCV<LinearRegression, MSE> weightedCV2(0.3, allData, allResponces,
weights2);
REQUIRE(std::abs(weightedCV2.Evaluate() - expectedMSE) > 1e-5);
}
/**
* Test that scores of -nan are filtered out.
*/
TEST_CASE("FilterNANCVTest", "[CVTest]")
{
// Create a dataset with only one positive label, so it will not be in every
// fold.
arma::mat data(3, 10, arma::fill::randu);
arma::Row<size_t> labels(10, arma::fill::zeros);
labels[0] = 1;
const size_t numClasses = 2;
KFoldCV<NaiveBayesClassifier<>, F1<Binary>> kfoldcv(2, data, labels,
numClasses);
const double result = kfoldcv.Evaluate();
REQUIRE(!std::isnan(result));
REQUIRE(!std::isinf(result));
}
template<typename... DTArgs>
arma::Row<size_t> PredictLabelsWithDT(const arma::mat& data,
const DTArgs&... args)
{
DecisionTree<InformationGain> dt(args...);
arma::Row<size_t> predictedLabels;
dt.Classify(data, predictedLabels);
return predictedLabels;
}
/**
* Test the simple cross-validation strategy implementation with decision trees
* constructed in multiple ways.
*/
TEST_CASE("SimpleCVWithDTTest", "[CVTest]")
{
arma::mat data;
arma::Row<size_t> labels;
data::DatasetInfo datasetInfo;
MockCategoricalData(data, labels, datasetInfo);
arma::mat trainingData = data.cols(0, 1999);
arma::mat testData = data.cols(2000, 3999);
arma::Row<size_t> trainingLabels = labels.subvec(0, 1999);
arma::rowvec weights(4000, arma::fill::randu);
size_t numClasses = 5;
size_t minimumLeafSize = 8;
{
arma::Row<size_t> predictedLabels = PredictLabelsWithDT(testData,
trainingData, trainingLabels, numClasses, minimumLeafSize);
SimpleCV<DecisionTree<InformationGain>, Accuracy> cv(0.5, data,
arma::join_rows(trainingLabels, predictedLabels), numClasses);
REQUIRE(cv.Evaluate(minimumLeafSize) == Approx(1.0).epsilon(1e-7));
}
{
arma::Row<size_t> predictedLabels = PredictLabelsWithDT(testData,
trainingData, datasetInfo, trainingLabels, numClasses, minimumLeafSize);
SimpleCV<DecisionTree<InformationGain>, Accuracy> cv(0.5, data, datasetInfo,
arma::join_rows(trainingLabels, predictedLabels), numClasses);
REQUIRE(cv.Evaluate(minimumLeafSize) == Approx(1.0).epsilon(1e-7));
}
{
arma::Row<size_t> predictedLabels = PredictLabelsWithDT(testData,
trainingData, trainingLabels, numClasses, weights, minimumLeafSize);
SimpleCV<DecisionTree<InformationGain>, Accuracy> cv(0.5, data,
arma::join_rows(trainingLabels, predictedLabels), numClasses, weights);
REQUIRE(cv.Evaluate(minimumLeafSize) == Approx(1.0).epsilon(1e-7));
}
{
arma::Row<size_t> predictedLabels = PredictLabelsWithDT(testData,
trainingData, datasetInfo, trainingLabels, numClasses, weights,
minimumLeafSize);
SimpleCV<DecisionTree<InformationGain>, Accuracy> cv(0.5, data, datasetInfo,
arma::join_rows(trainingLabels, predictedLabels), numClasses, weights);
REQUIRE(cv.Evaluate(minimumLeafSize) == Approx(1.0).epsilon(1e-7));
}
}
/**
* Test k-fold cross-validation with the MSE metric.
*/
TEST_CASE("KFoldCVMSETest", "[CVTest]")
{
// Defining dataset with two sets of responses for the same two data points.
arma::mat data("0 1 0 1");
arma::rowvec responses("0 1 1 3");
// 2-fold cross-validation, no shuffling.
KFoldCV<LinearRegression, MSE> cv(2, data, responses, false);
// In each of two validation tests the MSE value should be the same.
double expectedMSE =
double((1 - 0) * (1 - 0) + (3 - 1) * (3 - 1)) / 2 * 2 / 2;
REQUIRE(cv.Evaluate() == Approx(expectedMSE).epsilon(1e-7));
// Assert we can access a trained model without the exception of
// uninitialization.
REQUIRE_NOTHROW(cv.Model());
}
/**
* Test k-fold cross-validation with the Accuracy metric.
*/
TEST_CASE("KFoldCVAccuracyTest", "[CVTest]")
{
// Making a 10-points dataset. The last point should be classified wrong when
// it is tested separately.
arma::mat data("0 1 2 3 100 101 102 103 104 5");
arma::Row<size_t> labels("0 0 0 0 1 1 1 1 1 1");
size_t numClasses = 2;
// 10-fold cross-validation, no shuffling.
KFoldCV<NaiveBayesClassifier<>, Accuracy> cv(10, data, labels, numClasses,
false);
// We should succeed in classifying separately the first nine samples, and
// fail with the remaining one.
double expectedAccuracy = (9 * 1.0 + 0.0) / 10;
REQUIRE(cv.Evaluate() == Approx(expectedAccuracy).epsilon(1e-7));
// Assert we can access a trained model without the exception of
// uninitialization.
REQUIRE_NOTHROW(cv.Model());
}
/**
* Test k-fold cross-validation with weighted linear regression.
*/
TEST_CASE("KFoldCVWithWeightedLRTest", "[CVTest]")
{
// Each fold will be filled with this dataset.
arma::mat data("1 2 3 4");
arma::rowvec responses("1 2 30 40");
arma::rowvec weights("1 1 0 0");
KFoldCV<LinearRegression, MSE> cv(2, arma::join_rows(data, data),
arma::join_rows(responses, responses), arma::join_rows(weights, weights),
false);
cv.Evaluate();
arma::mat testData("3 4");
arma::rowvec testResponses("3 4");
double mse = MSE::Evaluate(cv.Model(), testData, testResponses);
REQUIRE((1.0 - mse) == Approx(1.0).epsilon(1e-7));
}
/**
* Test k-fold cross-validation with decision trees constructed in multiple
* ways.
*/
TEST_CASE("KFoldCVWithDTTest", "[CVTest]")
{
arma::mat originalData;
arma::Row<size_t> originalLabels;
data::DatasetInfo datasetInfo;
MockCategoricalData(originalData, originalLabels, datasetInfo);
// Each fold will be filled with this dataset.
arma::mat data = originalData.cols(0, 1199);
arma::Row<size_t> labels = originalLabels.cols(0, 1199);
arma::rowvec weights(data.n_cols, arma::fill::randu);
arma::mat doubledData = arma::join_rows(data, data);
arma::Row<size_t> doubledLabels = arma::join_rows(labels, labels);
arma::rowvec doubledWeights = arma::join_rows(weights, weights);
size_t numClasses = 5;
size_t minimumLeafSize = 8;
{
KFoldCV<DecisionTree<InformationGain>, Accuracy> cv(2, doubledData,
doubledLabels, numClasses, false);
cv.Evaluate(minimumLeafSize);
arma::Row<size_t> predictedLabels = PredictLabelsWithDT(data, data, labels,
numClasses, minimumLeafSize);
double accuracy = Accuracy::Evaluate(cv.Model(), data, predictedLabels);
REQUIRE(accuracy == Approx(1.0).epsilon(1e-7));
}
{
KFoldCV<DecisionTree<InformationGain>, Accuracy> cv(2, doubledData,
datasetInfo, doubledLabels, numClasses, false);
cv.Evaluate(minimumLeafSize);
arma::Row<size_t> predictedLabels = PredictLabelsWithDT(data, data,
datasetInfo, labels, numClasses, minimumLeafSize);
double accuracy = Accuracy::Evaluate(cv.Model(), data, predictedLabels);
REQUIRE(accuracy == Approx(1.0).epsilon(1e-7));
}
{
KFoldCV<DecisionTree<InformationGain>, Accuracy> cv(2, doubledData,
doubledLabels, numClasses, doubledWeights, false);
cv.Evaluate(minimumLeafSize);
arma::Row<size_t> predictedLabels = PredictLabelsWithDT(data, data, labels,
numClasses, weights, minimumLeafSize);
double accuracy = Accuracy::Evaluate(cv.Model(), data, predictedLabels);
REQUIRE(accuracy == Approx(1.0).epsilon(1e-7));
}
{
KFoldCV<DecisionTree<InformationGain>, Accuracy> cv(2, doubledData,
datasetInfo, doubledLabels, numClasses, doubledWeights, false);
cv.Evaluate(minimumLeafSize);
arma::Row<size_t> predictedLabels = PredictLabelsWithDT(data, data,
datasetInfo, labels, numClasses, weights, minimumLeafSize);
double accuracy = Accuracy::Evaluate(cv.Model(), data, predictedLabels);
REQUIRE(accuracy == Approx(1.0).epsilon(1e-7));
}
}
/**
* Test k-fold cross-validation with decision trees constructed in multiple
* ways, but with larger k and no shuffling.
*/
TEST_CASE("KFoldCVWithDTTestLargeKNoShuffle", "[CVTest]")
{
arma::mat data;
arma::Row<size_t> labels;
data::DatasetInfo datasetInfo;
MockCategoricalData(data, labels, datasetInfo);
size_t numClasses = 5;
size_t minimumLeafSize = 5;
KFoldCV<DecisionTree<InformationGain>, Accuracy> cv(5, data,
datasetInfo, labels, numClasses, false);
cv.Evaluate(minimumLeafSize);
double accuracy = Accuracy::Evaluate(cv.Model(), data, labels);
// This is a very loose tolerance, but we expect about the same as we would
// from an individual decision tree training.
REQUIRE(accuracy > 0.7);
}
/**
* Test k-fold cross-validation with decision trees constructed in multiple
* ways, but with larger k such that the number of points in each
* cross-validation bin is not even (the last is smaller), and also with no
* shuffling.
*/
TEST_CASE("KFoldCVWithDTTestUnevenBinsNoShuffle", "[CVTest]")
{
arma::mat data;
arma::Row<size_t> labels;
data::DatasetInfo datasetInfo;
MockCategoricalData(data, labels, datasetInfo);
size_t numClasses = 5;
size_t minimumLeafSize = 5;
KFoldCV<DecisionTree<InformationGain>, Accuracy> cv(7, data, datasetInfo,
labels, numClasses, false);
cv.Evaluate(minimumLeafSize);
double accuracy = Accuracy::Evaluate(cv.Model(), data, labels);
// This is a very loose tolerance, but we expect about the same as we would
// from an individual decision tree training.
REQUIRE(accuracy > 0.7);
}
/**
* Test k-fold cross-validation with decision trees constructed in multiple
* ways, but with larger k.
*/
TEST_CASE("KFoldCVWithDTTestLargeK", "[CVTest]")
{
arma::mat data;
arma::Row<size_t> labels;
data::DatasetInfo datasetInfo;
MockCategoricalData(data, labels, datasetInfo);
size_t numClasses = 5;
size_t minimumLeafSize = 5;
KFoldCV<DecisionTree<InformationGain>, Accuracy> cv(5, data,
datasetInfo, labels, numClasses);
cv.Evaluate(minimumLeafSize);
double accuracy = Accuracy::Evaluate(cv.Model(), data, labels);
// This is a very loose tolerance, but we expect about the same as we would
// from an individual decision tree training.
REQUIRE(accuracy > 0.7);
}
/**
* Test k-fold cross-validation with decision trees constructed in multiple
* ways, but with larger k such that the number of points in each
* cross-validation bin is not even (the last is smaller).
*/
TEST_CASE("KFoldCVWithDTTestUnevenBins", "[CVTest]")
{
arma::mat data;
arma::Row<size_t> labels;
data::DatasetInfo datasetInfo;
MockCategoricalData(data, labels, datasetInfo);
size_t numClasses = 5;
size_t minimumLeafSize = 5;
KFoldCV<DecisionTree<InformationGain>, Accuracy> cv(7, data, datasetInfo,
labels, numClasses);
cv.Evaluate(minimumLeafSize);
double accuracy = Accuracy::Evaluate(cv.Model(), data, labels);
// This is a very loose tolerance, but we expect about the same as we would
// from an individual decision tree training.
REQUIRE(accuracy > 0.7);
}
/**
* Test k-fold cross-validation with decision trees constructed in multiple
* ways, but with larger k and weights.
*/
TEST_CASE("KFoldCVWithDTTestLargeKWeighted", "[CVTest]")
{
arma::mat data;
arma::Row<size_t> labels;
data::DatasetInfo datasetInfo;
MockCategoricalData(data, labels, datasetInfo);
arma::rowvec weights(data.n_cols, arma::fill::randu);
size_t numClasses = 5;
size_t minimumLeafSize = 5;
KFoldCV<DecisionTree<InformationGain>, Accuracy> cv(5, data,
datasetInfo, labels, numClasses, weights);
cv.Evaluate(minimumLeafSize);
double accuracy = Accuracy::Evaluate(cv.Model(), data, labels);
// This is a very loose tolerance, but we expect about the same as we would
// from an individual decision tree training.
REQUIRE(accuracy > 0.7);
}
/**
* Test k-fold cross-validation with decision trees constructed in multiple
* ways, but with larger k such that the number of points in each
* cross-validation bin is not even (the last is smaller) and weights.
*/
TEST_CASE("KFoldCVWithDTTestUnevenBinsWeighted", "[CVTest]")
{
arma::mat data;
arma::Row<size_t> labels;
data::DatasetInfo datasetInfo;
MockCategoricalData(data, labels, datasetInfo);
arma::rowvec weights(data.n_cols, arma::fill::randu);
size_t numClasses = 5;
size_t minimumLeafSize = 5;
KFoldCV<DecisionTree<InformationGain>, Accuracy> cv(7, data, datasetInfo,
labels, numClasses, weights);
cv.Evaluate(minimumLeafSize);
double accuracy = Accuracy::Evaluate(cv.Model(), data, labels);
// This is a very loose tolerance, but we expect about the same as we would
// from an individual decision tree training.
REQUIRE(accuracy > 0.7);
}
/**
* Test Silhouette Score
*/
TEST_CASE("SilhouetteScoreTest", "[CVTest]")
{
arma::mat X;
X = { { 0, 1, 1, 0, 0 },
{ 0, 1, 2, 0, 0 },
{ 1, 1, 3, 2, 0 } };
arma::Row<size_t> labels = { 0, 1, 2, 0, 0 };
metric::EuclideanDistance metric;
double silhouetteScore = SilhouetteScore::Overall(X, labels, metric);
REQUIRE(silhouetteScore == Approx(0.1121684822489150).epsilon(1e-7));
}