diff --git a/src/mlpack/core/data/CMakeLists.txt b/src/mlpack/core/data/CMakeLists.txt index 6f838754f6..e3c7beb62f 100644 --- a/src/mlpack/core/data/CMakeLists.txt +++ b/src/mlpack/core/data/CMakeLists.txt @@ -27,6 +27,7 @@ set(SOURCES save_image.cpp serialization_template_version.hpp split_data.hpp + stratified_split_data.hpp imputer.hpp binarize.hpp string_encoding.hpp diff --git a/src/mlpack/core/data/stratified_split_data.hpp b/src/mlpack/core/data/stratified_split_data.hpp index 9d8496de76..0246ee660d 100644 --- a/src/mlpack/core/data/stratified_split_data.hpp +++ b/src/mlpack/core/data/stratified_split_data.hpp @@ -57,6 +57,8 @@ void StratifiedSplit(const arma::Mat& input, const double testRatio, const bool shuffleData = true) { + arma::uvec trainIndexes; + arma::uvec testIndexes; if (shuffleData) { arma::uvec order = arma::shuffle(arma::linspace( @@ -64,6 +66,70 @@ void StratifiedSplit(const arma::Mat& input, input = input.cols(order); inputLabel = inputLabel.cols(order); } + arma::Row uniqueLabel = arma::unique(inputLabel); - //for (U ) + + for (typename U label : uniqueLabel) + { + arma::uvec uniqueIndexes = arma::find(inputLabel == label); + + const size_t testStrataSize = + static_cast(uniqueIndexes.n_rows*testRatio); + const size_t trainStrataSize = uniqueIndexes.n_rows - testSize; + + arma::uvec testStrataIndexes = + uniqueIndexes.subvec(0, testStrataSize - 1); + arma::uvec trainStrataIndexes = + uniqueIndexes.subvec(testStrataSize - 1, uniqueIndexes.n_rows - 1); + + testIndexes = join_cols(testIndexes, testStrataIndexes); + trainIndexes = join_cols(trainIndexes, trainStrataIndexes); + } + + testData = input.cols(testIndexes); + testLabel = inputLabel.cols(testIndexes); + trainData = input.cols(trainIndexes); + trainLabel = inputLabel.cols(trainIndexes); +} + +/** + * Given an input dataset and labels, split into a training set and test set. + * Example usage below. This overload returns the split dataset as a std::tuple + * with four elements: an arma::Mat containing the training data, an + * arma::Mat containing the test data, an arma::Row containing the + * training labels, and an arma::Row containing the test labels. + * + * @code + * arma::mat input = loadData(); + * arma::Row label = loadLabel(); + * auto splitResult = Split(input, label, 0.2); + * @endcode + * + * @param input Input dataset to split. + * @param inputLabel Input labels to split. + * @param testRatio Percentage of dataset to use for test set (between 0 and 1). + * @param shuffleData If true, the sample order is shuffled; otherwise, each + * sample is visited in linear order. (Default true). + * @return std::tuple containing trainData (arma::Mat), testData + * (arma::Mat), trainLabel (arma::Row), and testLabel (arma::Row). + */ +template +std::tuple, arma::Mat, arma::Row, arma::Row> +StratifiedSplit(const arma::Mat& input, + const arma::Row& inputLabel, + const double testRatio, + const bool shuffleData = true) +{ + arma::Mat trainData; + arma::Mat testData; + arma::Row trainLabel; + arma::Row testLabel; + + StratifiedSplit(input, inputLabel, trainData, testData, trainLabel, testLabel, + testRatio, shuffleData); + + return std::make_tuple(std::move(trainData), + std::move(testData), + std::move(trainLabel), + std::move(testLabel)); } diff --git a/src/mlpack/methods/preprocess/preprocess_split_main.cpp b/src/mlpack/methods/preprocess/preprocess_split_main.cpp index b6a36967a1..2c01589e52 100644 --- a/src/mlpack/methods/preprocess/preprocess_split_main.cpp +++ b/src/mlpack/methods/preprocess/preprocess_split_main.cpp @@ -148,7 +148,14 @@ static void mlpackMain() IO::GetParam>("input_labels"); arma::Row labelsRow = labels.row(0); - const auto value = data::Split(data, labelsRow, testRatio, !shuffleData); + if(IO::HasParam("stratify")) + { + const auto value = + data::StratifiedSplit(data, labelsRow, testRatio, !shuffleData); + } + else { + const auto value = data::Split(data, labelsRow, testRatio, !shuffleData); + } Log::Info << "Training data contains " << get<0>(value).n_cols << " points." << endl; Log::Info << "Test data contains " << get<1>(value).n_cols << " points."