thanks @rcurtin Co-authored-by: Ryan Curtin <ryan@ratml.org>
907 lines
38 KiB
C++
907 lines
38 KiB
C++
/*! @page trees The TreeType policy in mlpack
|
|
|
|
@section treeintro Introduction
|
|
|
|
Trees are an important data structure in mlpack and are used in a number of the
|
|
machine learning algorithms that mlpack implements. Often, the use of trees can
|
|
allow significant acceleration of an algorithm; this is generally done by
|
|
pruning away large parts of the tree during computation.
|
|
|
|
Most mlpack algorithms that use trees are not tied to a specific tree but
|
|
instead allow the user to choose a tree via the \c TreeType template parameter.
|
|
Any tree passed as a \c TreeType template parameter will need to implement a
|
|
certain set of functions. In addition, a tree may optionally specify some
|
|
traits about itself with the \c TreeTraits trait class.
|
|
|
|
This document aims to clarify the abstractions underlying mlpack trees, list and
|
|
describe the required functionality of the \c TreeType policy, and point users
|
|
towards existing types of trees. A table of contents is below:
|
|
|
|
- \ref treeintro
|
|
- \ref whatistree
|
|
- \ref treetype_template_params
|
|
- \ref treetype_api
|
|
- \ref treetype_rigorous
|
|
- \ref treetype_rigorous_template
|
|
- \ref treetype_rigorous_constructor
|
|
- \ref treetype_rigorous_basic
|
|
- \ref treetype_rigorous_complex
|
|
- \ref treetype_rigorous_serialization
|
|
- \ref treetype_traits
|
|
- \ref treetype_more
|
|
|
|
Although this document is long, there may still be errors and unclear areas. If
|
|
you are having trouble understanding anything, please get in touch on Github or
|
|
on the mailing list and someone will help you (and possibly update the
|
|
documentation afterwards).
|
|
|
|
@section whatistree What is a tree?
|
|
|
|
In mlpack, we assume that we have some sort of data matrix, which might be
|
|
sparse or dense (that is, it could be of type \c arma::mat or \c arma::sp_mat,
|
|
or any variant that implements the Armadillo API). This data matrix corresponds
|
|
to a collection of points in some space (usually a Euclidean space). A tree is
|
|
a way of organizing this data matrix in a hierarchical manner---so, points that
|
|
are nearby should lie in similar nodes.
|
|
|
|
We can rigorously define what a tree is, using the definition of **space tree**
|
|
introduced in the following paper:
|
|
|
|
@code
|
|
@quote
|
|
R.R. Curtin, W.B. March, P. Ram, D.V. Anderson, A.G. Gray, and C.L. Isbell Jr.,
|
|
"Tree-independent dual-tree algorithms," in Proceedings of the 30th
|
|
International Conference on Machine Learning (ICML '13), pp. 1435--1443, 2013.
|
|
@endquote
|
|
@endcode
|
|
|
|
The definition is:
|
|
|
|
A **space tree** on a dataset \f$ S \in \mathcal{R}^{N \times d} \f$ is an
|
|
undirected, connected, acyclic, rooted simple graph with the following
|
|
properties:
|
|
|
|
- Each node (or vertex) holds a number of points (possibly zero) and is
|
|
connected to one parent node and a number of child nodes (possibly zero).
|
|
|
|
- There is one node in every space tree with no parent; this is the root node
|
|
of the tree.
|
|
|
|
- Each point in \f$S\f$ is contained in at least one node.
|
|
|
|
- Each node corresponds to some subset of \f$\mathcal{R}^d\f$ that contains
|
|
each point in the node and also the subsets that correspond to each child of the
|
|
node.
|
|
|
|
This is really a quite straightforward definition: a tree is hierarchical, and
|
|
each node corresponds to some region of the input space. Each node may have
|
|
some number of children, and may hold some number of points. However, there is
|
|
an important terminology distinction to make: the term **points held by a node**
|
|
has a different meaning than the term **descendant points held by a node**. The
|
|
points held in a node are just that---points held only in the node. The
|
|
descendant points of a node are the combination of the points held in a node
|
|
with the points held in the node's children and the points held in the node's
|
|
children's children (and so forth). For the purposes of clarity in all
|
|
discussions about trees, care is taken to differentiate the terms "descendant
|
|
point" and "point".
|
|
|
|
Now, it's also important to note that a point does not *need* to hold any
|
|
children, and that a node *can* hold the same points as its children (or its
|
|
parent). Some types of trees do this. For instance, each node in the cover
|
|
tree holds only one point, and may have a child that holds the same point. As
|
|
another example, the \f$kd\f$-tree holds its points only in the leaves (at the
|
|
bottom of the tree). More information on space trees can be found in either the
|
|
"Tree-independent dual-tree algorithms" paper or any of the related literature.
|
|
|
|
So there is a huge amount of possible variety in the types of trees that can
|
|
fall into the class of *space trees*. Therefore, it's important to treat them
|
|
abstractly, and the \c TreeType policy allows us to do just that. All we need
|
|
to remember is that a node in a tree can be represented as the combination of
|
|
some points held in the node, some child nodes, and some geometric structure
|
|
that represents the space that all of the descendant points fall into (this is a
|
|
restatement of the fourth part of the definition).
|
|
|
|
@section treetype_template_params Template parameters required by the TreeType policy
|
|
|
|
Most everything in mlpack is decomposed into a series of configurable template
|
|
parameters, and trees are no exception. In order to ease usage of high-level
|
|
mlpack algorithms, each \c TreeType itself must be a template class taking three
|
|
parameters:
|
|
|
|
- \c MetricType -- the underlying metric that the tree will be built on (see
|
|
\ref metrics "the MetricType policy documentation")
|
|
- \c StatisticType -- holds any auxiliary information that individual
|
|
algorithms may need
|
|
- \c MatType -- the type of the matrix used to represent the data
|
|
|
|
The reason that these three template parameters are necessary is so that each
|
|
\c TreeType can be used as a template template parameter, which can radically
|
|
simplify the required syntax for instantiating mlpack algorithms. By using
|
|
template template parameters, a user needs only to write
|
|
|
|
@code
|
|
// The RangeSearch class takes a MetricType and a TreeType template parameter.
|
|
|
|
// This code instantiates RangeSearch with the ManhattanDistance and a
|
|
// QuadTree. Note that the QuadTree itself is a template, and takes a
|
|
// MetricType, StatisticType, and MatType, just like the policy requires.
|
|
|
|
// This example ignores the constructor parameters, for the sake of simplicity.
|
|
RangeSearch<ManhattanDistance, QuadTree> rs(...);
|
|
@endcode
|
|
|
|
as opposed to the far more complicated alternative, where the user must specify
|
|
the values of each template parameter of the tree type:
|
|
|
|
@code
|
|
// This is a much worse alternative, where the user must specify the template
|
|
// arguments of their tree.
|
|
RangeSearch<ManhattanDistance,
|
|
QuadTree<ManhattanDistance, EmptyStatistic, arma::mat>> rs(...);
|
|
@endcode
|
|
|
|
Unfortunately, the price to pay for this user convenience is that *every*
|
|
\c TreeType must have three template parameters, and they must be in exactly
|
|
that order. Fortunately, there is an additional benefit: we are guaranteed that
|
|
the tree is built using the same metric as the method (that is, a user can't
|
|
specify different metric types to the algorithm and to the tree, which they can
|
|
without template template parameters).
|
|
|
|
There are two important notes about this:
|
|
|
|
- Not every possible input of MetricType, StatisticType, and/or MatType
|
|
necessarily need to be valid or work correctly for each type of tree. For
|
|
instance, the QuadTree is limited to Euclidean metrics and will not work
|
|
otherwise. Either compile-time static checks or detailed documentation can help
|
|
keep users from using invalid combinations of template arguments.
|
|
|
|
- Some types of trees have more template parameters than just these three. One
|
|
example is the generalized binary space tree, where the bounding shape of each
|
|
node is easily made into a fourth template parameter (the \c BinarySpaceTree
|
|
class calls this the \c BoundType parameter), and the procedure used to split a
|
|
node is easily made into a fifth template parameter (the \c BinarySpaceTree
|
|
class calls this the \c SplitType parameter). However, the syntax of template
|
|
template parameters *requires* that the class only has the correct number of
|
|
template parameters---no more, no less. Fortunately, C++11 allows template
|
|
typedefs, which can be used to provide partial specialization of template
|
|
classes:
|
|
|
|
@code
|
|
// This is the definition of the BinarySpaceTree class, which has five template
|
|
// parameters.
|
|
template<typename MetricType,
|
|
typename StatisticType,
|
|
typename MatType,
|
|
typename BoundType,
|
|
typename SplitType>
|
|
class BinarySpaceTree;
|
|
|
|
// The 'using' keyword gives us a template typedef, so we can define the
|
|
// MeanSplitKDTree template class, which has three parameters and is a valid
|
|
// TreeType policy class.
|
|
template<typename MetricType, typename StatisticType, typename MatType>
|
|
using MeanSplitKDTree = BinarySpaceTree<MetricType,
|
|
StatisticType,
|
|
MatType,
|
|
HRectBound<MetricType>
|
|
MeanSplit<BoundType, MetricType>>;
|
|
@endcode
|
|
|
|
Now, the \c MeanSplitKDTree class has only three template parameters and can be
|
|
used as a \c TreeType policy class in various mlpack algorithms. Many types of
|
|
trees in mlpack have more than three template parameters and rely on template
|
|
typedefs to provide simplified \c TreeType interfaces.
|
|
|
|
@section treetype_api The TreeType API
|
|
|
|
As a result of the definition of *space tree* in the previous section, a
|
|
simplified API presents itself quite easily. However, more complex
|
|
functionality is often necessary in mlpack, so this leads to more functions
|
|
being necessary for a class to satisfy the \c TreeType policy. Combining this
|
|
with the template parameters required for trees given in the previous section
|
|
gives us the complete API required for a class implementing the \c TreeType
|
|
policy. Below is the minimal set of functions required with minor
|
|
documentation for each function. (More extensive documentation and explanation
|
|
is given afterwards.)
|
|
|
|
@code
|
|
// The three template parameters will be supplied by the user, and are detailed
|
|
// in the previous section.
|
|
template<typename MetricType,
|
|
typename StatisticType,
|
|
typename MatType>
|
|
class ExampleTree
|
|
{
|
|
public:
|
|
//////////////////////
|
|
//// Constructors ////
|
|
//////////////////////
|
|
|
|
// This batch constructor does not modify the dataset, and builds the entire
|
|
// tree using a default-constructed MetricType.
|
|
ExampleTree(const MatType& data);
|
|
|
|
// This batch constructor does not modify the dataset, and builds the entire
|
|
// tree using the given MetricType.
|
|
ExampleTree(const MatType& data, MetricType& metric);
|
|
|
|
// Initialize the tree from a given boost::serialization archive. SFINAE (the
|
|
// second argument) is necessary to ensure that the archive is loading, not
|
|
// saving.
|
|
template<typename Archive>
|
|
ExampleTree(
|
|
Archive& ar,
|
|
const typename boost::enable_if<typename Archive::is_loading>::type* = 0);
|
|
|
|
// Release any resources held by the tree.
|
|
~ExampleTree();
|
|
|
|
// ///////////////////////// //
|
|
// // Basic functionality // //
|
|
// ///////////////////////// //
|
|
|
|
// Get the dataset that the tree is built on.
|
|
const MatType& Dataset();
|
|
|
|
// Get the metric that the tree is built with.
|
|
MetricType& Metric();
|
|
|
|
// Get/modify the StatisticType for this node.
|
|
StatisticType& Stat();
|
|
|
|
// Return the parent of the node, or NULL if this is the root.
|
|
ExampleTree* Parent();
|
|
|
|
// Return the number of children held by the node.
|
|
size_t NumChildren();
|
|
// Return the i'th child held by the node.
|
|
ExampleTree& Child(const size_t i);
|
|
|
|
// Return the number of points held in the node.
|
|
size_t NumPoints();
|
|
// Return the index of the i'th point held in the node.
|
|
size_t Point(const size_t i);
|
|
|
|
// Return the number of descendant nodes of this node.
|
|
size_t NumDescendantNodes();
|
|
// Return the i'th descendant node of this node.
|
|
ExampleTree& DescendantNode(const size_t i);
|
|
|
|
// Return the number of descendant points of this node.
|
|
size_t NumDescendants();
|
|
// Return the index of the i'th descendant point of this node.
|
|
size_t Descendant(const size_t i);
|
|
|
|
// Store the center of the bounding region of the node in the given vector.
|
|
void Center(arma::vec& center);
|
|
|
|
// ///////////////////////////////////////////////// //
|
|
// // More complex distance-related functionality // //
|
|
// ///////////////////////////////////////////////// //
|
|
|
|
// Return the distance between the center of this node and the center of
|
|
// its parent.
|
|
double ParentDistance();
|
|
|
|
// Return an upper bound on the furthest possible distance between the
|
|
// center of the node and any point held in the node.
|
|
double FurthestPointDistance();
|
|
|
|
// Return an upper bound on the furthest possible distance between the
|
|
// center of the node and any descendant point of the node.
|
|
double FurthestDescendantDistance();
|
|
|
|
// Return a lower bound on the minimum distance between the center and any
|
|
// edge of the node's bounding shape.
|
|
double MinimumBoundDistance();
|
|
|
|
// Return a lower bound on the minimum distance between the given point and
|
|
// the node.
|
|
template<typename VecType>
|
|
double MinDistance(VecType& point);
|
|
|
|
// Return a lower bound on the minimum distance between the given node and
|
|
// this node.
|
|
double MinDistance(ExampleTree& otherNode);
|
|
|
|
// Return an upper bound on the maximum distance between the given point and
|
|
// the node.
|
|
template<typename VecType>
|
|
double MaxDistance(VecType& point);
|
|
|
|
// Return an upper bound on the maximum distance between the given node and
|
|
// this node.
|
|
double MaxDistance(ExampleTree& otherNode);
|
|
|
|
// Return the combined results of MinDistance() and MaxDistance().
|
|
template<typename VecType>
|
|
math::Range RangeDistance(VecType& point);
|
|
|
|
// Return the combined results of MinDistance() and MaxDistance().
|
|
math::Range RangeDistance(ExampleTree& otherNode);
|
|
|
|
// //////////////////////////////////// //
|
|
// // Serialization (loading/saving) // //
|
|
// //////////////////////////////////// //
|
|
|
|
// Return a string representation of the tree.
|
|
std::string ToString() const;
|
|
|
|
// Serialize the tree (load from the given archive / save to the given
|
|
// archive, depending on its type).
|
|
template<typename Archive>
|
|
void Serialize(Archive& ar, const unsigned int version);
|
|
|
|
protected:
|
|
// A default constructor; only meant to be used by boost::serialization. This
|
|
// must be protected so that boost::serialization will work; it does not need
|
|
// to return a valid tree.
|
|
ExampleTree();
|
|
|
|
// Friend access must be given for the default constructor.
|
|
friend class boost::serialization::access;
|
|
};
|
|
@endcode
|
|
|
|
Although this is significantly more complex than the four-item definition of
|
|
*space tree* might suggest, it turns out many of these methods are not
|
|
difficult to implement for most reasonable tree types. It is also important to
|
|
realize that this is a *minimum* API; you may implement more complex tree types
|
|
at your leisure (and you may include more template parameters too, though you
|
|
will have to use template typedefs to provide versions with three parameters;
|
|
see \ref treetype_template_params "the previous section").
|
|
|
|
Before diving into the detailed documentation for each function, let us consider
|
|
a few important points about the implications of this API:
|
|
|
|
- **Trees are not default-constructible** and should not (in general) provide
|
|
a default constructor. This helps prevent invalid trees. In general, any
|
|
instantiated mlpack object should be valid and ready to use---and a tree built
|
|
on no points is not valid or ready to use.
|
|
|
|
- **Trees only need to provide batch constructors.** Although many tree types
|
|
do have algorithms for incremental insertions, in mlpack this is not required
|
|
because the tree-based algorithms that mlpack implements generally assume
|
|
fully-built, non-modifiable trees. For this purpose, batch construction is
|
|
perfectly sufficient. (It's also worth pointing out that for some types of
|
|
trees, like kd-trees, the cost of a handful of insertions often outweighs the
|
|
cost of completely rebuilding the tree.)
|
|
|
|
- **Trees must provide a number of distance bounding functions.** The utility
|
|
of trees generally stems from the ability to place quick bounds on
|
|
distance-related quantities. For instance, if all the descendant points of a
|
|
node are bounded by a ball of radius \f$\lambda\f$ and the center of the node
|
|
is a point \f$c\f$, then the minimum distance between some point \f$p\f$ and any
|
|
descendant point of the node is equal to the distance between \f$p\f$ and
|
|
\f$c\f$ minus the radius \f$\lambda\f$: \f$d(p, c) - \lambda\f$. This is a fast
|
|
calculation, and (usually) provides a decent bound on the minimum distance
|
|
between \f$p\f$ and any descendant point of the node.
|
|
|
|
- **Trees need to be able to be serialized.** mlpack uses the
|
|
boost::serialization library for saving and loading objects. Trees---which can
|
|
be a part of machine learning models---therefore must have the ability to be
|
|
saved and loaded. Making this all work requires a protected constructor (part
|
|
of the API) and generally makes it impossible to hold references instead of
|
|
pointers internally, because if a tree is loaded from a file then it must own
|
|
the dataset it is built on and the metric it uses (this also means that a
|
|
destructor must exist for freeing these resources).
|
|
|
|
Now, we can consider each part of the API more rigorously.
|
|
|
|
@section treetype_rigorous Rigorous API documentation
|
|
|
|
This section is divided into five parts:
|
|
|
|
- \ref treetype_rigorous_template
|
|
- \ref treetype_rigorous_constructor
|
|
- \ref treetype_rigorous_basic
|
|
- \ref treetype_rigorous_complex
|
|
- \ref treetype_rigorous_serialization
|
|
|
|
@subsection treetype_rigorous_template Template parameters
|
|
|
|
\ref treetype_template_params "An earlier section" discussed the three different
|
|
template parameters that are required by the \c TreeType policy.
|
|
|
|
The \ref metrics "MetricType policy" provides one method that will be useful for
|
|
tree building and other operations:
|
|
|
|
@code
|
|
// This function is required by the MetricType policy.
|
|
// Evaluate the metric between two points (which may be of different types).
|
|
template<typename VecTypeA, typename VecTypeB>
|
|
double Evaluate(const VecTypeA& a, const VecTypeB& b);
|
|
@endcode
|
|
|
|
Note that this method is not necessarily static, so a \c MetricType object
|
|
should be held internally and its \c Evaluate() method should be called whenever
|
|
the distance between two points is required. **It is generally a bad idea to
|
|
hardcode any distance calculation in your tree.** This will make the tree
|
|
unable to generalize to arbitrary metrics. If your tree must depend on certain
|
|
assumptions holding about the metric (i.e. the metric is a Euclidean metric),
|
|
then make that clear in the documentation of the tree, so users do not try to
|
|
use the tree with an inappropriate metric.
|
|
|
|
The second template parameter, \c StatisticType, is for auxiliary information
|
|
that is required by certain algorithms. For instance, consider an algorithm
|
|
which repeatedly uses the variance of the descendant points of a node. It might
|
|
be tempting to add a \c Variance() method to the required \c TreeType API, but
|
|
this quickly leads to code bloat (after all, the API already has quite enough
|
|
functions as it is). Instead, it is better to create a \c StatisticType class
|
|
which provides the \c Variance() method, and then call \c Stat().Variance() when
|
|
the variance is required. This also holds true for cached data members.
|
|
|
|
Each node should have its own instance of a \c StatisticType class. The
|
|
\c StatisticType must provide the following constructors:
|
|
|
|
@code
|
|
// Default constructor required by the StatisticType policy.
|
|
StatisticType();
|
|
|
|
// This constructor is required by the StatisticType policy.
|
|
template<typename TreeType>
|
|
StatisticType(TreeType& node);
|
|
@endcode
|
|
|
|
This constructor should be called with \c (*this) after the node is constructed
|
|
(usually, this ends up being the last line in the constructor of a node).
|
|
|
|
The last template parameter is the \c MatType parameter. This is generally
|
|
\c arma::mat or \c arma::sp_mat, but could be any Armadillo type, including
|
|
matrices that hold data points of different precisions (such as \c float or even
|
|
\c int). It generally suffices to write \c MatType assuming that \c arma::mat
|
|
will be used, since the vast majority of the time this will be what is used.
|
|
|
|
@subsection treetype_rigorous_constructor Constructors and destructors
|
|
|
|
The \c TreeType API requires at least three constructors. Technically, it does
|
|
not *require* a destructor, but almost certainly your tree class will be doing
|
|
some memory management internally and should have one (though not always).
|
|
|
|
The first two constructors are variations of the same idea:
|
|
|
|
@code
|
|
// This batch constructor does not modify the dataset, and builds the entire
|
|
// tree using a default-constructed MetricType.
|
|
ExampleTree(const MatType& data);
|
|
|
|
// This batch constructor does not modify the dataset, and builds the entire
|
|
// tree using the given MetricType.
|
|
ExampleTree(const MatType& data, MetricType& metric);
|
|
@endcode
|
|
|
|
All that is required here is that a constructor is available that takes a
|
|
dataset and optionally an instantiated metric. If no metric is provided, then
|
|
it should be assumed that the \c MetricType class has a default constructor and
|
|
a default-constructed metric should be used. The constructor *must* return a
|
|
valid, fully-constructed, ready-to-use tree that satisfies the definition
|
|
of *space tree* that was \ref whatistree "given earlier".
|
|
|
|
It is possible to implement both these constructors as one by using \c
|
|
boost::optional.
|
|
|
|
The third constructor requires the tree to be initializable from a \c
|
|
boost::serialization archive:
|
|
|
|
@code
|
|
// Initialize the tree from a given boost::serialization archive. SFINAE (the
|
|
// second argument) is necessary to ensure that the archive is loading, not
|
|
// saving.
|
|
template<typename Archive>
|
|
ExampleTree(
|
|
Archive& ar,
|
|
const typename boost::enable_if<typename Archive::is_loading>::type* = 0);
|
|
@endcode
|
|
|
|
This has implications on how the tree must be stored. In this case, the dataset
|
|
is *not yet loaded* and therefore the tree **may be required to have
|
|
ownership of the data matrix**. This means that realistically the most
|
|
reasonable way to represent the data matrix internally in a tree class is not
|
|
with a reference but instead with a pointer. If this is true, then a destructor
|
|
will be required:
|
|
|
|
@code
|
|
// Release any resources held by the tree.
|
|
~ExampleTree();
|
|
@endcode
|
|
|
|
and, if the data matrix is represented internally with a pointer, this
|
|
destructor will need to release the memory for the data matrix (in the case that
|
|
the tree was created via \c boost::serialization ).
|
|
|
|
Note that these constructors are not necessarily the only constructors that a
|
|
\c TreeType implementation can provide. One important example of when more
|
|
constructors are useful is when the tree rearranges points internally; this
|
|
might be desired for the sake of speed or memory optimization. But to do this
|
|
with the required constructors would necessarily incur a copy of the data
|
|
matrix, because the user will pass a \c "const MatType&". One alternate
|
|
solution is to provide a constructor which takes an rvalue reference to a
|
|
\c MatType:
|
|
|
|
@code
|
|
template<typename Archive>
|
|
ExampleTree(MatType&& data);
|
|
@endcode
|
|
|
|
(and another overload that takes an instantiated metric), and then the user can
|
|
use \c std::move() to build the tree without copying the data matrix, although
|
|
the data matrix will be modified:
|
|
|
|
@code
|
|
ExampleTree exTree(std::move(dataset));
|
|
@endcode
|
|
|
|
It is, of course, possible to add even more constructors if desired.
|
|
|
|
@subsection treetype_rigorous_basic Basic tree functionality
|
|
|
|
The basic functionality of a class implementing the \c TreeType API is quite
|
|
straightforward and intuitive.
|
|
|
|
@code
|
|
// Get the dataset that the tree is built on.
|
|
const MatType& Dataset();
|
|
@endcode
|
|
|
|
This should return a \c const reference to the dataset the tree is built on.
|
|
The fact that this function is required essentially means that each node in the
|
|
tree must store a pointer to the dataset (this is not the only option, but it is
|
|
the most obvious option).
|
|
|
|
@code
|
|
// Get the metric that the tree is built with.
|
|
MetricType& Metric();
|
|
@endcode
|
|
|
|
Each node must also store an instantiated metric or a pointer to one (note that
|
|
this is required even for metrics that have no state and have a \c static \c
|
|
Evaluate() function).
|
|
|
|
@code
|
|
// Get/modify the StatisticType for this node.
|
|
StatisticType& Stat();
|
|
@endcode
|
|
|
|
As discussed earlier, each node must hold a \c StatisticType; this is accessible
|
|
through the \c Stat() function.
|
|
|
|
@code
|
|
// Return the parent of the node, or NULL if this is the root.
|
|
ExampleTree* Parent();
|
|
|
|
// Return the number of children held by the node.
|
|
size_t NumChildren();
|
|
// Return the i'th child held by the node.
|
|
ExampleTree& Child(const size_t i);
|
|
|
|
// Return the number of points held in the node.
|
|
size_t NumPoints();
|
|
// Return the index of the i'th point held in the node.
|
|
size_t Point(const size_t i);
|
|
|
|
// Return the number of descendant nodes of this node.
|
|
size_t NumDescendantNodes();
|
|
// Return the i'th descendant node of this node.
|
|
ExampleTree& DescendantNode(const size_t i);
|
|
|
|
// Return the number of descendant points of this node.
|
|
size_t NumDescendants();
|
|
// Return the index of the i'th descendant point of this node.
|
|
size_t Descendant(const size_t i);
|
|
@endcode
|
|
|
|
These functions are all fairly self-explanatory. Most algorithms will use the
|
|
\c Parent(), \c Children(), \c NumChildren(), \c Point(), and \c NumPoints()
|
|
functions, so care should be taken when implementing those functions to ensure
|
|
they will be efficient. Note that \c Point() and \c Descendant() should return
|
|
indices of points, so the actual points can be accessed by calling
|
|
\c "Dataset().col(Point(i))" for some index \c i (or something similar).
|
|
|
|
An important note about the \c Descendant() function is that each descendant
|
|
point should be unique. So if a node holds the point with index 6 and it has
|
|
one child that holds the points with indices 6 and 7, then \c NumDescendants()
|
|
should return 2, not 3. The ordering in which the descendants are returned can
|
|
be arbitrary; so, \c Descendant(0) can return 6 \b or 7, and \c Descendant(1)
|
|
should return the other index.
|
|
|
|
@code
|
|
// Store the center of the bounding region of the node in the given vector.
|
|
void Center(arma::vec& center);
|
|
@endcode
|
|
|
|
The last function, \c Center(), should calculate the center of the bounding
|
|
shape and store it in the given vector. So, for instance, if the tree is a ball
|
|
tree, then the center is simply the center of the ball. Algorithm writers would
|
|
be wise to try and avoid the use of \c Center() if possible, since it will
|
|
necessarily cost a copy of a vector.
|
|
|
|
@subsection treetype_rigorous_complex Complex tree functionality and bounds
|
|
|
|
A node in a tree should also be able to calculate various distance-related
|
|
bounds; these are particularly useful in tree-based algorithms. Note that any
|
|
of these bounds does not necessarily need to be maximally tight; generally it is
|
|
more important that each bound can be easily calculated.
|
|
|
|
Details on each bounding function that the \c TreeType API requires are given
|
|
below.
|
|
|
|
@code
|
|
// Return the distance between the center of this node and the center of
|
|
// its parent.
|
|
double ParentDistance();
|
|
@endcode
|
|
|
|
Remember that each node corresponds to some region in the space that the dataset
|
|
lies in. For most tree types this shape is often something geometrically
|
|
simple: a ball, a cone, a hyperrectangle, a slice, or something similar. The
|
|
\c ParentDistance() function should return the distance between the center of
|
|
this node's region and the center of the parent node's region.
|
|
|
|
In practice this bound is often used in dual-tree (or single-tree) algorithms to
|
|
place an easy \c MinDistance() (or \c MaxDistance() ) bound for a child node;
|
|
the parent's \c MinDistance() (or \c MaxDistance() ) function is called and then
|
|
adjusted with \c ParentDistance() to provide a possibly loose but efficient
|
|
bound on what the result of \c MinDistance() (or \c MaxDistance() ) would be
|
|
with the child.
|
|
|
|
@code
|
|
// Return an upper bound on the furthest possible distance between the
|
|
// center of the node and any point held in the node.
|
|
double FurthestPointDistance();
|
|
|
|
// Return an upper bound on the furthest possible distance between the
|
|
// center of the node and any descendant point of the node.
|
|
double FurthestDescendantDistance();
|
|
@endcode
|
|
|
|
It is often very useful to be able to bound the radius of a node, which is
|
|
effectively what \c FurthestDescendantDistance() does. Often it is easiest to
|
|
simply calculate and cache the furthest descendant distance at tree construction
|
|
time. Some trees, such as the cover tree, are able to give guarantees that the
|
|
points held in the node will necessarily be closer than the descendant points;
|
|
therefore, the \c FurthestPointDistance() function is also useful.
|
|
|
|
It is permissible to simply have \c FurthestPointDistance() return the result of
|
|
\c FurthestDescendantDistance(), and that will still be a valid bound, but
|
|
depending on the type of tree it may be possible to have \c
|
|
FurthestPointDistance() return a tighter bound.
|
|
|
|
@code
|
|
// Return a lower bound on the minimum distance between the center and any
|
|
// edge of the node's bounding shape.
|
|
double MinimumBoundDistance();
|
|
@endcode
|
|
|
|
This is, admittedly, a somewhat complex and weird quantity. It is one of the
|
|
less important bounding functions, so it is valid to simply return 0...
|
|
|
|
The bound is a bound on the minimum distance between the center of the node and
|
|
any edge of the shape that bounds all of the descendants of the node. So, if
|
|
the bounding shape is a ball (as in a ball tree or a cover tree), then
|
|
\c MinimumBoundDistance() should just return the radius of the ball. If the
|
|
bounding shape is a hypercube (as in a generalized octree), then
|
|
\c MinimumBoundDistance() should return the side length divided by two. If the
|
|
bounding shape is a hyperrectangle (as in a kd-tree or a spill tree), then
|
|
\c MinimumBoundDistance() should return half the side length of the
|
|
hyperrectangle's smallest side.
|
|
|
|
@code
|
|
// Return a lower bound on the minimum distance between the given point and
|
|
// the node.
|
|
template<typename VecType>
|
|
double MinDistance(VecType& point);
|
|
|
|
// Return a lower bound on the minimum distance between the given node and
|
|
// this node.
|
|
double MinDistance(ExampleTree& otherNode);
|
|
|
|
// Return an upper bound on the maximum distance between the given point and
|
|
// the node.
|
|
template<typename VecType>
|
|
double MaxDistance(VecType& point);
|
|
|
|
// Return an upper bound on the maximum distance between the given node and
|
|
// this node.
|
|
double MaxDistance(ExampleTree& otherNode);
|
|
|
|
// Return the combined results of MinDistance() and MaxDistance().
|
|
template<typename VecType>
|
|
math::Range RangeDistance(VecType& point);
|
|
|
|
// Return the combined results of MinDistance() and MaxDistance().
|
|
math::Range RangeDistance(ExampleTree& otherNode);
|
|
@endcode
|
|
|
|
These six functions are almost without a doubt the most important functionality
|
|
of a tree. Therefore, it is preferable that these methods be implemented as
|
|
efficiently as possible, as they may potentially be called many millions of
|
|
times in a tree-based algorithm. It is also preferable that these bounds be as
|
|
tight as possible. In tree-based algorithms, these are used for pruning away
|
|
work, and tighter bounds mean that more pruning is possible.
|
|
|
|
Of these six functions, there are only really two bounds that are desired here:
|
|
the *minimum distance* between a node and an object, and the *maximum distance*
|
|
between a node and an object. The object may be either a vector (usually \c
|
|
arma::vec ) or another tree node.
|
|
|
|
Consider the first case, where the object is a vector. The result of
|
|
\c MinDistance() needs to be less than or equal to the true minimum distance,
|
|
which could be calculated as below:
|
|
|
|
@code
|
|
// We assume that we have a vector 'vec', and a tree node 'node'.
|
|
double trueMinDist = DBL_MAX;
|
|
for (size_t i = 0; i < node.NumDescendants(); ++i)
|
|
{
|
|
const double dist = node.Metric().Evaluate(vec,
|
|
node.Dataset().col(node.Descendant(i)));
|
|
if (dist < trueMinDist)
|
|
trueMinDist = dist;
|
|
}
|
|
// At the end of the loop, trueMinDist will hold the true minimum distance
|
|
// between 'vec' and any descendant point of 'node'.
|
|
@endcode
|
|
|
|
Often the bounding shape of a node will allow a quick calculation that will make
|
|
a reasonable bound. For instance, if the node's bounding shape is a ball with
|
|
radius \c r and center \c ctr, the calculation is simply
|
|
\c "(node.Metric().Evaluate(vec, ctr) - r)". Usually a good \c MinDistance() or
|
|
\c MaxDistance() function will make only one call to the \c Evaluate() function
|
|
of the metric.
|
|
|
|
The \c RangeDistance() function allows a way for both bounds to be calculated at
|
|
once. It is possible to implement this as a call to \c MinDistance() followed
|
|
by a call to \c MaxDistance(), but this may incur more metric \c Evaluate()
|
|
calls than necessary. Often calculating both bounds at once can be more
|
|
efficient and can be done with fewer \c Evaluate() calls than calling both
|
|
\c MinDistance() and \c MaxDistance().
|
|
|
|
@subsection treetype_rigorous_serialization Serialization
|
|
|
|
The last two public functions that the \c TreeType API requires are related to
|
|
serialization and printing.
|
|
|
|
@code
|
|
// Return a string representation of the tree.
|
|
std::string ToString() const;
|
|
@endcode
|
|
|
|
There are few restrictions on the precise way that the \c ToString() function
|
|
should operate, but generally it should behave similarly to the \c ToString()
|
|
function in other mlpack methods. Generally, a user will call \c ToString()
|
|
when they want to inspect the object and see what it looks like. For a tree,
|
|
printing the entire tree may be way more information than the user was
|
|
expecting, so it may be a better option to print either only the node itself or
|
|
the node plus one or two levels of children.
|
|
|
|
@code
|
|
// Serialize the tree (load from the given archive / save to the given
|
|
// archive, depending on its type).
|
|
template<typename Archive>
|
|
void Serialize(Archive& ar, const unsigned int version);
|
|
|
|
protected:
|
|
// A default constructor; only meant to be used by boost::serialization. This
|
|
// must be protected so that boost::serialization will work; it does not need
|
|
// to return a valid tree.
|
|
ExampleTree();
|
|
|
|
// Friend access must be given for the default constructor.
|
|
friend class boost::serialization::access;
|
|
@endcode
|
|
|
|
On the other hand, the specifics of the functionality required for the
|
|
\c Serialize() function are somewhat more difficult. The \c Serialize()
|
|
function will be called either when a tree is being saved to disk or loaded from
|
|
disk. The \c boost::serialization documentation is fairly comprehensive, but
|
|
when writing a \c Serialize() method for mlpack trees you should use
|
|
\c data::CreateNVP() instead of \c BOOST_SERIALIZATION_NVP(). This is because
|
|
mlpack classes implement \c Serialize() instead of \c serialize() in order to
|
|
conform to the mlpack style guidelines, and making this work requires some
|
|
interesting shim code, which is hidden inside of \c data::CreateNVP(). It may
|
|
be useful to look at other \c Serialize() methods contained in other mlpack
|
|
classes as an example.
|
|
|
|
An important note is that it is very difficult to use references with
|
|
\c boost::serialization, because \c Serialize() may be called at any time during
|
|
the object's lifetime, and references cannot be re-seated. In general this will
|
|
require the use of pointers, which then require manual memory management.
|
|
Therefore, be careful that \c Serialize() (and the tree's destructor) properly
|
|
handle memory management!
|
|
|
|
@section treetype_traits The TreeTraits trait class
|
|
|
|
Some tree-based algorithms can specialize if the tree fulfills certain
|
|
conditions. For instance, if the regions represented by two sibling nodes
|
|
cannot overlap, an algorithm may be able to perform a simpler computation.
|
|
Based on this reasoning, the \c TreeTraits trait class (much like the
|
|
mlpack::kernel::KernelTraits class) exists in order to allow a tree to specify
|
|
(via a \c const \c static \c bool) when these types of conditions are
|
|
satisfied. **Note that a TreeTraits class is not required,** but may be
|
|
helpful.
|
|
|
|
The \c TreeTraits trait class is a template class that takes a \c TreeType as a
|
|
parameter, and exposes \c const \c static \c bool values that depend on the
|
|
tree. Setting these values is achieved by specialization. The code below shows
|
|
the default \c TreeTraits values (these are the values that will be used if no
|
|
specialization is provided for a given \c TreeType).
|
|
|
|
@code
|
|
template<typename TreeType>
|
|
class TreeTraits
|
|
{
|
|
public:
|
|
// This is true if the subspaces represented by the children of a node can
|
|
// overlap.
|
|
static const bool HasOverlappingChildren = true;
|
|
|
|
// This is true if Point(0) is the centroid of the node.
|
|
static const bool FirstPointIsCentroid = false;
|
|
|
|
// This is true if the points contained in the first child of a node
|
|
// (Child(0)) are also contained in that node.
|
|
static const bool HasSelfChildren = false;
|
|
|
|
// This is true if the tree rearranges points in the dataset when it is built.
|
|
static const bool RearrangesDataset = false;
|
|
|
|
// This is true if the tree always has only two children.
|
|
static const bool BinaryTree = false;
|
|
};
|
|
@endcode
|
|
|
|
An example specialization for the \ref mlpack::tree::KDTree class is given
|
|
below. Note that \ref mlpack::tree::KDTree is itself a template class (like
|
|
every class satisfying the \c TreeType policy), so we are specializing to a
|
|
template parameter.
|
|
|
|
@code
|
|
template<typename MetricType,
|
|
typename StatisticType,
|
|
typename MatType>
|
|
template<>
|
|
class TreeTraits<KDTree<MetricType, StatisticType, MatType>>
|
|
{
|
|
public:
|
|
// The regions represented by the two children of a node may not overlap.
|
|
static const bool HasOverlappingChildren = false;
|
|
|
|
// There is no guarantee that the first point of a node is the centroid.
|
|
static const bool FirstPointIsCentroid = false;
|
|
|
|
// Points are not contained at multiple levels (only at the leaves).
|
|
static const bool HasSelfChildren = false;
|
|
|
|
// Points are rearranged during the building of the tree.
|
|
static const bool RearrangesDataset = true;
|
|
|
|
// The tree is always binary.
|
|
static const bool BinaryTree = true;
|
|
};
|
|
@endcode
|
|
|
|
Currently, the traits available are each of the five detailed above. For more
|
|
information, see the \ref mlpack::tree::TreeTraits documentation.
|
|
|
|
@section treetype_more A list of trees in mlpack and more information
|
|
|
|
mlpack contains several ready-to-use implementations of trees that satisfy the
|
|
TreeType policy API:
|
|
|
|
- mlpack::tree::KDTree
|
|
- mlpack::tree::MeanSplitKDTree
|
|
- mlpack::tree::BallTree
|
|
- mlpack::tree::MeanSplitBallTree
|
|
- mlpack::tree::RTree
|
|
- mlpack::tree::RStarTree
|
|
- mlpack::tree::StandardCoverTree
|
|
|
|
Often, these are template typedefs of more flexible tree classes:
|
|
|
|
- mlpack::tree::BinarySpaceTree -- binary trees, such as the KD-tree and ball
|
|
tree
|
|
- mlpack::tree::RectangleTree -- the R tree and variants
|
|
- mlpack::tree::CoverTree -- the cover tree and variants
|
|
|
|
*/
|