Merge pull request #1126 from thejonan/feature/det_path_printing
DET path printing
This commit is contained in:
@@ -120,6 +120,7 @@ set(SOURCES
|
||||
statistic.hpp
|
||||
traversal_info.hpp
|
||||
tree_traits.hpp
|
||||
enumerate_tree.hpp
|
||||
)
|
||||
|
||||
# add directory name to sources
|
||||
|
||||
@@ -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 <class TreeType, class Walker>
|
||||
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 <class TreeType, class Walker>
|
||||
inline void EnumerateTree(TreeType* tree, Walker& walker)
|
||||
{
|
||||
enumerate::EnumerateTreeImpl(tree, walker, true);
|
||||
}
|
||||
|
||||
} // namespace tree
|
||||
} // namespace mlpack
|
||||
|
||||
|
||||
#endif // MLPACK_CORE_TREE_ENUMERATE_TREE_HPP
|
||||
@@ -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<arma::mat, int>* tree;
|
||||
arma::mat trainingData;
|
||||
arma::mat testData;
|
||||
|
||||
if (CLI::HasParam("training"))
|
||||
{
|
||||
arma::mat trainingData = std::move(CLI::GetParam<arma::mat>("training"));
|
||||
|
||||
// Cross-validation here.
|
||||
size_t folds = CLI::GetParam<int>("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<arma::mat>("training"));
|
||||
|
||||
const bool regularization = false;
|
||||
// const bool regularization = CLI::HasParam("volume_regularization");
|
||||
const int maxLeafSize = CLI::GetParam<int>("max_leaf_size");
|
||||
const int minLeafSize = CLI::GetParam<int>("min_leaf_size");
|
||||
const bool skipPruning = CLI::HasParam("skip_pruning");
|
||||
size_t folds = CLI::GetParam<int>("folds");
|
||||
|
||||
if (folds == 0)
|
||||
folds = trainingData.n_cols;
|
||||
|
||||
// Obtain the optimal tree.
|
||||
Timer::Start("det_training");
|
||||
tree = Trainer<arma::mat, int>(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<arma::mat>("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<arma::mat>("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<arma::mat>("test_set_estimates") = std::move(testDensities);
|
||||
}
|
||||
|
||||
// Print variable importance.
|
||||
if (CLI::HasParam("vi"))
|
||||
{
|
||||
arma::vec importances;
|
||||
tree->ComputeVariableImportance(importances);
|
||||
CLI::GetParam<arma::mat>("vi") = importances.t();
|
||||
}
|
||||
}
|
||||
|
||||
// Print variable importance.
|
||||
if (CLI::HasParam("vi"))
|
||||
if (CLI::HasParam("tag_file"))
|
||||
{
|
||||
arma::vec importances;
|
||||
tree->ComputeVariableImportance(importances);
|
||||
CLI::GetParam<arma::mat>("vi") = std::move(importances.t());
|
||||
const arma::mat& estimationData =
|
||||
CLI::HasParam("test") ? testData : trainingData;
|
||||
const string tagFile = CLI::GetParam<string>("tag_file");
|
||||
std::ofstream ofs;
|
||||
ofs.open(tagFile, std::ofstream::out);
|
||||
|
||||
arma::Row<size_t> 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<string>("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<string>("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<string>("tag_counters_file"), counters);
|
||||
}
|
||||
|
||||
Timer::Stop("det_test_set_tagging");
|
||||
ofs.close();
|
||||
}
|
||||
|
||||
// Save the model, if desired.
|
||||
|
||||
@@ -35,7 +35,7 @@ void PrintLeafMembership(DTree<MatType, TagType>* dtree,
|
||||
const MatType& data,
|
||||
const arma::Mat<size_t>& 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<MatType, TagType>* 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 <typename MatType>
|
||||
PathCacher(PathFormat fmt, DTree<MatType, int>* tree);
|
||||
|
||||
template <typename MatType>
|
||||
void Enter(const DTree<MatType, int>* node,
|
||||
const DTree<MatType, int>* parent);
|
||||
|
||||
template <typename MatType>
|
||||
void Leave(const DTree<MatType, int>* node,
|
||||
const DTree<MatType, int>* parent);
|
||||
|
||||
const std::string& PathFor(int tag) const;
|
||||
|
||||
int ParentOf(int tag) const;
|
||||
|
||||
size_t NumNodes() const { return pathCache.size(); }
|
||||
|
||||
protected:
|
||||
typedef std::list<std::pair<bool, int> > PathType;
|
||||
typedef std::vector<std::pair<int, std::string> > PathCacheType;
|
||||
|
||||
PathType path;
|
||||
PathFormat format;
|
||||
PathCacheType pathCache;
|
||||
|
||||
std::string BuildString();
|
||||
};
|
||||
|
||||
} // namespace det
|
||||
} // namespace mlpack
|
||||
|
||||
@@ -14,19 +14,20 @@
|
||||
#define MLPACK_METHODS_DET_DT_UTILS_IMPL_HPP
|
||||
|
||||
#include "dt_utils.hpp"
|
||||
#include <mlpack/core/tree/enumerate_tree.hpp>
|
||||
|
||||
namespace mlpack {
|
||||
namespace det {
|
||||
|
||||
template <typename MatType, typename TagType>
|
||||
void PrintLeafMembership(DTree<MatType, TagType>* dtree,
|
||||
template <typename MatType>
|
||||
void PrintLeafMembership(DTree<MatType, int>* dtree,
|
||||
const MatType& data,
|
||||
const arma::Mat<size_t>& 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<size_t> table(numLeaves, (numClasses + 1));
|
||||
table.zeros();
|
||||
@@ -34,7 +35,7 @@ void PrintLeafMembership(DTree<MatType, TagType>* 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<MatType, TagType>* 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<MatType, TagType> dtree(dataset);
|
||||
DTree<MatType, TagType>* dtree = new DTree<MatType, TagType>(dataset);
|
||||
|
||||
Timer::Start("tree_growing");
|
||||
// Prepare to grow the tree...
|
||||
arma::Col<size_t> oldFromNew(dataset.n_cols);
|
||||
for (size_t i = 0; i < oldFromNew.n_elem; i++)
|
||||
@@ -126,60 +128,52 @@ DTree<MatType, TagType>* 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<std::pair<double, double> > prunedSequence;
|
||||
while (dtree.SubtreeLeaves() > 1)
|
||||
while (dtree->SubtreeLeaves() > 1)
|
||||
{
|
||||
std::pair<double, double> 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<double>::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<double, double> treeSeq(oldAlpha, dtree.SubtreeLeavesLogNegError());
|
||||
std::pair<double, double> 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<MatType, TagType>* 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<MatType, TagType>* Trainer(MatType& dataset,
|
||||
|
||||
Log::Info << "Optimal alpha: " << optimalAlpha << "." << std::endl;
|
||||
|
||||
// Initialize the tree.
|
||||
DTree<MatType, TagType>* dtreeOpt = new DTree<MatType, TagType>(dataset);
|
||||
// Re-Initialize the tree.
|
||||
delete dtree;
|
||||
dtree = new DTree<MatType, TagType>(dataset);
|
||||
|
||||
// Getting ready to grow the tree...
|
||||
for (size_t i = 0; i < oldFromNew.n_elem; i++)
|
||||
@@ -301,31 +296,97 @@ DTree<MatType, TagType>* 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<double>::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 <typename MatType>
|
||||
PathCacher::PathCacher(PathCacher::PathFormat fmt, DTree<MatType, int>* 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 <typename MatType>
|
||||
void PathCacher::Enter(const DTree<MatType, int>* node,
|
||||
const DTree<MatType, int>* 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 <typename MatType>
|
||||
void PathCacher::Leave(const DTree<MatType, int>* ,
|
||||
const DTree<MatType, int>* 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
|
||||
|
||||
@@ -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<size_t>& oldFromNew) const;
|
||||
|
||||
void FillMinMax(const StatType& mins,
|
||||
const StatType& maxs);
|
||||
};
|
||||
|
||||
} // namespace det
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace details
|
||||
const size_t minLeafSize)
|
||||
{
|
||||
typedef std::pair<ElemType, size_t> 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 <typename MatType, typename TagType>
|
||||
DTree<MatType, TagType>& DTree<MatType, TagType>::operator=(
|
||||
const DTree<MatType, TagType>& obj)
|
||||
{
|
||||
if (this == &obj)
|
||||
return *this;
|
||||
|
||||
// Copy the values from the other tree.
|
||||
start = obj.start;
|
||||
end = obj.end;
|
||||
@@ -213,7 +216,7 @@ DTree<MatType, TagType>& DTree<MatType, TagType>::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 <typename MatType, typename TagType>
|
||||
DTree<MatType, TagType>& DTree<MatType, TagType>::operator=(
|
||||
DTree<MatType, TagType>&& 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<MatType, TagType>::ComputeValue(const VecType& query) const
|
||||
|
||||
// Index the buckets for possible usage later.
|
||||
template <typename MatType, typename TagType>
|
||||
TagType DTree<MatType, TagType>::TagTree(const TagType& tag)
|
||||
TagType DTree<MatType, TagType>::TagTree(const TagType& tag, bool every)
|
||||
{
|
||||
if (subtreeLeaves == 1)
|
||||
{
|
||||
@@ -891,10 +897,17 @@ TagType DTree<MatType, TagType>::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 <typename MatType, typename TagType>
|
||||
@@ -902,7 +915,15 @@ TagType DTree<MatType, TagType>::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<MatType, TagType>::ComputeVariableImportance(arma::vec& importances) const
|
||||
}
|
||||
}
|
||||
|
||||
template <typename MatType, typename TagType>
|
||||
void DTree<MatType, TagType>::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 <typename MatType, typename TagType>
|
||||
template <typename Archive>
|
||||
void DTree<MatType, TagType>::Serialize(Archive& ar,
|
||||
@@ -953,8 +997,6 @@ void DTree<MatType, TagType>::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<MatType, TagType>::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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user