Document SpillTree and its four variants (#3925)
* Commit initial tested documentation of spill tree. * Document all four variants of spill trees. * Correct documentation: a rho of 0.5 will result in no overlapping. * Add new trees to sidebar. * Don't upgrade pip since it is installed by Homebrew on OS X.
This commit is contained in:
@@ -26,7 +26,6 @@ runs:
|
||||
if: inputs.lang == 'Python' && runner.os == 'macOS'
|
||||
shell: bash
|
||||
run: |
|
||||
/opt/homebrew/bin/python3 -m pip install --break-system-packages --upgrade pip
|
||||
/opt/homebrew/bin/python3 -m pip install --break-system-packages setuptools cython pandas zipp configparser wheel pytest
|
||||
echo "CMAKE_BINDING_ARGS=-DPYTHON_EXECUTABLE=/opt/homebrew/bin/python3" >> $GITHUB_ENV
|
||||
|
||||
|
||||
@@ -190,6 +190,31 @@ when the sidebar is built for each page.
|
||||
<code>RectangleTree</code>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="LINKROOTuser/core/trees/sp_tree.html">
|
||||
<code>SPTree</code>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="LINKROOTuser/core/trees/mean_sp_tree.html">
|
||||
<code>MeanSPTree</code>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="LINKROOTuser/core/trees/non_ort_sp_tree.html">
|
||||
<code>NonOrtSPTree</code>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="LINKROOTuser/core/trees/non_ort_mean_sp_tree.html">
|
||||
<code>NonOrtMeanSPTree</code>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="LINKROOTuser/core/trees/spill_tree.html">
|
||||
<code>SpillTree</code>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
|
||||
@@ -16,10 +16,13 @@ different trees. The following tree types are available in mlpack:
|
||||
* [`MeanSplitBallTree`](trees/mean_split_ball_tree.md)
|
||||
* [`RPTree`](trees/rp_tree.md)
|
||||
* [`MaxRPTree`](trees/max_rp_tree.md)
|
||||
* [`BinarySpaceTree`](trees/binary_space_tree.md)
|
||||
* [`UBTree`](trees/ub_tree.md)
|
||||
* [`BinarySpaceTree`](trees/binary_space_tree.md)
|
||||
|
||||
* [`CoverTree`](trees/cover_tree.md)
|
||||
|
||||
* [`Octree`](trees/octree.md)
|
||||
|
||||
* [`RTree`](trees/r_tree.md)
|
||||
* [`RStarTree`](trees/r_star_tree.md)
|
||||
* [`XTree`](trees/x_tree.md)
|
||||
@@ -28,8 +31,11 @@ different trees. The following tree types are available in mlpack:
|
||||
* [`HilbertRTree`](trees/hilbert_r_tree.md)
|
||||
* [`RectangleTree`](trees/rectangle_tree.md)
|
||||
|
||||
*Note:* this documentation is a work in progress. Not all trees are documented
|
||||
yet.
|
||||
* [`SPTree`](trees/sp_tree.md)
|
||||
* [`MeanSPTree`](trees/mean_sp_tree.md)
|
||||
* [`NonOrtSPTree`](trees/non_ort_sp_tree.md)
|
||||
* [`NonOrtMeanSPTree`](trees/non_ort_mean_sp_tree.md)
|
||||
* [`SpillTree`](trees/spill_tree.md)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -142,13 +142,11 @@ different.
|
||||
loose bounding balls. It is better to simply build a new `BallTree` on the
|
||||
modified dataset. For trees that support individual insertion and deletions,
|
||||
see the [`RectangleTree`](rectangle_tree.md) class and all its variants (e.g.
|
||||
[`RTree`](r_tree.md), `RStarTree`, etc.).
|
||||
[`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
|
||||
|
||||
- See also the
|
||||
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
|
||||
|
||||
<!-- TODO: add links to RectangleTree above when it is documented -->
|
||||
|
||||
---
|
||||
|
||||
### Constructor parameters:
|
||||
@@ -433,7 +431,7 @@ mlpack::data::Load("cloud.csv", dataset, true);
|
||||
//
|
||||
// Note that the '<>' isn't necessary if C++20 is being used (e.g.
|
||||
// `mlpack::BallTree tree(...)` will work fine in C++20 or newer).
|
||||
mlpack::BallTree<> tree(std::move(dataset));
|
||||
mlpack::BallTree<> tree(std::move(dataset), 10);
|
||||
|
||||
// Print the bounding ball of the root node.
|
||||
std::cout << "Bounding ball of root node:" << std::endl;
|
||||
|
||||
@@ -168,13 +168,12 @@ different.
|
||||
with very loose bounding boxes. It is better to simply build a new
|
||||
`BinarySpaceTree` on the modified dataset. For trees that support individual
|
||||
insertion and deletions, see the [`RectangleTree`](rectangle_tree.md) class
|
||||
and all its variants (e.g. [`RTree`](r_tree.md), `RStarTree`, etc.).
|
||||
and all its variants (e.g. [`RTree`](r_tree.md),
|
||||
[`RStarTree`](r_star_tree.md), etc.).
|
||||
|
||||
- See also the
|
||||
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
|
||||
|
||||
<!-- TODO: add links to RectangleTree above when it is documented -->
|
||||
|
||||
---
|
||||
|
||||
### Constructor parameters:
|
||||
@@ -2240,7 +2239,7 @@ mlpack::BinarySpaceTree<mlpack::EuclideanDistance,
|
||||
mlpack::EmptyStatistic,
|
||||
arma::mat,
|
||||
mlpack::HRectBound,
|
||||
mlpack::MidpointSplit> tree(std::move(dataset));
|
||||
mlpack::MidpointSplit> tree(std::move(dataset), 10);
|
||||
|
||||
// Print the bounding box of the root node.
|
||||
std::cout << "Bounding box of root node:" << std::endl;
|
||||
|
||||
@@ -101,14 +101,12 @@ dataset.
|
||||
is not supported, because this generally results in a cover tree with very
|
||||
loose bounding balls. It is better to simply build a new `CoverTree` on the
|
||||
modified dataset. For trees that support individual insertion and deletions,
|
||||
see the `RectangleTree` class and all its variants (e.g. `RTree`,
|
||||
`RStarTree`, etc.).
|
||||
see the [`RectangleTree`](rectangle_tree.md) class and all its variants (e.g.
|
||||
[`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
|
||||
|
||||
- See also the
|
||||
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
|
||||
|
||||
<!-- TODO: add links to RectangleTree above when it is documented -->
|
||||
|
||||
---
|
||||
|
||||
### Constructor parameters:
|
||||
|
||||
@@ -153,7 +153,7 @@ The dataset is not permuted during the construction process.
|
||||
|
||||
| **name** | **type** | **description** | **default** |
|
||||
|----------|----------|-----------------|-------------|
|
||||
| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. Pass with `std::move(data)` to avoid copying the matrix. | _(N/A)_ |
|
||||
| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
|
||||
| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
|
||||
| `minLeafSize` | `size_t` | Minimum number of points to store in each leaf. | `8` |
|
||||
| `maxNumChildren` | `size_t` | Maximum number of children allowed in each non-leaf node. | `5` |
|
||||
|
||||
@@ -126,13 +126,11 @@ different.
|
||||
bounding boxes. It is better to simply build a new `KDTree` on the modified
|
||||
dataset. For trees that support individual insertion and deletions, see the
|
||||
[`RectangleTree`](rectangle_tree.md) class and all its variants (e.g.
|
||||
[`RTree`](r_tree.md), `RStarTree`, etc.).
|
||||
[`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
|
||||
|
||||
- See also the
|
||||
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
|
||||
|
||||
<!-- TODO: add links to RectangleTree above when it is documented -->
|
||||
|
||||
---
|
||||
|
||||
### Constructor parameters:
|
||||
@@ -421,7 +419,7 @@ mlpack::data::Load("cloud.csv", dataset, true);
|
||||
//
|
||||
// Note that the '<>' isn't necessary if C++20 is being used (e.g.
|
||||
// `mlpack::KDTree tree(...)` will work fine in C++20 or newer).
|
||||
mlpack::KDTree<> tree(std::move(dataset));
|
||||
mlpack::KDTree<> tree(std::move(dataset), 10);
|
||||
|
||||
// Print the bounding box of the root node.
|
||||
std::cout << "Bounding box of root node:" << std::endl;
|
||||
|
||||
@@ -9,10 +9,9 @@ nearest neighbor search) in low dimensions---typically less than 100.
|
||||
|
||||
An `MaxRPTree` (or the similar [`RPTree`](rp_tree.md)) may be preferred over
|
||||
a [`KDTree`](kdtree.md) or other tree structures as it is theoretically known to
|
||||
adapt to the intrinsic dimension of the data. This is similar to the cover
|
||||
tree, but the implementation is far simpler and as a result, more efficient.
|
||||
|
||||
<!-- TODO: add cover tree link above -->
|
||||
adapt to the intrinsic dimension of the data. This is similar to the
|
||||
[cover tree](cover_tree.md), but the implementation is far simpler and as a
|
||||
result, more efficient.
|
||||
|
||||
mlpack's `MaxRPTree` implementation supports three template parameters for
|
||||
configurable behavior, and implements all the functionality required by the
|
||||
@@ -137,13 +136,12 @@ different.
|
||||
with very loose bounding boxes. It is better to simply build a new
|
||||
`MaxRPTree` on the modified dataset. For trees that support individual
|
||||
insertion and deletions, see the [`RectangleTree`](rectangle_tree.md) class
|
||||
and all its variants (e.g. [`RTree`](r_tree.md), `RStarTree`, etc.).
|
||||
and all its variants (e.g. [`RTree`](r_tree.md),
|
||||
[`RStarTree`](r_star_tree.md), etc.).
|
||||
|
||||
- See also the
|
||||
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
|
||||
|
||||
<!-- TODO: add links to RectangleTree above when it is documented -->
|
||||
|
||||
---
|
||||
|
||||
### Constructor parameters:
|
||||
@@ -432,7 +430,7 @@ mlpack::data::Load("cloud.csv", dataset, true);
|
||||
//
|
||||
// Note that the '<>' isn't necessary if C++20 is being used (e.g.
|
||||
// `mlpack::MaxRPTree tree(...)` will work fine in C++20 or newer).
|
||||
mlpack::MaxRPTree<> tree(std::move(dataset));
|
||||
mlpack::MaxRPTree<> tree(std::move(dataset), 10);
|
||||
|
||||
// Print the bounding box of the root node.
|
||||
std::cout << "Bounding box of root node:" << std::endl;
|
||||
|
||||
@@ -0,0 +1,754 @@
|
||||
# `MeanSPTree`
|
||||
|
||||
The `MeanSPTree` class implements the mean-split hybrid spill tree, a binary
|
||||
space partitioning tree that allows overlapping volumes between nodes. This
|
||||
type of tree can be more effective than trees like the [`KDTree`](kdtree.md) for
|
||||
approximate nearest neighbor search and related tasks. `MeanSPTree` is the same
|
||||
tree as [`SPTree`](sp_tree.md), except nodes are split using the mean value of
|
||||
data points projected onto the splitting hyperplane's tangent vector
|
||||
([`SPTree`](sp_tree.md) instead uses the midpoint).
|
||||
|
||||
`MeanSPTree` supports three template parameters for configurable behavior, and
|
||||
implements all the functionality required by the [TreeType
|
||||
API](../../../developer/trees.md#the-treetype-api), plus some additional
|
||||
functionality specific to spill trees. `MeanSPTree` is built on the more
|
||||
generic [`SpillTree`](spill_tree.md) class, so if fully custom behavior is
|
||||
desired, that
|
||||
|
||||
* [Template parameters](#template-parameters)
|
||||
* [Constructors](#constructors)
|
||||
* [Basic tree properties](#basic-tree-properties)
|
||||
* [Bounding distances with the tree](#bounding-distances-with-the-tree)
|
||||
* [Tree traversals](#tree-traversals)
|
||||
* [Example usage](#example-usage)
|
||||
|
||||
## See also
|
||||
|
||||
<!-- TODO: add links to all distance-based algorithms and other trees? -->
|
||||
|
||||
* [`SpillTree`](spill_tree.md)
|
||||
* [`SPTree`](sp_tree.md)
|
||||
* [`NonOrtSPTree`](non_ort_sp_tree.md)
|
||||
* [`NonOrtMeanSPTree`](non_ort_mean_sp_tree.md)
|
||||
* [`BinarySpaceTree`](binary_space_tree.md)
|
||||
* [An Investigation of Practical Approximate Nearest Neighbor Algorithms (pdf)](https://proceedings.neurips.cc/paper/2004/file/1102a326d5f7c9e04fc3c89d0ede88c9-Paper.pdf)
|
||||
* [Tree-Independent Dual-Tree Algorithms (pdf)](https://www.ratml.org/pub/pdf/2013tree.pdf)
|
||||
|
||||
## Template parameters
|
||||
|
||||
In accordance with the
|
||||
[TreeType API](../../../developer/trees.md#template-parameters-required-by-the-treetype-policy)
|
||||
(see also
|
||||
[this more detailed section](../../../developer/trees.md#template-parameters)),
|
||||
the `MeanSPTree` class takes three template parameters:
|
||||
|
||||
```
|
||||
MeanSPTree<DistanceType, StatisticType, MatType>
|
||||
```
|
||||
|
||||
* `DistanceType`: the [distance metric](../distances.md) to use for distance
|
||||
computations. Because the `MeanSPTree` internally uses
|
||||
[`HRectBound`](binary_space_tree.md#hrectbound), this is required to be
|
||||
[`EuclideanDistance`](../distances.md#lmetric). See
|
||||
[`NonOrtMeanSPTree`](non_ort_mean_sp_tree.md) for a version of the mean-split
|
||||
spill tree where arbitrary distance metrics are allowed.
|
||||
|
||||
* `StatisticType`: this holds auxiliary information in each tree node. By
|
||||
default, [`EmptyStatistic`](binary_space_tree.md#emptystatistic) is used,
|
||||
which holds no information.
|
||||
- See the [`StatisticType`](binary_space_tree.md#statistictype) section in
|
||||
the `BinarySpaceTree` documentation for more details.
|
||||
|
||||
* `MatType`: the type of matrix used to represent points. Must be a type
|
||||
matching the [Armadillo API](../../matrices.md). By default, `arma::mat` is
|
||||
used, but other types such as `arma::fmat` or similar will work just fine.
|
||||
|
||||
The `MeanSPTree` class itself is a convenience typedef of the generic
|
||||
[`SpillTree`](spill_tree.md) class, using the
|
||||
[`AxisOrthogonalHyperplane`](spill_tree.md#axisorthogonalhyperplane) class as
|
||||
the splitting hyperplane type, and the
|
||||
[`MeanSpaceSplit`](spill_tree.md#meanspacesplit) class as the splitting
|
||||
strategy.
|
||||
|
||||
If no template parameters are explicitly specified, then defaults are used:
|
||||
|
||||
```
|
||||
MeanSPTree<> = MeanSPTree<EuclideanDistance, EmptyStatistic, arma::mat>
|
||||
```
|
||||
|
||||
## Constructors
|
||||
|
||||
`MeanSPTree`s are constructed by iteratively finding splitting hyperplanes, and
|
||||
points within a margin of the hyperplane are assigned to *both* child nodes.
|
||||
Unlike the constructors of
|
||||
[`BinarySpaceTree`](binary_space_tree.md#constructors), the dataset is not
|
||||
permuted during construction.
|
||||
|
||||
---
|
||||
|
||||
* `node = MeanSPTree(data, tau=0.0, maxLeafSize=20, rho=0.7)`
|
||||
- Construct a `MeanSPTree` on the given `data`, using the specified
|
||||
hyperparameters to control tree construction behavior.
|
||||
- By default, a reference to `data` is stored. If `data` goes out of scope
|
||||
after tree construction, memory errors will occur! To avoid this, either
|
||||
pass the dataset or a copy with `std::move()` (e.g. `std::move(data)`);
|
||||
when doing this, `data` will be set to an empty matrix.
|
||||
|
||||
---
|
||||
|
||||
* `node = MeanSPTree<DistanceType, StatisticType, MatType>(data, tau=0.0, maxLeafSize=20, rho=0.7)`
|
||||
- Construct a `MeanSPTree` on the given `data`, using custom template
|
||||
parameters, and using the specified hyperparameters to control tree
|
||||
construction behavior.
|
||||
- By default, a reference to `data` is stored. If `data` goes out of scope
|
||||
after tree construction, memory errors will occur! To avoid this, either
|
||||
pass the dataset or a copy with `std::move()` (e.g. `std::move(data)`);
|
||||
when doing this, `data` will be set to an empty matrix.
|
||||
|
||||
---
|
||||
|
||||
* `node = MeanSPTree()`
|
||||
- Construct an empty `MeanSPTree` with no children, no points, and default
|
||||
template parameters.
|
||||
|
||||
---
|
||||
|
||||
***Notes:***
|
||||
|
||||
- The name `node` is used here for `MeanSPTree` objects instead of `tree`,
|
||||
because each `MeanSPTree` object is a single node in the tree. The
|
||||
constructor returns the node that is the root of the tree.
|
||||
|
||||
- Inserting individual points or removing individual points from a `MeanSPTree`
|
||||
is not supported, because this generally results in a tree with very
|
||||
suboptimal hyperplane splits. It is better to simply build a new
|
||||
`MeanSPTree` on the modified dataset. For trees that support individual
|
||||
insertion and deletions, see the [`RectangleTree`](rectangle_tree.md) class
|
||||
and all its variants (e.g. [`RTree`](r_tree.md),
|
||||
[`RStarTree`](r_star_tree.md), etc.).
|
||||
|
||||
- See also the
|
||||
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
|
||||
|
||||
---
|
||||
|
||||
### Constructor parameters:
|
||||
|
||||
| **name** | **type** | **description** | **default** |
|
||||
|----------|----------|-----------------|-------------|
|
||||
| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
|
||||
| `tau` | `double` | Width of spill margin: points within `tau` of the splitting hyperplane of a node will be contained in both left and right children. | `0.0` |
|
||||
| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
|
||||
| `rho` | `double` | Balance threshold. When splitting, if either overlapping node would contain a fraction of more than `rho` of the points, a non-overlapping split is performed. Must be in the range `[0.0, 1.0)`. | `0.7` |
|
||||
|
||||
***Caveats***:
|
||||
|
||||
* `tau` must be manually tuned for the properties of each dataset; the default,
|
||||
`0.0`, will never allow overlap between nodes (and thus the created tree will
|
||||
essentially be a non-overlapping [`BinarySpaceTree`](binary_space_tree.md)).
|
||||
|
||||
* If `tau` is set too large, nodes will overlap too much and search quality
|
||||
will be degraded.
|
||||
|
||||
* `rho` implicitly controls the depth of the tree by forcing very overlapping
|
||||
children to be non-overlapping. As `rho` gets closer to `1`, more overlap is
|
||||
allowed, which in turn makes the tree deeper. If `rho` is set to `0.5` or
|
||||
less, then all splits will be non-overlapping (and the tree will essentially
|
||||
be a [`BinarySpaceTree`](binary_space_tree.md)).
|
||||
|
||||
## Basic tree properties
|
||||
|
||||
Once a `MeanSPTree` object is constructed, various properties of the tree can be
|
||||
accessed or inspected. Many of these functions are required by the
|
||||
[TreeType API](../../../developer/trees.md#the-treetype-api).
|
||||
|
||||
### Navigating the tree
|
||||
|
||||
* `node.NumChildren()` returns the number of children in `node`. This is
|
||||
either `2` if `node` has children, or `0` if `node` is a leaf.
|
||||
|
||||
* `node.IsLeaf()` returns a `bool` indicating whether or not `node` is a leaf.
|
||||
|
||||
* `node.Child(i)` returns an `MeanSPTree&` that is the `i`th child.
|
||||
- `i` must be `0` or `1`.
|
||||
- This function should only be called if `node.NumChildren()` is not `0`
|
||||
(e.g. if `node` is not a leaf). Note that this returns a valid
|
||||
`MeanSPTree&` that can itself be used just like the root node of the
|
||||
tree!
|
||||
- `node.Left()` and `node.Right()` are convenience functions specific to
|
||||
`MeanSPTree` that will return `MeanSPTree*` (pointers) to the left and
|
||||
right children, respectively, or `NULL` if `node` has no children.
|
||||
|
||||
* `node.Parent()` will return a `MeanSPTree*` that points to the parent of
|
||||
`node`, or `NULL` if `node` is the root of the `MeanSPTree`.
|
||||
|
||||
---
|
||||
|
||||
### Accessing members of a tree
|
||||
|
||||
* `node.Overlap()` will return a `bool` that is `true` if `node`'s children are
|
||||
overlapping, and `false` otherwise.
|
||||
|
||||
* `node.Hyperplane()` will return an
|
||||
[`AxisOrthogonalHyperplane`](spill_tree.md#axisorthogonalhyperplane) object
|
||||
that represents the axis-aligned splitting hyperplane of `node`.
|
||||
- All points in `node.Left()` are to the left of `node.Hyperplane()` if
|
||||
`node.Overlap()` is `false`; otherwise, all points in `node.Left()` are to
|
||||
the left of `node.Hyperplane() + tau`.
|
||||
- All points in `node.Right()` are to the right of `node.Hyperplane()` if
|
||||
`node.Overlap()` is `false`; otherwise, all points in `node.Right()` are to
|
||||
the right of `node.Hyperplane() - tau`.
|
||||
|
||||
* `node.Bound()` will return a
|
||||
[`const HRectBound&`](binary_space_tree.md#hrectbound) representing the
|
||||
bounding box associated with `node`.
|
||||
- If a [custom `DistanceType` and/or `MatType`](#template-parameters) are
|
||||
specified, then a `const HRectBound<DistanceType, ElemType>&` is returned.
|
||||
* `ElemType` is the element type of the specified `MatType` (e.g. `double`
|
||||
for `arma::mat`, `float` for `arma::fmat`, etc.).
|
||||
|
||||
* `node.Stat()` will return a `StatisticType&` holding the statistics of the
|
||||
node that were computed during tree construction.
|
||||
|
||||
* `node.Distance()` will return a `EuclideanDistance&`. Because
|
||||
`EuclideanDistance` has no instantiated members, this is unlikely to be
|
||||
useful, but is required to satisfy the
|
||||
[`TreeType` API](../../../developer/trees.md#the-treetype-api).
|
||||
|
||||
See also the
|
||||
[developer documentation](../../../developer/trees.md#basic-tree-functionality)
|
||||
for basic tree functionality in mlpack.
|
||||
|
||||
---
|
||||
|
||||
### Accessing data held in a tree
|
||||
|
||||
* `node.Dataset()` will return a `const MatType&` that is the dataset the
|
||||
tree was built on.
|
||||
|
||||
* `node.NumPoints()` returns a `size_t` indicating the number of points held
|
||||
directly in `node`.
|
||||
- If `node` is not a leaf, this will return `0`, as `MeanSPTree` only holds
|
||||
points directly in its leaves.
|
||||
- If `node` is a leaf, then the number of points will be less than or equal
|
||||
to the `maxLeafSize` that was specified when the tree was constructed.
|
||||
|
||||
* `node.Point(i)` returns a `size_t` indicating the index of the `i`'th point
|
||||
in `node.Dataset()`.
|
||||
- `i` must be in the range `[0, node.NumPoints() - 1]` (inclusive).
|
||||
- `node` must be a leaf (as non-leaves do not hold any points).
|
||||
- The `i`'th point in `node` can then be accessed as
|
||||
`node.Dataset().col(node.Point(i))`.
|
||||
- Accessing the actual `i`'th point itself can be done with, e.g.,
|
||||
`node.Dataset().col(node.Point(i))`.
|
||||
|
||||
* `node.NumDescendants()` returns a `size_t` indicating the number of points
|
||||
held in all descendant leaves of `node`.
|
||||
- If `node` is the root of the tree, then `node.NumDescendants()` will be
|
||||
equal to `node.Dataset().n_cols`.
|
||||
|
||||
* `node.Descendant(i)` returns a `size_t` indicating the index of the `i`'th
|
||||
descendant point in `node.Dataset()`.
|
||||
- `i` must be in the range `[0, node.NumDescendants() - 1]` (inclusive).
|
||||
- `node` does not need to be a leaf.
|
||||
- The `i`'th descendant point in `node` can then be accessed as
|
||||
`node.Dataset().col(node.Descendant(i))`.
|
||||
- Accessing the actual `i`'th descendant itself can be done with, e.g.,
|
||||
`node.Dataset().col(node.Descendant(i))`.
|
||||
|
||||
---
|
||||
|
||||
### Accessing computed bound quantities of a tree
|
||||
|
||||
The following quantities are cached for each node in a `MeanSPTree`, and so
|
||||
accessing them does not require any computation. In the documentation below,
|
||||
`ElemType` is the element type of the given `MatType`; e.g., if `MatType` is
|
||||
`arma::mat`, then `ElemType` is `double`.
|
||||
|
||||
* `node.FurthestPointDistance()` returns an `ElemType` representing the
|
||||
distance between the center of the bound of `node` and the furthest point
|
||||
held by `node`.
|
||||
- If `node` is not a leaf, this returns 0 (because `node` does not hold any
|
||||
points).
|
||||
|
||||
* `node.FurthestDescendantDistance()` returns an `ElemType` representing the
|
||||
distance between the center of the bound of `node` and the furthest
|
||||
descendant point held by `node`.
|
||||
|
||||
* `node.MinimumBoundDistance()` returns an `ElemType` representing the minimum
|
||||
possible distance from the center of the node to any edge of its bound.
|
||||
|
||||
* `node.ParentDistance()` returns an `ElemType` representing the distance
|
||||
between the center of the bound of `node` and the center of the bound of its
|
||||
parent.
|
||||
- If `node` is the root of the tree, `0` is returned.
|
||||
|
||||
***Note:*** for more details on each bound quantity, see the [developer
|
||||
documentation](../../../developer/trees.md#complex-tree-functionality-and-bounds)
|
||||
on bound quantities for trees.
|
||||
|
||||
---
|
||||
|
||||
### Other functionality
|
||||
|
||||
* `node.Center(center)` computes the center of the bound of `node` and stores
|
||||
it in `center`.
|
||||
- `center` should be of type `arma::Col<ElemType>&`, where `ElemType` is the
|
||||
element type of the specified `MatType`.
|
||||
- `center` will be set to have size equivalent to the dimensionality of the
|
||||
dataset held by `node`.
|
||||
- This is equivalent to calling `node.Bound().Center(center)`.
|
||||
|
||||
* A `MeanSPTree` can be serialized with
|
||||
[`data::Save()` and `data::Load()`](../../load_save.md#mlpack-objects).
|
||||
|
||||
## Bounding distances with the tree
|
||||
|
||||
The primary use of trees in mlpack is bounding distances to points or other tree
|
||||
nodes. The following functions can be used for these tasks.
|
||||
|
||||
* `node.GetNearestChild(point)`
|
||||
* `node.GetFurthestChild(point)`
|
||||
- Return a `size_t` indicating the index of the child (`0` for left, `1` for
|
||||
right) that is closest to (or furthest from) `point`, with respect
|
||||
to the `MinDistance()` (or `MaxDistance()`) function.
|
||||
- If there is a tie, `0` (the left child) is returned.
|
||||
- If `node` is a leaf, `0` is returned.
|
||||
- `point` should be a column vector type of the same type as `MatType`.
|
||||
(e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
|
||||
|
||||
* `node.GetNearestChild(other)`
|
||||
* `node.GetFurthestChild(other)`
|
||||
- Return a `size_t` indicating the index of the child (`0` for left, `1` for
|
||||
right) that is closest to (or furthest from) the `MeanSPTree` node `other`,
|
||||
with respect to the `MinDistance()` (or `MaxDistance()`) function.
|
||||
- If there is a tie, `0` (the left child) is returned.
|
||||
- If `node` is a leaf, `0` is returned.
|
||||
|
||||
---
|
||||
|
||||
* `node.MinDistance(point)`
|
||||
* `node.MinDistance(other)`
|
||||
- Return a `double` indicating the minimum possible distance between `node`
|
||||
and `point`, or the `MeanSPTree` node `other`.
|
||||
- This is equivalent to the minimum possible distance between any point
|
||||
contained in the bounding hyperrectangle of `node` and `point`, or between
|
||||
any point contained in the bounding hyperrectangle of `node` and any point
|
||||
contained in the bounding hyperrectangle of `other`.
|
||||
- `point` should be a column vector type of the same type as `MatType`.
|
||||
(e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
|
||||
|
||||
* `node.MaxDistance(point)`
|
||||
* `node.MaxDistance(other)`
|
||||
- Return a `double` indicating the maximum possible distance between `node`
|
||||
and `point`, or the `MeanSPTree` node `other`.
|
||||
- This is equivalent to the maximum possible distance between any point
|
||||
contained in the bounding hyperrectangle of `node` and `point`, or between
|
||||
any point contained in the bounding hyperrectangle of `node` and any point
|
||||
contained in the bounding hyperrectangle of `other`.
|
||||
- `point` should be a column vector type of the same type as `MatType`.
|
||||
(e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
|
||||
|
||||
* `node.RangeDistance(point)`
|
||||
* `node.RangeDistance(other)`
|
||||
- Return a [`RangeType<ElemType>`](../math.md#range) whose lower bound is
|
||||
`node.MinDistance(point)` or `node.MinDistance(other)`, and whose upper
|
||||
bound is `node.MaxDistance(point)` or `node.MaxDistance(other)`.
|
||||
- `ElemType` is the element type of `MatType`.
|
||||
- `point` should be a column vector type of the same type as `MatType`.
|
||||
(e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
|
||||
|
||||
## Tree traversals
|
||||
|
||||
Like every mlpack tree, the `MeanSPTree` class provides a [single-tree and
|
||||
dual-tree traversal](../../../developer/trees.md#traversals) that can be paired
|
||||
with a [`RuleType` class](../../../developer/trees.md#rules) to implement a
|
||||
single-tree or dual-tree algorithm.
|
||||
|
||||
* `MeanSPTree::SingleTreeTraverser`
|
||||
- Implements a depth-first single-tree traverser.
|
||||
|
||||
* `MeanSPTree::DualTreeTraverser`
|
||||
- Implements a dual-depth-first dual-tree traverser.
|
||||
|
||||
However, spill trees are primarily useful because the overlapping nodes allow
|
||||
*defeatist* search to be effective. Defeatist search is non-backtracking: the
|
||||
tree is traversed to one leaf only. For example, finding the approximate
|
||||
nearest neighbor of a point `p` with defeatist search is done by recursing in
|
||||
the tree, choosing the child with smallest minimum distance to `p`, and when a
|
||||
leaf is encountered, choosing the closest point in the leaf to `p` as the
|
||||
nearest neighbor. This is the strategy used in the
|
||||
[original spill tree paper (pdf)](https://proceedings.neurips.cc/paper/2004/file/1102a326d5f7c9e04fc3c89d0ede88c9-Paper.pdf).
|
||||
|
||||
Defeatist traversers, matching the API for a regular
|
||||
[traversal](../../../developer/trees.md#traversals) are made available as the
|
||||
following two classes:
|
||||
|
||||
* `MeanSPTree::DefeatistSingleTreeTraverser`
|
||||
- Implements a depth-first single-tree defeatist traverser with no
|
||||
backtracking. Traversal will terminate after the first leaf is visited.
|
||||
|
||||
* `MeanSPTree::DefeatistDualTreeTraverser`
|
||||
- Implements a dual-depth-first dual-tree defeatist traversal with no
|
||||
backtracking. For each query leaf node, traversal will terminate after the
|
||||
first reference leaf node is visited.
|
||||
|
||||
Any [`RuleType`](../../../developer/trees.md#rules) that is being used with a
|
||||
defeatist traversal, in addition to the functions required by the `RuleType`
|
||||
API, must implement the following functions:
|
||||
|
||||
```
|
||||
// This is only required for single-tree defeatist traversals.
|
||||
// It should return the index of the branch that should be chosen for the given
|
||||
// query point and reference node.
|
||||
template<typename VecType, typename TreeType>
|
||||
size_t GetBestChild(const VecType& queryPoint, TreeType& referenceNode);
|
||||
|
||||
// This is only required for dual-tree defeatist traversals.
|
||||
// It should return the index of the best child of the reference node that
|
||||
// should be chosen for the given query node.
|
||||
template<typename TreeType>
|
||||
size_t GetBestChild(TreeType& queryNode, TreeType& referenceNode);
|
||||
|
||||
// Return the minimum number of base cases (point-to-point computations) that
|
||||
// are required during the traversal.
|
||||
size_t MinimumBaseCases();
|
||||
```
|
||||
|
||||
## Example usage
|
||||
|
||||
Build a `MeanSPTree` on the `cloud` dataset and print basic statistics about the
|
||||
tree.
|
||||
|
||||
```c++
|
||||
// See https://datasets.mlpack.org/cloud.csv.
|
||||
arma::mat dataset;
|
||||
mlpack::data::Load("cloud.csv", dataset, true);
|
||||
|
||||
// Build the mean-split spill tree with a tau (margin) of 0.2 and a leaf size of
|
||||
// 10. (This means that nodes are split until they contain 10 or fewer points.)
|
||||
//
|
||||
// The std::move() means that `dataset` will be empty after this call, and no
|
||||
// data will be copied during tree building.
|
||||
//
|
||||
// When C++20 is enabled, then the <> is not necessary and the following line
|
||||
// will work:
|
||||
// mlpack::MeanSPTree tree(std::move(dataset), 0.2, 10);
|
||||
mlpack::MeanSPTree<> tree(std::move(dataset), 0.2, 10);
|
||||
|
||||
// Print the bounding box of the root node.
|
||||
std::cout << "Bounding box of root node:" << std::endl;
|
||||
for (size_t i = 0; i < tree.Bound().Dim(); ++i)
|
||||
{
|
||||
std::cout << " - Dimension " << i << ": [" << tree.Bound()[i].Lo() << ", "
|
||||
<< tree.Bound()[i].Hi() << "]." << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
// Print the number of descendant points of the root, and of each of its
|
||||
// children.
|
||||
std::cout << "Descendant points of root: "
|
||||
<< tree.NumDescendants() << "." << std::endl;
|
||||
std::cout << "Descendant points of left child: "
|
||||
<< tree.Left()->NumDescendants() << "." << std::endl;
|
||||
std::cout << "Descendant points of right child: "
|
||||
<< tree.Right()->NumDescendants() << "." << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Compute the center of the MeanSPTree.
|
||||
arma::vec center;
|
||||
tree.Center(center);
|
||||
std::cout << "Center of tree: " << center.t();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Build two `MeanSPTree`s on subsets of the corel dataset and compute minimum and
|
||||
maximum distances between different nodes in the tree.
|
||||
|
||||
```c++
|
||||
// See https://datasets.mlpack.org/corel-histogram.csv.
|
||||
arma::mat dataset;
|
||||
mlpack::data::Load("corel-histogram.csv", dataset, true);
|
||||
|
||||
// Build trees on the first half and the second half of points. Use a tau
|
||||
// (overlap) parameter of 0.3, which is tuned to this dataset, and a rho value
|
||||
// of 0.6 to prevent the trees getting too deep.
|
||||
mlpack::MeanSPTree<> tree1(dataset.cols(0, dataset.n_cols / 2), 0.3, 20, 0.6);
|
||||
mlpack::MeanSPTree<> tree2(dataset.cols(dataset.n_cols / 2 + 1,
|
||||
dataset.n_cols - 1), 0.3, 20, 0.6);
|
||||
|
||||
// Compute the maximum distance between the trees.
|
||||
std::cout << "Maximum distance between tree root nodes: "
|
||||
<< tree1.MaxDistance(tree2) << "." << std::endl;
|
||||
|
||||
// Get the leftmost grandchild of the first tree's root---if it exists.
|
||||
if (!tree1.IsLeaf() && !tree1.Child(0).IsLeaf())
|
||||
{
|
||||
mlpack::MeanSPTree<>& node1 = tree1.Child(0).Child(0);
|
||||
|
||||
// Get the rightmost grandchild of the second tree's root---if it exists.
|
||||
if (!tree2.IsLeaf() && !tree2.Child(1).IsLeaf())
|
||||
{
|
||||
mlpack::MeanSPTree<>& node2 = tree2.Child(1).Child(1);
|
||||
|
||||
// Print the minimum and maximum distance between the nodes.
|
||||
mlpack::Range dists = node1.RangeDistance(node2);
|
||||
std::cout << "Possible distances between two grandchild nodes: ["
|
||||
<< dists.Lo() << ", " << dists.Hi() << "]." << std::endl;
|
||||
|
||||
// Print the minimum distance between the first node and the first
|
||||
// descendant point of the second node.
|
||||
const size_t descendantIndex = node2.Descendant(0);
|
||||
const double descendantMinDist =
|
||||
node1.MinDistance(node2.Dataset().col(descendantIndex));
|
||||
std::cout << "Minimum distance between grandchild node and descendant "
|
||||
<< "point: " << descendantMinDist << "." << std::endl;
|
||||
|
||||
// Which child of node2 is closer to node1?
|
||||
const size_t closerIndex = node2.GetNearestChild(node1);
|
||||
if (closerIndex == 0)
|
||||
std::cout << "The left child of node2 is closer to node1." << std::endl;
|
||||
else if (closerIndex == 1)
|
||||
std::cout << "The right child of node2 is closer to node1." << std::endl;
|
||||
else // closerIndex == 2 in this case.
|
||||
std::cout << "Both children of node2 are equally close to node1."
|
||||
<< std::endl;
|
||||
|
||||
// And which child of node1 is further from node2?
|
||||
const size_t furtherIndex = node1.GetFurthestChild(node2);
|
||||
if (furtherIndex == 0)
|
||||
std::cout << "The left child of node1 is further from node2."
|
||||
<< std::endl;
|
||||
else if (furtherIndex == 1)
|
||||
std::cout << "The right child of node1 is further from node2."
|
||||
<< std::endl;
|
||||
else // furtherIndex == 2 in this case.
|
||||
std::cout << "Both children of node1 are equally far from node2."
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Build a `MeanSPTree` on 32-bit floating point data and save it to disk.
|
||||
|
||||
```c++
|
||||
// See https://datasets.mlpack.org/corel-histogram.csv.
|
||||
arma::fmat dataset;
|
||||
mlpack::data::Load("corel-histogram.csv", dataset);
|
||||
|
||||
// Build the MeanSPTree using 32-bit floating point data as the matrix type.
|
||||
// We will still use the default EmptyStatistic and EuclideanDistance
|
||||
// parameters.
|
||||
mlpack::MeanSPTree<mlpack::EuclideanDistance,
|
||||
mlpack::EmptyStatistic,
|
||||
arma::fmat> tree(std::move(dataset), 0.1, 20, 0.95);
|
||||
|
||||
// Save the tree to disk with the name 'tree'.
|
||||
mlpack::data::Save("tree.bin", "tree", tree);
|
||||
|
||||
std::cout << "Saved tree with " << tree.Dataset().n_cols << " points to "
|
||||
<< "'tree.bin'." << std::endl;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Load a 32-bit floating point `MeanSPTree` from disk, then traverse it manually
|
||||
and find the number of nodes whose children overlap.
|
||||
|
||||
```c++
|
||||
// This assumes the tree has already been saved to 'tree.bin' (as in the example
|
||||
// above).
|
||||
|
||||
// This convenient typedef saves us a long type name!
|
||||
using TreeType = mlpack::MeanSPTree<mlpack::EuclideanDistance,
|
||||
mlpack::EmptyStatistic,
|
||||
arma::fmat>;
|
||||
|
||||
TreeType tree;
|
||||
mlpack::data::Load("tree.bin", "tree", tree);
|
||||
std::cout << "Tree loaded with " << tree.NumDescendants() << " points."
|
||||
<< std::endl;
|
||||
|
||||
// Recurse in a depth-first manner. Count both the total number of non-leaves,
|
||||
// and the number of non-leaves that have overlapping children.
|
||||
size_t overlapCount = 0;
|
||||
size_t totalInternalNodeCount = 0;
|
||||
std::stack<TreeType*> stack;
|
||||
stack.push(&tree);
|
||||
while (!stack.empty())
|
||||
{
|
||||
TreeType* node = stack.top();
|
||||
stack.pop();
|
||||
|
||||
if (node->IsLeaf())
|
||||
continue;
|
||||
|
||||
if (node->Overlap())
|
||||
++overlapCount;
|
||||
++totalInternalNodeCount;
|
||||
|
||||
stack.push(node->Left());
|
||||
stack.push(node->Right());
|
||||
}
|
||||
|
||||
// Note that it would be possible to use TreeType::SingleTreeTraverser to
|
||||
// perform the recursion above, but that is more well-suited for more complex
|
||||
// tasks that require pruning and other non-trivial behavior; so using a simple
|
||||
// stack is the better option here.
|
||||
|
||||
// Print the results.
|
||||
std::cout << overlapCount << " out of " << totalInternalNodeCount
|
||||
<< " internal nodes have overlapping children." << std::endl;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Use a defeatist traversal to find the approximate nearest neighbor of the third
|
||||
and fourth points in the `corel-histogram` dataset. (Note: this can also be
|
||||
done more easily with the `KNN` class! This example is a demonstration of how
|
||||
to use the defeatist traverser.)
|
||||
|
||||
<!-- TODO: link to KNN class -->
|
||||
|
||||
For this example, we must first define a
|
||||
[`RuleType` class](../../../developer/trees.md#rules).
|
||||
|
||||
```c++
|
||||
// For simplicity, this only implements those methods required by single-tree
|
||||
// traversals, and cannot be used with a dual-tree traversal.
|
||||
//
|
||||
// `.Reset()` must be called before any additional single-tree traversals after
|
||||
// the first is run.
|
||||
class SpillNearestNeighborRule
|
||||
{
|
||||
public:
|
||||
// Store the dataset internally.
|
||||
SpillNearestNeighborRule(const arma::mat& dataset) :
|
||||
dataset(dataset),
|
||||
nearestNeighbor(size_t(-1)),
|
||||
nearestDistance(DBL_MAX) { }
|
||||
|
||||
// Compute the base case (point-to-point comparison).
|
||||
double BaseCase(const size_t queryIndex, const size_t referenceIndex)
|
||||
{
|
||||
// Skip the base case if the points are the same.
|
||||
if (queryIndex == referenceIndex)
|
||||
return 0.0;
|
||||
|
||||
const double dist = mlpack::EuclideanDistance::Evaluate(
|
||||
dataset.col(queryIndex), dataset.col(referenceIndex));
|
||||
|
||||
if (dist < nearestDistance)
|
||||
{
|
||||
nearestNeighbor = referenceIndex;
|
||||
nearestDistance = dist;
|
||||
}
|
||||
|
||||
return dist;
|
||||
}
|
||||
|
||||
// Score the given node in the tree; if it is sufficiently far away that it
|
||||
// cannot contain a better nearest neighbor candidate, we can prune it.
|
||||
template<typename TreeType>
|
||||
double Score(const size_t queryIndex, const TreeType& referenceNode) const
|
||||
{
|
||||
const double minDist = referenceNode.MinDistance(dataset.col(queryIndex));
|
||||
if (minDist > nearestDistance)
|
||||
return DBL_MAX; // Prune: this cannot contain a better candidate!
|
||||
|
||||
return minDist;
|
||||
}
|
||||
|
||||
// Rescore the given node/point combination. Note that this will not be used
|
||||
// by the defeatist traversal as it never backtracks, but we include it for
|
||||
// completeness because the RuleType API requires it.
|
||||
template<typename TreeType>
|
||||
double Rescore(const size_t, const TreeType&, const double oldScore) const
|
||||
{
|
||||
if (oldScore > nearestDistance)
|
||||
return DBL_MAX; // Prune: the node is too far away.
|
||||
return oldScore;
|
||||
}
|
||||
|
||||
// This is required by defeatist traversals to select the best reference
|
||||
// child to recurse into for overlapping nodes.
|
||||
template<typename TreeType>
|
||||
size_t GetBestChild(const size_t queryIndex, TreeType& referenceNode)
|
||||
const
|
||||
{
|
||||
return referenceNode.GetNearestChild(dataset.col(queryIndex));
|
||||
}
|
||||
|
||||
// We must perform at least two base cases in order to have a result. Note
|
||||
// that this is two, and not one, because we skip base cases where the query
|
||||
// and reference points are the same. That can only happen a maximum of once,
|
||||
// so to ensure that we compare a query point to a different reference point
|
||||
// at least once, we must return 2 here.
|
||||
size_t MinimumBaseCases() const { return 2; }
|
||||
|
||||
// Get the results (to be called after the traversal).
|
||||
size_t NearestNeighbor() const { return nearestNeighbor; }
|
||||
double NearestDistance() const { return nearestDistance; }
|
||||
|
||||
// Reset the internal statistics for an additional traversal.
|
||||
void Reset()
|
||||
{
|
||||
nearestNeighbor = size_t(-1);
|
||||
nearestDistance = DBL_MAX;
|
||||
}
|
||||
|
||||
private:
|
||||
const arma::mat& dataset;
|
||||
|
||||
size_t nearestNeighbor;
|
||||
double nearestDistance;
|
||||
};
|
||||
```
|
||||
|
||||
```c++
|
||||
// See https://datasets.mlpack.org/corel-histogram.csv.
|
||||
arma::mat dataset;
|
||||
mlpack::data::Load("corel-histogram.csv", dataset, true);
|
||||
|
||||
// Build two trees, one with a lot of overlap, and one with no overlap
|
||||
// (e.g. tau = 0).
|
||||
mlpack::MeanSPTree<> tree1(dataset, 0.5, 10), tree2(dataset, 0.0, 10);
|
||||
|
||||
// Construct the rule types, and then the traversals.
|
||||
SpillNearestNeighborRule r1(dataset), r2(dataset);
|
||||
|
||||
mlpack::MeanSPTree<>::DefeatistSingleTreeTraverser<SpillNearestNeighborRule>
|
||||
t1(r1), t2(r2);
|
||||
|
||||
// Search for the approximate nearest neighbor of point 3 using both trees.
|
||||
t1.Traverse(3, tree1);
|
||||
t2.Traverse(3, tree2);
|
||||
|
||||
std::cout << "Approximate nearest neighbor of point 3:" << std::endl;
|
||||
std::cout << " - Mean-split spill tree with overlap 0.5 found: point "
|
||||
<< r1.NearestNeighbor() << ", distance " << r1.NearestDistance()
|
||||
<< "." << std::endl;
|
||||
|
||||
std::cout << " - Mean-split spill tree with no overlap found: point "
|
||||
<< r2.NearestNeighbor() << ", distance " << r2.NearestDistance()
|
||||
<< "." << std::endl;
|
||||
|
||||
// Now search for point 6.
|
||||
r1.Reset();
|
||||
r2.Reset();
|
||||
|
||||
t1.Traverse(6, tree1);
|
||||
t2.Traverse(6, tree2);
|
||||
|
||||
std::cout << "Approximate nearest neighbor of point 6:" << std::endl;
|
||||
std::cout << " - Mean-split spill tree with overlap 0.5 found: point "
|
||||
<< r1.NearestNeighbor() << ", distance " << r1.NearestDistance()
|
||||
<< "." << std::endl;
|
||||
|
||||
std::cout << " - Mean-split spill tree with no overlap found: point "
|
||||
<< r2.NearestNeighbor() << ", distance " << r2.NearestDistance()
|
||||
<< "." << std::endl;
|
||||
```
|
||||
@@ -140,13 +140,11 @@ may be different.
|
||||
`MeanSplitBallTree` on the modified dataset. For trees that support
|
||||
individual insertion and deletions, see the
|
||||
[`RectangleTree`](rectangle_tree.md) class and all its variants (e.g.
|
||||
[`RTree`](r_tree.md), `RStarTree`, etc.).
|
||||
[`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
|
||||
|
||||
- See also the
|
||||
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
|
||||
|
||||
<!-- TODO: add links to RectangleTree above when it is documented -->
|
||||
|
||||
---
|
||||
|
||||
### Constructor parameters:
|
||||
@@ -432,7 +430,7 @@ mlpack::data::Load("cloud.csv", dataset, true);
|
||||
//
|
||||
// Note that the '<>' isn't necessary if C++20 is being used (e.g.
|
||||
// `mlpack::MeanSplitBallTree tree(...)` will work fine in C++20 or newer).
|
||||
mlpack::MeanSplitBallTree<> tree(std::move(dataset));
|
||||
mlpack::MeanSplitBallTree<> tree(std::move(dataset), 10);
|
||||
|
||||
// Print the bounding box of the root node.
|
||||
std::cout << "Bounding ball of root node:" << std::endl;
|
||||
|
||||
@@ -137,13 +137,11 @@ different.
|
||||
build a new `MeanSplitKDTree` on the modified dataset. For trees that
|
||||
support individual insertion and deletions, see the
|
||||
[`RectangleTree`](rectangle_tree.md) class and all its variants (e.g.
|
||||
[`RTree`](r_tree.md), `RStarTree`, etc.).
|
||||
[`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
|
||||
|
||||
- See also the
|
||||
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
|
||||
|
||||
<!-- TODO: add links to RectangleTree above when it is documented -->
|
||||
|
||||
---
|
||||
|
||||
### Constructor parameters:
|
||||
@@ -434,7 +432,7 @@ mlpack::data::Load("cloud.csv", dataset, true);
|
||||
//
|
||||
// Note that the '<>' isn't necessary if C++20 is being used (e.g.
|
||||
// `mlpack::MeanSplitKDTree tree(...)` will work fine in C++20 or newer).
|
||||
mlpack::MeanSplitKDTree<> tree(std::move(dataset));
|
||||
mlpack::MeanSplitKDTree<> tree(std::move(dataset), 10);
|
||||
|
||||
// Print the bounding box of the root node.
|
||||
std::cout << "Bounding box of root node:" << std::endl;
|
||||
|
||||
@@ -0,0 +1,746 @@
|
||||
# `NonOrtMeanSPTree`
|
||||
|
||||
The `NonOrtMeanSPTree` class implements the hybrid spill tree with
|
||||
non-axis-orthogonal splitting hyperplanes and mean-split behavior; this is a
|
||||
binary space partitioning tree that allows overlapping volumes between nodes.
|
||||
This type of tree can be more effective than trees like the
|
||||
[`KDTree`](kdtree.md) for approximate nearest neighbor search and related tasks.
|
||||
|
||||
`NonOrtMeanSPTree` supports three template parameters for configurable behavior,
|
||||
and implements all the functionality required by the
|
||||
[TreeType API](../../../developer/trees.md#the-treetype-api), plus some
|
||||
additional functionality specific to spill trees. `NonOrtMeanSPTree` is built
|
||||
on the more generic [`SpillTree`](spill_tree.md) class, so if fully custom
|
||||
behavior is desired, that
|
||||
|
||||
* [Template parameters](#template-parameters)
|
||||
* [Constructors](#constructors)
|
||||
* [Basic tree properties](#basic-tree-properties)
|
||||
* [Bounding distances with the tree](#bounding-distances-with-the-tree)
|
||||
* [Tree traversals](#tree-traversals)
|
||||
* [Example usage](#example-usage)
|
||||
|
||||
## See also
|
||||
|
||||
<!-- TODO: add links to all distance-based algorithms and other trees? -->
|
||||
|
||||
* [`SpillTree`](spill_tree.md)
|
||||
* [`SPTree`](sp_tree.md)
|
||||
* [`MeanSPTree`](mean_sp_tree.md)
|
||||
* [`NonOrtSPTree`](non_ort_sp_tree.md)
|
||||
* [`BinarySpaceTree`](binary_space_tree.md)
|
||||
* [An Investigation of Practical Approximate Nearest Neighbor Algorithms (pdf)](https://proceedings.neurips.cc/paper/2004/file/1102a326d5f7c9e04fc3c89d0ede88c9-Paper.pdf)
|
||||
* [Tree-Independent Dual-Tree Algorithms (pdf)](https://www.ratml.org/pub/pdf/2013tree.pdf)
|
||||
|
||||
## Template parameters
|
||||
|
||||
In accordance with the
|
||||
[TreeType API](../../../developer/trees.md#template-parameters-required-by-the-treetype-policy)
|
||||
(see also
|
||||
[this more detailed section](../../../developer/trees.md#template-parameters)),
|
||||
the `NonOrtMeanSPTree` class takes three template parameters:
|
||||
|
||||
```
|
||||
NonOrtMeanSPTree<DistanceType, StatisticType, MatType>
|
||||
```
|
||||
|
||||
* `DistanceType`: the [distance metric](../distances.md) to use for distance
|
||||
computations.
|
||||
|
||||
* `StatisticType`: this holds auxiliary information in each tree node. By
|
||||
default, [`EmptyStatistic`](binary_space_tree.md#emptystatistic) is used,
|
||||
which holds no information.
|
||||
- See the [`StatisticType`](binary_space_tree.md#statistictype) section in
|
||||
the `BinarySpaceTree` documentation for more details.
|
||||
|
||||
* `MatType`: the type of matrix used to represent points. Must be a type
|
||||
matching the [Armadillo API](../../matrices.md). By default, `arma::mat` is
|
||||
used, but other types such as `arma::fmat` or similar will work just fine.
|
||||
|
||||
The `NonOrtMeanSPTree` class itself is a convenience typedef of the generic
|
||||
[`SpillTree`](spill_tree.md) class, using the
|
||||
[`Hyperplane`](spill_tree.md#hyperplane) class as the splitting hyperplane type,
|
||||
and the [`MeanSpaceSplit`](spill_tree.md#meanspacesplit) class as the splitting
|
||||
strategy.
|
||||
|
||||
If no template parameters are explicitly specified, then defaults are used:
|
||||
|
||||
```
|
||||
NonOrtMeanSPTree<> = NonOrtMeanSPTree<EuclideanDistance,
|
||||
EmptyStatistic,
|
||||
arma::mat>
|
||||
```
|
||||
|
||||
## Constructors
|
||||
|
||||
`NonOrtMeanSPTree`s are constructed by iteratively finding splitting
|
||||
hyperplanes, and points within a margin of the hyperplane are assigned to *both*
|
||||
child nodes. Unlike the constructors of
|
||||
[`BinarySpaceTree`](binary_space_tree.md#constructors), the dataset is not
|
||||
permuted during construction.
|
||||
|
||||
---
|
||||
|
||||
* `node = NonOrtMeanSPTree(data, tau=0.0, maxLeafSize=20, rho=0.7)`
|
||||
- Construct a `NonOrtMeanSPTree` on the given `data`, using the specified
|
||||
hyperparameters to control tree construction behavior.
|
||||
- By default, a reference to `data` is stored. If `data` goes out of scope
|
||||
after tree construction, memory errors will occur! To avoid this, either
|
||||
pass the dataset or a copy with `std::move()` (e.g. `std::move(data)`);
|
||||
when doing this, `data` will be set to an empty matrix.
|
||||
|
||||
---
|
||||
|
||||
* `node = NonOrtMeanSPTree<DistanceType, StatisticType, MatType>(data, tau=0.0, maxLeafSize=20, rho=0.7)`
|
||||
- Construct a `NonOrtMeanSPTree` on the given `data`, using custom template
|
||||
parameters, and using the specified hyperparameters to control tree
|
||||
construction behavior.
|
||||
- By default, a reference to `data` is stored. If `data` goes out of scope
|
||||
after tree construction, memory errors will occur! To avoid this, either
|
||||
pass the dataset or a copy with `std::move()` (e.g. `std::move(data)`);
|
||||
when doing this, `data` will be set to an empty matrix.
|
||||
|
||||
---
|
||||
|
||||
* `node = NonOrtMeanSPTree()`
|
||||
- Construct an empty `NonOrtMeanSPTree` with no children, no points, and
|
||||
default template parameters.
|
||||
|
||||
---
|
||||
|
||||
***Notes:***
|
||||
|
||||
- The name `node` is used here for `NonOrtMeanSPTree` objects instead of
|
||||
`tree`, because each `NonOrtMeanSPTree` object is a single node in the tree.
|
||||
The constructor returns the node that is the root of the tree.
|
||||
|
||||
- Inserting individual points or removing individual points from a
|
||||
`NonOrtMeanSPTree` is not supported, because this generally results in a tree
|
||||
with very suboptimal hyperplane splits. It is better to simply build a new
|
||||
`NonOrtMeanSPTree` on the modified dataset. For trees that support
|
||||
individual insertion and deletions, see the
|
||||
[`RectangleTree`](rectangle_tree.md) class and all its variants (e.g.
|
||||
[`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
|
||||
|
||||
- See also the
|
||||
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
|
||||
|
||||
---
|
||||
|
||||
### Constructor parameters:
|
||||
|
||||
| **name** | **type** | **description** | **default** |
|
||||
|----------|----------|-----------------|-------------|
|
||||
| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
|
||||
| `tau` | `double` | Width of spill margin: points within `tau` of the splitting hyperplane of a node will be contained in both left and right children. | `0.0` |
|
||||
| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
|
||||
| `rho` | `double` | Balance threshold. When splitting, if either overlapping node would contain a fraction of more than `rho` of the points, a non-overlapping split is performed. Must be in the range `[0.0, 1.0)`. | `0.7` |
|
||||
|
||||
***Caveats***:
|
||||
|
||||
* `tau` must be manually tuned for the properties of each dataset; the default,
|
||||
`0.0`, will never allow overlap between nodes (and thus the created tree will
|
||||
essentially be a non-overlapping [`BinarySpaceTree`](binary_space_tree.md)).
|
||||
|
||||
* If `tau` is set too large, nodes will overlap too much and search quality
|
||||
will be degraded.
|
||||
|
||||
* `rho` implicitly controls the depth of the tree by forcing very overlapping
|
||||
children to be non-overlapping. As `rho` gets closer to `1`, more overlap is
|
||||
allowed, which in turn makes the tree deeper. If `rho` is set to `0.5` or
|
||||
less, then all splits will be non-overlapping (and the tree will essentially
|
||||
be a [`BinarySpaceTree`](binary_space_tree.md)).
|
||||
|
||||
## Basic tree properties
|
||||
|
||||
Once an `NonOrtMeanSPTree` object is constructed, various properties of the tree
|
||||
can be accessed or inspected. Many of these functions are required by the
|
||||
[TreeType API](../../../developer/trees.md#the-treetype-api).
|
||||
|
||||
### Navigating the tree
|
||||
|
||||
* `node.NumChildren()` returns the number of children in `node`. This is
|
||||
either `2` if `node` has children, or `0` if `node` is a leaf.
|
||||
|
||||
* `node.IsLeaf()` returns a `bool` indicating whether or not `node` is a leaf.
|
||||
|
||||
* `node.Child(i)` returns a `NonOrtMeanSPTree&` that is the `i`th child.
|
||||
- `i` must be `0` or `1`.
|
||||
- This function should only be called if `node.NumChildren()` is not `0`
|
||||
(e.g. if `node` is not a leaf). Note that this returns a valid
|
||||
`NonOrtMeanSPTree&` that can itself be used just like the root node of the
|
||||
tree!
|
||||
- `node.Left()` and `node.Right()` are convenience functions specific to
|
||||
`NonOrtMeanSPTree` that will return `NonOrtMeanSPTree*` (pointers) to the
|
||||
left and right children, respectively, or `NULL` if `node` has no children.
|
||||
|
||||
* `node.Parent()` will return an `NonOrtMeanSPTree*` that points to the parent
|
||||
of `node`, or `NULL` if `node` is the root of the `NonOrtMeanSPTree`.
|
||||
|
||||
---
|
||||
|
||||
### Accessing members of a tree
|
||||
|
||||
* `node.Overlap()` will return a `bool` that is `true` if `node`'s children are
|
||||
overlapping, and `false` otherwise.
|
||||
|
||||
* `node.Hyperplane()` will return an [`Hyperplane`](spill_tree.md#hyperplane)
|
||||
object that represents the splitting hyperplane of `node`.
|
||||
- All points in `node.Left()` are to the left of `node.Hyperplane()` if
|
||||
`node.Overlap()` is `false`; otherwise, all points in `node.Left()` are to
|
||||
the left of `node.Hyperplane() + tau`.
|
||||
- All points in `node.Right()` are to the right of `node.Hyperplane()` if
|
||||
`node.Overlap()` is `false`; otherwise, all points in `node.Right()` are to
|
||||
the right of `node.Hyperplane() - tau`.
|
||||
|
||||
* `node.Bound()` will return a
|
||||
[`const BallBound&`](binary_space_tree.md#ballbound) representing the
|
||||
bounding box associated with `node`.
|
||||
- If a [custom `DistanceType` and/or `MatType`](#template-parameters) are
|
||||
specified, then a `const BallBound<DistanceType, ElemType>&` is returned.
|
||||
* `ElemType` is the element type of the specified `MatType` (e.g. `double`
|
||||
for `arma::mat`, `float` for `arma::fmat`, etc.).
|
||||
|
||||
* `node.Stat()` will return a `StatisticType&` holding the statistics of the
|
||||
node that were computed during tree construction.
|
||||
|
||||
* `node.Distance()` will return a `DistanceType&` that can be used to make
|
||||
distance computations.
|
||||
|
||||
See also the
|
||||
[developer documentation](../../../developer/trees.md#basic-tree-functionality)
|
||||
for basic tree functionality in mlpack.
|
||||
|
||||
---
|
||||
|
||||
### Accessing data held in a tree
|
||||
|
||||
* `node.Dataset()` will return a `const MatType&` that is the dataset the
|
||||
tree was built on.
|
||||
|
||||
* `node.NumPoints()` returns a `size_t` indicating the number of points held
|
||||
directly in `node`.
|
||||
- If `node` is not a leaf, this will return `0`, as `NonOrtMeanSPTree` only
|
||||
holds points directly in its leaves.
|
||||
- If `node` is a leaf, then the number of points will be less than or equal
|
||||
to the `maxLeafSize` that was specified when the tree was constructed.
|
||||
|
||||
* `node.Point(i)` returns a `size_t` indicating the index of the `i`'th point
|
||||
in `node.Dataset()`.
|
||||
- `i` must be in the range `[0, node.NumPoints() - 1]` (inclusive).
|
||||
- `node` must be a leaf (as non-leaves do not hold any points).
|
||||
- The `i`'th point in `node` can then be accessed as
|
||||
`node.Dataset().col(node.Point(i))`.
|
||||
- Accessing the actual `i`'th point itself can be done with, e.g.,
|
||||
`node.Dataset().col(node.Point(i))`.
|
||||
|
||||
* `node.NumDescendants()` returns a `size_t` indicating the number of points
|
||||
held in all descendant leaves of `node`.
|
||||
- If `node` is the root of the tree, then `node.NumDescendants()` will be
|
||||
equal to `node.Dataset().n_cols`.
|
||||
|
||||
* `node.Descendant(i)` returns a `size_t` indicating the index of the `i`'th
|
||||
descendant point in `node.Dataset()`.
|
||||
- `i` must be in the range `[0, node.NumDescendants() - 1]` (inclusive).
|
||||
- `node` does not need to be a leaf.
|
||||
- The `i`'th descendant point in `node` can then be accessed as
|
||||
`node.Dataset().col(node.Descendant(i))`.
|
||||
- Accessing the actual `i`'th descendant itself can be done with, e.g.,
|
||||
`node.Dataset().col(node.Descendant(i))`.
|
||||
|
||||
---
|
||||
|
||||
### Accessing computed bound quantities of a tree
|
||||
|
||||
The following quantities are cached for each node in a `NonOrtMeanSPTree`, and
|
||||
so accessing them does not require any computation. In the documentation below,
|
||||
`ElemType` is the element type of the given `MatType`; e.g., if `MatType` is
|
||||
`arma::mat`, then `ElemType` is `double`.
|
||||
|
||||
* `node.FurthestPointDistance()` returns an `ElemType` representing the
|
||||
distance between the center of the bound of `node` and the furthest point
|
||||
held by `node`.
|
||||
- If `node` is not a leaf, this returns 0 (because `node` does not hold any
|
||||
points).
|
||||
|
||||
* `node.FurthestDescendantDistance()` returns an `ElemType` representing the
|
||||
distance between the center of the bound of `node` and the furthest
|
||||
descendant point held by `node`.
|
||||
|
||||
* `node.MinimumBoundDistance()` returns an `ElemType` representing the minimum
|
||||
possible distance from the center of the node to any edge of its bound.
|
||||
|
||||
* `node.ParentDistance()` returns an `ElemType` representing the distance
|
||||
between the center of the bound of `node` and the center of the bound of its
|
||||
parent.
|
||||
- If `node` is the root of the tree, `0` is returned.
|
||||
|
||||
***Note:*** for more details on each bound quantity, see the [developer
|
||||
documentation](../../../developer/trees.md#complex-tree-functionality-and-bounds)
|
||||
on bound quantities for trees.
|
||||
|
||||
---
|
||||
|
||||
### Other functionality
|
||||
|
||||
* `node.Center(center)` computes the center of the bound of `node` and stores
|
||||
it in `center`.
|
||||
- `center` should be of type `arma::Col<ElemType>&`, where `ElemType` is the
|
||||
element type of the specified `MatType`.
|
||||
- `center` will be set to have size equivalent to the dimensionality of the
|
||||
dataset held by `node`.
|
||||
- This is equivalent to calling `node.Bound().Center(center)`.
|
||||
|
||||
* A `NonOrtMeanSPTree` can be serialized with
|
||||
[`data::Save()` and `data::Load()`](../../load_save.md#mlpack-objects).
|
||||
|
||||
## Bounding distances with the tree
|
||||
|
||||
The primary use of trees in mlpack is bounding distances to points or other tree
|
||||
nodes. The following functions can be used for these tasks.
|
||||
|
||||
* `node.GetNearestChild(point)`
|
||||
* `node.GetFurthestChild(point)`
|
||||
- Return a `size_t` indicating the index of the child (`0` for left, `1` for
|
||||
right) that is closest to (or furthest from) `point`, with respect
|
||||
to the `MinDistance()` (or `MaxDistance()`) function.
|
||||
- If there is a tie, `0` (the left child) is returned.
|
||||
- If `node` is a leaf, `0` is returned.
|
||||
- `point` should be a column vector type of the same type as `MatType`.
|
||||
(e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
|
||||
|
||||
* `node.GetNearestChild(other)`
|
||||
* `node.GetFurthestChild(other)`
|
||||
- Return a `size_t` indicating the index of the child (`0` for left, `1` for
|
||||
right) that is closest to (or furthest from) the `NonOrtMeanSPTree` node
|
||||
`other`, with respect to the `MinDistance()` (or `MaxDistance()`) function.
|
||||
- If there is a tie, `0` (the left child) is returned.
|
||||
- If `node` is a leaf, `0` is returned.
|
||||
|
||||
---
|
||||
|
||||
* `node.MinDistance(point)`
|
||||
* `node.MinDistance(other)`
|
||||
- Return a `double` indicating the minimum possible distance between `node`
|
||||
and `point`, or the `NonOrtMeanSPTree` node `other`.
|
||||
- This is equivalent to the minimum possible distance between any point
|
||||
contained in the bounding hyperrectangle of `node` and `point`, or between
|
||||
any point contained in the bounding hyperrectangle of `node` and any point
|
||||
contained in the bounding hyperrectangle of `other`.
|
||||
- `point` should be a column vector type of the same type as `MatType`.
|
||||
(e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
|
||||
|
||||
* `node.MaxDistance(point)`
|
||||
* `node.MaxDistance(other)`
|
||||
- Return a `double` indicating the maximum possible distance between `node`
|
||||
and `point`, or the `NonOrtMeanSPTree` node `other`.
|
||||
- This is equivalent to the maximum possible distance between any point
|
||||
contained in the bounding hyperrectangle of `node` and `point`, or between
|
||||
any point contained in the bounding hyperrectangle of `node` and any point
|
||||
contained in the bounding hyperrectangle of `other`.
|
||||
- `point` should be a column vector type of the same type as `MatType`.
|
||||
(e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
|
||||
|
||||
* `node.RangeDistance(point)`
|
||||
* `node.RangeDistance(other)`
|
||||
- Return a [`RangeType<ElemType>`](../math.md#range) whose lower bound is
|
||||
`node.MinDistance(point)` or `node.MinDistance(other)`, and whose upper
|
||||
bound is `node.MaxDistance(point)` or `node.MaxDistance(other)`.
|
||||
- `ElemType` is the element type of `MatType`.
|
||||
- `point` should be a column vector type of the same type as `MatType`.
|
||||
(e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
|
||||
|
||||
## Tree traversals
|
||||
|
||||
Like every mlpack tree, the `NonOrtMeanSPTree` class provides a [single-tree and
|
||||
dual-tree traversal](../../../developer/trees.md#traversals) that can be paired
|
||||
with a [`RuleType` class](../../../developer/trees.md#rules) to implement a
|
||||
single-tree or dual-tree algorithm.
|
||||
|
||||
* `NonOrtMeanSPTree::SingleTreeTraverser`
|
||||
- Implements a depth-first single-tree traverser.
|
||||
|
||||
* `NonOrtMeanSPTree::DualTreeTraverser`
|
||||
- Implements a dual-depth-first dual-tree traverser.
|
||||
|
||||
However, spill trees are primarily useful because the overlapping nodes allow
|
||||
*defeatist* search to be effective. Defeatist search is non-backtracking: the
|
||||
tree is traversed to one leaf only. For example, finding the approximate
|
||||
nearest neighbor of a point `p` with defeatist search is done by recursing in
|
||||
the tree, choosing the child with smallest minimum distance to `p`, and when a
|
||||
leaf is encountered, choosing the closest point in the leaf to `p` as the
|
||||
nearest neighbor. This is the strategy used in the
|
||||
[original spill tree paper (pdf)](https://proceedings.neurips.cc/paper/2004/file/1102a326d5f7c9e04fc3c89d0ede88c9-Paper.pdf).
|
||||
|
||||
Defeatist traversers, matching the API for a regular
|
||||
[traversal](../../../developer/trees.md#traversals) are made available as the
|
||||
following two classes:
|
||||
|
||||
* `NonOrtMeanSPTree::DefeatistSingleTreeTraverser`
|
||||
- Implements a depth-first single-tree defeatist traverser with no
|
||||
backtracking. Traversal will terminate after the first leaf is visited.
|
||||
|
||||
* `NonOrtMeanSPTree::DefeatistDualTreeTraverser`
|
||||
- Implements a dual-depth-first dual-tree defeatist traversal with no
|
||||
backtracking. For each query leaf node, traversal will terminate after the
|
||||
first reference leaf node is visited.
|
||||
|
||||
Any [`RuleType`](../../../developer/trees.md#rules) that is being used with a
|
||||
defeatist traversal, in addition to the functions required by the `RuleType`
|
||||
API, must implement the following functions:
|
||||
|
||||
```
|
||||
// This is only required for single-tree defeatist traversals.
|
||||
// It should return the index of the branch that should be chosen for the given
|
||||
// query point and reference node.
|
||||
template<typename VecType, typename TreeType>
|
||||
size_t GetBestChild(const VecType& queryPoint, TreeType& referenceNode);
|
||||
|
||||
// This is only required for dual-tree defeatist traversals.
|
||||
// It should return the index of the best child of the reference node that
|
||||
// should be chosen for the given query node.
|
||||
template<typename TreeType>
|
||||
size_t GetBestChild(TreeType& queryNode, TreeType& referenceNode);
|
||||
|
||||
// Return the minimum number of base cases (point-to-point computations) that
|
||||
// are required during the traversal.
|
||||
size_t MinimumBaseCases();
|
||||
```
|
||||
|
||||
## Example usage
|
||||
|
||||
Build an `NonOrtMeanSPTree` on the `cloud` dataset and print basic statistics
|
||||
about the tree.
|
||||
|
||||
```c++
|
||||
// See https://datasets.mlpack.org/cloud.csv.
|
||||
arma::mat dataset;
|
||||
mlpack::data::Load("cloud.csv", dataset, true);
|
||||
|
||||
// Build the spill tree with a tau (margin) of 0.2 and a leaf size of 10.
|
||||
// (This means that nodes are split until they contain 10 or fewer points.)
|
||||
//
|
||||
// The std::move() means that `dataset` will be empty after this call, and no
|
||||
// data will be copied during tree building.
|
||||
//
|
||||
// When C++20 is enabled, then the <> is not necessary and the following line
|
||||
// will work:
|
||||
// mlpack::NonOrtMeanSPTree tree(std::move(dataset), 0.2, 10);
|
||||
mlpack::NonOrtMeanSPTree<> tree(std::move(dataset), 0.2, 10);
|
||||
|
||||
// Print the bounding ball of the root node.
|
||||
std::cout << "Bounding ball of root node:" << std::endl;
|
||||
std::cout << " Center: " << tree.Bound().Center().t();
|
||||
std::cout << " Radius: " << tree.Bound().Radius() << "." << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Print the number of descendant points of the root, and of each of its
|
||||
// children.
|
||||
std::cout << "Descendant points of root: "
|
||||
<< tree.NumDescendants() << "." << std::endl;
|
||||
std::cout << "Descendant points of left child: "
|
||||
<< tree.Left()->NumDescendants() << "." << std::endl;
|
||||
std::cout << "Descendant points of right child: "
|
||||
<< tree.Right()->NumDescendants() << "." << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Compute the center of the NonOrtMeanSPTree. THis is the same as the center
|
||||
// of the bounding ball of the root.
|
||||
arma::vec center;
|
||||
tree.Center(center);
|
||||
std::cout << "Center of tree: " << center.t();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Build two `NonOrtMeanSPTree`s on subsets of the corel dataset and compute
|
||||
minimum and maximum distances between different nodes in the tree.
|
||||
|
||||
```c++
|
||||
// See https://datasets.mlpack.org/corel-histogram.csv.
|
||||
arma::mat dataset;
|
||||
mlpack::data::Load("corel-histogram.csv", dataset, true);
|
||||
|
||||
// Build trees on the first half and the second half of points. Use a tau
|
||||
// (overlap) parameter of 0.3, which is tuned to this dataset, and a rho value
|
||||
// of 0.6 to prevent the trees getting too deep.
|
||||
mlpack::NonOrtMeanSPTree<> tree1(dataset.cols(0, dataset.n_cols / 2),
|
||||
0.3, 20, 0.6);
|
||||
mlpack::NonOrtMeanSPTree<> tree2(dataset.cols(dataset.n_cols / 2 + 1,
|
||||
dataset.n_cols - 1),
|
||||
0.3, 20, 0.6);
|
||||
|
||||
// Compute the maximum distance between the trees.
|
||||
std::cout << "Maximum distance between tree root nodes: "
|
||||
<< tree1.MaxDistance(tree2) << "." << std::endl;
|
||||
|
||||
// Get the leftmost grandchild of the first tree's root---if it exists.
|
||||
if (!tree1.IsLeaf() && !tree1.Child(0).IsLeaf())
|
||||
{
|
||||
mlpack::NonOrtMeanSPTree<>& node1 = tree1.Child(0).Child(0);
|
||||
|
||||
// Get the rightmost grandchild of the second tree's root---if it exists.
|
||||
if (!tree2.IsLeaf() && !tree2.Child(1).IsLeaf())
|
||||
{
|
||||
mlpack::NonOrtMeanSPTree<>& node2 = tree2.Child(1).Child(1);
|
||||
|
||||
// Print the minimum and maximum distance between the nodes.
|
||||
mlpack::Range dists = node1.RangeDistance(node2);
|
||||
std::cout << "Possible distances between two grandchild nodes: ["
|
||||
<< dists.Lo() << ", " << dists.Hi() << "]." << std::endl;
|
||||
|
||||
// Print the minimum distance between the first node and the first
|
||||
// descendant point of the second node.
|
||||
const size_t descendantIndex = node2.Descendant(0);
|
||||
const double descendantMinDist =
|
||||
node1.MinDistance(node2.Dataset().col(descendantIndex));
|
||||
std::cout << "Minimum distance between grandchild node and descendant "
|
||||
<< "point: " << descendantMinDist << "." << std::endl;
|
||||
|
||||
// Which child of node2 is closer to node1?
|
||||
const size_t closerIndex = node2.GetNearestChild(node1);
|
||||
if (closerIndex == 0)
|
||||
std::cout << "The left child of node2 is closer to node1." << std::endl;
|
||||
else if (closerIndex == 1)
|
||||
std::cout << "The right child of node2 is closer to node1." << std::endl;
|
||||
else // closerIndex == 2 in this case.
|
||||
std::cout << "Both children of node2 are equally close to node1."
|
||||
<< std::endl;
|
||||
|
||||
// And which child of node1 is further from node2?
|
||||
const size_t furtherIndex = node1.GetFurthestChild(node2);
|
||||
if (furtherIndex == 0)
|
||||
std::cout << "The left child of node1 is further from node2."
|
||||
<< std::endl;
|
||||
else if (furtherIndex == 1)
|
||||
std::cout << "The right child of node1 is further from node2."
|
||||
<< std::endl;
|
||||
else // furtherIndex == 2 in this case.
|
||||
std::cout << "Both children of node1 are equally far from node2."
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Build a `NonOrtMeanSPTree` on 32-bit floating point data and save it to disk.
|
||||
|
||||
```c++
|
||||
// See https://datasets.mlpack.org/corel-histogram.csv.
|
||||
arma::fmat dataset;
|
||||
mlpack::data::Load("corel-histogram.csv", dataset);
|
||||
|
||||
// Build the NonOrtMeanSPTree using 32-bit floating point data as the matrix
|
||||
// type. We will still use the default EmptyStatistic and EuclideanDistance
|
||||
// parameters.
|
||||
mlpack::NonOrtSPTree<mlpack::EuclideanDistance,
|
||||
mlpack::EmptyStatistic,
|
||||
arma::fmat> tree(std::move(dataset), 0.1, 20, 0.6);
|
||||
|
||||
// Save the tree to disk with the name 'tree'.
|
||||
mlpack::data::Save("tree.bin", "tree", tree);
|
||||
|
||||
std::cout << "Saved tree with " << tree.Dataset().n_cols << " points to "
|
||||
<< "'tree.bin'." << std::endl;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Load a 32-bit floating point `NonOrtMeanSPTree` from disk, then traverse it
|
||||
manually and find the number of nodes whose children overlap.
|
||||
|
||||
```c++
|
||||
// This assumes the tree has already been saved to 'tree.bin' (as in the example
|
||||
// above).
|
||||
|
||||
// This convenient typedef saves us a long type name!
|
||||
using TreeType = mlpack::NonOrtMeanSPTree<mlpack::EuclideanDistance,
|
||||
mlpack::EmptyStatistic,
|
||||
arma::fmat>;
|
||||
|
||||
TreeType tree;
|
||||
mlpack::data::Load("tree.bin", "tree", tree);
|
||||
std::cout << "Tree loaded with " << tree.NumDescendants() << " points."
|
||||
<< std::endl;
|
||||
|
||||
// Recurse in a depth-first manner. Count both the total number of non-leaves,
|
||||
// and the number of non-leaves that have overlapping children.
|
||||
size_t overlapCount = 0;
|
||||
size_t totalInternalNodeCount = 0;
|
||||
std::stack<TreeType*> stack;
|
||||
stack.push(&tree);
|
||||
while (!stack.empty())
|
||||
{
|
||||
TreeType* node = stack.top();
|
||||
stack.pop();
|
||||
|
||||
if (node->IsLeaf())
|
||||
continue;
|
||||
|
||||
if (node->Overlap())
|
||||
++overlapCount;
|
||||
++totalInternalNodeCount;
|
||||
|
||||
stack.push(node->Left());
|
||||
stack.push(node->Right());
|
||||
}
|
||||
|
||||
// Note that it would be possible to use TreeType::SingleTreeTraverser to
|
||||
// perform the recursion above, but that is more well-suited for more complex
|
||||
// tasks that require pruning and other non-trivial behavior; so using a simple
|
||||
// stack is the better option here.
|
||||
|
||||
// Print the results.
|
||||
std::cout << overlapCount << " out of " << totalInternalNodeCount
|
||||
<< " internal nodes have overlapping children." << std::endl;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Use a defeatist traversal to find the approximate nearest neighbor of the third
|
||||
and fourth points in the `corel-histogram` dataset. (Note: this can also be
|
||||
done more easily with the `KNN` class! This example is a demonstration of how
|
||||
to use the defeatist traverser.)
|
||||
|
||||
<!-- TODO: link to KNN class -->
|
||||
|
||||
For this example, we must first define a
|
||||
[`RuleType` class](../../../developer/trees.md#rules).
|
||||
|
||||
```c++
|
||||
// For simplicity, this only implements those methods required by single-tree
|
||||
// traversals, and cannot be used with a dual-tree traversal.
|
||||
//
|
||||
// `.Reset()` must be called before any additional single-tree traversals after
|
||||
// the first is run.
|
||||
class SpillNearestNeighborRule
|
||||
{
|
||||
public:
|
||||
// Store the dataset internally.
|
||||
SpillNearestNeighborRule(const arma::mat& dataset) :
|
||||
dataset(dataset),
|
||||
nearestNeighbor(size_t(-1)),
|
||||
nearestDistance(DBL_MAX) { }
|
||||
|
||||
// Compute the base case (point-to-point comparison).
|
||||
double BaseCase(const size_t queryIndex, const size_t referenceIndex)
|
||||
{
|
||||
// Skip the base case if the points are the same.
|
||||
if (queryIndex == referenceIndex)
|
||||
return 0.0;
|
||||
|
||||
const double dist = mlpack::EuclideanDistance::Evaluate(
|
||||
dataset.col(queryIndex), dataset.col(referenceIndex));
|
||||
|
||||
if (dist < nearestDistance)
|
||||
{
|
||||
nearestNeighbor = referenceIndex;
|
||||
nearestDistance = dist;
|
||||
}
|
||||
|
||||
return dist;
|
||||
}
|
||||
|
||||
// Score the given node in the tree; if it is sufficiently far away that it
|
||||
// cannot contain a better nearest neighbor candidate, we can prune it.
|
||||
template<typename TreeType>
|
||||
double Score(const size_t queryIndex, const TreeType& referenceNode) const
|
||||
{
|
||||
const double minDist = referenceNode.MinDistance(dataset.col(queryIndex));
|
||||
if (minDist > nearestDistance)
|
||||
return DBL_MAX; // Prune: this cannot contain a better candidate!
|
||||
|
||||
return minDist;
|
||||
}
|
||||
|
||||
// Rescore the given node/point combination. Note that this will not be used
|
||||
// by the defeatist traversal as it never backtracks, but we include it for
|
||||
// completeness because the RuleType API requires it.
|
||||
template<typename TreeType>
|
||||
double Rescore(const size_t, const TreeType&, const double oldScore) const
|
||||
{
|
||||
if (oldScore > nearestDistance)
|
||||
return DBL_MAX; // Prune: the node is too far away.
|
||||
return oldScore;
|
||||
}
|
||||
|
||||
// This is required by defeatist traversals to select the best reference
|
||||
// child to recurse into for overlapping nodes.
|
||||
template<typename TreeType>
|
||||
size_t GetBestChild(const size_t queryIndex, TreeType& referenceNode)
|
||||
const
|
||||
{
|
||||
return referenceNode.GetNearestChild(dataset.col(queryIndex));
|
||||
}
|
||||
|
||||
// We must perform at least two base cases in order to have a result. Note
|
||||
// that this is two, and not one, because we skip base cases where the query
|
||||
// and reference points are the same. That can only happen a maximum of once,
|
||||
// so to ensure that we compare a query point to a different reference point
|
||||
// at least once, we must return 2 here.
|
||||
size_t MinimumBaseCases() const { return 2; }
|
||||
|
||||
// Get the results (to be called after the traversal).
|
||||
size_t NearestNeighbor() const { return nearestNeighbor; }
|
||||
double NearestDistance() const { return nearestDistance; }
|
||||
|
||||
// Reset the internal statistics for an additional traversal.
|
||||
void Reset()
|
||||
{
|
||||
nearestNeighbor = size_t(-1);
|
||||
nearestDistance = DBL_MAX;
|
||||
}
|
||||
|
||||
private:
|
||||
const arma::mat& dataset;
|
||||
|
||||
size_t nearestNeighbor;
|
||||
double nearestDistance;
|
||||
};
|
||||
```
|
||||
|
||||
```c++
|
||||
// See https://datasets.mlpack.org/corel-histogram.csv.
|
||||
arma::mat dataset;
|
||||
mlpack::data::Load("corel-histogram.csv", dataset, true);
|
||||
|
||||
// Build two trees, one with a lot of overlap, and one with no overlap
|
||||
// (e.g. tau = 0).
|
||||
mlpack::NonOrtMeanSPTree<> tree1(dataset, 0.5, 10), tree2(dataset, 0.0, 10);
|
||||
|
||||
// Construct the rule types, and then the traversals.
|
||||
SpillNearestNeighborRule r1(dataset), r2(dataset);
|
||||
|
||||
mlpack::NonOrtMeanSPTree<>::DefeatistSingleTreeTraverser<
|
||||
SpillNearestNeighborRule> t1(r1), t2(r2);
|
||||
|
||||
// Search for the approximate nearest neighbor of point 3 using both trees.
|
||||
t1.Traverse(3, tree1);
|
||||
t2.Traverse(3, tree2);
|
||||
|
||||
std::cout << "Approximate nearest neighbor of point 3:" << std::endl;
|
||||
std::cout << " - Non-axis-aligned mean-split spill tree with overlap 0.5 "
|
||||
<< "found: point " << r1.NearestNeighbor() << ", distance "
|
||||
<< r1.NearestDistance() << "." << std::endl;
|
||||
|
||||
std::cout << " - Non-axis-aligned mean-split spill tree with no overlap "
|
||||
<< "found: point " << r2.NearestNeighbor() << ", distance "
|
||||
<< r2.NearestDistance() << "." << std::endl;
|
||||
|
||||
// Now search for point 6.
|
||||
r1.Reset();
|
||||
r2.Reset();
|
||||
|
||||
t1.Traverse(6, tree1);
|
||||
t2.Traverse(6, tree2);
|
||||
|
||||
std::cout << "Approximate nearest neighbor of point 6:" << std::endl;
|
||||
std::cout << " - Non-axis-aligned mean-split spill tree with overlap 0.5 "
|
||||
<< "found: point " << r1.NearestNeighbor() << ", distance "
|
||||
<< r1.NearestDistance() << "." << std::endl;
|
||||
|
||||
std::cout << " - Non-axis-aligned mean-split spill tree with no overlap "
|
||||
<< "found: point " << r2.NearestNeighbor() << ", distance "
|
||||
<< r2.NearestDistance() << "." << std::endl;
|
||||
```
|
||||
@@ -0,0 +1,742 @@
|
||||
# `NonOrtSPTree`
|
||||
|
||||
The `NonOrtSPTree` class implements the hybrid spill tree with
|
||||
non-axis-orthogonal splitting hyperplanes; this is a binary space partitioning
|
||||
tree that allows overlapping volumes between nodes. This type of tree can be
|
||||
more effective than trees like the [`KDTree`](kdtree.md) for approximate nearest
|
||||
neighbor search and related tasks.
|
||||
|
||||
`NonOrtSPTree` supports three template parameters for configurable behavior, and
|
||||
implements all the functionality required by the [TreeType
|
||||
API](../../../developer/trees.md#the-treetype-api), plus some additional
|
||||
functionality specific to spill trees. `NonOrtSPTree` is built on the more
|
||||
generic [`SpillTree`](spill_tree.md) class, so if fully custom behavior is
|
||||
desired, that
|
||||
|
||||
* [Template parameters](#template-parameters)
|
||||
* [Constructors](#constructors)
|
||||
* [Basic tree properties](#basic-tree-properties)
|
||||
* [Bounding distances with the tree](#bounding-distances-with-the-tree)
|
||||
* [Tree traversals](#tree-traversals)
|
||||
* [Example usage](#example-usage)
|
||||
|
||||
## See also
|
||||
|
||||
<!-- TODO: add links to all distance-based algorithms and other trees? -->
|
||||
|
||||
* [`SpillTree`](spill_tree.md)
|
||||
* [`SPTree`](sp_tree.md)
|
||||
* [`MeanSPTree`](mean_sp_tree.md)
|
||||
* [`NonOrtMeanSPTree`](non_ort_mean_sp_tree.md)
|
||||
* [`BinarySpaceTree`](binary_space_tree.md)
|
||||
* [An Investigation of Practical Approximate Nearest Neighbor Algorithms (pdf)](https://proceedings.neurips.cc/paper/2004/file/1102a326d5f7c9e04fc3c89d0ede88c9-Paper.pdf)
|
||||
* [Tree-Independent Dual-Tree Algorithms (pdf)](https://www.ratml.org/pub/pdf/2013tree.pdf)
|
||||
|
||||
## Template parameters
|
||||
|
||||
In accordance with the
|
||||
[TreeType API](../../../developer/trees.md#template-parameters-required-by-the-treetype-policy)
|
||||
(see also
|
||||
[this more detailed section](../../../developer/trees.md#template-parameters)),
|
||||
the `NonOrtSPTree` class takes three template parameters:
|
||||
|
||||
```
|
||||
NonOrtSPTree<DistanceType, StatisticType, MatType>
|
||||
```
|
||||
|
||||
* `DistanceType`: the [distance metric](../distances.md) to use for distance
|
||||
computations.
|
||||
|
||||
* `StatisticType`: this holds auxiliary information in each tree node. By
|
||||
default, [`EmptyStatistic`](binary_space_tree.md#emptystatistic) is used,
|
||||
which holds no information.
|
||||
- See the [`StatisticType`](binary_space_tree.md#statistictype) section in
|
||||
the `BinarySpaceTree` documentation for more details.
|
||||
|
||||
* `MatType`: the type of matrix used to represent points. Must be a type
|
||||
matching the [Armadillo API](../../matrices.md). By default, `arma::mat` is
|
||||
used, but other types such as `arma::fmat` or similar will work just fine.
|
||||
|
||||
The `NonOrtSPTree` class itself is a convenience typedef of the generic
|
||||
[`SpillTree`](spill_tree.md) class, using the
|
||||
[`Hyperplane`](spill_tree.md#hyperplane) class as the splitting hyperplane type,
|
||||
and the [`MidpointSpaceSplit`](spill_tree.md#midpointspacesplit) class as the
|
||||
splitting strategy.
|
||||
|
||||
If no template parameters are explicitly specified, then defaults are used:
|
||||
|
||||
```
|
||||
NonOrtSPTree<> = NonOrtSPTree<EuclideanDistance, EmptyStatistic, arma::mat>
|
||||
```
|
||||
|
||||
## Constructors
|
||||
|
||||
`NonOrtSPTree`s are constructed by iteratively finding splitting hyperplanes,
|
||||
and points within a margin of the hyperplane are assigned to *both* child nodes.
|
||||
Unlike the constructors of
|
||||
[`BinarySpaceTree`](binary_space_tree.md#constructors), the dataset is not
|
||||
permuted during construction.
|
||||
|
||||
---
|
||||
|
||||
* `node = NonOrtSPTree(data, tau=0.0, maxLeafSize=20, rho=0.7)`
|
||||
- Construct a `NonOrtSPTree` on the given `data`, using the specified
|
||||
hyperparameters to control tree construction behavior.
|
||||
- By default, a reference to `data` is stored. If `data` goes out of scope
|
||||
after tree construction, memory errors will occur! To avoid this, either
|
||||
pass the dataset or a copy with `std::move()` (e.g. `std::move(data)`);
|
||||
when doing this, `data` will be set to an empty matrix.
|
||||
|
||||
---
|
||||
|
||||
* `node = NonOrtSPTree<DistanceType, StatisticType, MatType>(data, tau=0.0, maxLeafSize=20, rho=0.7)`
|
||||
- Construct a `NonOrtSPTree` on the given `data`, using custom template
|
||||
parameters, and using the specified hyperparameters to control tree
|
||||
construction behavior.
|
||||
- By default, a reference to `data` is stored. If `data` goes out of scope
|
||||
after tree construction, memory errors will occur! To avoid this, either
|
||||
pass the dataset or a copy with `std::move()` (e.g. `std::move(data)`);
|
||||
when doing this, `data` will be set to an empty matrix.
|
||||
|
||||
---
|
||||
|
||||
* `node = NonOrtSPTree()`
|
||||
- Construct an empty `NonOrtSPTree` with no children, no points, and default
|
||||
template parameters.
|
||||
|
||||
---
|
||||
|
||||
***Notes:***
|
||||
|
||||
- The name `node` is used here for `NonOrtSPTree` objects instead of `tree`,
|
||||
because each `NonOrtSPTree` object is a single node in the tree. The
|
||||
constructor returns the node that is the root of the tree.
|
||||
|
||||
- Inserting individual points or removing individual points from a
|
||||
`NonOrtSPTree` is not supported, because this generally results in a tree
|
||||
with very suboptimal hyperplane splits. It is better to simply build a new
|
||||
`NonOrtSPTree` on the modified dataset. For trees that support individual
|
||||
insertion and deletions, see the [`RectangleTree`](rectangle_tree.md) class
|
||||
and all its variants (e.g. [`RTree`](r_tree.md),
|
||||
[`RStarTree`](r_star_tree.md), etc.).
|
||||
|
||||
- See also the
|
||||
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
|
||||
|
||||
---
|
||||
|
||||
### Constructor parameters:
|
||||
|
||||
| **name** | **type** | **description** | **default** |
|
||||
|----------|----------|-----------------|-------------|
|
||||
| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
|
||||
| `tau` | `double` | Width of spill margin: points within `tau` of the splitting hyperplane of a node will be contained in both left and right children. | `0.0` |
|
||||
| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
|
||||
| `rho` | `double` | Balance threshold. When splitting, if either overlapping node would contain a fraction of more than `rho` of the points, a non-overlapping split is performed. Must be in the range `[0.0, 1.0)`. | `0.7` |
|
||||
|
||||
***Caveats***:
|
||||
|
||||
* `tau` must be manually tuned for the properties of each dataset; the default,
|
||||
`0.0`, will never allow overlap between nodes (and thus the created tree will
|
||||
essentially be a non-overlapping [`BinarySpaceTree`](binary_space_tree.md)).
|
||||
|
||||
* If `tau` is set too large, nodes will overlap too much and search quality
|
||||
will be degraded.
|
||||
|
||||
* `rho` implicitly controls the depth of the tree by forcing very overlapping
|
||||
children to be non-overlapping. As `rho` gets closer to `1`, more overlap is
|
||||
allowed, which in turn makes the tree deeper. If `rho` is set to `0.5` or
|
||||
less, then all splits will be non-overlapping (and the tree will essentially
|
||||
be a [`BinarySpaceTree`](binary_space_tree.md)).
|
||||
|
||||
## Basic tree properties
|
||||
|
||||
Once an `NonOrtSPTree` object is constructed, various properties of the tree can
|
||||
be accessed or inspected. Many of these functions are required by the
|
||||
[TreeType API](../../../developer/trees.md#the-treetype-api).
|
||||
|
||||
### Navigating the tree
|
||||
|
||||
* `node.NumChildren()` returns the number of children in `node`. This is
|
||||
either `2` if `node` has children, or `0` if `node` is a leaf.
|
||||
|
||||
* `node.IsLeaf()` returns a `bool` indicating whether or not `node` is a leaf.
|
||||
|
||||
* `node.Child(i)` returns a `NonOrtSPTree&` that is the `i`th child.
|
||||
- `i` must be `0` or `1`.
|
||||
- This function should only be called if `node.NumChildren()` is not `0`
|
||||
(e.g. if `node` is not a leaf). Note that this returns a valid
|
||||
`NonOrtSPTree&` that can itself be used just like the root node of the
|
||||
tree!
|
||||
- `node.Left()` and `node.Right()` are convenience functions specific to
|
||||
`NonOrtSPTree` that will return `NonOrtSPTree*` (pointers) to the left and
|
||||
right children, respectively, or `NULL` if `node` has no children.
|
||||
|
||||
* `node.Parent()` will return an `NonOrtSPTree*` that points to the parent of
|
||||
`node`, or `NULL` if `node` is the root of the `NonOrtSPTree`.
|
||||
|
||||
---
|
||||
|
||||
### Accessing members of a tree
|
||||
|
||||
* `node.Overlap()` will return a `bool` that is `true` if `node`'s children are
|
||||
overlapping, and `false` otherwise.
|
||||
|
||||
* `node.Hyperplane()` will return an [`Hyperplane`](spill_tree.md#hyperplane)
|
||||
object that represents the splitting hyperplane of `node`.
|
||||
- All points in `node.Left()` are to the left of `node.Hyperplane()` if
|
||||
`node.Overlap()` is `false`; otherwise, all points in `node.Left()` are to
|
||||
the left of `node.Hyperplane() + tau`.
|
||||
- All points in `node.Right()` are to the right of `node.Hyperplane()` if
|
||||
`node.Overlap()` is `false`; otherwise, all points in `node.Right()` are to
|
||||
the right of `node.Hyperplane() - tau`.
|
||||
|
||||
* `node.Bound()` will return a
|
||||
[`const BallBound&`](binary_space_tree.md#ballbound) representing the
|
||||
bounding box associated with `node`.
|
||||
- If a [custom `DistanceType` and/or `MatType`](#template-parameters) are
|
||||
specified, then a `const BallBound<DistanceType, ElemType>&` is returned.
|
||||
* `ElemType` is the element type of the specified `MatType` (e.g. `double`
|
||||
for `arma::mat`, `float` for `arma::fmat`, etc.).
|
||||
|
||||
* `node.Stat()` will return a `StatisticType&` holding the statistics of the
|
||||
node that were computed during tree construction.
|
||||
|
||||
* `node.Distance()` will return a `DistanceType&` that can be used to make
|
||||
distance computations.
|
||||
|
||||
See also the
|
||||
[developer documentation](../../../developer/trees.md#basic-tree-functionality)
|
||||
for basic tree functionality in mlpack.
|
||||
|
||||
---
|
||||
|
||||
### Accessing data held in a tree
|
||||
|
||||
* `node.Dataset()` will return a `const MatType&` that is the dataset the
|
||||
tree was built on.
|
||||
|
||||
* `node.NumPoints()` returns a `size_t` indicating the number of points held
|
||||
directly in `node`.
|
||||
- If `node` is not a leaf, this will return `0`, as `NonOrtSPTree` only holds
|
||||
points directly in its leaves.
|
||||
- If `node` is a leaf, then the number of points will be less than or equal
|
||||
to the `maxLeafSize` that was specified when the tree was constructed.
|
||||
|
||||
* `node.Point(i)` returns a `size_t` indicating the index of the `i`'th point
|
||||
in `node.Dataset()`.
|
||||
- `i` must be in the range `[0, node.NumPoints() - 1]` (inclusive).
|
||||
- `node` must be a leaf (as non-leaves do not hold any points).
|
||||
- The `i`'th point in `node` can then be accessed as
|
||||
`node.Dataset().col(node.Point(i))`.
|
||||
- Accessing the actual `i`'th point itself can be done with, e.g.,
|
||||
`node.Dataset().col(node.Point(i))`.
|
||||
|
||||
* `node.NumDescendants()` returns a `size_t` indicating the number of points
|
||||
held in all descendant leaves of `node`.
|
||||
- If `node` is the root of the tree, then `node.NumDescendants()` will be
|
||||
equal to `node.Dataset().n_cols`.
|
||||
|
||||
* `node.Descendant(i)` returns a `size_t` indicating the index of the `i`'th
|
||||
descendant point in `node.Dataset()`.
|
||||
- `i` must be in the range `[0, node.NumDescendants() - 1]` (inclusive).
|
||||
- `node` does not need to be a leaf.
|
||||
- The `i`'th descendant point in `node` can then be accessed as
|
||||
`node.Dataset().col(node.Descendant(i))`.
|
||||
- Accessing the actual `i`'th descendant itself can be done with, e.g.,
|
||||
`node.Dataset().col(node.Descendant(i))`.
|
||||
|
||||
---
|
||||
|
||||
### Accessing computed bound quantities of a tree
|
||||
|
||||
The following quantities are cached for each node in a `NonOrtSPTree`, and so
|
||||
accessing them does not require any computation. In the documentation below,
|
||||
`ElemType` is the element type of the given `MatType`; e.g., if `MatType` is
|
||||
`arma::mat`, then `ElemType` is `double`.
|
||||
|
||||
* `node.FurthestPointDistance()` returns an `ElemType` representing the
|
||||
distance between the center of the bound of `node` and the furthest point
|
||||
held by `node`.
|
||||
- If `node` is not a leaf, this returns 0 (because `node` does not hold any
|
||||
points).
|
||||
|
||||
* `node.FurthestDescendantDistance()` returns an `ElemType` representing the
|
||||
distance between the center of the bound of `node` and the furthest
|
||||
descendant point held by `node`.
|
||||
|
||||
* `node.MinimumBoundDistance()` returns an `ElemType` representing the minimum
|
||||
possible distance from the center of the node to any edge of its bound.
|
||||
|
||||
* `node.ParentDistance()` returns an `ElemType` representing the distance
|
||||
between the center of the bound of `node` and the center of the bound of its
|
||||
parent.
|
||||
- If `node` is the root of the tree, `0` is returned.
|
||||
|
||||
***Note:*** for more details on each bound quantity, see the [developer
|
||||
documentation](../../../developer/trees.md#complex-tree-functionality-and-bounds)
|
||||
on bound quantities for trees.
|
||||
|
||||
---
|
||||
|
||||
### Other functionality
|
||||
|
||||
* `node.Center(center)` computes the center of the bound of `node` and stores
|
||||
it in `center`.
|
||||
- `center` should be of type `arma::Col<ElemType>&`, where `ElemType` is the
|
||||
element type of the specified `MatType`.
|
||||
- `center` will be set to have size equivalent to the dimensionality of the
|
||||
dataset held by `node`.
|
||||
- This is equivalent to calling `node.Bound().Center(center)`.
|
||||
|
||||
* A `NonOrtSPTree` can be serialized with
|
||||
[`data::Save()` and `data::Load()`](../../load_save.md#mlpack-objects).
|
||||
|
||||
## Bounding distances with the tree
|
||||
|
||||
The primary use of trees in mlpack is bounding distances to points or other tree
|
||||
nodes. The following functions can be used for these tasks.
|
||||
|
||||
* `node.GetNearestChild(point)`
|
||||
* `node.GetFurthestChild(point)`
|
||||
- Return a `size_t` indicating the index of the child (`0` for left, `1` for
|
||||
right) that is closest to (or furthest from) `point`, with respect
|
||||
to the `MinDistance()` (or `MaxDistance()`) function.
|
||||
- If there is a tie, `0` (the left child) is returned.
|
||||
- If `node` is a leaf, `0` is returned.
|
||||
- `point` should be a column vector type of the same type as `MatType`.
|
||||
(e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
|
||||
|
||||
* `node.GetNearestChild(other)`
|
||||
* `node.GetFurthestChild(other)`
|
||||
- Return a `size_t` indicating the index of the child (`0` for left, `1` for
|
||||
right) that is closest to (or furthest from) the `NonOrtSPTree` node
|
||||
`other`, with respect to the `MinDistance()` (or `MaxDistance()`) function.
|
||||
- If there is a tie, `0` (the left child) is returned.
|
||||
- If `node` is a leaf, `0` is returned.
|
||||
|
||||
---
|
||||
|
||||
* `node.MinDistance(point)`
|
||||
* `node.MinDistance(other)`
|
||||
- Return a `double` indicating the minimum possible distance between `node`
|
||||
and `point`, or the `NonOrtSPTree` node `other`.
|
||||
- This is equivalent to the minimum possible distance between any point
|
||||
contained in the bounding hyperrectangle of `node` and `point`, or between
|
||||
any point contained in the bounding hyperrectangle of `node` and any point
|
||||
contained in the bounding hyperrectangle of `other`.
|
||||
- `point` should be a column vector type of the same type as `MatType`.
|
||||
(e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
|
||||
|
||||
* `node.MaxDistance(point)`
|
||||
* `node.MaxDistance(other)`
|
||||
- Return a `double` indicating the maximum possible distance between `node`
|
||||
and `point`, or the `NonOrtSPTree` node `other`.
|
||||
- This is equivalent to the maximum possible distance between any point
|
||||
contained in the bounding hyperrectangle of `node` and `point`, or between
|
||||
any point contained in the bounding hyperrectangle of `node` and any point
|
||||
contained in the bounding hyperrectangle of `other`.
|
||||
- `point` should be a column vector type of the same type as `MatType`.
|
||||
(e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
|
||||
|
||||
* `node.RangeDistance(point)`
|
||||
* `node.RangeDistance(other)`
|
||||
- Return a [`RangeType<ElemType>`](../math.md#range) whose lower bound is
|
||||
`node.MinDistance(point)` or `node.MinDistance(other)`, and whose upper
|
||||
bound is `node.MaxDistance(point)` or `node.MaxDistance(other)`.
|
||||
- `ElemType` is the element type of `MatType`.
|
||||
- `point` should be a column vector type of the same type as `MatType`.
|
||||
(e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
|
||||
|
||||
## Tree traversals
|
||||
|
||||
Like every mlpack tree, the `NonOrtSPTree` class provides a [single-tree and
|
||||
dual-tree traversal](../../../developer/trees.md#traversals) that can be paired
|
||||
with a [`RuleType` class](../../../developer/trees.md#rules) to implement a
|
||||
single-tree or dual-tree algorithm.
|
||||
|
||||
* `NonOrtSPTree::SingleTreeTraverser`
|
||||
- Implements a depth-first single-tree traverser.
|
||||
|
||||
* `NonOrtSPTree::DualTreeTraverser`
|
||||
- Implements a dual-depth-first dual-tree traverser.
|
||||
|
||||
However, spill trees are primarily useful because the overlapping nodes allow
|
||||
*defeatist* search to be effective. Defeatist search is non-backtracking: the
|
||||
tree is traversed to one leaf only. For example, finding the approximate
|
||||
nearest neighbor of a point `p` with defeatist search is done by recursing in
|
||||
the tree, choosing the child with smallest minimum distance to `p`, and when a
|
||||
leaf is encountered, choosing the closest point in the leaf to `p` as the
|
||||
nearest neighbor. This is the strategy used in the
|
||||
[original spill tree paper (pdf)](https://proceedings.neurips.cc/paper/2004/file/1102a326d5f7c9e04fc3c89d0ede88c9-Paper.pdf).
|
||||
|
||||
Defeatist traversers, matching the API for a regular
|
||||
[traversal](../../../developer/trees.md#traversals) are made available as the
|
||||
following two classes:
|
||||
|
||||
* `NonOrtSPTree::DefeatistSingleTreeTraverser`
|
||||
- Implements a depth-first single-tree defeatist traverser with no
|
||||
backtracking. Traversal will terminate after the first leaf is visited.
|
||||
|
||||
* `NonOrtSPTree::DefeatistDualTreeTraverser`
|
||||
- Implements a dual-depth-first dual-tree defeatist traversal with no
|
||||
backtracking. For each query leaf node, traversal will terminate after the
|
||||
first reference leaf node is visited.
|
||||
|
||||
Any [`RuleType`](../../../developer/trees.md#rules) that is being used with a
|
||||
defeatist traversal, in addition to the functions required by the `RuleType`
|
||||
API, must implement the following functions:
|
||||
|
||||
```
|
||||
// This is only required for single-tree defeatist traversals.
|
||||
// It should return the index of the branch that should be chosen for the given
|
||||
// query point and reference node.
|
||||
template<typename VecType, typename TreeType>
|
||||
size_t GetBestChild(const VecType& queryPoint, TreeType& referenceNode);
|
||||
|
||||
// This is only required for dual-tree defeatist traversals.
|
||||
// It should return the index of the best child of the reference node that
|
||||
// should be chosen for the given query node.
|
||||
template<typename TreeType>
|
||||
size_t GetBestChild(TreeType& queryNode, TreeType& referenceNode);
|
||||
|
||||
// Return the minimum number of base cases (point-to-point computations) that
|
||||
// are required during the traversal.
|
||||
size_t MinimumBaseCases();
|
||||
```
|
||||
|
||||
## Example usage
|
||||
|
||||
Build an `NonOrtSPTree` on the `cloud` dataset and print basic statistics about
|
||||
the tree.
|
||||
|
||||
```c++
|
||||
// See https://datasets.mlpack.org/cloud.csv.
|
||||
arma::mat dataset;
|
||||
mlpack::data::Load("cloud.csv", dataset, true);
|
||||
|
||||
// Build the spill tree with a tau (margin) of 0.2 and a leaf size of 10.
|
||||
// (This means that nodes are split until they contain 10 or fewer points.)
|
||||
//
|
||||
// The std::move() means that `dataset` will be empty after this call, and no
|
||||
// data will be copied during tree building.
|
||||
//
|
||||
// When C++20 is enabled, then the <> is not necessary and the following line
|
||||
// will work:
|
||||
// mlpack::NonOrtSPTree tree(std::move(dataset), 0.2, 10);
|
||||
mlpack::NonOrtSPTree<> tree(std::move(dataset), 0.2, 10);
|
||||
|
||||
// Print the bounding ball of the root node.
|
||||
std::cout << "Bounding ball of root node:" << std::endl;
|
||||
std::cout << " Center: " << tree.Bound().Center().t();
|
||||
std::cout << " Radius: " << tree.Bound().Radius() << "." << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Print the number of descendant points of the root, and of each of its
|
||||
// children.
|
||||
std::cout << "Descendant points of root: "
|
||||
<< tree.NumDescendants() << "." << std::endl;
|
||||
std::cout << "Descendant points of left child: "
|
||||
<< tree.Left()->NumDescendants() << "." << std::endl;
|
||||
std::cout << "Descendant points of right child: "
|
||||
<< tree.Right()->NumDescendants() << "." << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Compute the center of the NonOrtSPTree. THis is the same as the center of
|
||||
// the bounding ball of the root.
|
||||
arma::vec center;
|
||||
tree.Center(center);
|
||||
std::cout << "Center of tree: " << center.t();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Build two `NonOrtSPTree`s on subsets of the corel dataset and compute minimum
|
||||
and maximum distances between different nodes in the tree.
|
||||
|
||||
```c++
|
||||
// See https://datasets.mlpack.org/corel-histogram.csv.
|
||||
arma::mat dataset;
|
||||
mlpack::data::Load("corel-histogram.csv", dataset, true);
|
||||
|
||||
// Build trees on the first half and the second half of points. Use a tau
|
||||
// (overlap) parameter of 0.3, which is tuned to this dataset, and a rho value
|
||||
// of 0.6 to prevent the trees getting too deep.
|
||||
mlpack::NonOrtSPTree<> tree1(dataset.cols(0, dataset.n_cols / 2), 0.3, 20, 0.6);
|
||||
mlpack::NonOrtSPTree<> tree2(dataset.cols(dataset.n_cols / 2 + 1,
|
||||
dataset.n_cols - 1), 0.3, 20, 0.6);
|
||||
|
||||
// Compute the maximum distance between the trees.
|
||||
std::cout << "Maximum distance between tree root nodes: "
|
||||
<< tree1.MaxDistance(tree2) << "." << std::endl;
|
||||
|
||||
// Get the leftmost grandchild of the first tree's root---if it exists.
|
||||
if (!tree1.IsLeaf() && !tree1.Child(0).IsLeaf())
|
||||
{
|
||||
mlpack::NonOrtSPTree<>& node1 = tree1.Child(0).Child(0);
|
||||
|
||||
// Get the rightmost grandchild of the second tree's root---if it exists.
|
||||
if (!tree2.IsLeaf() && !tree2.Child(1).IsLeaf())
|
||||
{
|
||||
mlpack::NonOrtSPTree<>& node2 = tree2.Child(1).Child(1);
|
||||
|
||||
// Print the minimum and maximum distance between the nodes.
|
||||
mlpack::Range dists = node1.RangeDistance(node2);
|
||||
std::cout << "Possible distances between two grandchild nodes: ["
|
||||
<< dists.Lo() << ", " << dists.Hi() << "]." << std::endl;
|
||||
|
||||
// Print the minimum distance between the first node and the first
|
||||
// descendant point of the second node.
|
||||
const size_t descendantIndex = node2.Descendant(0);
|
||||
const double descendantMinDist =
|
||||
node1.MinDistance(node2.Dataset().col(descendantIndex));
|
||||
std::cout << "Minimum distance between grandchild node and descendant "
|
||||
<< "point: " << descendantMinDist << "." << std::endl;
|
||||
|
||||
// Which child of node2 is closer to node1?
|
||||
const size_t closerIndex = node2.GetNearestChild(node1);
|
||||
if (closerIndex == 0)
|
||||
std::cout << "The left child of node2 is closer to node1." << std::endl;
|
||||
else if (closerIndex == 1)
|
||||
std::cout << "The right child of node2 is closer to node1." << std::endl;
|
||||
else // closerIndex == 2 in this case.
|
||||
std::cout << "Both children of node2 are equally close to node1."
|
||||
<< std::endl;
|
||||
|
||||
// And which child of node1 is further from node2?
|
||||
const size_t furtherIndex = node1.GetFurthestChild(node2);
|
||||
if (furtherIndex == 0)
|
||||
std::cout << "The left child of node1 is further from node2."
|
||||
<< std::endl;
|
||||
else if (furtherIndex == 1)
|
||||
std::cout << "The right child of node1 is further from node2."
|
||||
<< std::endl;
|
||||
else // furtherIndex == 2 in this case.
|
||||
std::cout << "Both children of node1 are equally far from node2."
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Build a `NonOrtSPTree` on 32-bit floating point data and save it to disk.
|
||||
|
||||
```c++
|
||||
// See https://datasets.mlpack.org/corel-histogram.csv.
|
||||
arma::fmat dataset;
|
||||
mlpack::data::Load("corel-histogram.csv", dataset);
|
||||
|
||||
// Build the NonOrtSPTree using 32-bit floating point data as the matrix type.
|
||||
// We will still use the default EmptyStatistic and EuclideanDistance
|
||||
// parameters.
|
||||
mlpack::NonOrtSPTree<mlpack::EuclideanDistance,
|
||||
mlpack::EmptyStatistic,
|
||||
arma::fmat> tree(std::move(dataset), 0.1, 20, 0.6);
|
||||
|
||||
// Save the tree to disk with the name 'tree'.
|
||||
mlpack::data::Save("tree.bin", "tree", tree);
|
||||
|
||||
std::cout << "Saved tree with " << tree.Dataset().n_cols << " points to "
|
||||
<< "'tree.bin'." << std::endl;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Load a 32-bit floating point `NonOrtSPTree` from disk, then traverse it manually
|
||||
and find the number of nodes whose children overlap.
|
||||
|
||||
```c++
|
||||
// This assumes the tree has already been saved to 'tree.bin' (as in the example
|
||||
// above).
|
||||
|
||||
// This convenient typedef saves us a long type name!
|
||||
using TreeType = mlpack::NonOrtSPTree<mlpack::EuclideanDistance,
|
||||
mlpack::EmptyStatistic,
|
||||
arma::fmat>;
|
||||
|
||||
TreeType tree;
|
||||
mlpack::data::Load("tree.bin", "tree", tree);
|
||||
std::cout << "Tree loaded with " << tree.NumDescendants() << " points."
|
||||
<< std::endl;
|
||||
|
||||
// Recurse in a depth-first manner. Count both the total number of non-leaves,
|
||||
// and the number of non-leaves that have overlapping children.
|
||||
size_t overlapCount = 0;
|
||||
size_t totalInternalNodeCount = 0;
|
||||
std::stack<TreeType*> stack;
|
||||
stack.push(&tree);
|
||||
while (!stack.empty())
|
||||
{
|
||||
TreeType* node = stack.top();
|
||||
stack.pop();
|
||||
|
||||
if (node->IsLeaf())
|
||||
continue;
|
||||
|
||||
if (node->Overlap())
|
||||
++overlapCount;
|
||||
++totalInternalNodeCount;
|
||||
|
||||
stack.push(node->Left());
|
||||
stack.push(node->Right());
|
||||
}
|
||||
|
||||
// Note that it would be possible to use TreeType::SingleTreeTraverser to
|
||||
// perform the recursion above, but that is more well-suited for more complex
|
||||
// tasks that require pruning and other non-trivial behavior; so using a simple
|
||||
// stack is the better option here.
|
||||
|
||||
// Print the results.
|
||||
std::cout << overlapCount << " out of " << totalInternalNodeCount
|
||||
<< " internal nodes have overlapping children." << std::endl;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Use a defeatist traversal to find the approximate nearest neighbor of the third
|
||||
and fourth points in the `corel-histogram` dataset. (Note: this can also be
|
||||
done more easily with the `KNN` class! This example is a demonstration of how
|
||||
to use the defeatist traverser.)
|
||||
|
||||
<!-- TODO: link to KNN class -->
|
||||
|
||||
For this example, we must first define a
|
||||
[`RuleType` class](../../../developer/trees.md#rules).
|
||||
|
||||
```c++
|
||||
// For simplicity, this only implements those methods required by single-tree
|
||||
// traversals, and cannot be used with a dual-tree traversal.
|
||||
//
|
||||
// `.Reset()` must be called before any additional single-tree traversals after
|
||||
// the first is run.
|
||||
class SpillNearestNeighborRule
|
||||
{
|
||||
public:
|
||||
// Store the dataset internally.
|
||||
SpillNearestNeighborRule(const arma::mat& dataset) :
|
||||
dataset(dataset),
|
||||
nearestNeighbor(size_t(-1)),
|
||||
nearestDistance(DBL_MAX) { }
|
||||
|
||||
// Compute the base case (point-to-point comparison).
|
||||
double BaseCase(const size_t queryIndex, const size_t referenceIndex)
|
||||
{
|
||||
// Skip the base case if the points are the same.
|
||||
if (queryIndex == referenceIndex)
|
||||
return 0.0;
|
||||
|
||||
const double dist = mlpack::EuclideanDistance::Evaluate(
|
||||
dataset.col(queryIndex), dataset.col(referenceIndex));
|
||||
|
||||
if (dist < nearestDistance)
|
||||
{
|
||||
nearestNeighbor = referenceIndex;
|
||||
nearestDistance = dist;
|
||||
}
|
||||
|
||||
return dist;
|
||||
}
|
||||
|
||||
// Score the given node in the tree; if it is sufficiently far away that it
|
||||
// cannot contain a better nearest neighbor candidate, we can prune it.
|
||||
template<typename TreeType>
|
||||
double Score(const size_t queryIndex, const TreeType& referenceNode) const
|
||||
{
|
||||
const double minDist = referenceNode.MinDistance(dataset.col(queryIndex));
|
||||
if (minDist > nearestDistance)
|
||||
return DBL_MAX; // Prune: this cannot contain a better candidate!
|
||||
|
||||
return minDist;
|
||||
}
|
||||
|
||||
// Rescore the given node/point combination. Note that this will not be used
|
||||
// by the defeatist traversal as it never backtracks, but we include it for
|
||||
// completeness because the RuleType API requires it.
|
||||
template<typename TreeType>
|
||||
double Rescore(const size_t, const TreeType&, const double oldScore) const
|
||||
{
|
||||
if (oldScore > nearestDistance)
|
||||
return DBL_MAX; // Prune: the node is too far away.
|
||||
return oldScore;
|
||||
}
|
||||
|
||||
// This is required by defeatist traversals to select the best reference
|
||||
// child to recurse into for overlapping nodes.
|
||||
template<typename TreeType>
|
||||
size_t GetBestChild(const size_t queryIndex, TreeType& referenceNode)
|
||||
const
|
||||
{
|
||||
return referenceNode.GetNearestChild(dataset.col(queryIndex));
|
||||
}
|
||||
|
||||
// We must perform at least two base cases in order to have a result. Note
|
||||
// that this is two, and not one, because we skip base cases where the query
|
||||
// and reference points are the same. That can only happen a maximum of once,
|
||||
// so to ensure that we compare a query point to a different reference point
|
||||
// at least once, we must return 2 here.
|
||||
size_t MinimumBaseCases() const { return 2; }
|
||||
|
||||
// Get the results (to be called after the traversal).
|
||||
size_t NearestNeighbor() const { return nearestNeighbor; }
|
||||
double NearestDistance() const { return nearestDistance; }
|
||||
|
||||
// Reset the internal statistics for an additional traversal.
|
||||
void Reset()
|
||||
{
|
||||
nearestNeighbor = size_t(-1);
|
||||
nearestDistance = DBL_MAX;
|
||||
}
|
||||
|
||||
private:
|
||||
const arma::mat& dataset;
|
||||
|
||||
size_t nearestNeighbor;
|
||||
double nearestDistance;
|
||||
};
|
||||
```
|
||||
|
||||
```c++
|
||||
// See https://datasets.mlpack.org/corel-histogram.csv.
|
||||
arma::mat dataset;
|
||||
mlpack::data::Load("corel-histogram.csv", dataset, true);
|
||||
|
||||
// Build two trees, one with a lot of overlap, and one with no overlap
|
||||
// (e.g. tau = 0).
|
||||
mlpack::NonOrtSPTree<> tree1(dataset, 0.5, 10), tree2(dataset, 0.0, 10);
|
||||
|
||||
// Construct the rule types, and then the traversals.
|
||||
SpillNearestNeighborRule r1(dataset), r2(dataset);
|
||||
|
||||
mlpack::NonOrtSPTree<>::DefeatistSingleTreeTraverser<SpillNearestNeighborRule>
|
||||
t1(r1), t2(r2);
|
||||
|
||||
// Search for the approximate nearest neighbor of point 3 using both trees.
|
||||
t1.Traverse(3, tree1);
|
||||
t2.Traverse(3, tree2);
|
||||
|
||||
std::cout << "Approximate nearest neighbor of point 3:" << std::endl;
|
||||
std::cout << " - Non-axis-aligned spill tree with overlap 0.5 found: point "
|
||||
<< r1.NearestNeighbor() << ", distance " << r1.NearestDistance()
|
||||
<< "." << std::endl;
|
||||
|
||||
std::cout << " - Non-axis-aligned spill tree with no overlap found: point "
|
||||
<< r2.NearestNeighbor() << ", distance " << r2.NearestDistance()
|
||||
<< "." << std::endl;
|
||||
|
||||
// Now search for point 6.
|
||||
r1.Reset();
|
||||
r2.Reset();
|
||||
|
||||
t1.Traverse(6, tree1);
|
||||
t2.Traverse(6, tree2);
|
||||
|
||||
std::cout << "Approximate nearest neighbor of point 6:" << std::endl;
|
||||
std::cout << " - Non-axis-aligned spill tree with overlap 0.5 found: point "
|
||||
<< r1.NearestNeighbor() << ", distance " << r1.NearestDistance()
|
||||
<< "." << std::endl;
|
||||
|
||||
std::cout << " - Non-axis-aligned spill tree with no overlap found: point "
|
||||
<< r2.NearestNeighbor() << ", distance " << r2.NearestDistance()
|
||||
<< "." << std::endl;
|
||||
```
|
||||
@@ -131,13 +131,12 @@ different.
|
||||
not supported, because this generally results in a octree with very loose
|
||||
bounding boxes. It is better to simply build a new `Octree` on the modified
|
||||
dataset. For trees that support individual insertion and deletions, see the
|
||||
`RectangleTree` class and all its variants (e.g. `RTree`, `RStarTree`, etc.).
|
||||
[`RectangleTree`](rectangle_tree.md) class and all its variants (e.g.
|
||||
[`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
|
||||
|
||||
- See also the
|
||||
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
|
||||
|
||||
<!-- TODO: add links to RectangleTree above when it is documented -->
|
||||
|
||||
---
|
||||
|
||||
### Constructor parameters:
|
||||
|
||||
@@ -152,7 +152,7 @@ The dataset is not permuted during the construction process.
|
||||
|
||||
| **name** | **type** | **description** | **default** |
|
||||
|----------|----------|-----------------|-------------|
|
||||
| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. Pass with `std::move(data)` to avoid copying the matrix. | _(N/A)_ |
|
||||
| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
|
||||
| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
|
||||
| `minLeafSize` | `size_t` | Minimum number of points to store in each leaf. | `8` |
|
||||
| `maxNumChildren` | `size_t` | Maximum number of children allowed in each non-leaf node. | `5` |
|
||||
|
||||
@@ -148,7 +148,7 @@ The dataset is not permuted during the construction process.
|
||||
|
||||
| **name** | **type** | **description** | **default** |
|
||||
|----------|----------|-----------------|-------------|
|
||||
| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. Pass with `std::move(data)` to avoid copying the matrix. | _(N/A)_ |
|
||||
| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
|
||||
| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
|
||||
| `minLeafSize` | `size_t` | Minimum number of points to store in each leaf. | `8` |
|
||||
| `maxNumChildren` | `size_t` | Maximum number of children allowed in each non-leaf node. | `5` |
|
||||
|
||||
@@ -151,7 +151,7 @@ The dataset is not permuted during the construction process.
|
||||
|
||||
| **name** | **type** | **description** | **default** |
|
||||
|----------|----------|-----------------|-------------|
|
||||
| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. Pass with `std::move(data)` to avoid copying the matrix. | _(N/A)_ |
|
||||
| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
|
||||
| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
|
||||
| `minLeafSize` | `size_t` | Minimum number of points to store in each leaf. | `8` |
|
||||
| `maxNumChildren` | `size_t` | Maximum number of children allowed in each non-leaf node. | `5` |
|
||||
|
||||
@@ -145,7 +145,7 @@ The dataset is not permuted during the construction process.
|
||||
|
||||
| **name** | **type** | **description** | **default** |
|
||||
|----------|----------|-----------------|-------------|
|
||||
| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. Pass with `std::move(data)` to avoid copying the matrix. | _(N/A)_ |
|
||||
| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
|
||||
| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
|
||||
| `minLeafSize` | `size_t` | Minimum number of points to store in each leaf. | `8` |
|
||||
| `maxNumChildren` | `size_t` | Maximum number of children allowed in each non-leaf node. | `5` |
|
||||
|
||||
@@ -183,15 +183,13 @@ The dataset is not permuted during the construction process.
|
||||
- See also the
|
||||
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
|
||||
|
||||
<!-- TODO: add links to RectangleTree above when it is documented -->
|
||||
|
||||
---
|
||||
|
||||
### Constructor parameters:
|
||||
|
||||
| **name** | **type** | **description** | **default** |
|
||||
|----------|----------|-----------------|-------------|
|
||||
| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. Pass with `std::move(data)` to avoid copying the matrix. | _(N/A)_ |
|
||||
| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
|
||||
| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
|
||||
| `minLeafSize` | `size_t` | Minimum number of points to store in each leaf. | `8` |
|
||||
| `maxNumChildren` | `size_t` | Maximum number of children allowed in each non-leaf node. | `5` |
|
||||
|
||||
@@ -9,10 +9,9 @@ nearest neighbor search) in low dimensions---typically less than 100.
|
||||
|
||||
An `RPTree` (or the similar [`MaxRPTree`](max_rp_tree.md)) may be preferred over
|
||||
a [`KDTree`](kdtree.md) or other tree structures as it is theoretically known to
|
||||
adapt to the intrinsic dimension of the data. This is similar to the cover
|
||||
tree, but the implementation is far simpler and as a result, more efficient.
|
||||
|
||||
<!-- TODO: add cover tree link above -->
|
||||
adapt to the intrinsic dimension of the data. This is similar to the
|
||||
[cover tree](cover_tree.md), but the implementation is far simpler and as a
|
||||
result, more efficient.
|
||||
|
||||
mlpack's `RPTree` implementation supports three template parameters for
|
||||
configurable behavior, and implements all the functionality required by the
|
||||
@@ -137,13 +136,11 @@ different.
|
||||
with very loose bounding boxes. It is better to simply build a new `RPTree`
|
||||
on the modified dataset. For trees that support individual insertion and
|
||||
deletions, see the [`RectangleTree`](rectangle_tree.md) class and all its
|
||||
variants (e.g. [`RTree`](r_tree.md), `RStarTree`, etc.).
|
||||
variants (e.g. [`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
|
||||
|
||||
- See also the
|
||||
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
|
||||
|
||||
<!-- TODO: add links to RectangleTree above when it is documented -->
|
||||
|
||||
---
|
||||
|
||||
### Constructor parameters:
|
||||
@@ -432,7 +429,7 @@ mlpack::data::Load("cloud.csv", dataset, true);
|
||||
//
|
||||
// Note that the '<>' isn't necessary if C++20 is being used (e.g.
|
||||
// `mlpack::RPTree tree(...)` will work fine in C++20 or newer).
|
||||
mlpack::RPTree<> tree(std::move(dataset));
|
||||
mlpack::RPTree<> tree(std::move(dataset), 10);
|
||||
|
||||
// Print the bounding box of the root node.
|
||||
std::cout << "Bounding box of root node:" << std::endl;
|
||||
|
||||
@@ -0,0 +1,749 @@
|
||||
# `SPTree`
|
||||
|
||||
The `SPTree` class implements the standard hybrid spill tree, a binary space
|
||||
partitioning tree that allows overlapping volumes between nodes. This type of
|
||||
tree can be more effective than trees like the [`KDTree`](kdtree.md) for
|
||||
approximate nearest neighbor search and related tasks.
|
||||
|
||||
`SPTree` supports three template parameters for configurable behavior, and
|
||||
implements all the functionality required by the [TreeType
|
||||
API](../../../developer/trees.md#the-treetype-api), plus some additional
|
||||
functionality specific to spill trees. `SPTree` is built on the more generic
|
||||
[`SpillTree`](spill_tree.md) class, so if fully custom behavior is desired, that
|
||||
|
||||
* [Template parameters](#template-parameters)
|
||||
* [Constructors](#constructors)
|
||||
* [Basic tree properties](#basic-tree-properties)
|
||||
* [Bounding distances with the tree](#bounding-distances-with-the-tree)
|
||||
* [Tree traversals](#tree-traversals)
|
||||
* [Example usage](#example-usage)
|
||||
|
||||
## See also
|
||||
|
||||
<!-- TODO: add links to all distance-based algorithms and other trees? -->
|
||||
|
||||
* [`SpillTree`](spill_tree.md)
|
||||
* [`MeanSPTree`](mean_sp_tree.md)
|
||||
* [`NonOrtSPTree`](non_ort_sp_tree.md)
|
||||
* [`NonOrtMeanSPTree`](non_ort_mean_sp_tree.md)
|
||||
* [`BinarySpaceTree`](binary_space_tree.md)
|
||||
* [An Investigation of Practical Approximate Nearest Neighbor Algorithms (pdf)](https://proceedings.neurips.cc/paper/2004/file/1102a326d5f7c9e04fc3c89d0ede88c9-Paper.pdf)
|
||||
* [Tree-Independent Dual-Tree Algorithms (pdf)](https://www.ratml.org/pub/pdf/2013tree.pdf)
|
||||
|
||||
## Template parameters
|
||||
|
||||
In accordance with the
|
||||
[TreeType API](../../../developer/trees.md#template-parameters-required-by-the-treetype-policy)
|
||||
(see also
|
||||
[this more detailed section](../../../developer/trees.md#template-parameters)),
|
||||
the `SPTree` class takes three template parameters:
|
||||
|
||||
```
|
||||
SPTree<DistanceType, StatisticType, MatType>
|
||||
```
|
||||
|
||||
* `DistanceType`: the [distance metric](../distances.md) to use for distance
|
||||
computations. Because the `SPTree` internally uses
|
||||
[`HRectBound`](binary_space_tree.md#hrectbound), this is required to be
|
||||
[`EuclideanDistance`](../distances.md#lmetric). See
|
||||
[`NonOrtSPTree`](non_ort_sp_tree.md) for a version of the spill tree where
|
||||
arbitrary distance metrics are allowed.
|
||||
|
||||
* `StatisticType`: this holds auxiliary information in each tree node. By
|
||||
default, [`EmptyStatistic`](binary_space_tree.md#emptystatistic) is used,
|
||||
which holds no information.
|
||||
- See the [`StatisticType`](binary_space_tree.md#statistictype) section in
|
||||
the `BinarySpaceTree` documentation for more details.
|
||||
|
||||
* `MatType`: the type of matrix used to represent points. Must be a type
|
||||
matching the [Armadillo API](../../matrices.md). By default, `arma::mat` is
|
||||
used, but other types such as `arma::fmat` or similar will work just fine.
|
||||
|
||||
The `SPTree` class itself is a convenience typedef of the generic
|
||||
[`SpillTree`](spill_tree.md) class, using the
|
||||
[`AxisOrthogonalHyperplane`](spill_tree.md#axisorthogonalhyperplane) class as
|
||||
the splitting hyperplane type, and the
|
||||
[`MidpointSpaceSplit`](spill_tree.md#midpointspacesplit) class as the splitting
|
||||
strategy.
|
||||
|
||||
If no template parameters are explicitly specified, then defaults are used:
|
||||
|
||||
```
|
||||
SPTree<> = SPTree<EuclideanDistance, EmptyStatistic, arma::mat>
|
||||
```
|
||||
|
||||
## Constructors
|
||||
|
||||
`SPTree`s are constructed by iteratively finding splitting hyperplanes, and
|
||||
points within a margin of the hyperplane are assigned to *both* child nodes.
|
||||
Unlike the constructors of
|
||||
[`BinarySpaceTree`](binary_space_tree.md#constructors), the dataset is not
|
||||
permuted during construction.
|
||||
|
||||
---
|
||||
|
||||
* `node = SPTree(data, tau=0.0, maxLeafSize=20, rho=0.7)`
|
||||
- Construct an `SPTree` on the given `data`, using the specified
|
||||
hyperparameters to control tree construction behavior.
|
||||
- By default, a reference to `data` is stored. If `data` goes out of scope
|
||||
after tree construction, memory errors will occur! To avoid this, either
|
||||
pass the dataset or a copy with `std::move()` (e.g. `std::move(data)`);
|
||||
when doing this, `data` will be set to an empty matrix.
|
||||
|
||||
---
|
||||
|
||||
* `node = SPTree<DistanceType, StatisticType, MatType>(data, tau=0.0, maxLeafSize=20, rho=0.7)`
|
||||
- Construct an `SPTree` on the given `data`, using custom template
|
||||
parameters, and using the specified hyperparameters to control tree
|
||||
construction behavior.
|
||||
- By default, a reference to `data` is stored. If `data` goes out of scope
|
||||
after tree construction, memory errors will occur! To avoid this, either
|
||||
pass the dataset or a copy with `std::move()` (e.g. `std::move(data)`);
|
||||
when doing this, `data` will be set to an empty matrix.
|
||||
|
||||
---
|
||||
|
||||
* `node = SPTree()`
|
||||
- Construct an empty `SPTree` with no children, no points, and default
|
||||
template parameters.
|
||||
|
||||
---
|
||||
|
||||
***Notes:***
|
||||
|
||||
- The name `node` is used here for `SPTree` objects instead of `tree`, because
|
||||
each `SPTree` object is a single node in the tree. The constructor returns
|
||||
the node that is the root of the tree.
|
||||
|
||||
- Inserting individual points or removing individual points from an `SPTree` is
|
||||
not supported, because this generally results in a tree with very suboptimal
|
||||
hyperplane splits. It is better to simply build a new `SPTree` on the
|
||||
modified dataset. For trees that support individual insertion and deletions,
|
||||
see the [`RectangleTree`](rectangle_tree.md) class and all its variants (e.g.
|
||||
[`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
|
||||
|
||||
- See also the
|
||||
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
|
||||
|
||||
---
|
||||
|
||||
### Constructor parameters:
|
||||
|
||||
| **name** | **type** | **description** | **default** |
|
||||
|----------|----------|-----------------|-------------|
|
||||
| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
|
||||
| `tau` | `double` | Width of spill margin: points within `tau` of the splitting hyperplane of a node will be contained in both left and right children. | `0.0` |
|
||||
| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
|
||||
| `rho` | `double` | Balance threshold. When splitting, if either overlapping node would contain a fraction of more than `rho` of the points, a non-overlapping split is performed. Must be in the range `[0.0, 1.0)`. | `0.7` |
|
||||
|
||||
***Caveats***:
|
||||
|
||||
* `tau` must be manually tuned for the properties of each dataset; the default,
|
||||
`0.0`, will never allow overlap between nodes (and thus the created tree will
|
||||
essentially be a non-overlapping [`BinarySpaceTree`](binary_space_tree.md)).
|
||||
|
||||
* If `tau` is set too large, nodes will overlap too much and search quality
|
||||
will be degraded.
|
||||
|
||||
* `rho` implicitly controls the depth of the tree by forcing very overlapping
|
||||
children to be non-overlapping. As `rho` gets closer to `1`, more overlap is
|
||||
allowed, which in turn makes the tree deeper. If `rho` is set to `0.5` or
|
||||
less, then all splits will be non-overlapping (and the tree will essentially
|
||||
be a [`BinarySpaceTree`](binary_space_tree.md)).
|
||||
|
||||
## Basic tree properties
|
||||
|
||||
Once an `SPTree` object is constructed, various properties of the tree can be
|
||||
accessed or inspected. Many of these functions are required by the
|
||||
[TreeType API](../../../developer/trees.md#the-treetype-api).
|
||||
|
||||
### Navigating the tree
|
||||
|
||||
* `node.NumChildren()` returns the number of children in `node`. This is
|
||||
either `2` if `node` has children, or `0` if `node` is a leaf.
|
||||
|
||||
* `node.IsLeaf()` returns a `bool` indicating whether or not `node` is a leaf.
|
||||
|
||||
* `node.Child(i)` returns an `SPTree&` that is the `i`th child.
|
||||
- `i` must be `0` or `1`.
|
||||
- This function should only be called if `node.NumChildren()` is not `0`
|
||||
(e.g. if `node` is not a leaf). Note that this returns a valid
|
||||
`SPTree&` that can itself be used just like the root node of the
|
||||
tree!
|
||||
- `node.Left()` and `node.Right()` are convenience functions specific to
|
||||
`SPTree` that will return `SPTree*` (pointers) to the left and right
|
||||
children, respectively, or `NULL` if `node` has no children.
|
||||
|
||||
* `node.Parent()` will return an `SPTree*` that points to the parent of
|
||||
`node`, or `NULL` if `node` is the root of the `SPTree`.
|
||||
|
||||
---
|
||||
|
||||
### Accessing members of a tree
|
||||
|
||||
* `node.Overlap()` will return a `bool` that is `true` if `node`'s children are
|
||||
overlapping, and `false` otherwise.
|
||||
|
||||
* `node.Hyperplane()` will return an
|
||||
[`AxisOrthogonalHyperplane`](spill_tree.md#axisorthogonalhyperplane) object
|
||||
that represents the axis-aligned splitting hyperplane of `node`.
|
||||
- All points in `node.Left()` are to the left of `node.Hyperplane()` if
|
||||
`node.Overlap()` is `false`; otherwise, all points in `node.Left()` are to
|
||||
the left of `node.Hyperplane() + tau`.
|
||||
- All points in `node.Right()` are to the right of `node.Hyperplane()` if
|
||||
`node.Overlap()` is `false`; otherwise, all points in `node.Right()` are to
|
||||
the right of `node.Hyperplane() - tau`.
|
||||
|
||||
* `node.Bound()` will return a
|
||||
[`const HRectBound&`](binary_space_tree.md#hrectbound) representing the
|
||||
bounding box associated with `node`.
|
||||
- If a [custom `DistanceType` and/or `MatType`](#template-parameters) are
|
||||
specified, then a `const HRectBound<DistanceType, ElemType>&` is returned.
|
||||
* `ElemType` is the element type of the specified `MatType` (e.g. `double`
|
||||
for `arma::mat`, `float` for `arma::fmat`, etc.).
|
||||
|
||||
* `node.Stat()` will return a `StatisticType&` holding the statistics of the
|
||||
node that were computed during tree construction.
|
||||
|
||||
* `node.Distance()` will return a `EuclideanDistance&`. Because
|
||||
`EuclideanDistance` has no instantiated members, this is unlikely to be
|
||||
useful, but is required to satisfy the
|
||||
[`TreeType` API](../../../developer/trees.md#the-treetype-api).
|
||||
|
||||
See also the
|
||||
[developer documentation](../../../developer/trees.md#basic-tree-functionality)
|
||||
for basic tree functionality in mlpack.
|
||||
|
||||
---
|
||||
|
||||
### Accessing data held in a tree
|
||||
|
||||
* `node.Dataset()` will return a `const MatType&` that is the dataset the
|
||||
tree was built on.
|
||||
|
||||
* `node.NumPoints()` returns a `size_t` indicating the number of points held
|
||||
directly in `node`.
|
||||
- If `node` is not a leaf, this will return `0`, as `SPTree` only holds
|
||||
points directly in its leaves.
|
||||
- If `node` is a leaf, then the number of points will be less than or equal
|
||||
to the `maxLeafSize` that was specified when the tree was constructed.
|
||||
|
||||
* `node.Point(i)` returns a `size_t` indicating the index of the `i`'th point
|
||||
in `node.Dataset()`.
|
||||
- `i` must be in the range `[0, node.NumPoints() - 1]` (inclusive).
|
||||
- `node` must be a leaf (as non-leaves do not hold any points).
|
||||
- The `i`'th point in `node` can then be accessed as
|
||||
`node.Dataset().col(node.Point(i))`.
|
||||
- Accessing the actual `i`'th point itself can be done with, e.g.,
|
||||
`node.Dataset().col(node.Point(i))`.
|
||||
|
||||
* `node.NumDescendants()` returns a `size_t` indicating the number of points
|
||||
held in all descendant leaves of `node`.
|
||||
- If `node` is the root of the tree, then `node.NumDescendants()` will be
|
||||
equal to `node.Dataset().n_cols`.
|
||||
|
||||
* `node.Descendant(i)` returns a `size_t` indicating the index of the `i`'th
|
||||
descendant point in `node.Dataset()`.
|
||||
- `i` must be in the range `[0, node.NumDescendants() - 1]` (inclusive).
|
||||
- `node` does not need to be a leaf.
|
||||
- The `i`'th descendant point in `node` can then be accessed as
|
||||
`node.Dataset().col(node.Descendant(i))`.
|
||||
- Accessing the actual `i`'th descendant itself can be done with, e.g.,
|
||||
`node.Dataset().col(node.Descendant(i))`.
|
||||
|
||||
---
|
||||
|
||||
### Accessing computed bound quantities of a tree
|
||||
|
||||
The following quantities are cached for each node in an `SPTree`, and so
|
||||
accessing them does not require any computation. In the documentation below,
|
||||
`ElemType` is the element type of the given `MatType`; e.g., if `MatType` is
|
||||
`arma::mat`, then `ElemType` is `double`.
|
||||
|
||||
* `node.FurthestPointDistance()` returns an `ElemType` representing the
|
||||
distance between the center of the bound of `node` and the furthest point
|
||||
held by `node`.
|
||||
- If `node` is not a leaf, this returns 0 (because `node` does not hold any
|
||||
points).
|
||||
|
||||
* `node.FurthestDescendantDistance()` returns an `ElemType` representing the
|
||||
distance between the center of the bound of `node` and the furthest
|
||||
descendant point held by `node`.
|
||||
|
||||
* `node.MinimumBoundDistance()` returns an `ElemType` representing the minimum
|
||||
possible distance from the center of the node to any edge of its bound.
|
||||
|
||||
* `node.ParentDistance()` returns an `ElemType` representing the distance
|
||||
between the center of the bound of `node` and the center of the bound of its
|
||||
parent.
|
||||
- If `node` is the root of the tree, `0` is returned.
|
||||
|
||||
***Note:*** for more details on each bound quantity, see the [developer
|
||||
documentation](../../../developer/trees.md#complex-tree-functionality-and-bounds)
|
||||
on bound quantities for trees.
|
||||
|
||||
---
|
||||
|
||||
### Other functionality
|
||||
|
||||
* `node.Center(center)` computes the center of the bound of `node` and stores
|
||||
it in `center`.
|
||||
- `center` should be of type `arma::Col<ElemType>&`, where `ElemType` is the
|
||||
element type of the specified `MatType`.
|
||||
- `center` will be set to have size equivalent to the dimensionality of the
|
||||
dataset held by `node`.
|
||||
- This is equivalent to calling `node.Bound().Center(center)`.
|
||||
|
||||
* An `SPTree` can be serialized with
|
||||
[`data::Save()` and `data::Load()`](../../load_save.md#mlpack-objects).
|
||||
|
||||
## Bounding distances with the tree
|
||||
|
||||
The primary use of trees in mlpack is bounding distances to points or other tree
|
||||
nodes. The following functions can be used for these tasks.
|
||||
|
||||
* `node.GetNearestChild(point)`
|
||||
* `node.GetFurthestChild(point)`
|
||||
- Return a `size_t` indicating the index of the child (`0` for left, `1` for
|
||||
right) that is closest to (or furthest from) `point`, with respect
|
||||
to the `MinDistance()` (or `MaxDistance()`) function.
|
||||
- If there is a tie, `0` (the left child) is returned.
|
||||
- If `node` is a leaf, `0` is returned.
|
||||
- `point` should be a column vector type of the same type as `MatType`.
|
||||
(e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
|
||||
|
||||
* `node.GetNearestChild(other)`
|
||||
* `node.GetFurthestChild(other)`
|
||||
- Return a `size_t` indicating the index of the child (`0` for left, `1` for
|
||||
right) that is closest to (or furthest from) the `SPTree` node `other`,
|
||||
with respect to the `MinDistance()` (or `MaxDistance()`) function.
|
||||
- If there is a tie, `0` (the left child) is returned.
|
||||
- If `node` is a leaf, `0` is returned.
|
||||
|
||||
---
|
||||
|
||||
* `node.MinDistance(point)`
|
||||
* `node.MinDistance(other)`
|
||||
- Return a `double` indicating the minimum possible distance between `node`
|
||||
and `point`, or the `SPTree` node `other`.
|
||||
- This is equivalent to the minimum possible distance between any point
|
||||
contained in the bounding hyperrectangle of `node` and `point`, or between
|
||||
any point contained in the bounding hyperrectangle of `node` and any point
|
||||
contained in the bounding hyperrectangle of `other`.
|
||||
- `point` should be a column vector type of the same type as `MatType`.
|
||||
(e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
|
||||
|
||||
* `node.MaxDistance(point)`
|
||||
* `node.MaxDistance(other)`
|
||||
- Return a `double` indicating the maximum possible distance between `node`
|
||||
and `point`, or the `SPTree` node `other`.
|
||||
- This is equivalent to the maximum possible distance between any point
|
||||
contained in the bounding hyperrectangle of `node` and `point`, or between
|
||||
any point contained in the bounding hyperrectangle of `node` and any point
|
||||
contained in the bounding hyperrectangle of `other`.
|
||||
- `point` should be a column vector type of the same type as `MatType`.
|
||||
(e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
|
||||
|
||||
* `node.RangeDistance(point)`
|
||||
* `node.RangeDistance(other)`
|
||||
- Return a [`RangeType<ElemType>`](../math.md#range) whose lower bound is
|
||||
`node.MinDistance(point)` or `node.MinDistance(other)`, and whose upper
|
||||
bound is `node.MaxDistance(point)` or `node.MaxDistance(other)`.
|
||||
- `ElemType` is the element type of `MatType`.
|
||||
- `point` should be a column vector type of the same type as `MatType`.
|
||||
(e.g., if `MatType` is `arma::mat`, then `point` should be an `arma::vec`.)
|
||||
|
||||
## Tree traversals
|
||||
|
||||
Like every mlpack tree, the `SPTree` class provides a [single-tree and
|
||||
dual-tree traversal](../../../developer/trees.md#traversals) that can be paired
|
||||
with a [`RuleType` class](../../../developer/trees.md#rules) to implement a
|
||||
single-tree or dual-tree algorithm.
|
||||
|
||||
* `SPTree::SingleTreeTraverser`
|
||||
- Implements a depth-first single-tree traverser.
|
||||
|
||||
* `SPTree::DualTreeTraverser`
|
||||
- Implements a dual-depth-first dual-tree traverser.
|
||||
|
||||
However, spill trees are primarily useful because the overlapping nodes allow
|
||||
*defeatist* search to be effective. Defeatist search is non-backtracking: the
|
||||
tree is traversed to one leaf only. For example, finding the approximate
|
||||
nearest neighbor of a point `p` with defeatist search is done by recursing in
|
||||
the tree, choosing the child with smallest minimum distance to `p`, and when a
|
||||
leaf is encountered, choosing the closest point in the leaf to `p` as the
|
||||
nearest neighbor. This is the strategy used in the
|
||||
[original spill tree paper (pdf)](https://proceedings.neurips.cc/paper/2004/file/1102a326d5f7c9e04fc3c89d0ede88c9-Paper.pdf).
|
||||
|
||||
Defeatist traversers, matching the API for a regular
|
||||
[traversal](../../../developer/trees.md#traversals) are made available as the
|
||||
following two classes:
|
||||
|
||||
* `SPTree::DefeatistSingleTreeTraverser`
|
||||
- Implements a depth-first single-tree defeatist traverser with no
|
||||
backtracking. Traversal will terminate after the first leaf is visited.
|
||||
|
||||
* `SPTree::DefeatistDualTreeTraverser`
|
||||
- Implements a dual-depth-first dual-tree defeatist traversal with no
|
||||
backtracking. For each query leaf node, traversal will terminate after the
|
||||
first reference leaf node is visited.
|
||||
|
||||
Any [`RuleType`](../../../developer/trees.md#rules) that is being used with a
|
||||
defeatist traversal, in addition to the functions required by the `RuleType`
|
||||
API, must implement the following functions:
|
||||
|
||||
```
|
||||
// This is only required for single-tree defeatist traversals.
|
||||
// It should return the index of the branch that should be chosen for the given
|
||||
// query point and reference node.
|
||||
template<typename VecType, typename TreeType>
|
||||
size_t GetBestChild(const VecType& queryPoint, TreeType& referenceNode);
|
||||
|
||||
// This is only required for dual-tree defeatist traversals.
|
||||
// It should return the index of the best child of the reference node that
|
||||
// should be chosen for the given query node.
|
||||
template<typename TreeType>
|
||||
size_t GetBestChild(TreeType& queryNode, TreeType& referenceNode);
|
||||
|
||||
// Return the minimum number of base cases (point-to-point computations) that
|
||||
// are required during the traversal.
|
||||
size_t MinimumBaseCases();
|
||||
```
|
||||
|
||||
## Example usage
|
||||
|
||||
Build an `SPTree` on the `cloud` dataset and print basic statistics about the
|
||||
tree.
|
||||
|
||||
```c++
|
||||
// See https://datasets.mlpack.org/cloud.csv.
|
||||
arma::mat dataset;
|
||||
mlpack::data::Load("cloud.csv", dataset, true);
|
||||
|
||||
// Build the spill tree with a tau (margin) of 0.2 and a leaf size of 10.
|
||||
// (This means that nodes are split until they contain 10 or fewer points.)
|
||||
//
|
||||
// The std::move() means that `dataset` will be empty after this call, and no
|
||||
// data will be copied during tree building.
|
||||
//
|
||||
// When C++20 is enabled, then the <> is not necessary and the following line
|
||||
// will work:
|
||||
// mlpack::SPTree tree(std::move(dataset), 0.2, 10);
|
||||
mlpack::SPTree<> tree(std::move(dataset), 0.2, 10);
|
||||
|
||||
// Print the bounding box of the root node.
|
||||
std::cout << "Bounding box of root node:" << std::endl;
|
||||
for (size_t i = 0; i < tree.Bound().Dim(); ++i)
|
||||
{
|
||||
std::cout << " - Dimension " << i << ": [" << tree.Bound()[i].Lo() << ", "
|
||||
<< tree.Bound()[i].Hi() << "]." << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
// Print the number of descendant points of the root, and of each of its
|
||||
// children.
|
||||
std::cout << "Descendant points of root: "
|
||||
<< tree.NumDescendants() << "." << std::endl;
|
||||
std::cout << "Descendant points of left child: "
|
||||
<< tree.Left()->NumDescendants() << "." << std::endl;
|
||||
std::cout << "Descendant points of right child: "
|
||||
<< tree.Right()->NumDescendants() << "." << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Compute the center of the SPTree.
|
||||
arma::vec center;
|
||||
tree.Center(center);
|
||||
std::cout << "Center of tree: " << center.t();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Build two `SPTree`s on subsets of the corel dataset and compute minimum and
|
||||
maximum distances between different nodes in the tree.
|
||||
|
||||
```c++
|
||||
// See https://datasets.mlpack.org/corel-histogram.csv.
|
||||
arma::mat dataset;
|
||||
mlpack::data::Load("corel-histogram.csv", dataset, true);
|
||||
|
||||
// Build trees on the first half and the second half of points. Use a tau
|
||||
// (overlap) parameter of 0.3, which is tuned to this dataset, and a rho value
|
||||
// of 0.6 to prevent the trees getting too deep.
|
||||
mlpack::SPTree<> tree1(dataset.cols(0, dataset.n_cols / 2), 0.3, 20, 0.6);
|
||||
mlpack::SPTree<> tree2(dataset.cols(dataset.n_cols / 2 + 1, dataset.n_cols - 1),
|
||||
0.3, 20, 0.6);
|
||||
|
||||
// Compute the maximum distance between the trees.
|
||||
std::cout << "Maximum distance between tree root nodes: "
|
||||
<< tree1.MaxDistance(tree2) << "." << std::endl;
|
||||
|
||||
// Get the leftmost grandchild of the first tree's root---if it exists.
|
||||
if (!tree1.IsLeaf() && !tree1.Child(0).IsLeaf())
|
||||
{
|
||||
mlpack::SPTree<>& node1 = tree1.Child(0).Child(0);
|
||||
|
||||
// Get the rightmost grandchild of the second tree's root---if it exists.
|
||||
if (!tree2.IsLeaf() && !tree2.Child(1).IsLeaf())
|
||||
{
|
||||
mlpack::SPTree<>& node2 = tree2.Child(1).Child(1);
|
||||
|
||||
// Print the minimum and maximum distance between the nodes.
|
||||
mlpack::Range dists = node1.RangeDistance(node2);
|
||||
std::cout << "Possible distances between two grandchild nodes: ["
|
||||
<< dists.Lo() << ", " << dists.Hi() << "]." << std::endl;
|
||||
|
||||
// Print the minimum distance between the first node and the first
|
||||
// descendant point of the second node.
|
||||
const size_t descendantIndex = node2.Descendant(0);
|
||||
const double descendantMinDist =
|
||||
node1.MinDistance(node2.Dataset().col(descendantIndex));
|
||||
std::cout << "Minimum distance between grandchild node and descendant "
|
||||
<< "point: " << descendantMinDist << "." << std::endl;
|
||||
|
||||
// Which child of node2 is closer to node1?
|
||||
const size_t closerIndex = node2.GetNearestChild(node1);
|
||||
if (closerIndex == 0)
|
||||
std::cout << "The left child of node2 is closer to node1." << std::endl;
|
||||
else if (closerIndex == 1)
|
||||
std::cout << "The right child of node2 is closer to node1." << std::endl;
|
||||
else // closerIndex == 2 in this case.
|
||||
std::cout << "Both children of node2 are equally close to node1."
|
||||
<< std::endl;
|
||||
|
||||
// And which child of node1 is further from node2?
|
||||
const size_t furtherIndex = node1.GetFurthestChild(node2);
|
||||
if (furtherIndex == 0)
|
||||
std::cout << "The left child of node1 is further from node2."
|
||||
<< std::endl;
|
||||
else if (furtherIndex == 1)
|
||||
std::cout << "The right child of node1 is further from node2."
|
||||
<< std::endl;
|
||||
else // furtherIndex == 2 in this case.
|
||||
std::cout << "Both children of node1 are equally far from node2."
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Build an `SPTree` on 32-bit floating point data and save it to disk.
|
||||
|
||||
```c++
|
||||
// See https://datasets.mlpack.org/corel-histogram.csv.
|
||||
arma::fmat dataset;
|
||||
mlpack::data::Load("corel-histogram.csv", dataset);
|
||||
|
||||
// Build the SPTree using 32-bit floating point data as the matrix type.
|
||||
// We will still use the default EmptyStatistic and EuclideanDistance
|
||||
// parameters.
|
||||
mlpack::SPTree<mlpack::EuclideanDistance,
|
||||
mlpack::EmptyStatistic,
|
||||
arma::fmat> tree(std::move(dataset), 0.1, 20, 0.95);
|
||||
|
||||
// Save the tree to disk with the name 'tree'.
|
||||
mlpack::data::Save("tree.bin", "tree", tree);
|
||||
|
||||
std::cout << "Saved tree with " << tree.Dataset().n_cols << " points to "
|
||||
<< "'tree.bin'." << std::endl;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Load a 32-bit floating point `SPTree` from disk, then traverse it manually and
|
||||
find the number of nodes whose children overlap.
|
||||
|
||||
```c++
|
||||
// This assumes the tree has already been saved to 'tree.bin' (as in the example
|
||||
// above).
|
||||
|
||||
// This convenient typedef saves us a long type name!
|
||||
using TreeType = mlpack::SPTree<mlpack::EuclideanDistance,
|
||||
mlpack::EmptyStatistic,
|
||||
arma::fmat>;
|
||||
|
||||
TreeType tree;
|
||||
mlpack::data::Load("tree.bin", "tree", tree);
|
||||
std::cout << "Tree loaded with " << tree.NumDescendants() << " points."
|
||||
<< std::endl;
|
||||
|
||||
// Recurse in a depth-first manner. Count both the total number of non-leaves,
|
||||
// and the number of non-leaves that have overlapping children.
|
||||
size_t overlapCount = 0;
|
||||
size_t totalInternalNodeCount = 0;
|
||||
std::stack<TreeType*> stack;
|
||||
stack.push(&tree);
|
||||
while (!stack.empty())
|
||||
{
|
||||
TreeType* node = stack.top();
|
||||
stack.pop();
|
||||
|
||||
if (node->IsLeaf())
|
||||
continue;
|
||||
|
||||
if (node->Overlap())
|
||||
++overlapCount;
|
||||
++totalInternalNodeCount;
|
||||
|
||||
stack.push(node->Left());
|
||||
stack.push(node->Right());
|
||||
}
|
||||
|
||||
// Note that it would be possible to use TreeType::SingleTreeTraverser to
|
||||
// perform the recursion above, but that is more well-suited for more complex
|
||||
// tasks that require pruning and other non-trivial behavior; so using a simple
|
||||
// stack is the better option here.
|
||||
|
||||
// Print the results.
|
||||
std::cout << overlapCount << " out of " << totalInternalNodeCount
|
||||
<< " internal nodes have overlapping children." << std::endl;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Use a defeatist traversal to find the approximate nearest neighbor of the third
|
||||
and fourth points in the `corel-histogram` dataset. (Note: this can also be
|
||||
done more easily with the `KNN` class! This example is a demonstration of how
|
||||
to use the defeatist traverser.)
|
||||
|
||||
<!-- TODO: link to KNN class -->
|
||||
|
||||
For this example, we must first define a
|
||||
[`RuleType` class](../../../developer/trees.md#rules).
|
||||
|
||||
```c++
|
||||
// For simplicity, this only implements those methods required by single-tree
|
||||
// traversals, and cannot be used with a dual-tree traversal.
|
||||
//
|
||||
// `.Reset()` must be called before any additional single-tree traversals after
|
||||
// the first is run.
|
||||
class SpillNearestNeighborRule
|
||||
{
|
||||
public:
|
||||
// Store the dataset internally.
|
||||
SpillNearestNeighborRule(const arma::mat& dataset) :
|
||||
dataset(dataset),
|
||||
nearestNeighbor(size_t(-1)),
|
||||
nearestDistance(DBL_MAX) { }
|
||||
|
||||
// Compute the base case (point-to-point comparison).
|
||||
double BaseCase(const size_t queryIndex, const size_t referenceIndex)
|
||||
{
|
||||
// Skip the base case if the points are the same.
|
||||
if (queryIndex == referenceIndex)
|
||||
return 0.0;
|
||||
|
||||
const double dist = mlpack::EuclideanDistance::Evaluate(
|
||||
dataset.col(queryIndex), dataset.col(referenceIndex));
|
||||
|
||||
if (dist < nearestDistance)
|
||||
{
|
||||
nearestNeighbor = referenceIndex;
|
||||
nearestDistance = dist;
|
||||
}
|
||||
|
||||
return dist;
|
||||
}
|
||||
|
||||
// Score the given node in the tree; if it is sufficiently far away that it
|
||||
// cannot contain a better nearest neighbor candidate, we can prune it.
|
||||
template<typename TreeType>
|
||||
double Score(const size_t queryIndex, const TreeType& referenceNode) const
|
||||
{
|
||||
const double minDist = referenceNode.MinDistance(dataset.col(queryIndex));
|
||||
if (minDist > nearestDistance)
|
||||
return DBL_MAX; // Prune: this cannot contain a better candidate!
|
||||
|
||||
return minDist;
|
||||
}
|
||||
|
||||
// Rescore the given node/point combination. Note that this will not be used
|
||||
// by the defeatist traversal as it never backtracks, but we include it for
|
||||
// completeness because the RuleType API requires it.
|
||||
template<typename TreeType>
|
||||
double Rescore(const size_t, const TreeType&, const double oldScore) const
|
||||
{
|
||||
if (oldScore > nearestDistance)
|
||||
return DBL_MAX; // Prune: the node is too far away.
|
||||
return oldScore;
|
||||
}
|
||||
|
||||
// This is required by defeatist traversals to select the best reference
|
||||
// child to recurse into for overlapping nodes.
|
||||
template<typename TreeType>
|
||||
size_t GetBestChild(const size_t queryIndex, TreeType& referenceNode)
|
||||
const
|
||||
{
|
||||
return referenceNode.GetNearestChild(dataset.col(queryIndex));
|
||||
}
|
||||
|
||||
// We must perform at least two base cases in order to have a result. Note
|
||||
// that this is two, and not one, because we skip base cases where the query
|
||||
// and reference points are the same. That can only happen a maximum of once,
|
||||
// so to ensure that we compare a query point to a different reference point
|
||||
// at least once, we must return 2 here.
|
||||
size_t MinimumBaseCases() const { return 2; }
|
||||
|
||||
// Get the results (to be called after the traversal).
|
||||
size_t NearestNeighbor() const { return nearestNeighbor; }
|
||||
double NearestDistance() const { return nearestDistance; }
|
||||
|
||||
// Reset the internal statistics for an additional traversal.
|
||||
void Reset()
|
||||
{
|
||||
nearestNeighbor = size_t(-1);
|
||||
nearestDistance = DBL_MAX;
|
||||
}
|
||||
|
||||
private:
|
||||
const arma::mat& dataset;
|
||||
|
||||
size_t nearestNeighbor;
|
||||
double nearestDistance;
|
||||
};
|
||||
```
|
||||
|
||||
```c++
|
||||
// See https://datasets.mlpack.org/corel-histogram.csv.
|
||||
arma::mat dataset;
|
||||
mlpack::data::Load("corel-histogram.csv", dataset, true);
|
||||
|
||||
// Build two trees, one with a lot of overlap, and one with no overlap
|
||||
// (e.g. tau = 0).
|
||||
mlpack::SPTree<> tree1(dataset, 0.5, 10), tree2(dataset, 0.0, 10);
|
||||
|
||||
// Construct the rule types, and then the traversals.
|
||||
SpillNearestNeighborRule r1(dataset), r2(dataset);
|
||||
|
||||
mlpack::SPTree<>::DefeatistSingleTreeTraverser<SpillNearestNeighborRule>
|
||||
t1(r1), t2(r2);
|
||||
|
||||
// Search for the approximate nearest neighbor of point 3 using both trees.
|
||||
t1.Traverse(3, tree1);
|
||||
t2.Traverse(3, tree2);
|
||||
|
||||
std::cout << "Approximate nearest neighbor of point 3:" << std::endl;
|
||||
std::cout << " - Spill tree with overlap 0.5 found: point "
|
||||
<< r1.NearestNeighbor() << ", distance " << r1.NearestDistance()
|
||||
<< "." << std::endl;
|
||||
|
||||
std::cout << " - Spill tree with no overlap found: point "
|
||||
<< r2.NearestNeighbor() << ", distance " << r2.NearestDistance()
|
||||
<< "." << std::endl;
|
||||
|
||||
// Now search for point 6.
|
||||
r1.Reset();
|
||||
r2.Reset();
|
||||
|
||||
t1.Traverse(6, tree1);
|
||||
t2.Traverse(6, tree2);
|
||||
|
||||
std::cout << "Approximate nearest neighbor of point 6:" << std::endl;
|
||||
std::cout << " - Spill tree with overlap 0.5 found: point "
|
||||
<< r1.NearestNeighbor() << ", distance " << r1.NearestDistance()
|
||||
<< "." << std::endl;
|
||||
|
||||
std::cout << " - Spill tree with no overlap found: point "
|
||||
<< r2.NearestNeighbor() << ", distance " << r2.NearestDistance()
|
||||
<< "." << std::endl;
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
@@ -133,13 +133,11 @@ different.
|
||||
bounding boxes. It is better to simply build a new `UBTree` on the modified
|
||||
dataset. For trees that support individual insertion and deletions, see the
|
||||
[`RectangleTree`](rectangle_tree.md) class and all its variants (e.g.
|
||||
[`RTree`](r_tree.md), `RStarTree`, etc.).
|
||||
[`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
|
||||
|
||||
- See also the
|
||||
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
|
||||
|
||||
<!-- TODO: add links to RectangleTree above when it is documented -->
|
||||
|
||||
---
|
||||
|
||||
### Constructor parameters:
|
||||
@@ -427,7 +425,7 @@ mlpack::data::Load("cloud.csv", dataset, true);
|
||||
//
|
||||
// Note that the '<>' isn't necessary if C++20 is being used (e.g.
|
||||
// `mlpack::UBTree tree(...)` will work fine in C++20 or newer).
|
||||
mlpack::UBTree<> tree(std::move(dataset));
|
||||
mlpack::UBTree<> tree(std::move(dataset), 10);
|
||||
|
||||
// Print the bounding box of the root node.
|
||||
std::cout << "Outer bounding box of root node:" << std::endl;
|
||||
|
||||
@@ -128,13 +128,11 @@ different.
|
||||
very loose bounding balls. It is better to simply build a new `VPTree` on
|
||||
the modified dataset. For trees that support individual insertion and
|
||||
deletions, see the [`RectangleTree`](rectangle_tree.md) class and all its
|
||||
variants (e.g. [`RTree`](r_tree.md), `RStarTree`, etc.).
|
||||
variants (e.g. [`RTree`](r_tree.md), [`RStarTree`](r_star_tree.md), etc.).
|
||||
|
||||
- See also the
|
||||
[developer documentation on tree constructors](../../../developer/trees.md#constructors-and-destructors).
|
||||
|
||||
<!-- TODO: add links to RectangleTree above when it is documented -->
|
||||
|
||||
---
|
||||
|
||||
### Constructor parameters:
|
||||
@@ -417,7 +415,7 @@ mlpack::data::Load("cloud.csv", dataset, true);
|
||||
//
|
||||
// Note that the '<>' isn't necessary if C++20 is being used (e.g.
|
||||
// `mlpack::VPTree tree(...)` will work fine in C++20 or newer).
|
||||
mlpack::VPTree<> tree(std::move(dataset));
|
||||
mlpack::VPTree<> tree(std::move(dataset), 10);
|
||||
|
||||
// Print the bounding ball of the root node. (There will be no hollow ball.)
|
||||
std::cout << "Bounding ball of root node:" << std::endl;
|
||||
|
||||
@@ -148,7 +148,7 @@ The dataset is not permuted during the construction process.
|
||||
|
||||
| **name** | **type** | **description** | **default** |
|
||||
|----------|----------|-----------------|-------------|
|
||||
| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. Pass with `std::move(data)` to avoid copying the matrix. | _(N/A)_ |
|
||||
| `data` | [`MatType`](../../matrices.md) | [Column-major](../../matrices.md#representing-data-in-mlpack) matrix to build the tree on. | _(N/A)_ |
|
||||
| `maxLeafSize` | `size_t` | Maximum number of points to store in each leaf. | `20` |
|
||||
| `minLeafSize` | `size_t` | Minimum number of points to store in each leaf. | `8` |
|
||||
| `maxNumChildren` | `size_t` | Maximum number of children allowed in each non-leaf node. | `5` |
|
||||
|
||||
@@ -25,21 +25,23 @@ namespace mlpack {
|
||||
* @tparam ProjVectorT Type of projection vector (AxisParallelProjVector,
|
||||
* ProjVector).
|
||||
*/
|
||||
template<typename BoundT, typename ProjVectorT>
|
||||
template<typename BoundT, typename ProjVectorT, typename MatType>
|
||||
class HyperplaneBase
|
||||
{
|
||||
public:
|
||||
//! Useful typedef for the bound type.
|
||||
// Useful typedef for the bound type.
|
||||
using BoundType = BoundT;
|
||||
//! Useful typedef for the projection vector type.
|
||||
// Useful typedef for the projection vector type.
|
||||
using ProjVectorType = ProjVectorT;
|
||||
// Useful typedef for the element type held by data matrices.
|
||||
using ElemType = typename MatType::elem_type;
|
||||
|
||||
private:
|
||||
//! Projection vector.
|
||||
// Projection vector.
|
||||
ProjVectorType projVect;
|
||||
|
||||
//! Projection value that determines the decision boundary.
|
||||
double splitVal;
|
||||
// Projection value that determines the decision boundary.
|
||||
ElemType splitVal;
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -55,7 +57,7 @@ class HyperplaneBase
|
||||
* @param projVect Projection vector.
|
||||
* @param splitVal Split value.
|
||||
*/
|
||||
HyperplaneBase(const ProjVectorType& projVect, double splitVal) :
|
||||
HyperplaneBase(const ProjVectorType& projVect, ElemType splitVal) :
|
||||
projVect(projVect),
|
||||
splitVal(splitVal)
|
||||
{};
|
||||
@@ -67,8 +69,9 @@ class HyperplaneBase
|
||||
* @param point Point to be projected.
|
||||
*/
|
||||
template<typename VecType>
|
||||
double Project(const VecType& point,
|
||||
typename std::enable_if_t<IsVector<VecType>::value>* = 0) const
|
||||
ElemType Project(const VecType& point,
|
||||
typename std::enable_if_t<IsVector<VecType>::value>* = 0)
|
||||
const
|
||||
{
|
||||
if (splitVal == DBL_MAX)
|
||||
return 0;
|
||||
@@ -139,15 +142,18 @@ class HyperplaneBase
|
||||
/**
|
||||
* AxisOrthogonalHyperplane represents a hyperplane orthogonal to an axis.
|
||||
*/
|
||||
template<typename DistanceType>
|
||||
using AxisOrthogonalHyperplane = HyperplaneBase<HRectBound<DistanceType>,
|
||||
AxisParallelProjVector>;
|
||||
template<typename DistanceType, typename MatType>
|
||||
using AxisOrthogonalHyperplane = HyperplaneBase<
|
||||
HRectBound<DistanceType, typename MatType::elem_type>,
|
||||
AxisParallelProjVector, MatType>;
|
||||
|
||||
/**
|
||||
* Hyperplane represents a general hyperplane (not necessarily axis-orthogonal).
|
||||
*/
|
||||
template<typename DistanceType>
|
||||
using Hyperplane = HyperplaneBase<BallBound<DistanceType>, ProjVector>;
|
||||
template<typename DistanceType, typename MatType>
|
||||
using Hyperplane = HyperplaneBase<
|
||||
BallBound<DistanceType, typename MatType::elem_type>, ProjVector<MatType>,
|
||||
MatType>;
|
||||
|
||||
} // namespace mlpack
|
||||
|
||||
|
||||
@@ -27,13 +27,13 @@ bool MeanSpaceSplit<DistanceType, MatType>::SplitSpace(
|
||||
HyperplaneType& hyp)
|
||||
{
|
||||
typename HyperplaneType::ProjVectorType projVector;
|
||||
double midValue;
|
||||
typename MatType::elem_type midValue;
|
||||
|
||||
if (!SpaceSplit<DistanceType, MatType>::GetProjVector(bound, data, points,
|
||||
projVector, midValue))
|
||||
return false;
|
||||
|
||||
double splitVal = 0.0;
|
||||
typename MatType::elem_type splitVal = 0.0;
|
||||
for (size_t i = 0; i < points.n_elem; ++i)
|
||||
splitVal += projVector.Project(data.col(points[i]));
|
||||
splitVal /= points.n_elem;
|
||||
|
||||
@@ -27,7 +27,7 @@ bool MidpointSpaceSplit<DistanceType, MatType>::SplitSpace(
|
||||
HyperplaneType& hyp)
|
||||
{
|
||||
typename HyperplaneType::ProjVectorType projVector;
|
||||
double midValue;
|
||||
typename MatType::elem_type midValue;
|
||||
|
||||
if (!SpaceSplit<DistanceType, MatType>::GetProjVector(bound, data, points,
|
||||
projVector, midValue))
|
||||
|
||||
@@ -88,17 +88,18 @@ class AxisParallelProjVector
|
||||
* ProjVector defines a general projection vector (not necessarily
|
||||
* axis-parallel).
|
||||
*/
|
||||
template<typename MatType = arma::mat>
|
||||
class ProjVector
|
||||
{
|
||||
//! Projection vector.
|
||||
arma::vec projVect;
|
||||
using ProjVecType = typename GetColType<MatType>::type;
|
||||
|
||||
ProjVecType projVect;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Empty Constructor.
|
||||
*/
|
||||
ProjVector() :
|
||||
projVect()
|
||||
ProjVector() : projVect()
|
||||
{};
|
||||
|
||||
/**
|
||||
@@ -106,7 +107,7 @@ class ProjVector
|
||||
*
|
||||
* @param vect Vector to be considered.
|
||||
*/
|
||||
ProjVector(const arma::vec& vect) :
|
||||
ProjVector(const ProjVecType& vect) :
|
||||
projVect(normalise(vect))
|
||||
{};
|
||||
|
||||
|
||||
@@ -35,11 +35,11 @@ class SpaceSplit
|
||||
* @return Flag to determine if it is possible.
|
||||
*/
|
||||
static bool GetProjVector(
|
||||
const HRectBound<DistanceType>& bound,
|
||||
const HRectBound<DistanceType, typename MatType::elem_type>& bound,
|
||||
const MatType& data,
|
||||
const arma::Col<size_t>& points,
|
||||
AxisParallelProjVector& projVector,
|
||||
double& midValue);
|
||||
typename MatType::elem_type& midValue);
|
||||
|
||||
/**
|
||||
* Create a projection vector based on the given set of point. We efficiently
|
||||
@@ -58,8 +58,8 @@ class SpaceSplit
|
||||
const BoundType& bound,
|
||||
const MatType& data,
|
||||
const arma::Col<size_t>& points,
|
||||
ProjVector& projVector,
|
||||
double& midValue);
|
||||
ProjVector<MatType>& projVector,
|
||||
typename MatType::elem_type& midValue);
|
||||
};
|
||||
|
||||
} // namespace mlpack
|
||||
|
||||
@@ -14,24 +14,27 @@
|
||||
#define MLPACK_CORE_TREE_SPILL_TREE_SPACE_SPLIT_IMPL_HPP
|
||||
|
||||
#include "space_split.hpp"
|
||||
#include <mlpack/core/math/random.hpp>
|
||||
|
||||
namespace mlpack {
|
||||
|
||||
template<typename DistanceType, typename MatType>
|
||||
bool SpaceSplit<DistanceType, MatType>::GetProjVector(
|
||||
const HRectBound<DistanceType>& bound,
|
||||
const HRectBound<DistanceType, typename MatType::elem_type>& bound,
|
||||
const MatType& data,
|
||||
const arma::Col<size_t>& /* points */,
|
||||
AxisParallelProjVector& projVector,
|
||||
double& midValue)
|
||||
typename MatType::elem_type& midValue)
|
||||
{
|
||||
using ElemType = typename MatType::elem_type;
|
||||
|
||||
// Get the dimension that has the maximum width.
|
||||
size_t splitDim = data.n_rows; // Indicate invalid.
|
||||
double maxWidth = -1;
|
||||
ElemType maxWidth = -1;
|
||||
|
||||
for (size_t d = 0; d < data.n_rows; d++)
|
||||
{
|
||||
const double width = bound[d].Width();
|
||||
const ElemType width = bound[d].Width();
|
||||
|
||||
if (width > maxWidth)
|
||||
{
|
||||
@@ -56,19 +59,22 @@ bool SpaceSplit<DistanceType, MatType>::GetProjVector(
|
||||
const BoundType& /* bound */,
|
||||
const MatType& data,
|
||||
const arma::Col<size_t>& points,
|
||||
ProjVector& projVector,
|
||||
double& midValue)
|
||||
ProjVector<MatType>& projVector,
|
||||
typename MatType::elem_type& midValue)
|
||||
{
|
||||
using ElemType = typename MatType::elem_type;
|
||||
using VecType = typename GetColType<MatType>::type;
|
||||
|
||||
DistanceType distance;
|
||||
|
||||
// Efficiently estimate the farthest pair of points in the given set.
|
||||
size_t fst = points[rand() % points.n_elem];
|
||||
size_t fst = points[RandInt(points.n_elem)];
|
||||
size_t snd = points[0];
|
||||
double max = distance.Evaluate(data.col(fst), data.col(snd));
|
||||
ElemType max = distance.Evaluate(data.col(fst), data.col(snd));
|
||||
|
||||
for (size_t i = 1; i < points.n_elem; ++i)
|
||||
{
|
||||
double dist = distance.Evaluate(data.col(fst), data.col(points[i]));
|
||||
ElemType dist = distance.Evaluate(data.col(fst), data.col(points[i]));
|
||||
if (dist > max)
|
||||
{
|
||||
max = dist;
|
||||
@@ -80,7 +86,7 @@ bool SpaceSplit<DistanceType, MatType>::GetProjVector(
|
||||
|
||||
for (size_t i = 0; i < points.n_elem; ++i)
|
||||
{
|
||||
double dist = distance.Evaluate(data.col(fst), data.col(points[i]));
|
||||
ElemType dist = distance.Evaluate(data.col(fst), data.col(points[i]));
|
||||
if (dist > max)
|
||||
{
|
||||
max = dist;
|
||||
@@ -92,9 +98,9 @@ bool SpaceSplit<DistanceType, MatType>::GetProjVector(
|
||||
return false;
|
||||
|
||||
// Calculate the normalized projection vector.
|
||||
projVector = ProjVector(data.col(snd) - data.col(fst));
|
||||
projVector = ProjVector<MatType>(data.col(snd) - data.col(fst));
|
||||
|
||||
arma::vec midPoint = (data.col(snd) + data.col(fst)) / 2;
|
||||
VecType midPoint = (data.col(snd) + data.col(fst)) / 2;
|
||||
|
||||
midValue = projVector.Project(midPoint);
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ struct IsSpillTree
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType>
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
|
||||
@@ -27,7 +27,8 @@ namespace mlpack {
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
template<typename RuleType, bool Defeatist>
|
||||
|
||||
@@ -23,7 +23,8 @@ namespace mlpack {
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
template<typename RuleType, bool Defeatist>
|
||||
@@ -40,7 +41,8 @@ SpillDualTreeTraverser<RuleType, Defeatist>::SpillDualTreeTraverser(
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
template<typename RuleType, bool Defeatist>
|
||||
|
||||
@@ -26,7 +26,8 @@ namespace mlpack {
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
template<typename RuleType, bool Defeatist>
|
||||
|
||||
@@ -23,7 +23,8 @@ namespace mlpack {
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
template<typename RuleType, bool Defeatist>
|
||||
@@ -37,7 +38,8 @@ SpillSingleTreeTraverser<RuleType, Defeatist>::SpillSingleTreeTraverser(
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
template<typename RuleType, bool Defeatist>
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace mlpack {
|
||||
template<typename DistanceType,
|
||||
typename StatisticType = EmptyStatistic,
|
||||
typename MatType = arma::mat,
|
||||
template<typename HyperplaneDistanceType>
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType = AxisOrthogonalHyperplane,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType = MidpointSpaceSplit>
|
||||
@@ -77,7 +77,7 @@ class SpillTree
|
||||
//! The type of element held in MatType.
|
||||
using ElemType = typename MatType::elem_type;
|
||||
//! The bound type.
|
||||
using BoundType = typename HyperplaneType<DistanceType>::BoundType;
|
||||
using BoundType = typename HyperplaneType<DistanceType, MatType>::BoundType;
|
||||
|
||||
private:
|
||||
//! The left child node.
|
||||
@@ -95,7 +95,7 @@ class SpillTree
|
||||
//! Flag to distinguish overlapping nodes from non-overlapping nodes.
|
||||
bool overlappingNode;
|
||||
//! Splitting hyperplane represented by this node.
|
||||
HyperplaneType<DistanceType> hyperplane;
|
||||
HyperplaneType<DistanceType, MatType> hyperplane;
|
||||
//! The bound object for this node.
|
||||
BoundType bound;
|
||||
//! Any extra data contained in the node.
|
||||
@@ -144,6 +144,13 @@ class SpillTree
|
||||
template<typename RuleType>
|
||||
using DefeatistDualTreeTraverser = SpillDualTreeTraverser<RuleType, true>;
|
||||
|
||||
/**
|
||||
* A default constructor. This returns an empty tree, which is not useful.
|
||||
* In general this is only used for serialization or right before copying from
|
||||
* a different object.
|
||||
*/
|
||||
SpillTree();
|
||||
|
||||
/**
|
||||
* Construct this as the root node of a hybrid spill tree using the given
|
||||
* dataset. The dataset will not be modified during the building procedure
|
||||
@@ -274,7 +281,8 @@ class SpillTree
|
||||
bool Overlap() const { return overlappingNode; }
|
||||
|
||||
//! Get the Hyperplane instance.
|
||||
const HyperplaneType<DistanceType>& Hyperplane() const { return hyperplane; }
|
||||
const HyperplaneType<DistanceType, MatType>& Hyperplane() const
|
||||
{ return hyperplane; }
|
||||
|
||||
//! Get the distance metric that the tree uses.
|
||||
[[deprecated("Will be removed in mlpack 5.0.0; use Distance()")]]
|
||||
@@ -438,7 +446,7 @@ class SpillTree
|
||||
static bool HasSelfChildren() { return false; }
|
||||
|
||||
//! Store the center of the bounding region in the given vector.
|
||||
void Center(arma::vec& center) { bound.Center(center); }
|
||||
void Center(arma::Col<ElemType>& center) { bound.Center(center); }
|
||||
|
||||
private:
|
||||
/**
|
||||
@@ -469,17 +477,6 @@ class SpillTree
|
||||
const arma::Col<size_t>& points,
|
||||
arma::Col<size_t>& leftPoints,
|
||||
arma::Col<size_t>& rightPoints);
|
||||
protected:
|
||||
/**
|
||||
* A default constructor. This is meant to only be used with
|
||||
* cereal, which is allowed with the friend declaration below.
|
||||
* This does not return a valid tree! The method must be protected, so that
|
||||
* the serialization shim can work with the default constructor.
|
||||
*/
|
||||
SpillTree();
|
||||
|
||||
//! Friend access is given for the default constructor.
|
||||
friend class cereal::access;
|
||||
|
||||
public:
|
||||
/**
|
||||
|
||||
@@ -18,10 +18,36 @@
|
||||
|
||||
namespace mlpack {
|
||||
|
||||
// Default constructor (private), for cereal.
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
SpillTree<DistanceType, StatisticType, MatType, HyperplaneType, SplitType>::
|
||||
SpillTree() :
|
||||
left(NULL),
|
||||
right(NULL),
|
||||
parent(NULL),
|
||||
count(0),
|
||||
pointsIndex(NULL),
|
||||
overlappingNode(false),
|
||||
stat(*this),
|
||||
parentDistance(0),
|
||||
furthestDescendantDistance(0),
|
||||
dataset(NULL),
|
||||
localDataset(false)
|
||||
{
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
SpillTree<DistanceType, StatisticType, MatType, HyperplaneType, SplitType>::
|
||||
@@ -58,7 +84,8 @@ SpillTree(
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
SpillTree<DistanceType, StatisticType, MatType, HyperplaneType, SplitType>::
|
||||
@@ -95,7 +122,8 @@ SpillTree(
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
SpillTree<DistanceType, StatisticType, MatType, HyperplaneType, SplitType>::
|
||||
@@ -130,7 +158,8 @@ SpillTree(
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
SpillTree<DistanceType, StatisticType, MatType, HyperplaneType, SplitType>::
|
||||
@@ -197,7 +226,8 @@ SpillTree(const SpillTree& other) :
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
SpillTree<DistanceType, StatisticType, MatType, HyperplaneType, SplitType>&
|
||||
@@ -279,7 +309,8 @@ operator=(const SpillTree& other)
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
SpillTree<DistanceType, StatisticType, MatType, HyperplaneType, SplitType>::
|
||||
@@ -324,7 +355,8 @@ SpillTree(SpillTree&& other) :
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
SpillTree<DistanceType, StatisticType, MatType, HyperplaneType, SplitType>&
|
||||
@@ -384,7 +416,8 @@ operator=(SpillTree&& other)
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
template<typename Archive>
|
||||
@@ -407,7 +440,8 @@ SpillTree(
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
SpillTree<DistanceType, StatisticType, MatType, HyperplaneType, SplitType>::
|
||||
@@ -425,7 +459,8 @@ SpillTree<DistanceType, StatisticType, MatType, HyperplaneType, SplitType>::
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
inline bool SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
@@ -440,7 +475,8 @@ inline bool SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
inline size_t SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
@@ -463,7 +499,8 @@ inline size_t SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
template<typename VecType>
|
||||
@@ -489,7 +526,8 @@ size_t SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
template<typename VecType>
|
||||
@@ -515,7 +553,8 @@ size_t SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
size_t SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
@@ -541,7 +580,8 @@ size_t SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
size_t SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
@@ -565,7 +605,8 @@ size_t SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
inline typename SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
@@ -590,7 +631,8 @@ SpillTree<DistanceType, StatisticType, MatType, HyperplaneType, SplitType>::
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
inline typename SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
@@ -605,7 +647,8 @@ SpillTree<DistanceType, StatisticType, MatType, HyperplaneType, SplitType>::
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
inline typename SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
@@ -622,7 +665,8 @@ SpillTree<DistanceType, StatisticType, MatType, HyperplaneType, SplitType>::
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
inline
|
||||
@@ -642,7 +686,8 @@ SpillTree<DistanceType, StatisticType, MatType, HyperplaneType, SplitType>::
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
inline size_t SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
@@ -659,7 +704,8 @@ inline size_t SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
inline size_t SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
@@ -674,7 +720,8 @@ inline size_t SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
inline size_t SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
@@ -698,7 +745,8 @@ inline size_t SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
inline size_t SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
@@ -713,7 +761,8 @@ inline size_t SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
void
|
||||
@@ -772,7 +821,7 @@ SpillTree<DistanceType, StatisticType, MatType, HyperplaneType, SplitType>::
|
||||
right = new SpillTree(this, rightPoints, tau, maxLeafSize, rho);
|
||||
|
||||
// Calculate parent distances for those two nodes.
|
||||
arma::vec center, leftCenter, rightCenter;
|
||||
arma::Col<ElemType> center, leftCenter, rightCenter;
|
||||
Center(center);
|
||||
left->Center(leftCenter);
|
||||
right->Center(rightCenter);
|
||||
@@ -789,7 +838,8 @@ SpillTree<DistanceType, StatisticType, MatType, HyperplaneType, SplitType>::
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
bool
|
||||
@@ -872,37 +922,14 @@ SpillTree<DistanceType, StatisticType, MatType, HyperplaneType, SplitType>::
|
||||
return false;
|
||||
}
|
||||
|
||||
// Default constructor (private), for cereal.
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
SpillTree<DistanceType, StatisticType, MatType, HyperplaneType, SplitType>::
|
||||
SpillTree() :
|
||||
left(NULL),
|
||||
right(NULL),
|
||||
parent(NULL),
|
||||
count(0),
|
||||
pointsIndex(NULL),
|
||||
overlappingNode(false),
|
||||
stat(*this),
|
||||
parentDistance(0),
|
||||
furthestDescendantDistance(0),
|
||||
dataset(NULL),
|
||||
localDataset(false)
|
||||
{
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the tree.
|
||||
*/
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
template<typename Archive>
|
||||
|
||||
@@ -26,7 +26,8 @@ namespace mlpack {
|
||||
template<typename DistanceType,
|
||||
typename StatisticType,
|
||||
typename MatType,
|
||||
template<typename HyperplaneDistanceType> class HyperplaneType,
|
||||
template<typename HyperplaneDistanceType, typename HyperplaneMatType>
|
||||
class HyperplaneType,
|
||||
template<typename SplitDistanceType, typename SplitMatType>
|
||||
class SplitType>
|
||||
class TreeTraits<SpillTree<DistanceType, StatisticType, MatType, HyperplaneType,
|
||||
|
||||
@@ -53,7 +53,9 @@ namespace mlpack {
|
||||
*
|
||||
* @see @ref trees, SpillTree, MeanSPTree
|
||||
*/
|
||||
template<typename DistanceType, typename StatisticType, typename MatType>
|
||||
template<typename DistanceType = EuclideanDistance,
|
||||
typename StatisticType = EmptyStatistic,
|
||||
typename MatType = arma::mat>
|
||||
using SPTree = SpillTree<DistanceType,
|
||||
StatisticType,
|
||||
MatType,
|
||||
@@ -71,7 +73,9 @@ using SPTree = SpillTree<DistanceType,
|
||||
*
|
||||
* @see @ref trees, SpillTree, SPTree
|
||||
*/
|
||||
template<typename DistanceType, typename StatisticType, typename MatType>
|
||||
template<typename DistanceType = EuclideanDistance,
|
||||
typename StatisticType = EmptyStatistic,
|
||||
typename MatType = arma::mat>
|
||||
using MeanSPTree = SpillTree<DistanceType,
|
||||
StatisticType,
|
||||
MatType,
|
||||
@@ -91,7 +95,9 @@ using MeanSPTree = SpillTree<DistanceType,
|
||||
*
|
||||
* @see @ref trees, SpillTree, SPTree
|
||||
*/
|
||||
template<typename DistanceType, typename StatisticType, typename MatType>
|
||||
template<typename DistanceType = EuclideanDistance,
|
||||
typename StatisticType = EmptyStatistic,
|
||||
typename MatType = arma::mat>
|
||||
using NonOrtSPTree = SpillTree<DistanceType,
|
||||
StatisticType,
|
||||
MatType,
|
||||
@@ -110,7 +116,9 @@ using NonOrtSPTree = SpillTree<DistanceType,
|
||||
*
|
||||
* @see @ref trees, SpillTree, MeanSPTree, NonOrtSPTree
|
||||
*/
|
||||
template<typename DistanceType, typename StatisticType, typename MatType>
|
||||
template<typename DistanceType = EuclideanDistance,
|
||||
typename StatisticType = EmptyStatistic,
|
||||
typename MatType = arma::mat>
|
||||
using NonOrtMeanSPTree = SpillTree<DistanceType,
|
||||
StatisticType,
|
||||
MatType,
|
||||
|
||||
@@ -19,8 +19,8 @@ using namespace mlpack;
|
||||
*/
|
||||
TEST_CASE("HyperplaneEmptyConstructor", "[HyperplaneTest]")
|
||||
{
|
||||
Hyperplane<EuclideanDistance> h1;
|
||||
AxisOrthogonalHyperplane<EuclideanDistance> h2;
|
||||
Hyperplane<EuclideanDistance, arma::mat> h1;
|
||||
AxisOrthogonalHyperplane<EuclideanDistance, arma::mat> h2;
|
||||
|
||||
arma::mat dataset;
|
||||
dataset.randu(3, 20); // 20 points in 3 dimensions.
|
||||
@@ -40,8 +40,8 @@ TEST_CASE("HyperplaneEmptyConstructor", "[HyperplaneTest]")
|
||||
TEST_CASE("ProjectionTest", "[HyperplaneTest]")
|
||||
{
|
||||
// General hyperplane.
|
||||
ProjVector projVect1(arma::vec("1 1"));
|
||||
Hyperplane<EuclideanDistance> h1(projVect1, 0);
|
||||
ProjVector<arma::mat> projVect1(arma::vec("1 1"));
|
||||
Hyperplane<EuclideanDistance, arma::mat> h1(projVect1, 0);
|
||||
|
||||
REQUIRE(h1.Project(arma::vec("1 -1")) == 0);
|
||||
REQUIRE(h1.Left(arma::vec("1 -1")));
|
||||
@@ -86,7 +86,7 @@ TEST_CASE("AxisOrthogonalProjectionTest", "[HyperplaneTest]")
|
||||
{
|
||||
// AxisParallel hyperplane.
|
||||
AxisParallelProjVector projVect2(1);
|
||||
AxisOrthogonalHyperplane<EuclideanDistance> h2(projVect2, 1);
|
||||
AxisOrthogonalHyperplane<EuclideanDistance, arma::mat> h2(projVect2, 1);
|
||||
|
||||
REQUIRE(h2.Project(arma::vec("0 0")) == -1);
|
||||
REQUIRE(h2.Left(arma::vec("0 0")));
|
||||
|
||||
Reference in New Issue
Block a user