diff --git a/src/mlpack/core/tree/CMakeLists.txt b/src/mlpack/core/tree/CMakeLists.txt index adc5bae0b2..0b2f01a5f3 100644 --- a/src/mlpack/core/tree/CMakeLists.txt +++ b/src/mlpack/core/tree/CMakeLists.txt @@ -120,6 +120,7 @@ set(SOURCES statistic.hpp traversal_info.hpp tree_traits.hpp + enumerate_tree.hpp ) # add directory name to sources diff --git a/src/mlpack/core/tree/enumerate_tree.hpp b/src/mlpack/core/tree/enumerate_tree.hpp new file mode 100644 index 0000000000..8811c8f4c2 --- /dev/null +++ b/src/mlpack/core/tree/enumerate_tree.hpp @@ -0,0 +1,64 @@ +/** + * @file enumerate_tree.hpp + * @author Ivan (Jonan) Georgiev + * + * This file contains function that performs a simple depth-first walk on the tree + * calling `Enter` and `Leave` methods of a provided walker. + * + * 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. + */ +#ifndef MLPACK_CORE_TREE_ENUMERATE_TREE_HPP +#define MLPACK_CORE_TREE_ENUMERATE_TREE_HPP + +namespace mlpack { +namespace tree /** Trees and tree-building procedures. */ { +namespace enumerate { + +// Actual implementation of the enumeration. The problem is the unified +// detection if we're on the root, because Enter and Leave expect the +// parent being passed. +template +void EnumerateTreeImpl(TreeType* tree, Walker& walker, bool root) +{ + if (root) + walker.Enter(tree, (const TreeType*)nullptr); + + const size_t numChildren = tree->NumChildren(); + for (size_t i = 0; i < numChildren; ++i) + { + TreeType* child = tree->ChildPtr(i); + walker.Enter(child, tree); + EnumerateTreeImpl(child, walker, false); + walker.Leave(child, tree); + } + + if (root) + walker.Leave(tree, (const TreeType*)nullptr); +} + +} // namespace enumerate + + +/** + * Traverses all nodes of the tree, including the inner ones. On each node + * two methods of the `enumer` are called: + * + * Enter(TreeType* node, TreeType* parent); + * Leave(TreeType* node, TreeType* parent); + * + * @param walker An instance of custom class, receiver of the enumeration. + */ +template +inline void EnumerateTree(TreeType* tree, Walker& walker) +{ + enumerate::EnumerateTreeImpl(tree, walker, true); +} + +} // namespace tree +} // namespace mlpack + + +#endif // MLPACK_CORE_TREE_ENUMERATE_TREE_HPP diff --git a/src/mlpack/methods/det/det_main.cpp b/src/mlpack/methods/det/det_main.cpp index e9755fe415..cc55fccc34 100644 --- a/src/mlpack/methods/det/det_main.cpp +++ b/src/mlpack/methods/det/det_main.cpp @@ -33,6 +33,11 @@ PROGRAM_INFO("Density Estimation With Density Estimation Trees", " with the " + PRINT_PARAM_STRING("training_set_estimates") + " output " "parameter." "\n\n" + "Enabling path printing for each node outputs strings like `LRLRLR` for " + "each entry in the test set, or training one, if test is not provided. " + "If `lr-id` or `id-lr` is provided, it'll also print the id (tag) of the " + "node (not just leaf!) in tree along the path to the leaf." + "\n\n" "This program also can provide density estimates for a set of test points, " "specified in the " + PRINT_PARAM_STRING("test") + " parameter. The " "density estimation tree used for this task will be the tree that was " @@ -61,6 +66,19 @@ PARAM_MATRIX_OUT("test_set_estimates", "The output estimates on the test set " PARAM_MATRIX_OUT("vi", "The output variable importance values for each " "feature.", "i"); +// Tagging and path printing options +PARAM_STRING_IN("path_format", "The format of path printing - lr|id-lr|lr-id", + "p", "lr"); + +PARAM_STRING_OUT("tag_counters_file", "The file to output number of points " + "that went to each leaf.", "c"); + +PARAM_STRING_OUT("tag_file", "The file to output the tags (and possibly paths)" + " for each sample in the test set.", "g"); + +PARAM_FLAG("skip_pruning", "Whether to bypass the pruning process and output " + "the unpruned tree only.", "s"); + // Parameters for the training algorithm. PARAM_INT_IN("folds", "The number of folds of cross-validation to perform for " "the estimation (0 is LOOCV)", "f", 10); @@ -76,71 +94,80 @@ PARAM_FLAG("volume_regularization", "This flag gives the used the option to use" "penalize low volume leaves.", "R"); */ + void mlpackMain() { // Validate input parameters. if (CLI::HasParam("training") && CLI::HasParam("input_model")) - Log::Fatal << "Only one of --training_file (-t) or --input_model_file (-m) " - << "may be specified!" << endl; + Log::Fatal << "Only one of " << PRINT_PARAM_STRING("training") << " or " << + PRINT_PARAM_STRING("input_model") << " may be specified!" << endl; if (!CLI::HasParam("training") && !CLI::HasParam("input_model")) - Log::Fatal << "Neither --training_file (-t) nor --input_model_file (-m) " - << "are specified!" << endl; + Log::Fatal << "Neither " << PRINT_PARAM_STRING("training") << " nor " << + PRINT_PARAM_STRING("input_model") << " are specified!" << endl; + + if (CLI::HasParam("tag_file") && + !CLI::HasParam("training") && !CLI::HasParam("test")) + { + Log::Fatal << "Neither " << PRINT_PARAM_STRING("training") << " nor " << + PRINT_PARAM_STRING("test") << " are specified, but needed when " << + PRINT_PARAM_STRING("tag_file") << " is asked." << endl; + } if (!CLI::HasParam("training")) { if (CLI::HasParam("training_set_estimates")) - Log::Warn << "--training_set_estimates_file (-e) ignored because " - << "--training_file (-t) is not specified." << endl; + Log::Warn << PRINT_PARAM_STRING("training_set_estimates") << + " ignored because " << PRINT_PARAM_STRING("training") << + " is not specified." << endl; if (CLI::HasParam("folds")) - Log::Warn << "--folds (-f) ignored because --training_file (-t) is not " - << "specified." << endl; + Log::Warn << PRINT_PARAM_STRING("folds") << " ignored because " << + PRINT_PARAM_STRING("training") << " is not specified." << endl; if (CLI::HasParam("min_leaf_size")) - Log::Warn << "--min_leaf_size (-l) ignored because --training_file (-t) " - << "is not specified." << endl; + Log::Warn << PRINT_PARAM_STRING("min_leaf_size") << " ignored because " << + PRINT_PARAM_STRING("training") << " is not specified." << endl; if (CLI::HasParam("max_leaf_size")) - Log::Warn << "--max_leaf_size (-L) ignored because --training_file (-t) " - << "is not specified." << endl; + Log::Warn << PRINT_PARAM_STRING("max_leaf_size") << " ignored because " << + PRINT_PARAM_STRING("training") << " is not specified." << endl; } else if (!CLI::HasParam("output_model") && !CLI::HasParam("training_set_estimates") && !CLI::HasParam("vi")) { - Log::Warn << "None of --output_model_file (-M), --training_set_estimates " - << "(-e), or --vi (-i) are specified; no output will be saved!" << endl; + Log::Warn << "None of " << PRINT_PARAM_STRING("output_model") << ", " << + PRINT_PARAM_STRING("training_set_estimates") << ", or " << + PRINT_PARAM_STRING("vi") << " are specified; no output will be saved!" << + endl; } if (!CLI::HasParam("test") && CLI::HasParam("test_set_estimates")) - Log::Warn << "--test_set_estimates_file (-E) ignored because --test_file " - << "(-T) is not specified." << endl; + Log::Warn << PRINT_PARAM_STRING("test_set_estimates") << " ignored " << + "because " << PRINT_PARAM_STRING("test") << " is not specified." << endl; // Are we training a DET or loading from file? DTree* tree; + arma::mat trainingData; + arma::mat testData; + if (CLI::HasParam("training")) { - arma::mat trainingData = std::move(CLI::GetParam("training")); - - // Cross-validation here. - size_t folds = CLI::GetParam("folds"); - if (folds == 0) - { - folds = trainingData.n_cols; - Log::Info << "Performing leave-one-out cross validation." << endl; - } - else - { - Log::Info << "Performing " << folds << "-fold cross validation." << endl; - } + trainingData = std::move(CLI::GetParam("training")); const bool regularization = false; // const bool regularization = CLI::HasParam("volume_regularization"); const int maxLeafSize = CLI::GetParam("max_leaf_size"); const int minLeafSize = CLI::GetParam("min_leaf_size"); + const bool skipPruning = CLI::HasParam("skip_pruning"); + size_t folds = CLI::GetParam("folds"); + + if (folds == 0) + folds = trainingData.n_cols; // Obtain the optimal tree. Timer::Start("det_training"); tree = Trainer(trainingData, folds, regularization, - maxLeafSize, minLeafSize, ""); + maxLeafSize, minLeafSize, + skipPruning); Timer::Stop("det_training"); // Compute training set estimates, if desired. @@ -166,25 +193,112 @@ void mlpackMain() // the given file. if (CLI::HasParam("test")) { - arma::mat testData = std::move(CLI::GetParam("test")); - - // Compute test set densities. - Timer::Start("det_test_set_estimation"); - arma::rowvec testDensities(testData.n_cols); - for (size_t i = 0; i < testData.n_cols; i++) - testDensities[i] = tree->ComputeValue(testData.unsafe_col(i)); - Timer::Stop("det_test_set_estimation"); - + testData = std::move(CLI::GetParam("test")); if (CLI::HasParam("test_set_estimates")) + { + // Compute test set densities. + Timer::Start("det_test_set_estimation"); + arma::rowvec testDensities(testData.n_cols); + + for (size_t i = 0; i < testData.n_cols; i++) + testDensities[i] = tree->ComputeValue(testData.unsafe_col(i)); + + Timer::Stop("det_test_set_estimation"); + CLI::GetParam("test_set_estimates") = std::move(testDensities); + } + + // Print variable importance. + if (CLI::HasParam("vi")) + { + arma::vec importances; + tree->ComputeVariableImportance(importances); + CLI::GetParam("vi") = importances.t(); + } } - // Print variable importance. - if (CLI::HasParam("vi")) + if (CLI::HasParam("tag_file")) { - arma::vec importances; - tree->ComputeVariableImportance(importances); - CLI::GetParam("vi") = std::move(importances.t()); + const arma::mat& estimationData = + CLI::HasParam("test") ? testData : trainingData; + const string tagFile = CLI::GetParam("tag_file"); + std::ofstream ofs; + ofs.open(tagFile, std::ofstream::out); + + arma::Row counters; + + Timer::Start("det_test_set_tagging"); + if (!ofs.is_open()) + { + Log::Warn << "Unable to open file '" << tagFile + << "' to save tag membership info." + << std::endl; + } + else if (CLI::HasParam("path_format")) + { + const bool reqCounters = CLI::HasParam("tag_counters_file"); + const string pathFormat = CLI::GetParam("path_format"); + + PathCacher::PathFormat theFormat; + if (pathFormat == "lr" || pathFormat == "LR") + theFormat = PathCacher::FormatLR; + else if (pathFormat == "lr-id" || pathFormat == "LR-ID") + theFormat = PathCacher::FormatLR_ID; + else if (pathFormat == "id-lr" || pathFormat == "ID-LR") + theFormat = PathCacher::FormatID_LR; + else + { + Log::Warn << "Unknown path format specified: '" << pathFormat + << "'. Valid are: lr | lr-id | id-lr. Defaults to 'lr'." << endl; + theFormat = PathCacher::FormatLR; + } + + PathCacher path(theFormat, tree); + counters.zeros(path.NumNodes()); + + for (size_t i = 0; i < estimationData.n_cols; i++) + { + int tag = tree->FindBucket(estimationData.unsafe_col(i)); + + ofs << tag << " " << path.PathFor(tag) << std::endl; + for (; tag >= 0 && reqCounters; tag = path.ParentOf(tag)) + counters(tag) += 1; + } + + ofs.close(); + + if (reqCounters) + { + ofs.open(CLI::GetParam("tag_counters_file"), + std::ofstream::out); + + for (size_t j = 0; j < counters.n_elem; ++j) + ofs << j << " " + << counters(j) << " " + << path.PathFor(j) << endl; + + ofs.close(); + } + } + else + { + int numLeaves = tree->TagTree(); + counters.zeros(numLeaves); + + for (size_t i = 0; i < estimationData.n_cols; i++) + { + const int tag = tree->FindBucket(estimationData.unsafe_col(i)); + + ofs << tag << std::endl; + counters(tag) += 1; + } + + if (CLI::HasParam("tag_counters_file")) + data::Save(CLI::GetParam("tag_counters_file"), counters); + } + + Timer::Stop("det_test_set_tagging"); + ofs.close(); } // Save the model, if desired. diff --git a/src/mlpack/methods/det/dt_utils.hpp b/src/mlpack/methods/det/dt_utils.hpp index fd66e2e6ce..8e49597936 100644 --- a/src/mlpack/methods/det/dt_utils.hpp +++ b/src/mlpack/methods/det/dt_utils.hpp @@ -35,7 +35,7 @@ void PrintLeafMembership(DTree* dtree, const MatType& data, const arma::Mat& labels, const size_t numClasses, - const std::string leafClassMembershipFile = ""); + const std::string& leafClassMembershipFile = ""); /** * Print the variable importance of each dimension of a density estimation tree. @@ -67,7 +67,51 @@ DTree* Trainer(MatType& dataset, const bool useVolumeReg = false, const size_t maxLeafSize = 10, const size_t minLeafSize = 5, - const std::string unprunedTreeOutput = ""); + const std::string unprunedTreeOutput = "", + const bool skipPruning = false); + +/** + * The class responsible for cacheing the path to each node of the tree. Its instance + * is provided to EnumerateTree() utility ONCE and it caches the paths to all the + * leafs and then easily (and quickly) retrieves these paths for each test entry. + */ +class PathCacher +{ + public: + enum PathFormat + { + FormatLR, + FormatLR_ID, + FormatID_LR + }; + + template + PathCacher(PathFormat fmt, DTree* tree); + + template + void Enter(const DTree* node, + const DTree* parent); + + template + void Leave(const DTree* node, + const DTree* parent); + + const std::string& PathFor(int tag) const; + + int ParentOf(int tag) const; + + size_t NumNodes() const { return pathCache.size(); } + + protected: + typedef std::list > PathType; + typedef std::vector > PathCacheType; + + PathType path; + PathFormat format; + PathCacheType pathCache; + + std::string BuildString(); +}; } // namespace det } // namespace mlpack diff --git a/src/mlpack/methods/det/dt_utils_impl.hpp b/src/mlpack/methods/det/dt_utils_impl.hpp index d4dba48467..16aa42e427 100644 --- a/src/mlpack/methods/det/dt_utils_impl.hpp +++ b/src/mlpack/methods/det/dt_utils_impl.hpp @@ -14,19 +14,20 @@ #define MLPACK_METHODS_DET_DT_UTILS_IMPL_HPP #include "dt_utils.hpp" +#include namespace mlpack { namespace det { -template -void PrintLeafMembership(DTree* dtree, +template +void PrintLeafMembership(DTree* dtree, const MatType& data, const arma::Mat& labels, const size_t numClasses, - const std::string leafClassMembershipFile) + const std::string& leafClassMembershipFile) { // Tag the leaves with numbers. - TagType numLeaves = dtree->TagTree(); + int numLeaves = dtree->TagTree(); arma::Mat table(numLeaves, (numClasses + 1)); table.zeros(); @@ -34,7 +35,7 @@ void PrintLeafMembership(DTree* dtree, for (size_t i = 0; i < data.n_cols; i++) { const typename MatType::vec_type testPoint = data.unsafe_col(i); - const TagType leafTag = dtree->FindBucket(testPoint); + const int leafTag = dtree->FindBucket(testPoint); const size_t label = labels[i]; table(leafTag, label) += 1; } @@ -111,11 +112,12 @@ DTree* Trainer(MatType& dataset, const bool useVolumeReg, const size_t maxLeafSize, const size_t minLeafSize, - const std::string unprunedTreeOutput) + const bool skipPruning) { // Initialize the tree. - DTree dtree(dataset); + DTree* dtree = new DTree(dataset); + Timer::Start("tree_growing"); // Prepare to grow the tree... arma::Col oldFromNew(dataset.n_cols); for (size_t i = 0; i < oldFromNew.n_elem; i++) @@ -126,60 +128,52 @@ DTree* Trainer(MatType& dataset, // Growing the tree double oldAlpha = 0.0; - double alpha = dtree.Grow(newDataset, oldFromNew, useVolumeReg, maxLeafSize, + double alpha = dtree->Grow(newDataset, oldFromNew, useVolumeReg, maxLeafSize, minLeafSize); - Log::Info << dtree.SubtreeLeaves() << " leaf nodes in the tree using full " + Timer::Stop("tree_growing"); + Log::Info << dtree->SubtreeLeaves() << " leaf nodes in the tree using full " << "dataset; minimum alpha: " << alpha << "." << std::endl; - // Compute densities for the training points in the full tree, if we were - // asked for this. - if (unprunedTreeOutput != "") - { - std::ofstream outfile(unprunedTreeOutput.c_str()); - if (outfile.good()) - { - for (size_t i = 0; i < dataset.n_cols; ++i) - { - arma::vec testPoint = dataset.unsafe_col(i); - outfile << dtree.ComputeValue(testPoint) << std::endl; - } - } - else - { - Log::Warn << "Can't open '" << unprunedTreeOutput << "' to write computed" - << " densities to." << std::endl; - } + if (skipPruning) + return dtree; - outfile.close(); - } + if (folds == dataset.n_cols) + Log::Info << "Performing leave-one-out cross validation." << std::endl; + else + Log::Info << "Performing " << folds << "-fold cross validation." << + std::endl; + + Timer::Start("pruning_sequence"); // Sequentially prune and save the alpha values and the values of c_t^2 * r_t. std::vector > prunedSequence; - while (dtree.SubtreeLeaves() > 1) + while (dtree->SubtreeLeaves() > 1) { std::pair treeSeq(oldAlpha, - dtree.SubtreeLeavesLogNegError()); + dtree->SubtreeLeavesLogNegError()); prunedSequence.push_back(treeSeq); oldAlpha = alpha; - alpha = dtree.PruneAndUpdate(oldAlpha, dataset.n_cols, useVolumeReg); + alpha = dtree->PruneAndUpdate(oldAlpha, dataset.n_cols, useVolumeReg); // Some sanity checks. It seems that on some datasets, the error does not // increase as the tree is pruned but instead stays the same---hence the // "<=" in the final assert. Log::Assert((alpha < std::numeric_limits::max()) - || (dtree.SubtreeLeaves() == 1)); + || (dtree->SubtreeLeaves() == 1)); Log::Assert(alpha > oldAlpha); - Log::Assert(dtree.SubtreeLeavesLogNegError() <= treeSeq.second); + Log::Assert(dtree->SubtreeLeavesLogNegError() <= treeSeq.second); } - std::pair treeSeq(oldAlpha, dtree.SubtreeLeavesLogNegError()); + std::pair treeSeq(oldAlpha, + dtree->SubtreeLeavesLogNegError()); prunedSequence.push_back(treeSeq); + Timer::Stop("pruning_sequence"); Log::Info << prunedSequence.size() << " trees in the sequence; maximum alpha:" << " " << oldAlpha << "." << std::endl; - MatType cvData(dataset); + const MatType cvData(dataset); const size_t testSize = dataset.n_cols / folds; arma::vec regularizationConstants(prunedSequence.size()); @@ -191,7 +185,7 @@ DTree* Trainer(MatType& dataset, // implementation. omp_size_t is the appropriate type according to the // platform. #pragma omp parallel for default(none) \ - shared(cvData, prunedSequence, regularizationConstants) + shared(prunedSequence, regularizationConstants) for (omp_size_t fold = 0; fold < (omp_size_t) folds; fold++) { // Break up data into train and test sets. @@ -289,8 +283,9 @@ DTree* Trainer(MatType& dataset, Log::Info << "Optimal alpha: " << optimalAlpha << "." << std::endl; - // Initialize the tree. - DTree* dtreeOpt = new DTree(dataset); + // Re-Initialize the tree. + delete dtree; + dtree = new DTree(dataset); // Getting ready to grow the tree... for (size_t i = 0; i < oldFromNew.n_elem; i++) @@ -301,31 +296,97 @@ DTree* Trainer(MatType& dataset, // Grow the tree. oldAlpha = -DBL_MAX; - alpha = dtreeOpt->Grow(newDataset, + alpha = dtree->Grow(newDataset, oldFromNew, useVolumeReg, maxLeafSize, minLeafSize); // Prune with optimal alpha. - while ((oldAlpha < optimalAlpha) && (dtreeOpt->SubtreeLeaves() > 1)) + while ((oldAlpha < optimalAlpha) && (dtree->SubtreeLeaves() > 1)) { oldAlpha = alpha; - alpha = dtreeOpt->PruneAndUpdate(oldAlpha, newDataset.n_cols, useVolumeReg); + alpha = dtree->PruneAndUpdate(oldAlpha, newDataset.n_cols, useVolumeReg); // Some sanity checks. Log::Assert((alpha < std::numeric_limits::max()) || - (dtreeOpt->SubtreeLeaves() == 1)); + (dtree->SubtreeLeaves() == 1)); Log::Assert(alpha > oldAlpha); } - Log::Info << dtreeOpt->SubtreeLeaves() << " leaf nodes in the optimally " + Log::Info << dtree->SubtreeLeaves() << " leaf nodes in the optimally " << "pruned tree; optimal alpha: " << oldAlpha << "." << std::endl; - return dtreeOpt; + return dtree; +} + +template +PathCacher::PathCacher(PathCacher::PathFormat fmt, DTree* dtree) : + format(fmt) +{ + // Here we use TagTree()'s output to determine the + // number of _nodes_ in the tree. + pathCache.resize(dtree->TagTree(0, true)); + pathCache[0] = PathCacheType::value_type(-1, ""); + tree::EnumerateTree(dtree, *this); +} + +template +void PathCacher::Enter(const DTree* node, + const DTree* parent) +{ + if (parent == nullptr) + return; + + int tag = node->BucketTag(); + + path.push_back(PathType::value_type(parent->Left() == node, tag)); + pathCache[tag] = PathCacheType::value_type(parent->BucketTag(), + (node->SubtreeLeaves() > 1) ? + "" : BuildString()); +} + +template +void PathCacher::Leave(const DTree* , + const DTree* parent) +{ + if (parent != nullptr) + path.pop_back(); +} + +std::string PathCacher::BuildString() +{ + std::string str(""); + for (PathType::iterator it = path.begin(); it != path.end(); it++) + { + switch (format) + { + case FormatLR: + str += it->first ? "L" : "R"; + break; + case FormatLR_ID: + str += (it->first ? "L" : "R") + std::to_string(it->second); + break; + case FormatID_LR: + str += std::to_string(it->second) + (it->first ? "L" : "R"); + break; + } + } + + return str; +} + +int PathCacher::ParentOf(int tag) const +{ + return pathCache[tag].first; +} + +const std::string& PathCacher::PathFor(int tag) const +{ + return pathCache[tag].second; } } // namespace det } // namespace mlpack -#endif +#endif // MLPACK_METHODS_DET_DT_UTILS_IMPL_HPP diff --git a/src/mlpack/methods/det/dtree.hpp b/src/mlpack/methods/det/dtree.hpp index 04ce0b0743..7ebcb4aea5 100644 --- a/src/mlpack/methods/det/dtree.hpp +++ b/src/mlpack/methods/det/dtree.hpp @@ -184,11 +184,14 @@ class DTree /** * Index the buckets for possible usage later; this results in every leaf in * the tree having a specific tag (accessible with BucketTag()). This - * function calls itself recursively. + * function calls itself recursively. The tag is incremented with + * `operator++()`, so any `TagType` overriding it will do. * * @param tag Tag for the next leaf; leave at 0 for the initial call. + * @param everyNodde Whether to increment on every node, not just leaves. */ - TagType TagTree(const TagType& tag = 0); + TagType TagTree(const TagType& tag = 0, bool everyNode = false); + /** * Return the tag of the leaf containing the query. This is useful for @@ -198,6 +201,7 @@ class DTree */ TagType FindBucket(const VecType& query) const; + /** * Compute the variable importance of each dimension in the learned tree. * @@ -301,7 +305,19 @@ class DTree //! Return the upper part of the alpha sum. double AlphaUpper() const { return alphaUpper; } //! Return the current bucket's ID, if leaf, or -1 otherwise - TagType BucketTag() const { return subtreeLeaves == 1 ? bucketTag : -1; } + TagType BucketTag() const { return bucketTag; } + //! Return the number of children in this node. + size_t NumChildren() const { return !left ? 0 : 2; } + + /** + * Return the specified child (0 will be left, 1 will be right). If the index + * is greater than 1, this will return the right child. + * + * @param child Index of child to return. + */ + DTree& Child(const size_t child) const { return !child ? *left : *right; } + + DTree*& ChildPtr(const size_t child) { return (!child) ? left : right; } //! Return the maximum values. const StatType& MaxVals() const { return maxVals; } @@ -335,6 +351,9 @@ class DTree const size_t splitDim, const ElemType splitValue, arma::Col& oldFromNew) const; + + void FillMinMax(const StatType& mins, + const StatType& maxs); }; } // namespace det diff --git a/src/mlpack/methods/det/dtree_impl.hpp b/src/mlpack/methods/det/dtree_impl.hpp index 6372b19c0f..686b2804e4 100644 --- a/src/mlpack/methods/det/dtree_impl.hpp +++ b/src/mlpack/methods/det/dtree_impl.hpp @@ -66,7 +66,7 @@ namespace details const size_t minLeafSize) { typedef std::pair SplitItem; - arma::vec dimVec = data(dim, arma::span(start, end - 1)).t(); + arma::rowvec dimVec = data(dim, arma::span(start, end - 1)); // We sort these, in-place (it's a copy of the data, anyways). std::sort(dimVec.begin(), dimVec.end()); @@ -191,6 +191,9 @@ template DTree& DTree::operator=( const DTree& obj) { + if (this == &obj) + return *this; + // Copy the values from the other tree. start = obj.start; end = obj.end; @@ -213,7 +216,7 @@ DTree& DTree::operator=( // Copy the children. left = ((obj.left == NULL) ? NULL : new DTree(*obj.left)); - left = ((obj.right == NULL) ? NULL : new DTree(*obj.right)); + right = ((obj.right == NULL) ? NULL : new DTree(*obj.right)); return *this; } @@ -258,6 +261,9 @@ template DTree& DTree::operator=( DTree&& obj) { + if (this == &obj) + return *this; + // Move the values from the other tree. start = obj.start; end = obj.end; @@ -883,7 +889,7 @@ double DTree::ComputeValue(const VecType& query) const // Index the buckets for possible usage later. template -TagType DTree::TagTree(const TagType& tag) +TagType DTree::TagTree(const TagType& tag, bool every) { if (subtreeLeaves == 1) { @@ -891,10 +897,17 @@ TagType DTree::TagTree(const TagType& tag) bucketTag = tag; return (tag + 1); } - else + + TagType nextTag; + if (every) { - return right->TagTree(left->TagTree(tag)); + bucketTag = tag; + nextTag = (tag + 1); } + else + nextTag = tag; + + return right->TagTree(left->TagTree(nextTag, every), every); } template @@ -902,7 +915,15 @@ TagType DTree::FindBucket(const VecType& query) const { Log::Assert(query.n_elem == maxVals.n_elem); - if (subtreeLeaves == 1) // If we are a leaf... + if (root == 1) // If we are the root... + { + // Check if the query is within range. + if (!WithinRange(query)) + return -1; + } + + // If we are a leaf... + if (subtreeLeaves == 1) { return bucketTag; } @@ -944,6 +965,29 @@ DTree::ComputeVariableImportance(arma::vec& importances) const } } +template +void DTree::FillMinMax(const StatType& mins, + const StatType& maxs) +{ + if (!root) + { + minVals = mins; + maxVals = maxs; + } + + if (left && right) + { + StatType maxValsL(maxs); + StatType maxValsR(maxs); + StatType minValsL(mins); + StatType minValsR(mins); + + maxValsL[splitDim] = minValsR[splitDim] = splitValue; + left->FillMinMax(minValsL, maxValsL); + right->FillMinMax(minValsR, maxValsR); + } +} + template template void DTree::Serialize(Archive& ar, @@ -953,8 +997,6 @@ void DTree::Serialize(Archive& ar, ar & CreateNVP(start, "start"); ar & CreateNVP(end, "end"); - ar & CreateNVP(maxVals, "maxVals"); - ar & CreateNVP(minVals, "minVals"); ar & CreateNVP(splitDim, "splitDim"); ar & CreateNVP(splitValue, "splitValue"); ar & CreateNVP(logNegError, "logNegError"); @@ -976,5 +1018,14 @@ void DTree::Serialize(Archive& ar, ar & CreateNVP(left, "left"); ar & CreateNVP(right, "right"); -} + if (root) + { + ar & CreateNVP(maxVals, "maxVals"); + ar & CreateNVP(minVals, "minVals"); + + // This is added in order to reduce (dramatically!) the model file size. + if (Archive::is_loading::value && left && right) + FillMinMax(minVals, maxVals); + } +}