Refactor RASearch to take queries in Search().
This commit is contained in:
@@ -8,13 +8,13 @@
|
||||
#include <time.h>
|
||||
|
||||
#include <mlpack/core.hpp>
|
||||
#include <mlpack/core/tree/cover_tree.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "ra_search.hpp"
|
||||
#include <mlpack/methods/neighbor_search/unmap.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace mlpack;
|
||||
@@ -68,8 +68,6 @@ PARAM_FLAG("naive", "If true, sampling will be done without using a tree.",
|
||||
"N");
|
||||
PARAM_FLAG("single_mode", "If true, single-tree search is used (as opposed to "
|
||||
"dual-tree search.", "s");
|
||||
PARAM_FLAG("cover_tree", "If true, use cover trees to perform the search.",
|
||||
"c");
|
||||
|
||||
PARAM_FLAG("sample_at_leaves", "The flag to trigger sampling at leaves.", "L");
|
||||
PARAM_FLAG("first_leaf_exact", "The flag to trigger sampling only after "
|
||||
@@ -117,6 +115,15 @@ int main(int argc, char *argv[])
|
||||
Log::Fatal << referenceData.n_cols << ")." << endl;
|
||||
}
|
||||
|
||||
// Load query data, if necessary.
|
||||
if (CLI::HasParam("query_file"))
|
||||
{
|
||||
const string queryFile = CLI::GetParam<string>("query_file");
|
||||
data::Load(queryFile, queryData, true);
|
||||
Log::Info << "Loaded query data from '" << queryFile << "' ("
|
||||
<< queryData.n_rows << " x " << queryData.n_cols << ")." << endl;
|
||||
}
|
||||
|
||||
// Sanity check on the value of 'tau' with respect to 'k' so that
|
||||
// 'k' neighbors are not requested from the top-'rank_error' neighbors
|
||||
// where 'rank_error' <= 'k'.
|
||||
@@ -142,152 +149,82 @@ int main(int argc, char *argv[])
|
||||
|
||||
if (naive)
|
||||
{
|
||||
AllkRANN* allkrann;
|
||||
AllkRANN allkrann(referenceData, naive, false, tau, alpha);
|
||||
|
||||
Log::Info << "Computing " << k << " nearest neighbors " << "with "
|
||||
<< tau << "% rank approximation..." << endl;
|
||||
|
||||
if (CLI::GetParam<string>("query_file") != "")
|
||||
{
|
||||
string queryFile = CLI::GetParam<string>("query_file");
|
||||
|
||||
data::Load(queryFile, queryData, true);
|
||||
|
||||
Log::Info << "Loaded query data from '" << queryFile << "' (" <<
|
||||
queryData.n_rows << " x " << queryData.n_cols << ")." << endl;
|
||||
|
||||
allkrann = new AllkRANN(referenceData, queryData, naive);
|
||||
}
|
||||
allkrann.Search(queryData, k, neighbors, distances);
|
||||
else
|
||||
allkrann = new AllkRANN(referenceData, naive);
|
||||
|
||||
Log::Info << "Computing " << k << " nearest neighbors " << "with " <<
|
||||
tau << "% rank approximation..." << endl;
|
||||
|
||||
allkrann->Search(k, neighbors, distances, tau, alpha);
|
||||
allkrann.Search(k, neighbors, distances);
|
||||
|
||||
Log::Info << "Neighbors computed." << endl;
|
||||
|
||||
delete allkrann;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The results output by the AllkRANN class
|
||||
// shuffled because the tree construction shuffles the point sets.
|
||||
// The results output by the AllkRANN class are
|
||||
// shuffled if the tree construction shuffles the point sets.
|
||||
arma::Mat<size_t> neighborsOut;
|
||||
arma::mat distancesOut;
|
||||
|
||||
if (!CLI::HasParam("cover_tree"))
|
||||
// Mappings for when we build the tree.
|
||||
std::vector<size_t> oldFromNewRefs;
|
||||
std::vector<size_t> oldFromNewQueries;
|
||||
|
||||
// Build trees by hand, so we can save memory: if we pass a tree to
|
||||
// NeighborSearch, it does not copy the matrix.
|
||||
Log::Info << "Building reference tree..." << endl;
|
||||
Timer::Start("tree_building");
|
||||
typedef BinarySpaceTree<bound::HRectBound<2, false>,
|
||||
RAQueryStat<NearestNeighborSort> > TreeType;
|
||||
TreeType refTree(referenceData, oldFromNewRefs, leafSize);
|
||||
Timer::Stop("tree_building");
|
||||
|
||||
// Because we may construct it differently, we need a pointer.
|
||||
AllkRANN allkrann(&refTree, singleMode, tau, alpha, sampleAtLeaves,
|
||||
firstLeafExact, singleSampleLimit);
|
||||
|
||||
if (CLI::HasParam("query_file") && !singleMode)
|
||||
{
|
||||
// Because we may construct it differently, we need a pointer.
|
||||
AllkRANN* allkrann = NULL;
|
||||
|
||||
// Mappings for when we build the tree.
|
||||
std::vector<size_t> oldFromNewRefs;
|
||||
|
||||
// Build trees by hand, so we can save memory: if we pass a tree to
|
||||
// NeighborSearch, it does not copy the matrix.
|
||||
Log::Info << "Building reference tree..." << endl;
|
||||
Log::Info << "Building query tree..." << endl;
|
||||
Timer::Start("tree_building");
|
||||
|
||||
BinarySpaceTree<bound::HRectBound<2, false>,
|
||||
RAQueryStat<NearestNeighborSort> >
|
||||
refTree(referenceData, oldFromNewRefs, leafSize);
|
||||
BinarySpaceTree<bound::HRectBound<2, false>,
|
||||
RAQueryStat<NearestNeighborSort> >*
|
||||
queryTree = NULL; // Empty for now.
|
||||
|
||||
TreeType queryTree(queryData, oldFromNewQueries, leafSize);
|
||||
Timer::Stop("tree_building");
|
||||
|
||||
std::vector<size_t> oldFromNewQueries;
|
||||
|
||||
if (CLI::GetParam<string>("query_file") != "")
|
||||
{
|
||||
string queryFile = CLI::GetParam<string>("query_file");
|
||||
|
||||
data::Load(queryFile, queryData, true);
|
||||
|
||||
if (naive && leafSize < queryData.n_cols)
|
||||
leafSize = queryData.n_cols;
|
||||
|
||||
Log::Info << "Loaded query data from '" << queryFile << "' (" <<
|
||||
queryData.n_rows << " x " << queryData.n_cols << ")." << endl;
|
||||
|
||||
Log::Info << "Building query tree..." << endl;
|
||||
|
||||
// Build trees by hand, so we can save memory: if we pass a tree to
|
||||
// NeighborSearch, it does not copy the matrix.
|
||||
Timer::Start("tree_building");
|
||||
|
||||
queryTree = new BinarySpaceTree<bound::HRectBound<2, false>,
|
||||
RAQueryStat<NearestNeighborSort> >
|
||||
(queryData, oldFromNewQueries, leafSize);
|
||||
Timer::Stop("tree_building");
|
||||
|
||||
allkrann = new AllkRANN(&refTree, queryTree, referenceData, queryData,
|
||||
singleMode);
|
||||
|
||||
Log::Info << "Tree built." << endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
allkrann = new AllkRANN(&refTree, referenceData, singleMode);
|
||||
Log::Info << "Trees built." << endl;
|
||||
}
|
||||
Log::Info << "Tree built." << endl;
|
||||
|
||||
Log::Info << "Computing " << k << " nearest neighbors " << "with " <<
|
||||
tau << "% rank approximation..." << endl;
|
||||
allkrann->Search(k, neighborsOut, distancesOut,
|
||||
tau, alpha, sampleAtLeaves,
|
||||
firstLeafExact, singleSampleLimit);
|
||||
|
||||
Log::Info << "Neighbors computed." << endl;
|
||||
|
||||
// We have to map back to the original indices from before the tree
|
||||
// construction.
|
||||
Log::Info << "Re-mapping indices..." << endl;
|
||||
|
||||
neighbors.set_size(neighborsOut.n_rows, neighborsOut.n_cols);
|
||||
distances.set_size(distancesOut.n_rows, distancesOut.n_cols);
|
||||
|
||||
// Do the actual remapping.
|
||||
if (CLI::GetParam<string>("query_file") != "")
|
||||
{
|
||||
for (size_t i = 0; i < distancesOut.n_cols; ++i)
|
||||
{
|
||||
// Map distances (copy a column).
|
||||
distances.col(oldFromNewQueries[i]) = distancesOut.col(i);
|
||||
|
||||
// Map indices of neighbors.
|
||||
for (size_t j = 0; j < distancesOut.n_rows; ++j)
|
||||
{
|
||||
neighbors(j, oldFromNewQueries[i])
|
||||
= oldFromNewRefs[neighborsOut(j, i)];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < distancesOut.n_cols; ++i)
|
||||
{
|
||||
// Map distances (copy a column).
|
||||
distances.col(oldFromNewRefs[i]) = distancesOut.col(i);
|
||||
|
||||
// Map indices of neighbors.
|
||||
for (size_t j = 0; j < distancesOut.n_rows; ++j)
|
||||
{
|
||||
neighbors(j, oldFromNewRefs[i])
|
||||
= oldFromNewRefs[neighborsOut(j, i)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up.
|
||||
if (queryTree)
|
||||
delete queryTree;
|
||||
|
||||
delete allkrann;
|
||||
tau << "% rank approximation..." << endl;
|
||||
allkrann.Search(&queryTree, k, neighborsOut, distancesOut);
|
||||
}
|
||||
else // Cover trees.
|
||||
else if (CLI::HasParam("query_file") && singleMode)
|
||||
{
|
||||
Log::Fatal << "Cover tree case not implemented yet..." << endl;
|
||||
Log::Info << "Computing " << k << " nearest neighbors " << "with " <<
|
||||
tau << "% rank approximation..." << endl;
|
||||
allkrann.Search(queryData, k, neighborsOut, distancesOut);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::Info << "Computing " << k << " nearest neighbors " << "with " <<
|
||||
tau << "% rank approximation..." << endl;
|
||||
allkrann.Search(k, neighborsOut, distancesOut);
|
||||
}
|
||||
|
||||
Log::Info << "Neighbors computed." << endl;
|
||||
|
||||
// We have to map back to the original indices from before the tree
|
||||
// construction.
|
||||
Log::Info << "Re-mapping indices..." << endl;
|
||||
|
||||
// Map the results back to the correct places.
|
||||
if ((CLI::GetParam<string>("query_file") != "") && !singleMode)
|
||||
Unmap(neighborsOut, distancesOut, oldFromNewRefs, oldFromNewQueries,
|
||||
neighbors, distances);
|
||||
else if ((CLI::GetParam<string>("query_file") != "") && singleMode)
|
||||
Unmap(neighborsOut, distancesOut, oldFromNewRefs, neighbors, distances);
|
||||
else
|
||||
Unmap(neighborsOut, distancesOut, oldFromNewRefs, oldFromNewRefs,
|
||||
neighbors, distances);
|
||||
}
|
||||
|
||||
// Save output.
|
||||
|
||||
@@ -57,158 +57,38 @@ class RASearch
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Initialize the RASearch object, passing both a query and reference dataset.
|
||||
* Optionally, perform the computation in naive mode or single-tree mode, and
|
||||
* set the leaf size used for tree-building. An initialized distance metric
|
||||
* can be given, for cases where the metric has internal data (i.e. the
|
||||
* Initialize the RASearch object, passing both a reference dataset (this is
|
||||
* the dataset that will be searched). Optionally, perform the computation in
|
||||
* naive mode or single-tree mode. An initialized distance metric can be
|
||||
* given, for cases where the metric has internal data (i.e. the
|
||||
* distance::MahalanobisDistance class).
|
||||
*
|
||||
* This method will copy the matrices to internal copies, which are rearranged
|
||||
* during tree-building. You can avoid this extra copy by pre-constructing
|
||||
* the trees and passing them using a diferent constructor.
|
||||
* the trees and passing them using a different constructor.
|
||||
*
|
||||
* tau, the rank-approximation parameter, specifies that we are looking for k
|
||||
* neighbors with probability alpha of being in the top tau percent of nearest
|
||||
* neighbors. So, as an example, if our dataset has 1000 points, and we want
|
||||
* 5 nearest neighbors with 95% probability of being in the top 5% of nearest
|
||||
* neighbors (or, the top 50 nearest neighbors), we set k = 5, tau = 5, and
|
||||
* alpha = 0.95.
|
||||
*
|
||||
* The method will fail (and throw a std::invalid_argument exception) if the
|
||||
* value of tau is too low: tau must be set such that the number of points in
|
||||
* the corresponding percentile of the data is greater than k. Thus, if we
|
||||
* choose tau = 0.1 with a dataset of 1000 points and k = 5, then we are
|
||||
* attempting to choose 5 nearest neighbors out of the closest 1 point -- this
|
||||
* is invalid.
|
||||
*
|
||||
* @param referenceSet Set of reference points.
|
||||
* @param querySet Set of query points.
|
||||
* @param naive If true, the rank-approximate search will be performed by
|
||||
* directly sampling the whole set instead of using the stratified
|
||||
* sampling on the tree.
|
||||
* @param singleMode If true, single-tree search will be used (as opposed to
|
||||
* dual-tree search).
|
||||
* @param leafSize Leaf size for tree construction (ignored if tree is given).
|
||||
* dual-tree search). This is useful when Search() will be called with
|
||||
* few query points.
|
||||
* @param metric An optional instance of the MetricType class.
|
||||
*/
|
||||
RASearch(const typename TreeType::Mat& referenceSet,
|
||||
const typename TreeType::Mat& querySet,
|
||||
const bool naive = false,
|
||||
const bool singleMode = false,
|
||||
const MetricType metric = MetricType());
|
||||
|
||||
/**
|
||||
* Initialize the RASearch object, passing only one dataset, which is
|
||||
* used as both the query and the reference dataset. Optionally, perform the
|
||||
* computation in naive mode or single-tree mode, and set the leaf size used
|
||||
* for tree-building. An initialized distance metric can be given, for cases
|
||||
* where the metric has internal data (i.e. the distance::MahalanobisDistance
|
||||
* class).
|
||||
*
|
||||
* If naive mode is being used and a pre-built tree is given, it may not work:
|
||||
* naive mode operates by building a one-node tree (the root node holds all
|
||||
* the points). If that condition is not satisfied with the pre-built tree,
|
||||
* then naive mode will not work.
|
||||
*
|
||||
* @param referenceSet Set of reference points.
|
||||
* @param naive If true, the rank-approximate search will be performed
|
||||
* by directly sampling the whole set instead of using the stratified
|
||||
* sampling on the tree.
|
||||
* @param singleMode If true, single-tree search will be used (as opposed to
|
||||
* dual-tree search).
|
||||
* @param leafSize Leaf size for tree construction (ignored if tree is given).
|
||||
* @param metric An optional instance of the MetricType class.
|
||||
*/
|
||||
RASearch(const typename TreeType::Mat& referenceSet,
|
||||
const bool naive = false,
|
||||
const bool singleMode = false,
|
||||
const MetricType metric = MetricType());
|
||||
|
||||
/**
|
||||
* Initialize the RASearch object with the given datasets and
|
||||
* pre-constructed trees. It is assumed that the points in referenceSet and
|
||||
* querySet correspond to the points in referenceTree and queryTree,
|
||||
* respectively. Optionally, choose to use single-tree mode. Naive mode is
|
||||
* not available as an option for this constructor; instead, to run naive
|
||||
* computation, construct a tree with all of the points in one leaf (i.e.
|
||||
* leafSize = number of points). Additionally, an instantiated distance
|
||||
* metric can be given, for cases where the distance metric holds data.
|
||||
*
|
||||
* There is no copying of the data matrices in this constructor (because
|
||||
* tree-building is not necessary), so this is the constructor to use when
|
||||
* copies absolutely must be avoided.
|
||||
*
|
||||
* @note
|
||||
* Because tree-building (at least with BinarySpaceTree) modifies the ordering
|
||||
* of a matrix, be sure you pass the modified matrix to this object! In
|
||||
* addition, mapping the points of the matrix back to their original indices
|
||||
* is not done when this constructor is used.
|
||||
* @endnote
|
||||
*
|
||||
* @param referenceTree Pre-built tree for reference points.
|
||||
* @param queryTree Pre-built tree for query points.
|
||||
* @param referenceSet Set of reference points corresponding to referenceTree.
|
||||
* @param querySet Set of query points corresponding to queryTree.
|
||||
* @param singleMode Whether single-tree computation should be used (as
|
||||
* opposed to dual-tree computation).
|
||||
* @param metric Instantiated distance metric.
|
||||
*/
|
||||
RASearch(TreeType* referenceTree,
|
||||
TreeType* queryTree,
|
||||
const typename TreeType::Mat& referenceSet,
|
||||
const typename TreeType::Mat& querySet,
|
||||
const bool singleMode = false,
|
||||
const MetricType metric = MetricType());
|
||||
|
||||
/**
|
||||
* Initialize the RASearch object with the given reference dataset and
|
||||
* pre-constructed tree. It is assumed that the points in referenceSet
|
||||
* correspond to the points in referenceTree. Optionally, choose to use
|
||||
* single-tree mode. Naive mode is not available as an option for this
|
||||
* constructor; instead, to run naive computation, construct a tree with all
|
||||
* the points in one leaf (i.e. leafSize = number of points). Additionally,
|
||||
* an instantiated distance metric can be given, for the case where the
|
||||
* distance metric holds data.
|
||||
*
|
||||
* There is no copying of the data matrices in this constructor (because
|
||||
* tree-building is not necessary), so this is the constructor to use when
|
||||
* copies absolutely must be avoided.
|
||||
*
|
||||
* @note
|
||||
* Because tree-building (at least with BinarySpaceTree) modifies the ordering
|
||||
* of a matrix, be sure you pass the modified matrix to this object! In
|
||||
* addition, mapping the points of the matrix back to their original indices
|
||||
* is not done when this constructor is used.
|
||||
* @endnote
|
||||
*
|
||||
* @param referenceTree Pre-built tree for reference points.
|
||||
* @param referenceSet Set of reference points corresponding to referenceTree.
|
||||
* @param singleMode Whether single-tree computation should be used (as
|
||||
* opposed to dual-tree computation).
|
||||
* @param metric Instantiated distance metric.
|
||||
*/
|
||||
RASearch(TreeType* referenceTree,
|
||||
const typename TreeType::Mat& referenceSet,
|
||||
const bool singleMode = false,
|
||||
const MetricType metric = MetricType());
|
||||
|
||||
/**
|
||||
* Delete the RASearch object. The tree is the only member we are
|
||||
* responsible for deleting. The others will take care of themselves.
|
||||
*/
|
||||
~RASearch();
|
||||
|
||||
/**
|
||||
* Compute the rank approximate nearest neighbors and store the output in the
|
||||
* given matrices. The matrices will be set to the size of n columns by k
|
||||
* rows, where n is the number of points in the query dataset and k is the
|
||||
* number of neighbors being searched for.
|
||||
*
|
||||
* Note that tau, the rank-approximation parameter, specifies that we are
|
||||
* looking for k neighbors with probability alpha of being in the top tau
|
||||
* percent of nearest neighbors. So, as an example, if our dataset has 1000
|
||||
* points, and we want 5 nearest neighbors with 95% probability of being in
|
||||
* the top 5% of nearest neighbors (or, the top 50 nearest neighbors), we set
|
||||
* k = 5, tau = 5, and alpha = 0.95.
|
||||
*
|
||||
* The method will fail (and issue a failure message) if the value of tau is
|
||||
* too low: tau must be set such that the number of points in the
|
||||
* corresponding percentile of the data is greater than k. Thus, if we choose
|
||||
* tau = 0.1 with a dataset of 1000 points and k = 5, then we are attempting
|
||||
* to choose 5 nearest neighbors out of the closest 1 point -- this is
|
||||
* invalid.
|
||||
*
|
||||
* @param k Number of neighbors to search for.
|
||||
* @param resultingNeighbors Matrix storing lists of neighbors for each query
|
||||
* point.
|
||||
* @param distances Matrix storing distances of neighbors for each query
|
||||
* point.
|
||||
* @param tau The rank-approximation in percentile of the data. The default
|
||||
* value is 5%.
|
||||
* @param alpha The desired success probability. The default value is 0.95.
|
||||
@@ -220,70 +100,222 @@ class RASearch
|
||||
* @param singleSampleLimit The limit on the largest node that can be
|
||||
* approximated by sampling. This defaults to 20.
|
||||
*/
|
||||
void Search(const size_t k,
|
||||
arma::Mat<size_t>& resultingNeighbors,
|
||||
arma::mat& distances,
|
||||
const double tau = 5,
|
||||
const double alpha = 0.95,
|
||||
const bool sampleAtLeaves = false,
|
||||
const bool firstLeafExact = false,
|
||||
const size_t singleSampleLimit = 20);
|
||||
RASearch(const typename TreeType::Mat& referenceSet,
|
||||
const bool naive = false,
|
||||
const bool singleMode = false,
|
||||
const double tau = 5,
|
||||
const double alpha = 0.95,
|
||||
const bool sampleAtLeaves = false,
|
||||
const bool firstLeafExact = false,
|
||||
const size_t singleSampleLimit = 20,
|
||||
const MetricType metric = MetricType());
|
||||
|
||||
/**
|
||||
* This function recursively resets the RAQueryStat of the queryTree to set
|
||||
* 'bound' to WorstDistance and the 'numSamplesMade' to 0. This allows a user
|
||||
* to perform multiple searches on the same pair of trees, possibly with
|
||||
* different levels of approximation without requiring to build a new pair of
|
||||
* trees for every new (approximate) search.
|
||||
* Initialize the RASearch object with the given pre-constructed reference
|
||||
* tree. It is assumed that the points in the tree's dataset correspond to
|
||||
* the reference set. Optionally, choose to use single-tree mode. Naive mode
|
||||
* is not available as an option for this constructor; instead, to run naive
|
||||
* computation, use a different constructor. Additionally, an instantiated
|
||||
* distance metric can be given, for cases where the distance metric holds
|
||||
* data.
|
||||
*
|
||||
* There is no copying of the data matrices in this constructor (because
|
||||
* tree-building is not necessary), so this is the constructor to use when
|
||||
* copies absolutely must be avoided.
|
||||
*
|
||||
* tau, the rank-approximation parameter, specifies that we are looking for k
|
||||
* neighbors with probability alpha of being in the top tau percent of nearest
|
||||
* neighbors. So, as an example, if our dataset has 1000 points, and we want
|
||||
* 5 nearest neighbors with 95% probability of being in the top 5% of nearest
|
||||
* neighbors (or, the top 50 nearest neighbors), we set k = 5, tau = 5, and
|
||||
* alpha = 0.95.
|
||||
*
|
||||
* The method will fail (and throw a std::invalid_argument exception) if the
|
||||
* value of tau is too low: tau must be set such that the number of points in
|
||||
* the corresponding percentile of the data is greater than k. Thus, if we
|
||||
* choose tau = 0.1 with a dataset of 1000 points and k = 5, then we are
|
||||
* attempting to choose 5 nearest neighbors out of the closest 1 point -- this
|
||||
* is invalid.
|
||||
*
|
||||
* @note
|
||||
* Tree-building may (at least with BinarySpaceTree) modify the ordering
|
||||
* of a matrix, so be aware that the results you get from Search() will
|
||||
* correspond to the modified matrix.
|
||||
* @endnote
|
||||
*
|
||||
* @param referenceTree Pre-built tree for reference points.
|
||||
* @param singleMode Whether single-tree computation should be used (as
|
||||
* opposed to dual-tree computation).
|
||||
* @param metric Instantiated distance metric.
|
||||
* @param tau The rank-approximation in percentile of the data. The default
|
||||
* value is 5%.
|
||||
* @param alpha The desired success probability. The default value is 0.95.
|
||||
* @param sampleAtLeaves Sample at leaves for faster but less accurate
|
||||
* computation. This defaults to 'false'.
|
||||
* @param firstLeafExact Traverse to the first leaf without approximation.
|
||||
* This can ensure that the query definitely finds its (near) duplicate
|
||||
* if there exists one. This defaults to 'false' for now.
|
||||
* @param singleSampleLimit The limit on the largest node that can be
|
||||
* approximated by sampling. This defaults to 20.
|
||||
*/
|
||||
void ResetQueryTree();
|
||||
RASearch(TreeType* referenceTree,
|
||||
const bool singleMode = false,
|
||||
const double tau = 5,
|
||||
const double alpha = 0.95,
|
||||
const bool sampleAtLeaves = false,
|
||||
const bool firstLeafExact = false,
|
||||
const size_t singleSampleLimit = 20,
|
||||
const MetricType metric = MetricType());
|
||||
|
||||
// Returns a string representation of this object.
|
||||
/**
|
||||
* Delete the RASearch object. The tree is the only member we are
|
||||
* responsible for deleting. The others will take care of themselves.
|
||||
*/
|
||||
~RASearch();
|
||||
|
||||
/**
|
||||
* Compute the rank approximate nearest neighbors of each query point in the
|
||||
* query set and store the output in the given matrices. The matrices will be
|
||||
* set to the size of n columns by k rows, where n is the number of points in
|
||||
* the query dataset and k is the number of neighbors being searched for.
|
||||
*
|
||||
* If querySet is small or only contains one point, it can be faster to do
|
||||
* single-tree search; single-tree search can be set with the SingleMode()
|
||||
* function or in the constructor.
|
||||
*
|
||||
* @param querySet Set of query points (can be a single point).
|
||||
* @param k Number of neighbors to search for.
|
||||
* @param neighbors Matrix storing lists of neighbors for each query point.
|
||||
* @param distances Matrix storing distances of neighbors for each query
|
||||
* point.
|
||||
*/
|
||||
void Search(const typename TreeType::Mat& querySet,
|
||||
const size_t k,
|
||||
arma::Mat<size_t>& neighbors,
|
||||
arma::mat& distances);
|
||||
|
||||
/**
|
||||
* Compute the rank approximate nearest neighbors of each point in the
|
||||
* pre-built query tree and store the output in the given matrices. The
|
||||
* matrices will be set to the size of n columns by k rows, where n is the
|
||||
* number of points in the query dataset and k is the number of neighbors
|
||||
* being searched for.
|
||||
*
|
||||
* If singleMode or naive is enabled, then this method will throw a
|
||||
* std::invalid_argument exception; calling this function implies a dual-tree
|
||||
* algorithm.
|
||||
*
|
||||
* @note
|
||||
* If the tree type you are using modifies the data matrix, be aware that the
|
||||
* results returned from this function will be with respect to the modified
|
||||
* data matrix.
|
||||
* @endnote
|
||||
*
|
||||
* @param queryTree Tree built on query points.
|
||||
* @param k Number of neighbors to search for.
|
||||
* @param neighbors Matrix storing lists of neighbors for each query point.
|
||||
* @param distances Matrix storing distances of neighbors for each query
|
||||
* point.
|
||||
*/
|
||||
void Search(TreeType* queryTree,
|
||||
const size_t k,
|
||||
arma::Mat<size_t>& neighbors,
|
||||
arma::mat& distances);
|
||||
|
||||
/**
|
||||
* Compute the rank approximate nearest neighbors of each point in the
|
||||
* reference set (that is, the query set is taken to be the reference set),
|
||||
* and store the output in the given matrices. The matrices will be set to
|
||||
* the size of n columns by k rows, where n is the number of points in the
|
||||
* query dataset and k is the number of neighbors being searched for.
|
||||
*
|
||||
* @param k Number of neighbors to search for.
|
||||
* @param neighbors Matrix storing lists of neighbors for each point.
|
||||
* @param distances Matrix storing distances of neighbors for each query
|
||||
* point.
|
||||
*/
|
||||
void Search(const size_t k,
|
||||
arma::Mat<size_t>& neighbors,
|
||||
arma::mat& distances);
|
||||
|
||||
/**
|
||||
* This function recursively resets the RAQueryStat of the given query tree to
|
||||
* set 'bound' to SortPolicy::WorstDistance and 'numSamplesMade' to 0. This
|
||||
* allows a user to perform multiple searches with the same query tree,
|
||||
* possibly with different levels of approximation without requiring to build
|
||||
* a new pair of trees for every new (approximate) search.
|
||||
*
|
||||
* If Search() is called multiple times with the same query tree without
|
||||
* calling ResetQueryTree(), the results may not satisfy the theoretical
|
||||
* guarantees provided by the rank-approximate neighbor search algorithm.
|
||||
*
|
||||
* @param queryTree Tree whose statistics should be reset.
|
||||
*/
|
||||
void ResetQueryTree(TreeType* queryTree) const;
|
||||
|
||||
//! Get the rank-approximation in percentile of the data.
|
||||
double Tau() const { return tau; }
|
||||
//! Modify the rank-approximation in percentile of the data.
|
||||
double& Tau() { return tau; }
|
||||
|
||||
//! Get the desired success probability.
|
||||
double Alpha() const { return alpha; }
|
||||
//! Modify the desired success probability.
|
||||
double& Alpha() { return alpha; }
|
||||
|
||||
//! Get whether or not sampling is done at the leaves.
|
||||
bool SampleAtLeaves() const { return sampleAtLeaves; }
|
||||
//! Modify whether or not sampling is done at the leaves.
|
||||
bool& SampleAtLeaves() { return sampleAtLeaves; }
|
||||
|
||||
//! Get whether or not we traverse to the first leaf without approximation.
|
||||
bool FirstLeafExact() const { return firstLeafExact; }
|
||||
//! Modify whether or not we traverse to the first leaf without approximation.
|
||||
bool& FirstLeafExact() { return firstLeafExact; }
|
||||
|
||||
//! Get the limit on the size of a node that can be approximated.
|
||||
size_t SingleSampleLimit() const { return singleSampleLimit; }
|
||||
//! Modify the limit on the size of a node that can be approximation.
|
||||
size_t& SingleSampleLimit() { return singleSampleLimit; }
|
||||
|
||||
//! Returns a string representation of this object.
|
||||
std::string ToString() const;
|
||||
|
||||
private:
|
||||
//! Copy of reference dataset (if we need it, because tree building modifies
|
||||
//! it).
|
||||
arma::mat referenceCopy;
|
||||
//! Copy of query dataset (if we need it, because tree building modifies it).
|
||||
arma::mat queryCopy;
|
||||
|
||||
//! Reference dataset.
|
||||
const arma::mat& referenceSet;
|
||||
//! Query dataset (may not be given).
|
||||
const arma::mat& querySet;
|
||||
|
||||
//! Pointer to the root of the reference tree.
|
||||
TreeType* referenceTree;
|
||||
//! Pointer to the root of the query tree (might not exist).
|
||||
TreeType* queryTree;
|
||||
|
||||
//! If true, this object created the trees and is responsible for them.
|
||||
bool treeOwner;
|
||||
//! Indicates if a separate query set was passed.
|
||||
bool hasQuerySet;
|
||||
|
||||
//! Indicates if naive random sampling on the set is being used.
|
||||
bool naive;
|
||||
//! Indicates if single-tree search is being used (opposed to dual-tree).
|
||||
bool singleMode;
|
||||
|
||||
//! The rank-approximation in percentile of the data (between 0 and 100).
|
||||
double tau;
|
||||
//! The desired success probability (between 0 and 1).
|
||||
double alpha;
|
||||
//! Whether or not sampling is done at the leaves. Faster, but less accurate.
|
||||
bool sampleAtLeaves;
|
||||
//! If true, we will traverse to the first leaf without approximation.
|
||||
bool firstLeafExact;
|
||||
//! The limit on the number of points in the largest node that can be
|
||||
//! approximated by sampling.
|
||||
size_t singleSampleLimit;
|
||||
|
||||
//! Instantiation of kernel.
|
||||
MetricType metric;
|
||||
|
||||
//! Permutations of reference points during tree building.
|
||||
std::vector<size_t> oldFromNewReferences;
|
||||
//! Permutations of query points during tree building.
|
||||
std::vector<size_t> oldFromNewQueries;
|
||||
|
||||
//! Total number of pruned nodes during the neighbor search.
|
||||
size_t numberOfPrunes;
|
||||
|
||||
/**
|
||||
* @param treeNode The node of the tree whose RAQueryStat is reset
|
||||
* and whose children are to be explored recursively.
|
||||
*/
|
||||
void ResetRAQueryStat(TreeType* treeNode);
|
||||
}; // class RASearch
|
||||
|
||||
}; // namespace neighbor
|
||||
|
||||
@@ -41,53 +41,7 @@ TreeType* BuildTree(
|
||||
return new TreeType(dataset);
|
||||
}
|
||||
|
||||
}; // namespace aux
|
||||
|
||||
// Construct the object.
|
||||
template<typename SortPolicy, typename MetricType, typename TreeType>
|
||||
RASearch<SortPolicy, MetricType, TreeType>::
|
||||
RASearch(const typename TreeType::Mat& referenceSetIn,
|
||||
const typename TreeType::Mat& querySetIn,
|
||||
const bool naive,
|
||||
const bool singleMode,
|
||||
const MetricType metric) :
|
||||
referenceSet(tree::TreeTraits<TreeType>::RearrangesDataset ? referenceCopy :
|
||||
referenceSetIn),
|
||||
querySet((tree::TreeTraits<TreeType>::RearrangesDataset && !singleMode) ?
|
||||
queryCopy : querySetIn),
|
||||
referenceTree(NULL),
|
||||
queryTree(NULL),
|
||||
treeOwner(!naive),
|
||||
hasQuerySet(true),
|
||||
naive(naive),
|
||||
singleMode(!naive && singleMode), // No single mode if naive.
|
||||
metric(metric),
|
||||
numberOfPrunes(0)
|
||||
{
|
||||
// We'll time tree building.
|
||||
Timer::Start("tree_building");
|
||||
|
||||
if (tree::TreeTraits<TreeType>::RearrangesDataset)
|
||||
{
|
||||
referenceCopy = referenceSetIn;
|
||||
if (!singleMode)
|
||||
queryCopy = querySetIn;
|
||||
}
|
||||
|
||||
// Construct as a naive object if we need to.
|
||||
if (!naive)
|
||||
{
|
||||
referenceTree = aux::BuildTree<TreeType>(const_cast<typename
|
||||
TreeType::Mat&>(referenceSet), oldFromNewReferences);
|
||||
|
||||
if (!singleMode)
|
||||
queryTree = aux::BuildTree<TreeType>(const_cast<typename
|
||||
TreeType::Mat&>(querySet), oldFromNewQueries);
|
||||
}
|
||||
|
||||
// Stop the timer we started above.
|
||||
Timer::Stop("tree_building");
|
||||
}
|
||||
} // namespace aux
|
||||
|
||||
// Construct the object.
|
||||
template<typename SortPolicy, typename MetricType, typename TreeType>
|
||||
@@ -95,30 +49,37 @@ RASearch<SortPolicy, MetricType, TreeType>::
|
||||
RASearch(const typename TreeType::Mat& referenceSetIn,
|
||||
const bool naive,
|
||||
const bool singleMode,
|
||||
const double tau,
|
||||
const double alpha,
|
||||
const bool sampleAtLeaves,
|
||||
const bool firstLeafExact,
|
||||
const size_t singleSampleLimit,
|
||||
const MetricType metric) :
|
||||
referenceSet(tree::TreeTraits<TreeType>::RearrangesDataset ? referenceCopy :
|
||||
referenceSetIn),
|
||||
querySet(tree::TreeTraits<TreeType>::RearrangesDataset && !singleMode ?
|
||||
referenceCopy : referenceSetIn),
|
||||
referenceSet((tree::TreeTraits<TreeType>::RearrangesDataset && !naive)
|
||||
? referenceCopy : referenceSetIn),
|
||||
referenceTree(NULL),
|
||||
queryTree(NULL),
|
||||
treeOwner(!naive),
|
||||
hasQuerySet(false),
|
||||
naive(naive),
|
||||
singleMode(!naive && singleMode), // No single mode if naive.
|
||||
metric(metric),
|
||||
numberOfPrunes(0)
|
||||
tau(tau),
|
||||
alpha(alpha),
|
||||
sampleAtLeaves(sampleAtLeaves),
|
||||
firstLeafExact(firstLeafExact),
|
||||
singleSampleLimit(singleSampleLimit),
|
||||
metric(metric)
|
||||
{
|
||||
// We'll time tree building.
|
||||
Timer::Start("tree_building");
|
||||
|
||||
if (tree::TreeTraits<TreeType>::RearrangesDataset)
|
||||
referenceCopy = referenceSetIn;
|
||||
|
||||
// Construct as a naive object if we need to.
|
||||
if (!naive)
|
||||
referenceTree = aux::BuildTree<TreeType>(const_cast<typename
|
||||
TreeType::Mat&>(referenceSet), oldFromNewReferences);
|
||||
{
|
||||
if (tree::TreeTraits<TreeType>::RearrangesDataset)
|
||||
referenceCopy = referenceSetIn;
|
||||
|
||||
referenceTree = aux::BuildTree<TreeType>(
|
||||
const_cast<typename TreeType::Mat&>(referenceSet),
|
||||
oldFromNewReferences);
|
||||
}
|
||||
|
||||
// Stop the timer we started above.
|
||||
Timer::Stop("tree_building");
|
||||
@@ -128,44 +89,27 @@ RASearch(const typename TreeType::Mat& referenceSetIn,
|
||||
template<typename SortPolicy, typename MetricType, typename TreeType>
|
||||
RASearch<SortPolicy, MetricType, TreeType>::
|
||||
RASearch(TreeType* referenceTree,
|
||||
TreeType* queryTree,
|
||||
const typename TreeType::Mat& referenceSet,
|
||||
const typename TreeType::Mat& querySet,
|
||||
const bool singleMode,
|
||||
const double tau,
|
||||
const double alpha,
|
||||
const bool sampleAtLeaves,
|
||||
const bool firstLeafExact,
|
||||
const size_t singleSampleLimit,
|
||||
const MetricType metric) :
|
||||
referenceSet(referenceSet),
|
||||
querySet(querySet),
|
||||
referenceSet(referenceTree->Dataset()),
|
||||
referenceTree(referenceTree),
|
||||
queryTree(queryTree),
|
||||
treeOwner(false),
|
||||
hasQuerySet(true),
|
||||
naive(false),
|
||||
singleMode(singleMode),
|
||||
metric(metric),
|
||||
numberOfPrunes(0)
|
||||
tau(tau),
|
||||
alpha(alpha),
|
||||
sampleAtLeaves(sampleAtLeaves),
|
||||
firstLeafExact(firstLeafExact),
|
||||
singleSampleLimit(singleSampleLimit),
|
||||
metric(metric)
|
||||
// Nothing else to initialize.
|
||||
{ }
|
||||
|
||||
// Construct the object.
|
||||
template<typename SortPolicy, typename MetricType, typename TreeType>
|
||||
RASearch<SortPolicy, MetricType, TreeType>::
|
||||
RASearch(TreeType* referenceTree,
|
||||
const typename TreeType::Mat& referenceSet,
|
||||
const bool singleMode,
|
||||
const MetricType metric) :
|
||||
referenceSet(referenceSet),
|
||||
querySet(referenceSet),
|
||||
referenceTree(referenceTree),
|
||||
queryTree(NULL),
|
||||
treeOwner(false),
|
||||
hasQuerySet(false),
|
||||
naive(false),
|
||||
singleMode(singleMode),
|
||||
metric(metric),
|
||||
numberOfPrunes(0)
|
||||
// Nothing else to initialize.
|
||||
{ }
|
||||
|
||||
/**
|
||||
* The tree is the only member we may be responsible for deleting. The others
|
||||
* will take care of themselves.
|
||||
@@ -174,13 +118,8 @@ template<typename SortPolicy, typename MetricType, typename TreeType>
|
||||
RASearch<SortPolicy, MetricType, TreeType>::
|
||||
~RASearch()
|
||||
{
|
||||
if (treeOwner)
|
||||
{
|
||||
if (referenceTree)
|
||||
delete referenceTree;
|
||||
if (queryTree)
|
||||
delete queryTree;
|
||||
}
|
||||
if (treeOwner && referenceTree)
|
||||
delete referenceTree;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,30 +128,30 @@ RASearch<SortPolicy, MetricType, TreeType>::
|
||||
*/
|
||||
template<typename SortPolicy, typename MetricType, typename TreeType>
|
||||
void RASearch<SortPolicy, MetricType, TreeType>::
|
||||
Search(const size_t k,
|
||||
arma::Mat<size_t>& resultingNeighbors,
|
||||
arma::mat& distances,
|
||||
const double tau,
|
||||
const double alpha,
|
||||
const bool sampleAtLeaves,
|
||||
const bool firstLeafExact,
|
||||
const size_t singleSampleLimit)
|
||||
Search(const typename TreeType::Mat& querySet,
|
||||
const size_t k,
|
||||
arma::Mat<size_t>& neighbors,
|
||||
arma::mat& distances)
|
||||
{
|
||||
Timer::Start("computing_neighbors");
|
||||
|
||||
// This will hold mappings for query points, if necessary.
|
||||
std::vector<size_t> oldFromNewQueries;
|
||||
|
||||
// If we have built the trees ourselves, then we will have to map all the
|
||||
// indices back to their original indices when this computation is finished.
|
||||
// To avoid an extra copy, we will store the neighbors and distances in a
|
||||
// separate matrix.
|
||||
arma::Mat<size_t>* neighborPtr = &resultingNeighbors;
|
||||
arma::Mat<size_t>* neighborPtr = &neighbors;
|
||||
arma::mat* distancePtr = &distances;
|
||||
|
||||
// Mapping is only required if this tree type rearranges points and we are not
|
||||
// in naive mode.
|
||||
if (tree::TreeTraits<TreeType>::RearrangesDataset)
|
||||
{
|
||||
if (treeOwner && !(singleMode && hasQuerySet))
|
||||
if (!singleMode && !naive)
|
||||
distancePtr = new arma::mat; // Query indices need to be mapped.
|
||||
|
||||
if (treeOwner)
|
||||
neighborPtr = new arma::Mat<size_t>; // All indices need mapping.
|
||||
}
|
||||
@@ -222,18 +161,25 @@ Search(const size_t k,
|
||||
distancePtr->set_size(k, querySet.n_cols);
|
||||
distancePtr->fill(SortPolicy::WorstDistance());
|
||||
|
||||
size_t numPrunes = 0;
|
||||
// If we will be building a tree and it will modify the query set, make a copy
|
||||
// of the dataset.
|
||||
typename TreeType::Mat queryCopy;
|
||||
const bool needsCopy = (!naive && !singleMode &&
|
||||
tree::TreeTraits<TreeType>::RearrangesDataset);
|
||||
if (needsCopy)
|
||||
queryCopy = querySet;
|
||||
|
||||
const typename TreeType::Mat& querySetRef = (needsCopy) ? queryCopy :
|
||||
querySet;
|
||||
|
||||
// Create the helper object for the tree traversal.
|
||||
typedef RASearchRules<SortPolicy, MetricType, TreeType> RuleType;
|
||||
RuleType rules(referenceSet, querySetRef, *neighborPtr, *distancePtr,
|
||||
metric, tau, alpha, naive, sampleAtLeaves, firstLeafExact,
|
||||
singleSampleLimit, false);
|
||||
|
||||
if (naive)
|
||||
{
|
||||
// We don't need to run the base case on every possible combination of
|
||||
// points; we can achieve the rank approximation guarantee with probability
|
||||
// alpha by sampling the reference set.
|
||||
typedef RASearchRules<SortPolicy, MetricType, TreeType> RuleType;
|
||||
RuleType rules(referenceSet, querySet, *neighborPtr, *distancePtr,
|
||||
metric, tau, alpha, naive, sampleAtLeaves, firstLeafExact,
|
||||
singleSampleLimit);
|
||||
|
||||
// Find how many samples from the reference set we need and sample uniformly
|
||||
// from the reference set without replacement.
|
||||
const size_t numSamples = rules.MinimumSamplesReqd(referenceSet.n_cols, k,
|
||||
@@ -244,19 +190,12 @@ Search(const size_t k,
|
||||
|
||||
// Run the base case on each combination of query point and sampled
|
||||
// reference point.
|
||||
for (size_t i = 0; i < querySet.n_cols; ++i)
|
||||
for (size_t i = 0; i < querySetRef.n_cols; ++i)
|
||||
for (size_t j = 0; j < distinctSamples.n_elem; ++j)
|
||||
rules.BaseCase(i, (size_t) distinctSamples[j]);
|
||||
}
|
||||
else if (singleMode)
|
||||
{
|
||||
// Create the helper object for the tree traversal. Initialization of
|
||||
// RASearchRules already implicitly performs the naive tree traversal.
|
||||
typedef RASearchRules<SortPolicy, MetricType, TreeType> RuleType;
|
||||
RuleType rules(referenceSet, querySet, *neighborPtr, *distancePtr,
|
||||
metric, tau, alpha, naive, sampleAtLeaves, firstLeafExact,
|
||||
singleSampleLimit);
|
||||
|
||||
// If the reference root node is a leaf, then the sampling has already been
|
||||
// done in the RASearchRules constructor. This happens when naive = true.
|
||||
if (!referenceTree->IsLeaf())
|
||||
@@ -268,11 +207,9 @@ Search(const size_t k,
|
||||
traverser(rules);
|
||||
|
||||
// Now have it traverse for each point.
|
||||
for (size_t i = 0; i < querySet.n_cols; ++i)
|
||||
for (size_t i = 0; i < querySetRef.n_cols; ++i)
|
||||
traverser.Traverse(i, *referenceTree);
|
||||
|
||||
numPrunes = traverser.NumPrunes();
|
||||
|
||||
Log::Info << "Single-tree traversal complete." << std::endl;
|
||||
Log::Info << "Average number of distance calculations per query point: "
|
||||
<< (rules.NumDistComputations() / querySet.n_cols) << "."
|
||||
@@ -283,27 +220,20 @@ Search(const size_t k,
|
||||
{
|
||||
Log::Info << "Performing dual-tree traversal..." << std::endl;
|
||||
|
||||
typedef RASearchRules<SortPolicy, MetricType, TreeType> RuleType;
|
||||
RuleType rules(referenceSet, querySet, *neighborPtr, *distancePtr,
|
||||
metric, tau, alpha, sampleAtLeaves, firstLeafExact,
|
||||
singleSampleLimit);
|
||||
// Build the query tree.
|
||||
Timer::Stop("computing_neighbors");
|
||||
Timer::Start("tree_building");
|
||||
TreeType* queryTree = aux::BuildTree<TreeType>(
|
||||
const_cast<typename TreeType::Mat&>(querySetRef), oldFromNewQueries);
|
||||
Timer::Stop("tree_building");
|
||||
Timer::Start("computing_neighbors");
|
||||
|
||||
typename TreeType::template DualTreeTraverser<RuleType> traverser(rules);
|
||||
|
||||
if (queryTree)
|
||||
{
|
||||
Log::Info << "Query statistic pre-search: "
|
||||
<< queryTree->Stat().NumSamplesMade() << std::endl;
|
||||
traverser.Traverse(*queryTree, *referenceTree);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::Info << "Query statistic pre-search: "
|
||||
<< referenceTree->Stat().NumSamplesMade() << std::endl;
|
||||
traverser.Traverse(*referenceTree, *referenceTree);
|
||||
}
|
||||
Log::Info << "Query statistic pre-search: "
|
||||
<< queryTree->Stat().NumSamplesMade() << std::endl;
|
||||
|
||||
numPrunes = traverser.NumPrunes();
|
||||
traverser.Traverse(*queryTree, *referenceTree);
|
||||
|
||||
Log::Info << "Dual-tree traversal complete." << std::endl;
|
||||
Log::Info << "Average number of distance calculations per query point: "
|
||||
@@ -311,114 +241,257 @@ Search(const size_t k,
|
||||
}
|
||||
|
||||
Timer::Stop("computing_neighbors");
|
||||
Log::Info << "Pruned " << numPrunes << " nodes." << std::endl;
|
||||
|
||||
// Now, do we need to do mapping of indices?
|
||||
if (!treeOwner || !tree::TreeTraits<TreeType>::RearrangesDataset)
|
||||
// Map points back to original indices, if necessary.
|
||||
if (tree::TreeTraits<TreeType>::RearrangesDataset)
|
||||
{
|
||||
// No mapping needed. We are done.
|
||||
return;
|
||||
}
|
||||
else if (treeOwner && hasQuerySet && !singleMode) // Map both sets.
|
||||
{
|
||||
// Set size of output matrices correctly.
|
||||
resultingNeighbors.set_size(k, querySet.n_cols);
|
||||
distances.set_size(k, querySet.n_cols);
|
||||
|
||||
for (size_t i = 0; i < distances.n_cols; i++)
|
||||
if (!singleMode && !naive && treeOwner)
|
||||
{
|
||||
// Map distances (copy a column).
|
||||
distances.col(oldFromNewQueries[i]) = distancePtr->col(i);
|
||||
// We must map both query and reference indices.
|
||||
neighbors.set_size(k, querySet.n_cols);
|
||||
distances.set_size(k, querySet.n_cols);
|
||||
|
||||
for (size_t i = 0; i < distances.n_cols; i++)
|
||||
{
|
||||
// Map distances (copy a column).
|
||||
distances.col(oldFromNewQueries[i]) = distancePtr->col(i);
|
||||
|
||||
// Map indices of neighbors.
|
||||
for (size_t j = 0; j < distances.n_rows; j++)
|
||||
{
|
||||
neighbors(j, oldFromNewQueries[i]) =
|
||||
oldFromNewReferences[(*neighborPtr)(j, i)];
|
||||
}
|
||||
}
|
||||
|
||||
// Finished with temporary matrices.
|
||||
delete neighborPtr;
|
||||
delete distancePtr;
|
||||
}
|
||||
else if (!singleMode && !naive)
|
||||
{
|
||||
// We must map query indices only.
|
||||
neighbors.set_size(k, querySet.n_cols);
|
||||
distances.set_size(k, querySet.n_cols);
|
||||
|
||||
for (size_t i = 0; i < distances.n_cols; ++i)
|
||||
{
|
||||
// Map distances (copy a column).
|
||||
const size_t queryMapping = oldFromNewQueries[i];
|
||||
distances.col(queryMapping) = distancePtr->col(i);
|
||||
neighbors.col(queryMapping) = neighborPtr->col(i);
|
||||
}
|
||||
|
||||
// Finished with temporary matrices.
|
||||
delete neighborPtr;
|
||||
delete distancePtr;
|
||||
}
|
||||
else if (treeOwner)
|
||||
{
|
||||
// We must map reference indices only.
|
||||
neighbors.set_size(k, querySet.n_cols);
|
||||
|
||||
// Map indices of neighbors.
|
||||
for (size_t j = 0; j < distances.n_rows; j++)
|
||||
{
|
||||
resultingNeighbors(j, oldFromNewQueries[i]) =
|
||||
oldFromNewReferences[(*neighborPtr)(j, i)];
|
||||
}
|
||||
for (size_t i = 0; i < neighbors.n_cols; i++)
|
||||
for (size_t j = 0; j < neighbors.n_rows; j++)
|
||||
neighbors(j, i) = oldFromNewReferences[(*neighborPtr)(j, i)];
|
||||
|
||||
// Finished with temporary matrix.
|
||||
delete neighborPtr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename SortPolicy, typename MetricType, typename TreeType>
|
||||
void RASearch<SortPolicy, MetricType, TreeType>::Search(
|
||||
TreeType* queryTree,
|
||||
const size_t k,
|
||||
arma::Mat<size_t>& neighbors,
|
||||
arma::mat& distances)
|
||||
{
|
||||
Timer::Start("computing_neighbors");
|
||||
|
||||
// Get a reference to the query set.
|
||||
const typename TreeType::Mat& querySet = queryTree->Dataset();
|
||||
|
||||
// Make sure we are in dual-tree mode.
|
||||
if (singleMode || naive)
|
||||
throw std::invalid_argument("cannot call NeighborSearch::Search() with a "
|
||||
"query tree when naive or singleMode are set to true");
|
||||
|
||||
// We won't need to map query indices, but will we need to map distances?
|
||||
arma::Mat<size_t>* neighborPtr = &neighbors;
|
||||
|
||||
if (treeOwner && tree::TreeTraits<TreeType>::RearrangesDataset)
|
||||
neighborPtr = new arma::Mat<size_t>;
|
||||
|
||||
neighborPtr->set_size(k, querySet.n_cols);
|
||||
neighborPtr->fill(size_t() - 1);
|
||||
distances.set_size(k, querySet.n_cols);
|
||||
distances.fill(SortPolicy::WorstDistance());
|
||||
|
||||
// Create the helper object for the tree traversal.
|
||||
typedef RASearchRules<SortPolicy, MetricType, TreeType> RuleType;
|
||||
RuleType rules(referenceSet, queryTree->Dataset(), *neighborPtr, distances,
|
||||
metric, tau, alpha, naive, sampleAtLeaves, firstLeafExact,
|
||||
singleSampleLimit, false);
|
||||
|
||||
// Create the traverser.
|
||||
typename TreeType::template DualTreeTraverser<RuleType> traverser(rules);
|
||||
traverser.Traverse(*queryTree, *referenceTree);
|
||||
|
||||
Timer::Stop("computing_neighbors");
|
||||
|
||||
// Do we need to map indices?
|
||||
if (treeOwner && tree::TreeTraits<TreeType>::RearrangesDataset)
|
||||
{
|
||||
// We must map reference indices only.
|
||||
neighbors.set_size(k, querySet.n_cols);
|
||||
|
||||
// Map indices of neighbors.
|
||||
for (size_t i = 0; i < neighbors.n_cols; i++)
|
||||
for (size_t j = 0; j < neighbors.n_rows; j++)
|
||||
neighbors(j, i) = oldFromNewReferences[(*neighborPtr)(j, i)];
|
||||
|
||||
// Finished with temporary matrix.
|
||||
delete neighborPtr;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename SortPolicy, typename MetricType, typename TreeType>
|
||||
void RASearch<SortPolicy, MetricType, TreeType>::Search(
|
||||
const size_t k,
|
||||
arma::Mat<size_t>& neighbors,
|
||||
arma::mat& distances)
|
||||
{
|
||||
Timer::Start("computing_neighbors");
|
||||
|
||||
arma::Mat<size_t>* neighborPtr = &neighbors;
|
||||
arma::mat* distancePtr = &distances;
|
||||
|
||||
if (tree::TreeTraits<TreeType>::RearrangesDataset && treeOwner)
|
||||
{
|
||||
// We will always need to rearrange in this case.
|
||||
distancePtr = new arma::mat;
|
||||
neighborPtr = new arma::Mat<size_t>;
|
||||
}
|
||||
|
||||
// Initialize results.
|
||||
neighborPtr->set_size(k, referenceSet.n_cols);
|
||||
neighborPtr->fill(size_t() - 1);
|
||||
distancePtr->set_size(k, referenceSet.n_cols);
|
||||
distancePtr->fill(SortPolicy::WorstDistance());
|
||||
|
||||
// Create the helper object for the tree traversal.
|
||||
typedef RASearchRules<SortPolicy, MetricType, TreeType> RuleType;
|
||||
RuleType rules(referenceSet, referenceSet, *neighborPtr, *distancePtr,
|
||||
metric, tau, alpha, naive, sampleAtLeaves, firstLeafExact,
|
||||
singleSampleLimit, true /* sets are the same */);
|
||||
|
||||
if (naive)
|
||||
{
|
||||
// Find how many samples from the reference set we need and sample uniformly
|
||||
// from the reference set without replacement.
|
||||
const size_t numSamples = rules.MinimumSamplesReqd(referenceSet.n_cols, k,
|
||||
tau, alpha);
|
||||
arma::uvec distinctSamples;
|
||||
rules.ObtainDistinctSamples(numSamples, referenceSet.n_cols,
|
||||
distinctSamples);
|
||||
|
||||
// The naive brute-force solution.
|
||||
for (size_t i = 0; i < referenceSet.n_cols; ++i)
|
||||
for (size_t j = 0; j < referenceSet.n_cols; ++j)
|
||||
rules.BaseCase(i, j);
|
||||
}
|
||||
else if (singleMode)
|
||||
{
|
||||
// Create the traverser.
|
||||
typename TreeType::template SingleTreeTraverser<RuleType> traverser(rules);
|
||||
|
||||
// Now have it traverse for each point.
|
||||
for (size_t i = 0; i < referenceSet.n_cols; ++i)
|
||||
traverser.Traverse(i, *referenceTree);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create the traverser.
|
||||
typename TreeType::template DualTreeTraverser<RuleType> traverser(rules);
|
||||
|
||||
traverser.Traverse(*referenceTree, *referenceTree);
|
||||
}
|
||||
|
||||
Timer::Stop("computing_neighbors");
|
||||
|
||||
// Do we need to map the reference indices?
|
||||
if (treeOwner && tree::TreeTraits<TreeType>::RearrangesDataset)
|
||||
{
|
||||
neighbors.set_size(k, referenceSet.n_cols);
|
||||
distances.set_size(k, referenceSet.n_cols);
|
||||
|
||||
for (size_t i = 0; i < distances.n_cols; ++i)
|
||||
{
|
||||
// Map distances (copy a column).
|
||||
const size_t refMapping = oldFromNewReferences[i];
|
||||
distances.col(refMapping) = distancePtr->col(i);
|
||||
|
||||
// Map each neighbor's index.
|
||||
for (size_t j = 0; j < distances.n_rows; ++j)
|
||||
neighbors(j, refMapping) = oldFromNewReferences[(*neighborPtr)(j, i)];
|
||||
}
|
||||
|
||||
// Finished with temporary matrices.
|
||||
delete neighborPtr;
|
||||
delete distancePtr;
|
||||
}
|
||||
else if (treeOwner && !hasQuerySet)
|
||||
{
|
||||
// No query tree -- map both references and queries.
|
||||
resultingNeighbors.set_size(k, querySet.n_cols);
|
||||
distances.set_size(k, querySet.n_cols);
|
||||
|
||||
for (size_t i = 0; i < distances.n_cols; i++)
|
||||
{
|
||||
// Map distances (copy a column).
|
||||
distances.col(oldFromNewReferences[i]) = distancePtr->col(i);
|
||||
|
||||
// Map indices of neighbors.
|
||||
for (size_t j = 0; j < distances.n_rows; j++)
|
||||
{
|
||||
resultingNeighbors(j, oldFromNewReferences[i]) =
|
||||
oldFromNewReferences[(*neighborPtr)(j, i)];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (treeOwner && hasQuerySet && singleMode) // Map only references.
|
||||
{
|
||||
// Set size of neighbor indices matrix correctly.
|
||||
resultingNeighbors.set_size(k, querySet.n_cols);
|
||||
|
||||
// Map indices of neighbors.
|
||||
for (size_t i = 0; i < resultingNeighbors.n_cols; i++)
|
||||
{
|
||||
for (size_t j = 0; j < resultingNeighbors.n_rows; j++)
|
||||
{
|
||||
resultingNeighbors(j, i) = oldFromNewReferences[(*neighborPtr)(j, i)];
|
||||
}
|
||||
}
|
||||
|
||||
// Finished with temporary matrix.
|
||||
delete neighborPtr;
|
||||
}
|
||||
} // Search
|
||||
|
||||
template<typename SortPolicy, typename MetricType, typename TreeType>
|
||||
void RASearch<SortPolicy, MetricType, TreeType>::ResetQueryTree()
|
||||
{
|
||||
if (!singleMode)
|
||||
{
|
||||
if (queryTree)
|
||||
ResetRAQueryStat(queryTree);
|
||||
else
|
||||
ResetRAQueryStat(referenceTree);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename SortPolicy, typename MetricType, typename TreeType>
|
||||
void RASearch<SortPolicy, MetricType, TreeType>::ResetRAQueryStat(
|
||||
TreeType* treeNode)
|
||||
void RASearch<SortPolicy, MetricType, TreeType>::ResetQueryTree(
|
||||
TreeType* queryNode) const
|
||||
{
|
||||
treeNode->Stat().Bound() = SortPolicy::WorstDistance();
|
||||
treeNode->Stat().NumSamplesMade() = 0;
|
||||
queryNode->Stat().Bound() = SortPolicy::WorstDistance();
|
||||
queryNode->Stat().NumSamplesMade() = 0;
|
||||
|
||||
for (size_t i = 0; i < treeNode->NumChildren(); i++)
|
||||
ResetRAQueryStat(&treeNode->Child(i));
|
||||
for (size_t i = 0; i < queryNode->NumChildren(); i++)
|
||||
ResetQueryTree(&queryNode->Child(i));
|
||||
}
|
||||
|
||||
// Returns a String of the Object.
|
||||
// Returns a string representation of the object.
|
||||
template<typename SortPolicy, typename MetricType, typename TreeType>
|
||||
std::string RASearch<SortPolicy, MetricType, TreeType>::ToString() const
|
||||
{
|
||||
std::ostringstream convert;
|
||||
convert << "RA Search [" << this << "]" << std::endl;
|
||||
convert << " Reference Set: " << referenceSet.n_rows << "x" ;
|
||||
convert << referenceSet.n_cols << std::endl;
|
||||
if (&referenceSet != &querySet)
|
||||
convert << " QuerySet: " << querySet.n_rows << "x" << querySet.n_cols
|
||||
<< std::endl;
|
||||
convert << "RASearch [" << this << "]" << std::endl;
|
||||
convert << " referenceSet: " << referenceSet.n_rows << "x"
|
||||
<< referenceSet.n_cols << std::endl;
|
||||
|
||||
convert << " naive: ";
|
||||
if (naive)
|
||||
convert << " Naive: TRUE" << std::endl;
|
||||
convert << "true" << std::endl;
|
||||
else
|
||||
convert << "false" << std::endl;
|
||||
|
||||
convert << " singleMode: ";
|
||||
if (singleMode)
|
||||
convert << " Single Node: TRUE" << std::endl;
|
||||
convert << " Metric: " << std::endl <<
|
||||
convert << "true" << std::endl;
|
||||
else
|
||||
convert << "false" << std::endl;
|
||||
|
||||
convert << " tau: " << tau << std::endl;
|
||||
convert << " alpha: " << alpha << std::endl;
|
||||
convert << " sampleAtLeaves: ";
|
||||
if (sampleAtLeaves)
|
||||
convert << "true" << std::endl;
|
||||
else
|
||||
convert << "false" << std::endl;
|
||||
|
||||
convert << " firstLeafExact: ";
|
||||
if (firstLeafExact)
|
||||
convert << "true" << std::endl;
|
||||
else
|
||||
convert << "false" << std::endl;
|
||||
convert << " singleSampleLimit: " << singleSampleLimit << std::endl;
|
||||
convert << " metric: " << std::endl <<
|
||||
mlpack::util::Indent(metric.ToString(),2);
|
||||
return convert.str();
|
||||
}
|
||||
|
||||
@@ -29,9 +29,8 @@ class RASearchRules
|
||||
const bool naive = false,
|
||||
const bool sampleAtLeaves = false,
|
||||
const bool firstLeafExact = false,
|
||||
const size_t singleSampleLimit = 20);
|
||||
|
||||
|
||||
const size_t singleSampleLimit = 20,
|
||||
const bool sameSet = false);
|
||||
|
||||
double BaseCase(const size_t queryIndex, const size_t referenceIndex);
|
||||
|
||||
@@ -229,6 +228,9 @@ class RASearchRules
|
||||
// TO REMOVE: just for testing
|
||||
size_t numDistComputations;
|
||||
|
||||
//! If the query and reference set are identical, this is true.
|
||||
bool sameSet;
|
||||
|
||||
TraversalInfoType traversalInfo;
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,15 +25,17 @@ RASearchRules(const arma::mat& referenceSet,
|
||||
const bool naive,
|
||||
const bool sampleAtLeaves,
|
||||
const bool firstLeafExact,
|
||||
const size_t singleSampleLimit) :
|
||||
referenceSet(referenceSet),
|
||||
querySet(querySet),
|
||||
neighbors(neighbors),
|
||||
distances(distances),
|
||||
metric(metric),
|
||||
sampleAtLeaves(sampleAtLeaves),
|
||||
firstLeafExact(firstLeafExact),
|
||||
singleSampleLimit(singleSampleLimit)
|
||||
const size_t singleSampleLimit,
|
||||
const bool sameSet) :
|
||||
referenceSet(referenceSet),
|
||||
querySet(querySet),
|
||||
neighbors(neighbors),
|
||||
distances(distances),
|
||||
metric(metric),
|
||||
sampleAtLeaves(sampleAtLeaves),
|
||||
firstLeafExact(firstLeafExact),
|
||||
singleSampleLimit(singleSampleLimit),
|
||||
sameSet(sameSet)
|
||||
{
|
||||
// Validate tau to make sure that the rank approximation is greater than the
|
||||
// number of neighbors requested.
|
||||
@@ -268,7 +270,7 @@ double RASearchRules<SortPolicy, MetricType, TreeType>::BaseCase(
|
||||
{
|
||||
// If the datasets are the same, then this search is only using one dataset
|
||||
// and we should not return identical points.
|
||||
if ((&querySet == &referenceSet) && (queryIndex == referenceIndex))
|
||||
if (sameSet && (queryIndex == referenceIndex))
|
||||
return 0.0;
|
||||
|
||||
double distance = metric.Evaluate(querySet.unsafe_col(queryIndex),
|
||||
|
||||
Reference in New Issue
Block a user