706 lines
21 KiB
C++
706 lines
21 KiB
C++
/**
|
|
* @file allkrann_search_test.cpp
|
|
*
|
|
* Unit tests for the 'RASearch' class and consequently the
|
|
* 'RASearchRules' class
|
|
*/
|
|
#include <time.h>
|
|
#include <mlpack/core.hpp>
|
|
#include <mlpack/core/metrics/lmetric.hpp>
|
|
#include <mlpack/core/tree/cover_tree.hpp>
|
|
|
|
#include <boost/test/unit_test.hpp>
|
|
#include "test_tools.hpp"
|
|
|
|
#include <mlpack/methods/rann/ra_search.hpp>
|
|
#include <mlpack/methods/rann/ra_model.hpp>
|
|
|
|
using namespace std;
|
|
using namespace mlpack;
|
|
using namespace mlpack::neighbor;
|
|
using namespace mlpack::tree;
|
|
using namespace mlpack::metric;
|
|
using namespace mlpack::bound;
|
|
|
|
BOOST_AUTO_TEST_SUITE(KRANNTest);
|
|
|
|
// Test the correctness and guarantees of KRANN when in naive mode.
|
|
BOOST_AUTO_TEST_CASE(NaiveGuaranteeTest)
|
|
{
|
|
arma::Mat<size_t> neighbors;
|
|
arma::mat distances;
|
|
|
|
arma::mat refData;
|
|
arma::mat queryData;
|
|
|
|
data::Load("rann_test_r_3_900.csv", refData, true);
|
|
data::Load("rann_test_q_3_100.csv", queryData, true);
|
|
|
|
RASearch<> rsRann(refData, true, false, 1.0);
|
|
|
|
arma::mat qrRanks;
|
|
data::Load("rann_test_qr_ranks.csv", qrRanks, true, false); // No transpose.
|
|
|
|
size_t numRounds = 1000;
|
|
arma::Col<size_t> numSuccessRounds(queryData.n_cols);
|
|
numSuccessRounds.fill(0);
|
|
|
|
// 1% of 900 is 9, so the rank is expected to be less than 10
|
|
size_t expectedRankErrorUB = 10;
|
|
|
|
for (size_t rounds = 0; rounds < numRounds; rounds++)
|
|
{
|
|
rsRann.Search(queryData, 1, neighbors, distances);
|
|
|
|
for (size_t i = 0; i < queryData.n_cols; i++)
|
|
if (qrRanks(i, neighbors(0, i)) < expectedRankErrorUB)
|
|
numSuccessRounds[i]++;
|
|
|
|
neighbors.reset();
|
|
distances.reset();
|
|
}
|
|
|
|
// Find the 95%-tile threshold so that 95% of the queries should pass this
|
|
// threshold.
|
|
size_t threshold = floor(numRounds *
|
|
(0.95 - (1.96 * sqrt(0.95 * 0.05 / numRounds))));
|
|
size_t numQueriesFail = 0;
|
|
for (size_t i = 0; i < queryData.n_cols; i++)
|
|
if (numSuccessRounds[i] < threshold)
|
|
numQueriesFail++;
|
|
|
|
Log::Warn << "RANN-RS: RANN guarantee fails on " << numQueriesFail
|
|
<< " queries." << endl;
|
|
|
|
// assert that at most 5% of the queries fall out of this threshold
|
|
// 5% of 100 queries is 5.
|
|
size_t maxNumQueriesFail = 6;
|
|
|
|
BOOST_REQUIRE_LT(numQueriesFail, maxNumQueriesFail);
|
|
}
|
|
|
|
// Test single-tree rank-approximate search (harder to test because of
|
|
// the randomness involved).
|
|
BOOST_AUTO_TEST_CASE(SingleTreeSearch)
|
|
{
|
|
arma::mat refData;
|
|
arma::mat queryData;
|
|
|
|
data::Load("rann_test_r_3_900.csv", refData, true);
|
|
data::Load("rann_test_q_3_100.csv", queryData, true);
|
|
|
|
// Search for 1 rank-approximate nearest-neighbors in the top 30% of the point
|
|
// (rank error of 3).
|
|
arma::Mat<size_t> neighbors;
|
|
arma::mat distances;
|
|
|
|
RASearch<> tssRann(refData, false, true, 1.0, 0.95, false, false);
|
|
|
|
// The relative ranks for the given query reference pair
|
|
arma::Mat<size_t> qrRanks;
|
|
data::Load("rann_test_qr_ranks.csv", qrRanks, true, false); // No transpose.
|
|
|
|
size_t numRounds = 1000;
|
|
arma::Col<size_t> numSuccessRounds(queryData.n_cols);
|
|
numSuccessRounds.fill(0);
|
|
|
|
// 1% of 900 is 9, so the rank is expected to be less than 10.
|
|
size_t expectedRankErrorUB = 10;
|
|
|
|
for (size_t rounds = 0; rounds < numRounds; rounds++)
|
|
{
|
|
tssRann.Search(queryData, 1, neighbors, distances);
|
|
|
|
for (size_t i = 0; i < queryData.n_cols; i++)
|
|
if (qrRanks(i, neighbors(0, i)) < expectedRankErrorUB)
|
|
numSuccessRounds[i]++;
|
|
|
|
neighbors.reset();
|
|
distances.reset();
|
|
}
|
|
|
|
// Find the 95%-tile threshold so that 95% of the queries should pass this
|
|
// threshold.
|
|
size_t threshold = floor(numRounds *
|
|
(0.95 - (1.96 * sqrt(0.95 * 0.05 / numRounds))));
|
|
size_t numQueriesFail = 0;
|
|
for (size_t i = 0; i < queryData.n_cols; i++)
|
|
if (numSuccessRounds[i] < threshold)
|
|
numQueriesFail++;
|
|
|
|
Log::Warn << "RANN-TSS: RANN guarantee fails on " << numQueriesFail
|
|
<< " queries." << endl;
|
|
|
|
// Assert that at most 5% of the queries fall out of this threshold.
|
|
// 5% of 100 queries is 5.
|
|
size_t maxNumQueriesFail = 6;
|
|
|
|
BOOST_REQUIRE_LT(numQueriesFail, maxNumQueriesFail);
|
|
}
|
|
|
|
// Test dual-tree rank-approximate search (harder to test because of the
|
|
// randomness involved).
|
|
BOOST_AUTO_TEST_CASE(DualTreeSearch)
|
|
{
|
|
arma::mat refData;
|
|
arma::mat queryData;
|
|
|
|
data::Load("rann_test_r_3_900.csv", refData, true);
|
|
data::Load("rann_test_q_3_100.csv", queryData, true);
|
|
|
|
// Search for 1 rank-approximate nearest-neighbors in the top 30% of the point
|
|
// (rank error of 3).
|
|
arma::Mat<size_t> neighbors;
|
|
arma::mat distances;
|
|
|
|
RASearch<> tsdRann(refData, false, false, 1.0, 0.95, false, false, 5);
|
|
|
|
arma::Mat<size_t> qrRanks;
|
|
data::Load("rann_test_qr_ranks.csv", qrRanks, true, false); // No transpose.
|
|
|
|
size_t numRounds = 1000;
|
|
arma::Col<size_t> numSuccessRounds(queryData.n_cols);
|
|
numSuccessRounds.fill(0);
|
|
|
|
// 1% of 900 is 9, so the rank is expected to be less than 10.
|
|
size_t expectedRankErrorUB = 10;
|
|
|
|
// Build query tree by hand.
|
|
typedef KDTree<EuclideanDistance, RAQueryStat<NearestNeighborSort>,
|
|
arma::mat> TreeType;
|
|
std::vector<size_t> oldFromNewQueries;
|
|
TreeType queryTree(queryData, oldFromNewQueries);
|
|
|
|
for (size_t rounds = 0; rounds < numRounds; rounds++)
|
|
{
|
|
tsdRann.Search(&queryTree, 1, neighbors, distances);
|
|
|
|
for (size_t i = 0; i < queryData.n_cols; i++)
|
|
{
|
|
const size_t oldIndex = oldFromNewQueries[i];
|
|
if (qrRanks(oldIndex, neighbors(0, i)) < expectedRankErrorUB)
|
|
numSuccessRounds[i]++;
|
|
}
|
|
|
|
neighbors.reset();
|
|
distances.reset();
|
|
|
|
tsdRann.ResetQueryTree(&queryTree);
|
|
}
|
|
|
|
// Find the 95%-tile threshold so that 95% of the queries should pass this
|
|
// threshold.
|
|
size_t threshold = floor(numRounds *
|
|
(0.95 - (1.96 * sqrt(0.95 * 0.05 / numRounds))));
|
|
size_t numQueriesFail = 0;
|
|
for (size_t i = 0; i < queryData.n_cols; i++)
|
|
if (numSuccessRounds[i] < threshold)
|
|
numQueriesFail++;
|
|
|
|
Log::Warn << "RANN-TSD: RANN guarantee fails on " << numQueriesFail
|
|
<< " queries." << endl;
|
|
|
|
// assert that at most 5% of the queries fall out of this threshold
|
|
// 5% of 100 queries is 5.
|
|
size_t maxNumQueriesFail = 6;
|
|
|
|
BOOST_REQUIRE_LT(numQueriesFail, maxNumQueriesFail);
|
|
}
|
|
|
|
// Test rank-approximate search with just a single dataset. These tests just
|
|
// ensure that the method runs okay.
|
|
BOOST_AUTO_TEST_CASE(SingleDatasetNaiveSearch)
|
|
{
|
|
arma::mat dataset(5, 2500);
|
|
dataset.randn();
|
|
|
|
arma::Mat<size_t> neighbors;
|
|
arma::mat distances;
|
|
|
|
RASearch<> naive(dataset, true);
|
|
|
|
naive.Search(1, neighbors, distances);
|
|
|
|
BOOST_REQUIRE_EQUAL(neighbors.n_rows, 1);
|
|
BOOST_REQUIRE_EQUAL(neighbors.n_cols, 2500);
|
|
BOOST_REQUIRE_EQUAL(distances.n_rows, 1);
|
|
BOOST_REQUIRE_EQUAL(distances.n_cols, 2500);
|
|
}
|
|
|
|
// Test rank-approximate search with just a single dataset in single-tree mode.
|
|
// These tests just ensure that the method runs okay.
|
|
BOOST_AUTO_TEST_CASE(SingleDatasetSingleSearch)
|
|
{
|
|
arma::mat dataset(5, 2500);
|
|
dataset.randn();
|
|
|
|
arma::Mat<size_t> neighbors;
|
|
arma::mat distances;
|
|
|
|
RASearch<> single(dataset, false, true);
|
|
|
|
single.Search(1, neighbors, distances);
|
|
|
|
BOOST_REQUIRE_EQUAL(neighbors.n_rows, 1);
|
|
BOOST_REQUIRE_EQUAL(neighbors.n_cols, 2500);
|
|
BOOST_REQUIRE_EQUAL(distances.n_rows, 1);
|
|
BOOST_REQUIRE_EQUAL(distances.n_cols, 2500);
|
|
}
|
|
|
|
// Test rank-approximate search with just a single dataset in dual-tree mode.
|
|
// These tests just ensure that the method runs okay.
|
|
BOOST_AUTO_TEST_CASE(SingleDatasetSearch)
|
|
{
|
|
arma::mat dataset(5, 2500);
|
|
dataset.randn();
|
|
|
|
arma::Mat<size_t> neighbors;
|
|
arma::mat distances;
|
|
|
|
RASearch<> allkrann(dataset);
|
|
allkrann.Search(1, neighbors, distances);
|
|
|
|
BOOST_REQUIRE_EQUAL(neighbors.n_rows, 1);
|
|
BOOST_REQUIRE_EQUAL(neighbors.n_cols, 2500);
|
|
BOOST_REQUIRE_EQUAL(distances.n_rows, 1);
|
|
BOOST_REQUIRE_EQUAL(distances.n_cols, 2500);
|
|
}
|
|
|
|
// Test single-tree rank-approximate search with cover trees.
|
|
BOOST_AUTO_TEST_CASE(SingleCoverTreeTest)
|
|
{
|
|
arma::mat refData;
|
|
arma::mat queryData;
|
|
|
|
data::Load("rann_test_r_3_900.csv", refData, true);
|
|
data::Load("rann_test_q_3_100.csv", queryData, true);
|
|
|
|
// Search for 1 rank-approximate nearest-neighbors in the top 30% of the point
|
|
// (rank error of 3).
|
|
arma::Mat<size_t> neighbors;
|
|
arma::mat distances;
|
|
|
|
typedef RASearch<NearestNeighborSort, EuclideanDistance, arma::mat,
|
|
StandardCoverTree> RACoverTreeSearch;
|
|
|
|
RACoverTreeSearch tssRann(refData, false, true, 1.0, 0.95, false, false, 5);
|
|
|
|
// The relative ranks for the given query reference pair.
|
|
arma::Mat<size_t> qrRanks;
|
|
data::Load("rann_test_qr_ranks.csv", qrRanks, true, false); // No transpose.
|
|
|
|
size_t numRounds = 1000;
|
|
arma::Col<size_t> numSuccessRounds(queryData.n_cols);
|
|
numSuccessRounds.fill(0);
|
|
|
|
// 1% of 900 is 9, so the rank is expected to be less than 10.
|
|
size_t expectedRankErrorUB = 10;
|
|
|
|
for (size_t rounds = 0; rounds < numRounds; rounds++)
|
|
{
|
|
tssRann.Search(queryData, 1, neighbors, distances);
|
|
|
|
for (size_t i = 0; i < queryData.n_cols; i++)
|
|
if (qrRanks(i, neighbors(0, i)) < expectedRankErrorUB)
|
|
numSuccessRounds[i]++;
|
|
|
|
neighbors.reset();
|
|
distances.reset();
|
|
}
|
|
|
|
// Find the 95%-tile threshold so that 95% of the queries should pass this
|
|
// threshold.
|
|
size_t threshold = floor(numRounds *
|
|
(0.95 - (1.96 * sqrt(0.95 * 0.05 / numRounds))));
|
|
size_t numQueriesFail = 0;
|
|
for (size_t i = 0; i < queryData.n_cols; i++)
|
|
if (numSuccessRounds[i] < threshold)
|
|
numQueriesFail++;
|
|
|
|
Log::Warn << "RANN-TSS (cover tree): RANN guarantee fails on "
|
|
<< numQueriesFail << " queries." << endl;
|
|
|
|
// Assert that at most 5% of the queries fall out of this threshold.
|
|
// 5% of 100 queries is 5.
|
|
size_t maxNumQueriesFail = 6;
|
|
|
|
BOOST_REQUIRE_LT(numQueriesFail, maxNumQueriesFail);
|
|
}
|
|
|
|
// Test dual-tree rank-approximate search with cover trees.
|
|
BOOST_AUTO_TEST_CASE(DualCoverTreeTest)
|
|
{
|
|
arma::mat refData;
|
|
arma::mat queryData;
|
|
|
|
data::Load("rann_test_r_3_900.csv", refData, true);
|
|
data::Load("rann_test_q_3_100.csv", queryData, true);
|
|
|
|
// Search for 1 rank-approximate nearest-neighbors in the top 30% of the point
|
|
// (rank error of 3).
|
|
arma::Mat<size_t> neighbors;
|
|
arma::mat distances;
|
|
|
|
typedef StandardCoverTree<EuclideanDistance, RAQueryStat<NearestNeighborSort>,
|
|
arma::mat> TreeType;
|
|
typedef RASearch<NearestNeighborSort, EuclideanDistance, arma::mat,
|
|
StandardCoverTree> RACoverTreeSearch;
|
|
|
|
TreeType refTree(refData);
|
|
TreeType queryTree(queryData);
|
|
|
|
RACoverTreeSearch tsdRann(&refTree, false, 1.0, 0.95, false, false, 5);
|
|
|
|
arma::Mat<size_t> qrRanks;
|
|
data::Load("rann_test_qr_ranks.csv", qrRanks, true, false); // No transpose.
|
|
|
|
size_t numRounds = 1000;
|
|
arma::Col<size_t> numSuccessRounds(queryData.n_cols);
|
|
numSuccessRounds.fill(0);
|
|
|
|
// 1% of 900 is 9, so the rank is expected to be less than 10.
|
|
size_t expectedRankErrorUB = 10;
|
|
|
|
for (size_t rounds = 0; rounds < numRounds; rounds++)
|
|
{
|
|
tsdRann.Search(&queryTree, 1, neighbors, distances);
|
|
|
|
for (size_t i = 0; i < queryData.n_cols; i++)
|
|
if (qrRanks(i, neighbors(0, i)) < expectedRankErrorUB)
|
|
numSuccessRounds[i]++;
|
|
|
|
neighbors.reset();
|
|
distances.reset();
|
|
|
|
tsdRann.ResetQueryTree(&queryTree);
|
|
}
|
|
|
|
// Find the 95%-tile threshold so that 95% of the queries should pass this
|
|
// threshold.
|
|
size_t threshold = floor(numRounds *
|
|
(0.95 - (1.96 * sqrt(0.95 * 0.05 / numRounds))));
|
|
size_t numQueriesFail = 0;
|
|
for (size_t i = 0; i < queryData.n_cols; i++)
|
|
if (numSuccessRounds[i] < threshold)
|
|
numQueriesFail++;
|
|
|
|
Log::Warn << "RANN-TSD (cover tree): RANN guarantee fails on "
|
|
<< numQueriesFail << " queries." << endl;
|
|
|
|
// assert that at most 5% of the queries fall out of this threshold
|
|
// 5% of 100 queries is 5.
|
|
size_t maxNumQueriesFail = 6;
|
|
|
|
BOOST_REQUIRE_LT(numQueriesFail, maxNumQueriesFail);
|
|
}
|
|
|
|
// Test single-tree rank-approximate search with ball trees.
|
|
// This is known to not work right now.
|
|
/*
|
|
BOOST_AUTO_TEST_CASE(SingleBallTreeTest)
|
|
{
|
|
arma::mat refData;
|
|
arma::mat queryData;
|
|
|
|
data::Load("rann_test_r_3_900.csv", refData, true);
|
|
data::Load("rann_test_q_3_100.csv", queryData, true);
|
|
|
|
// Search for 1 rank-approximate nearest-neighbors in the top 30% of the point
|
|
// (rank error of 3).
|
|
arma::Mat<size_t> neighbors;
|
|
arma::mat distances;
|
|
|
|
typedef BinarySpaceTree<BallBound<>, RAQueryStat<NearestNeighborSort> >
|
|
TreeType;
|
|
typedef RASearch<NearestNeighborSort, metric::EuclideanDistance, TreeType>
|
|
RABallTreeSearch;
|
|
|
|
RABallTreeSearch tssRann(refData, queryData, false, true);
|
|
|
|
// The relative ranks for the given query reference pair.
|
|
arma::Mat<size_t> qrRanks;
|
|
data::Load("rann_test_qr_ranks.csv", qrRanks, true, false); // No transpose.
|
|
|
|
size_t numRounds = 30;
|
|
arma::Col<size_t> numSuccessRounds(queryData.n_cols);
|
|
numSuccessRounds.fill(0);
|
|
|
|
// 1% of 900 is 9, so the rank is expected to be less than 10.
|
|
size_t expectedRankErrorUB = 10;
|
|
|
|
for (size_t rounds = 0; rounds < numRounds; rounds++)
|
|
{
|
|
tssRann.Search(1, neighbors, distances, 1.0, 0.95, false, false, 5);
|
|
|
|
for (size_t i = 0; i < queryData.n_cols; i++)
|
|
if (qrRanks(i, neighbors(0, i)) < expectedRankErrorUB)
|
|
numSuccessRounds[i]++;
|
|
|
|
neighbors.reset();
|
|
distances.reset();
|
|
}
|
|
|
|
// Find the 95%-tile threshold so that 95% of the queries should pass this
|
|
// threshold.
|
|
size_t threshold = floor(numRounds *
|
|
(0.95 - (1.96 * sqrt(0.95 * 0.05 / numRounds))));
|
|
size_t numQueriesFail = 0;
|
|
for (size_t i = 0; i < queryData.n_cols; i++)
|
|
if (numSuccessRounds[i] < threshold)
|
|
numQueriesFail++;
|
|
|
|
Log::Warn << "RANN-TSS (ball tree): RANN guarantee fails on "
|
|
<< numQueriesFail << " queries." << endl;
|
|
|
|
// Assert that at most 5% of the queries fall out of this threshold.
|
|
// 5% of 100 queries is 5.
|
|
size_t maxNumQueriesFail = 6;
|
|
|
|
BOOST_REQUIRE_LT(numQueriesFail, maxNumQueriesFail);
|
|
}
|
|
|
|
// Test dual-tree rank-approximate search with Ball trees.
|
|
BOOST_AUTO_TEST_CASE(DualBallTreeTest)
|
|
{
|
|
arma::mat refData;
|
|
arma::mat queryData;
|
|
|
|
data::Load("rann_test_r_3_900.csv", refData, true);
|
|
data::Load("rann_test_q_3_100.csv", queryData, true);
|
|
|
|
// Search for 1 rank-approximate nearest-neighbors in the top 30% of the point
|
|
// (rank error of 3).
|
|
arma::Mat<size_t> neighbors;
|
|
arma::mat distances;
|
|
|
|
typedef BinarySpaceTree<BallBound<>, RAQueryStat<NearestNeighborSort> >
|
|
TreeType;
|
|
typedef RASearch<NearestNeighborSort, metric::EuclideanDistance, TreeType>
|
|
RABallTreeSearch;
|
|
|
|
TreeType refTree(refData);
|
|
TreeType queryTree(queryData);
|
|
|
|
RABallTreeSearch tsdRann(&refTree, &queryTree, refData, queryData, false);
|
|
|
|
arma::Mat<size_t> qrRanks;
|
|
data::Load("rann_test_qr_ranks.csv", qrRanks, true, false); // No transpose.
|
|
|
|
size_t numRounds = 1000;
|
|
arma::Col<size_t> numSuccessRounds(queryData.n_cols);
|
|
numSuccessRounds.fill(0);
|
|
|
|
// 1% of 900 is 9, so the rank is expected to be less than 10.
|
|
size_t expectedRankErrorUB = 10;
|
|
|
|
for (size_t rounds = 0; rounds < numRounds; rounds++)
|
|
{
|
|
tsdRann.Search(1, neighbors, distances, 1.0, 0.95, false, false, 5);
|
|
|
|
for (size_t i = 0; i < queryData.n_cols; i++)
|
|
if (qrRanks(i, neighbors(0, i)) < expectedRankErrorUB)
|
|
numSuccessRounds[i]++;
|
|
|
|
neighbors.reset();
|
|
distances.reset();
|
|
|
|
tsdRann.ResetQueryTree();
|
|
}
|
|
|
|
// Find the 95%-tile threshold so that 95% of the queries should pass this
|
|
// threshold.
|
|
size_t threshold = floor(numRounds *
|
|
(0.95 - (1.96 * sqrt(0.95 * 0.05 / numRounds))));
|
|
size_t numQueriesFail = 0;
|
|
for (size_t i = 0; i < queryData.n_cols; i++)
|
|
if (numSuccessRounds[i] < threshold)
|
|
numQueriesFail++;
|
|
|
|
Log::Warn << "RANN-TSD (Ball tree): RANN guarantee fails on "
|
|
<< numQueriesFail << " queries." << endl;
|
|
|
|
// assert that at most 5% of the queries fall out of this threshold
|
|
// 5% of 100 queries is 5.
|
|
size_t maxNumQueriesFail = 6;
|
|
|
|
BOOST_REQUIRE_LT(numQueriesFail, maxNumQueriesFail);
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* Make sure that the neighborPtr matrix isn't accidentally deleted.
|
|
* See issue #478.
|
|
*/
|
|
BOOST_AUTO_TEST_CASE(NeighborPtrDeleteTest)
|
|
{
|
|
arma::mat dataset = arma::randu<arma::mat>(5, 100);
|
|
|
|
// Build the tree ourselves.
|
|
std::vector<size_t> oldFromNewReferences;
|
|
RASearch<>::Tree tree(dataset);
|
|
RASearch<> allkrann(&tree);
|
|
|
|
// Now make a query set.
|
|
arma::mat queryset = arma::randu<arma::mat>(5, 50);
|
|
arma::mat distances;
|
|
arma::Mat<size_t> neighbors;
|
|
allkrann.Search(queryset, 3, neighbors, distances);
|
|
|
|
// These will (hopefully) fail is either the neighbors or the distances matrix
|
|
// has been accidentally deleted.
|
|
BOOST_REQUIRE_EQUAL(neighbors.n_cols, 50);
|
|
BOOST_REQUIRE_EQUAL(neighbors.n_rows, 3);
|
|
BOOST_REQUIRE_EQUAL(distances.n_cols, 50);
|
|
BOOST_REQUIRE_EQUAL(distances.n_rows, 3);
|
|
}
|
|
|
|
/**
|
|
* Test that the rvalue reference move constructor works.
|
|
*/
|
|
BOOST_AUTO_TEST_CASE(MoveConstructorTest)
|
|
{
|
|
arma::mat dataset = arma::randu<arma::mat>(3, 200);
|
|
arma::mat copy(dataset);
|
|
|
|
KRANN moveknn(std::move(copy));
|
|
KRANN knn(dataset);
|
|
|
|
BOOST_REQUIRE_EQUAL(copy.n_elem, 0);
|
|
BOOST_REQUIRE_EQUAL(moveknn.ReferenceSet().n_rows, 3);
|
|
BOOST_REQUIRE_EQUAL(moveknn.ReferenceSet().n_cols, 200);
|
|
|
|
arma::mat moveDistances, distances;
|
|
arma::Mat<size_t> moveNeighbors, neighbors;
|
|
|
|
moveknn.Search(1, moveNeighbors, moveDistances);
|
|
knn.Search(1, neighbors, distances);
|
|
|
|
BOOST_REQUIRE_EQUAL(moveNeighbors.n_rows, neighbors.n_rows);
|
|
BOOST_REQUIRE_EQUAL(moveNeighbors.n_rows, neighbors.n_rows);
|
|
BOOST_REQUIRE_EQUAL(moveNeighbors.n_cols, neighbors.n_cols);
|
|
BOOST_REQUIRE_EQUAL(moveDistances.n_rows, distances.n_rows);
|
|
BOOST_REQUIRE_EQUAL(moveDistances.n_cols, distances.n_cols);
|
|
}
|
|
|
|
/**
|
|
* Test that the dataset can be retrained with the move Train() function.
|
|
*/
|
|
BOOST_AUTO_TEST_CASE(MoveTrainTest)
|
|
{
|
|
arma::mat dataset = arma::randu<arma::mat>(3, 200);
|
|
|
|
// Do it in tree mode, and in naive mode.
|
|
KRANN knn;
|
|
knn.Train(std::move(dataset));
|
|
|
|
arma::mat distances;
|
|
arma::Mat<size_t> neighbors;
|
|
knn.Search(1, neighbors, distances);
|
|
|
|
BOOST_REQUIRE_EQUAL(dataset.n_elem, 0);
|
|
BOOST_REQUIRE_EQUAL(neighbors.n_cols, 200);
|
|
BOOST_REQUIRE_EQUAL(distances.n_cols, 200);
|
|
|
|
dataset = arma::randu<arma::mat>(3, 300);
|
|
knn.Naive() = true;
|
|
knn.Train(std::move(dataset));
|
|
knn.Search(1, neighbors, distances);
|
|
|
|
BOOST_REQUIRE_EQUAL(dataset.n_elem, 0);
|
|
BOOST_REQUIRE_EQUAL(neighbors.n_cols, 300);
|
|
BOOST_REQUIRE_EQUAL(distances.n_cols, 300);
|
|
}
|
|
|
|
/**
|
|
* Make sure the RAModel class works.
|
|
*/
|
|
BOOST_AUTO_TEST_CASE(RAModelTest)
|
|
{
|
|
// Ensure that we can build an RAModel<NearestNeighborSearch> and get correct
|
|
// results.
|
|
typedef RAModel<NearestNeighborSort> KNNModel;
|
|
|
|
arma::mat queryData, referenceData;
|
|
data::Load("rann_test_r_3_900.csv", referenceData, true);
|
|
data::Load("rann_test_q_3_100.csv", queryData, true);
|
|
|
|
// Build all the possible models.
|
|
KNNModel models[12];
|
|
models[0] = KNNModel(KNNModel::TreeTypes::KD_TREE, false);
|
|
models[1] = KNNModel(KNNModel::TreeTypes::KD_TREE, true);
|
|
models[2] = KNNModel(KNNModel::TreeTypes::COVER_TREE, false);
|
|
models[3] = KNNModel(KNNModel::TreeTypes::COVER_TREE, true);
|
|
models[4] = KNNModel(KNNModel::TreeTypes::R_TREE, false);
|
|
models[5] = KNNModel(KNNModel::TreeTypes::R_TREE, true);
|
|
models[6] = KNNModel(KNNModel::TreeTypes::R_STAR_TREE, false);
|
|
models[7] = KNNModel(KNNModel::TreeTypes::R_STAR_TREE, true);
|
|
models[8] = KNNModel(KNNModel::TreeTypes::X_TREE, false);
|
|
models[9] = KNNModel(KNNModel::TreeTypes::X_TREE, true);
|
|
models[10] = KNNModel(KNNModel::TreeTypes::HILBERT_R_TREE, false);
|
|
models[11] = KNNModel(KNNModel::TreeTypes::HILBERT_R_TREE, true);
|
|
|
|
arma::Mat<size_t> qrRanks;
|
|
data::Load("rann_test_qr_ranks.csv", qrRanks, true, false); // No transpose.
|
|
|
|
for (size_t j = 0; j < 3; ++j)
|
|
{
|
|
for (size_t i = 0; i < 12; ++i)
|
|
{
|
|
// We only have std::move() constructors so make a copy of our data.
|
|
arma::mat referenceCopy(referenceData);
|
|
if (j == 0)
|
|
models[i].BuildModel(std::move(referenceCopy), 20, false, false);
|
|
if (j == 1)
|
|
models[i].BuildModel(std::move(referenceCopy), 20, false, true);
|
|
if (j == 2)
|
|
models[i].BuildModel(std::move(referenceCopy), 20, true, false);
|
|
|
|
// Set the search parameters.
|
|
models[i].Tau() = 1.0;
|
|
models[i].Alpha() = 0.95;
|
|
models[i].SampleAtLeaves() = false;
|
|
models[i].FirstLeafExact() = false;
|
|
models[i].SingleSampleLimit() = 5;
|
|
|
|
arma::Mat<size_t> neighbors;
|
|
arma::mat distances;
|
|
|
|
arma::Col<size_t> numSuccessRounds(queryData.n_cols);
|
|
numSuccessRounds.fill(0);
|
|
|
|
// 1% of 900 is 9, so the rank is expected to be less than 10.
|
|
size_t expectedRankErrorUB = 10;
|
|
|
|
size_t numRounds = 100;
|
|
for (size_t round = 0; round < numRounds; round++)
|
|
{
|
|
arma::mat queryCopy(queryData);
|
|
models[i].Search(std::move(queryCopy), 1, neighbors, distances);
|
|
for (size_t k = 0; k < queryData.n_cols; k++)
|
|
if (qrRanks(k, neighbors(0, k)) < expectedRankErrorUB)
|
|
numSuccessRounds[k]++;
|
|
|
|
neighbors.reset();
|
|
distances.reset();
|
|
}
|
|
|
|
// Find the 95%-tile threshold so that 95% of the queries should pass this
|
|
// threshold.
|
|
size_t threshold = floor(numRounds *
|
|
(0.95 - (1.96 * sqrt(0.95 * 0.05 / numRounds))));
|
|
size_t numQueriesFail = 0;
|
|
for (size_t k = 0; k < queryData.n_cols; k++)
|
|
if (numSuccessRounds[k] < threshold)
|
|
numQueriesFail++;
|
|
|
|
// assert that at most 5% of the queries fall out of this threshold
|
|
// 5% of 100 queries is 5.
|
|
size_t maxNumQueriesFail = 12; // Looser bound due to multiple trials.
|
|
|
|
BOOST_REQUIRE_LT(numQueriesFail, maxNumQueriesFail);
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_SUITE_END();
|