Merge pull request #5 from mlpack/master

update
This commit is contained in:
Keon Kim
2016-06-15 10:14:16 +09:00
committed by GitHub
50 changed files with 763 additions and 427 deletions
+13 -9
View File
@@ -4,12 +4,12 @@ environment:
configuration: Release
os: Visual Studio 2015
install:
- ps: nuget install boost -o "${env:APPVEYOR_BUILD_FOLDER}"
- ps: nuget install boost_unit_test_framework-vc140 -o "${env:APPVEYOR_BUILD_FOLDER}"
- ps: nuget install boost_program_options-vc140 -o "${env:APPVEYOR_BUILD_FOLDER}"
- ps: nuget install boost_random-vc140 -o "${env:APPVEYOR_BUILD_FOLDER}"
- ps: nuget install boost_serialization-vc140 -o "${env:APPVEYOR_BUILD_FOLDER}"
- ps: nuget install boost_math_c99-vc140 -o "${env:APPVEYOR_BUILD_FOLDER}"
- ps: nuget install boost -o "${env:APPVEYOR_BUILD_FOLDER}" -Version 1.60.0
- ps: nuget install boost_unit_test_framework-vc140 -o "${env:APPVEYOR_BUILD_FOLDER}" -Version 1.60.0
- ps: nuget install boost_program_options-vc140 -o "${env:APPVEYOR_BUILD_FOLDER}" -Version 1.60.0
- ps: nuget install boost_random-vc140 -o "${env:APPVEYOR_BUILD_FOLDER}" -Version 1.60.0
- ps: nuget install boost_serialization-vc140 -o "${env:APPVEYOR_BUILD_FOLDER}" -Version 1.60.0
- ps: nuget install boost_math_c99-vc140 -o "${env:APPVEYOR_BUILD_FOLDER}" -Version 1.60.0
- ps: nuget install OpenBLAS -o "${env:APPVEYOR_BUILD_FOLDER}"
build_script:
- mkdir boost_libs
@@ -18,13 +18,13 @@ build_script:
- ps: cp C:\projects\mlpack\boost_random-vc140.1.60.0.0\lib\native\address-model-64\lib\*.* C:\projects\mlpack\boost_libs\
- ps: cp C:\projects\mlpack\boost_serialization-vc140.1.60.0.0\lib\native\address-model-64\lib\*.* C:\projects\mlpack\boost_libs\
- ps: cp C:\projects\mlpack\boost_unit_test_framework-vc140.1.60.0.0\lib\native\address-model-64\lib\*.* C:\projects\mlpack\boost_libs\
- appveyor DownloadFile http://sourceforge.net/projects/arma/files/armadillo-6.500.5.tar.gz
- 7z x armadillo-6.500.5.tar.gz -so | 7z x -si -ttar > nul
- if not exist armadillo.tar.gz appveyor DownloadFile "http://sourceforge.net/projects/arma/files/armadillo-6.500.5.tar.gz" -FileName armadillo.tar.gz
- 7z x armadillo.tar.gz -so | 7z x -si -ttar > nul
- cd armadillo-6.500.5 && mkdir build && cd build
- cmake -G "Visual Studio 14 2015 Win64" -DBLAS_LIBRARY:FILEPATH="%APPVEYOR_BUILD_FOLDER%/OpenBLAS.0.2.14.1/lib/native/lib/x64/libopenblas.dll.a" -DLAPACK_LIBRARY:FILEPATH="%APPVEYOR_BUILD_FOLDER%/OpenBLAS.0.2.14.1/lib/native/lib/x64/libopenblas.dll.a" -DCMAKE_PREFIX:FILEPATH="%APPVEYOR_BUILD_FOLDER%/armadillo" -DBUILD_SHARED_LIBS=OFF ..
- '"C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe" "C:\projects\mlpack\armadillo-6.500.5\build\armadillo.sln" /m /verbosity:quiet /p:Configuration=Release;Platform=x64'
- cd C:\projects\mlpack && mkdir build && cd build
- cmake -G "Visual Studio 14 2015 Win64" -DBLAS_LIBRARY:FILEPATH="%APPVEYOR_BUILD_FOLDER%/OpenBLAS.0.2.14.1/lib/native/lib/x64/libopenblas.dll.a" -DLAPACK_LIBRARY:FILEPATH="%APPVEYOR_BUILD_FOLDER%/OpenBLAS.0.2.14.1/lib/native/lib/x64/libopenblas.dll.a" -DARMADILLO_INCLUDE_DIR="C:/projects/mlpack/armadillo-6.500.5/include" -DARMADILLO_LIBRARY:FILEPATH="C:\projects\mlpack\armadillo-6.500.5\build\Debug\armadillo.lib" -DBOOST_INCLUDEDIR:PATH="C:\projects\mlpack\boost.1.60.0.0\lib\native\include" -DBOOST_LIBRARYDIR:PATH="C:\projects\mlpack\boost_libs" -DDEBUG=OFF -DPROFILE=OFF ..
- cmake -G "Visual Studio 14 2015 Win64" -DBLAS_LIBRARY:FILEPATH="%APPVEYOR_BUILD_FOLDER%/OpenBLAS.0.2.14.1/lib/native/lib/x64/libopenblas.dll.a" -DLAPACK_LIBRARY:FILEPATH="%APPVEYOR_BUILD_FOLDER%/OpenBLAS.0.2.14.1/lib/native/lib/x64/libopenblas.dll.a" -DARMADILLO_INCLUDE_DIR="C:/projects/mlpack/armadillo-6.500.5/include" -DARMADILLO_LIBRARY:FILEPATH="C:\projects\mlpack\armadillo-6.500.5\build\Debug\armadillo.lib" -DBOOST_INCLUDEDIR:PATH="C:\projects\mlpack\boost.1.60.0.0\lib\native\include" -DBOOST_LIBRARYDIR:PATH="C:\projects\mlpack\boost_libs" -DDEBUG=ON -DPROFILE=ON ..
- '"C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe" "C:\projects\mlpack\build\mlpack.sln" /m /verbosity:normal /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" /nologo /p:BuildInParallel=true /p:Configuration=Release;Platform=x64'
- 7z a mlpack-windows-no-libs.zip "%APPVEYOR_BUILD_FOLDER%\build\Release\*.exe"
- 7z a mlpack-windows.zip "%APPVEYOR_BUILD_FOLDER%\build\Release\*.*" "%APPVEYOR_BUILD_FOLDER%/OpenBLAS.0.2.14.1/lib/native/lib/x64/*.*"
@@ -40,6 +40,10 @@ notifications:
on_build_failure: true
on_build_status_changed: true
cache:
- packages -> **\packages.config
- armadillo.tar.gz -> appveyor.yaml
# All plans have maximum build job execution time of 60 minutes. But right, now
# the machine takes 30 minutes to build the code and at least 50 minutes to run
# all tests.
+4
View File
@@ -55,6 +55,10 @@ Copyright:
Copyright 2016, Palash Ahuja <abhor902@gmail.com>
Copyright 2016, Yannis Mentekidis <mentekid@gmail.com>
Copyright 2016, Ranjan Mondal <ranjan.rev@gmail.com>
Copyright 2016, Mikhail Lozhnikov <lozhnikovma@gmail.com>
Copyright 2016, Marcos Pividori <marcos.pividori@gmail.com>
Copyright 2016, Keon Kim <kwk236@gmail.com>
Copyright 2016, Nilay Jain <nilayjain13@gmail.com>
License: BSD-3-clause
All rights reserved.
.
+1 -1
View File
@@ -8,7 +8,7 @@ learning researchers.
**Download [current stable version (2.0.1)](http://www.mlpack.org/files/mlpack-2.0.1.tar.gz).**
[![Build Status](http://big.mlpack.org:7780/job/mlpack%20-%20git%20commit%20test/badge/icon)](http://big.mlpack.org:7780/job/mlpack%20-%20git%20commit%20test/) <a href="https://ci.appveyor.com/project/mlpack/mlpack"><img src="https://ci.appveyor.com/api/projects/status/lmbfc78wi16agx4q?svg=true" alt="Build status" height="18"></a>
[![Build Status](http://big.mlpack.org/job/mlpack%20-%20git%20commit%20test/badge/icon)](http://big.mlpack.org/job/mlpack%20-%20git%20commit%20test/) <a href="https://ci.appveyor.com/project/mlpack/mlpack"><img src="https://ci.appveyor.com/api/projects/status/lmbfc78wi16agx4q?svg=true" alt="Build status" height="18"></a>
0. Contents
-----------
+34 -14
View File
@@ -1,20 +1,40 @@
## Tutorials
Tutorials for mlpack can be found [here : mlpack tutorials](http://www.mlpack.org/tutorial.html).
Tutorials for mlpack can be found [here : mlpack tutorials](http://www.mlpack.org/tutorials.html).
### Method-specific tutorials
* [NeighborSearch tutorial (mlpack_knn / mlpack_kfn)](http://www.mlpack.org/doxygen.php?doc=nstutorial.html)
* [RangeSearch tutorial (mlpack_range_search)](http://www.mlpack.org/doxygen.php?doc=rstutorial.html)
* [LinearRegression tutorial (mlpack_linear_regression)](http://www.mlpack.org/doxygen.php?doc=lrtutorial.html)
* [Density Estimation Trees tutorial (mlpack_det)](http://www.mlpack.org/doxygen.php?doc=dettutorial.html)
* [Euclidean Minimum Spanning Trees tutorial (mlpack_emst)](http://www.mlpack.org/doxygen.php?doc=emst_tutorial.html)
* [K-Means tutorial (mlpack_kmeans)](http://www.mlpack.org/doxygen.php?doc=kmtutorial.html)
* [FastMKS tutorial (mlpack_fastmks)](http://www.mlpack.org/doxygen.php?doc=fmkstutorial.html)
### General mlpack tutorials
* [Building mlpack from source](http://www.mlpack.org/doxygen.php?doc=build.html)
* [mlpack input and output](http://www.mlpack.org/doxygen.php?doc=iodoc.html)
* [Matrices in mlpack](http://www.mlpack.org/doxygen.php?doc=matrices.html)
* [Simple sample mlpack programs](http://www.mlpack.org/doxygen.php?doc=sample.html)
* [mlpack timers](http://www.mlpack.org/doxygen.php?doc=timer.html)
These tutorials introduce the basic concepts of working with mlpack, aimed at developers who want to use and contribute to mlpack but are not sure where to start.
* [Building mlpack from source](http://www.mlpack.org/docs/mlpack-git/doxygen.php?doc=build.html)
* [File Formats in mlpack](http://www.mlpack.org/docs/mlpack-git/doxygen.php?doc=formatdoc.html)
* [Matrices in mlpack](http://www.mlpack.org/docs/mlpack-git/doxygen.php?doc=matrices.html)
* [mlpack input and output](http://www.mlpack.org/docs/mlpack-git/doxygen.php?doc=iodoc.html)
* [mlpack timers](http://www.mlpack.org/docs/mlpack-git/doxygen.php?doc=timer.html)
* [Simple sample mlpack programs](http://www.mlpack.org/docs/mlpack-git/doxygen.php?doc=sample.html)
### Method-specific tutorials
These tutorials introduce the various methods mlpack offers, aimed at users who want to get started quickly. These tutorials start with simple examples and progress to complex, extensible uses.
* [NeighborSearch tutorial (mlpack_knn / mlpack_kfn)](http://www.mlpack.org/docs/mlpack-git/doxygen.php?doc=nstutorial.html)
* [LinearRegression tutorial (mlpack_linear_regression)](http://www.mlpack.org/docs/mlpack-git/doxygen.php?doc=lrtutorial.html)
* [RangeSearch tutorial (mlpack_range_search)](http://www.mlpack.org/docs/mlpack-git/doxygen.php?doc=rstutorial.html)
* [Density Estimation Trees tutorial (mlpack_det)](http://www.mlpack.org/docs/mlpack-git/doxygen.php?doc=dettutorial.html)
* [K-Means tutorial (mlpack_kmeans)](http://www.mlpack.org/docs/mlpack-git/doxygen.php?doc=kmtutorial.html)
* [FastMKS tutorial (mlpack_fastmks)](http://www.mlpack.org/docs/mlpack-git/doxygen.php?doc=fmkstutorial.html)
* [Euclidean Minimum Spanning Trees tutorial (mlpack_emst)](http://www.mlpack.org/docs/mlpack-git/doxygen.php?doc=emst_tutorial.html)
* [Alternating Matrix Factorization Tutorial](http://www.mlpack.org/docs/mlpack-git/doxygen.php?doc=amftutorial.html)
* [Collaborative Filtering Tutorial](http://www.mlpack.org/docs/mlpack-git/doxygen.php?doc=cftutorial.html)
### Policy Class Documentation
mlpack uses templates to achieve its genericity and flexibility. Some of the template types used by mlpack are common across multiple machine learning algorithms. The links below provide documentation for some of these common types.
* [The MetricType policy in mlpack](http://www.mlpack.org/docs/mlpack-git/doxygen.php?doc=metrics.html)
* [The KernelType policy in mlpack](http://www.mlpack.org/docs/mlpack-git/doxygen.php?doc=kernels.html)
* [The TreeType policy in mlpack](http://www.mlpack.org/docs/mlpack-git/doxygen.php?doc=trees.html)
+8 -2
View File
@@ -653,11 +653,13 @@ The \c LloydStepType policy also mandates three functions:
@code
/**
* Run a single iteration of the Lloyd algorithm, updating the given centroids
* into the newCentroids matrix.
* into the newCentroids matrix. If any cluster is empty (that is, if any
* cluster has no points assigned to it), then the centroid associated with
* that cluster may be filled with invalid data (it will be corrected later).
*
* @param centroids Current cluster centroids.
* @param newCentroids New cluster centroids.
* @param counts Counts of the number of points in each cluster.
* @param counts Number of points in each cluster at the end of the iteration.
*/
double Iterate(const arma::mat& centroids,
arma::mat& newCentroids,
@@ -670,6 +672,10 @@ double Iterate(const arma::mat& centroids,
size_t DistanceCalculations() const { return distanceCalculations; }
@endcode
Note that \c Iterate() does not need to return valid centroids if the cluster is
empty. This is because \c EmptyClusterPolicy will handle the empty centroid.
This behavior can be used to avoid small amounts of computation.
For examples, see the five aforementioned implementations of classes that
satisfy the \c LloydStepType policy.
+4
View File
@@ -189,6 +189,10 @@
* - Palash Ahuja <abhor902@gmail.com>
* - Yannis Mentekidis <mentekid@gmail.com>
* - Ranjan Mondal <ranjan.rev@gmail.com>
* - Mikhail Lozhnikov <lozhnikovma@gmail.com>
* - Marcos Pividori <marcos.pividori@gmail.com>
* - Keon Kim <kwk236@gmail.com>
* - Nilay Jain <nilayjain13@gmail.com>
*/
// First, include all of the prerequisites.
@@ -3,6 +3,7 @@
set(SOURCES
arma_extend.hpp
fn_ccov.hpp
fn_ind2sub.hpp
glue_ccov_meat.hpp
glue_ccov_proto.hpp
hdf5_misc.hpp
@@ -66,6 +66,8 @@ namespace arma {
#include "glue_ccov_meat.hpp"
#include "fn_ccov.hpp"
// index to subscript and vice versa
#include "fn_ind2sub.hpp"
// inplace_reshape()
#include "fn_inplace_reshape.hpp"
@@ -0,0 +1,70 @@
#if (ARMA_VERSION_MAJOR < 6 || \
(ARMA_VERSION_MAJOR == 6 && ARMA_VERSION_MINOR < 399))
inline
uvec
ind2sub(const SizeMat& s, const uword i)
{
arma_extra_debug_sigprint();
arma_debug_check( (i >= (s.n_rows * s.n_cols) ), "ind2sub(): index out of range" );
uvec out(2);
out[0] = i % s.n_rows;
out[1] = i / s.n_rows;
return out;
}
inline
uvec
ind2sub(const SizeCube& s, const uword i)
{
arma_extra_debug_sigprint();
arma_debug_check( (i >= (s.n_rows * s.n_cols * s.n_slices) ), "ind2sub(): index out of range" );
const uword n_elem_slice = s.n_rows * s.n_cols;
const uword slice = i / n_elem_slice;
const uword j = i - (slice * n_elem_slice);
const uword row = j % s.n_rows;
const uword col = j / s.n_rows;
uvec out(3);
out[0] = row;
out[1] = col;
out[2] = slice;
return out;
}
arma_inline
uword
sub2ind(const SizeMat& s, const uword row, const uword col)
{
arma_extra_debug_sigprint();
arma_debug_check( ((row >= s.n_rows) || (col >= s.n_cols)), "sub2ind(): subscript out of range" );
return uword(row + col*s.n_rows);
}
arma_inline
uword
sub2ind(const SizeCube& s, const uword row, const uword col, const uword slice)
{
arma_extra_debug_sigprint();
arma_debug_check( ((row >= s.n_rows) || (col >= s.n_cols) || (slice >= s.n_slices)), "sub2ind(): subscript out of range" );
return uword( (slice * s.n_rows * s.n_cols) + (col * s.n_rows) + row );
}
#endif
+1 -1
View File
@@ -46,7 +46,7 @@ class IPMetric
* @return Distance between the two points in kernel space.
*/
template<typename VecTypeA, typename VecTypeB>
double Evaluate(const VecTypeA& a, const VecTypeB& b);
typename VecTypeA::elem_type Evaluate(const VecTypeA& a, const VecTypeB& b);
//! Get the kernel.
const KernelType& Kernel() const { return *kernel; }
+6 -4
View File
@@ -44,8 +44,9 @@ IPMetric<KernelType>::~IPMetric()
template<typename KernelType>
template<typename Vec1Type, typename Vec2Type>
inline double IPMetric<KernelType>::Evaluate(const Vec1Type& a,
const Vec2Type& b)
inline typename Vec1Type::elem_type IPMetric<KernelType>::Evaluate(
const Vec1Type& a,
const Vec2Type& b)
{
// This is the metric induced by the kernel function.
// Maybe we can do better by caching some of this?
@@ -71,8 +72,9 @@ void IPMetric<KernelType>::Serialize(Archive& ar,
// the Euclidean distance.
template<>
template<typename Vec1Type, typename Vec2Type>
inline double IPMetric<kernel::LinearKernel>::Evaluate(const Vec1Type& a,
const Vec2Type& b)
inline typename Vec1Type::elem_type IPMetric<kernel::LinearKernel>::Evaluate(
const Vec1Type& a,
const Vec2Type& b)
{
return metric::LMetric<2, true>::Evaluate(a, b);
}
+2 -1
View File
@@ -75,7 +75,8 @@ class LMetric
* @return Distance between vectors a and b.
*/
template<typename VecTypeA, typename VecTypeB>
static double Evaluate(const VecTypeA& a, const VecTypeB& b);
static typename VecTypeA::elem_type Evaluate(const VecTypeA& a,
const VecTypeB& b);
//! Serialize the metric (nothing to do).
template<typename Archive>
+36 -21
View File
@@ -16,74 +16,89 @@ namespace metric {
// Unspecialized implementation. This should almost never be used...
template<int Power, bool TakeRoot>
template<typename VecTypeA, typename VecTypeB>
double LMetric<Power, TakeRoot>::Evaluate(const VecTypeA& a,
const VecTypeB& b)
typename VecTypeA::elem_type LMetric<Power, TakeRoot>::Evaluate(
const VecTypeA& a,
const VecTypeB& b)
{
double sum = 0;
typename VecTypeA::elem_type sum = 0;
for (size_t i = 0; i < a.n_elem; i++)
sum += pow(fabs(a[i] - b[i]), Power);
sum += std::pow(fabs(a[i] - b[i]), Power);
if (!TakeRoot) // The compiler should optimize this correctly at compile-time.
return sum;
return pow(sum, (1.0 / Power));
return std::pow(sum, (1.0 / Power));
}
// L1-metric specializations; the root doesn't matter.
template<>
template<typename VecTypeA, typename VecTypeB>
double LMetric<1, true>::Evaluate(const VecTypeA& a, const VecTypeB& b)
typename VecTypeA::elem_type LMetric<1, true>::Evaluate(
const VecTypeA& a,
const VecTypeB& b)
{
return accu(abs(a - b));
return arma::accu(abs(a - b));
}
template<>
template<typename VecTypeA, typename VecTypeB>
double LMetric<1, false>::Evaluate(const VecTypeA& a, const VecTypeB& b)
typename VecTypeA::elem_type LMetric<1, false>::Evaluate(
const VecTypeA& a,
const VecTypeB& b)
{
return accu(abs(a - b));
return arma::accu(abs(a - b));
}
// L2-metric specializations.
template<>
template<typename VecTypeA, typename VecTypeB>
double LMetric<2, true>::Evaluate(const VecTypeA& a, const VecTypeB& b)
typename VecTypeA::elem_type LMetric<2, true>::Evaluate(
const VecTypeA& a,
const VecTypeB& b)
{
return sqrt(accu(square(a - b)));
return sqrt(arma::accu(square(a - b)));
}
template<>
template<typename VecTypeA, typename VecTypeB>
double LMetric<2, false>::Evaluate(const VecTypeA& a, const VecTypeB& b)
typename VecTypeA::elem_type LMetric<2, false>::Evaluate(
const VecTypeA& a,
const VecTypeB& b)
{
return accu(square(a - b));
return accu(arma::square(a - b));
}
// L3-metric specialization (not very likely to be used, but just in case).
template<>
template<typename VecTypeA, typename VecTypeB>
double LMetric<3, true>::Evaluate(const VecTypeA& a, const VecTypeB& b)
typename VecTypeA::elem_type LMetric<3, true>::Evaluate(
const VecTypeA& a,
const VecTypeB& b)
{
double sum = 0;
typename VecTypeA::elem_type sum = 0;
for (size_t i = 0; i < a.n_elem; i++)
sum += pow(fabs(a[i] - b[i]), 3.0);
sum += std::pow(fabs(a[i] - b[i]), 3.0);
return pow(accu(pow(abs(a - b), 3.0)), 1.0 / 3.0);
return std::pow(arma::accu(arma::pow(arma::abs(a - b), 3.0)), 1.0 / 3.0);
}
template<>
template<typename VecTypeA, typename VecTypeB>
double LMetric<3, false>::Evaluate(const VecTypeA& a, const VecTypeB& b)
typename VecTypeA::elem_type LMetric<3, false>::Evaluate(
const VecTypeA& a,
const VecTypeB& b)
{
return accu(pow(abs(a - b), 3.0));
return arma::accu(arma::pow(arma::abs(a - b), 3.0));
}
// L-infinity (Chebyshev distance) specialization
template<>
template<typename VecTypeA, typename VecTypeB>
double LMetric<INT_MAX, false>::Evaluate(const VecTypeA& a, const VecTypeB& b)
typename VecTypeA::elem_type LMetric<INT_MAX, false>::Evaluate(
const VecTypeA& a,
const VecTypeB& b)
{
return arma::as_scalar(max(abs(a - b)));
return arma::as_scalar(arma::max(arma::abs(a - b)));
}
} // namespace metric
@@ -11,7 +11,6 @@
#include <mlpack/core/util/cli.hpp>
#include <mlpack/core/util/log.hpp>
#include <mlpack/core/util/string_util.hpp>
#include <queue>
namespace mlpack {
@@ -10,7 +10,6 @@
// In case it hasn't already been included.
#include "cover_tree.hpp"
#include <mlpack/core/util/string_util.hpp>
#include <string>
namespace mlpack {
@@ -22,27 +22,27 @@ template <typename TreeType>
class RStarTreeSplit
{
public:
//! Default constructor
RStarTreeSplit();
//! Default constructor.
RStarTreeSplit() { }
//! Construct this with the specified node.
RStarTreeSplit(const TreeType *node);
RStarTreeSplit(const TreeType* /* node */) { }
//! Create a copy of the other.split.
RStarTreeSplit(const TreeType &other);
RStarTreeSplit(const TreeType& /* other */) { }
/**
* Split a leaf node using the algorithm described in "The R*-tree: An
* Efficient and Robust Access method for Points and Rectangles." If
* necessary, this split will propagate upwards through the tree.
*/
void SplitLeafNode(TreeType *tree,std::vector<bool>& relevels);
void SplitLeafNode(TreeType* tree, std::vector<bool>& relevels);
/**
* Split a non-leaf node using the "default" algorithm. If this is a root
* node, the tree increases in depth.
*/
bool SplitNonLeafNode(TreeType *tree,std::vector<bool>& relevels);
bool SplitNonLeafNode(TreeType* tree, std::vector<bool>& relevels);
private:
/**
@@ -15,25 +15,6 @@
namespace mlpack {
namespace tree {
template<typename TreeType>
RStarTreeSplit<TreeType>::RStarTreeSplit()
{
}
template<typename TreeType>
RStarTreeSplit<TreeType>::RStarTreeSplit(const TreeType *)
{
}
template<typename TreeType>
RStarTreeSplit<TreeType>::RStarTreeSplit(const TreeType &)
{
}
/**
* We call GetPointSeeds to get the two points which will be the initial points
* in the new nodes We then call AssignPointDestNode to assign the remaining
@@ -288,7 +269,8 @@ void RStarTreeSplit<TreeType>::SplitLeafNode(TreeType *tree,std::vector<bool>& r
* higher up the tree because they were already updated if necessary.
*/
template<typename TreeType>
bool RStarTreeSplit<TreeType>::SplitNonLeafNode(TreeType *tree,std::vector<bool>& relevels)
bool RStarTreeSplit<TreeType>::SplitNonLeafNode(TreeType* tree,
std::vector<bool>& relevels)
{
// Convenience typedef.
typedef typename TreeType::ElemType ElemType;
@@ -691,7 +673,8 @@ bool RStarTreeSplit<TreeType>::SplitNonLeafNode(TreeType *tree,std::vector<bool>
* numberOfChildren.
*/
template<typename TreeType>
void RStarTreeSplit<TreeType>::InsertNodeIntoTree(TreeType* destTree, TreeType* srcNode)
void RStarTreeSplit<TreeType>::InsertNodeIntoTree(TreeType* destTree,
TreeType* srcNode)
{
destTree->Bound() |= srcNode->Bound();
destTree->Children()[destTree->NumChildren()++] = srcNode;
@@ -22,37 +22,37 @@ template<typename TreeType>
class RTreeSplit
{
public:
//! Default constructor
RTreeSplit();
//! Default constructor.
RTreeSplit() { }
//! Construct this with the specified node.
RTreeSplit(const TreeType *node);
RTreeSplit(const TreeType* /* node */) { }
//! Create a copy of the other.split.
RTreeSplit(const TreeType &other);
//! Create a copy of the other split.
RTreeSplit(const TreeType& /* other */) { }
/**
* Split a leaf node using the "default" algorithm. If necessary, this split
* will propagate upwards through the tree.
*/
void SplitLeafNode(TreeType *tree,std::vector<bool>& relevels);
void SplitLeafNode(TreeType* tree, std::vector<bool>& relevels);
/**
* Split a non-leaf node using the "default" algorithm. If this is a root
* node, the tree increases in depth.
*/
bool SplitNonLeafNode(TreeType *tree,std::vector<bool>& relevels);
bool SplitNonLeafNode(TreeType* tree, std::vector<bool>& relevels);
private:
/**
* Get the seeds for splitting a leaf node.
*/
static void GetPointSeeds(const TreeType *tree,int& i, int& j);
static void GetPointSeeds(const TreeType* tree, int& i, int& j);
/**
* Get the seeds for splitting a non-leaf node.
*/
static void GetBoundSeeds(const TreeType *tree,int& i, int& j);
static void GetBoundSeeds(const TreeType* tree, int& i, int& j);
/**
* Assign points to the two new nodes.
@@ -14,24 +14,6 @@
namespace mlpack {
namespace tree {
template<typename TreeType>
RTreeSplit<TreeType>::RTreeSplit()
{
}
template<typename TreeType>
RTreeSplit<TreeType>::RTreeSplit(const TreeType *)
{
}
template<typename TreeType>
RTreeSplit<TreeType>::RTreeSplit(const TreeType &)
{
}
/**
* We call GetPointSeeds to get the two points which will be the initial points
* in the new nodes We then call AssignPointDestNode to assign the remaining
@@ -39,7 +21,8 @@ RTreeSplit<TreeType>::RTreeSplit(const TreeType &)
* new nodes into the tree, spliting the parent if necessary.
*/
template<typename TreeType>
void RTreeSplit<TreeType>::SplitLeafNode(TreeType *tree,std::vector<bool>& relevels)
void RTreeSplit<TreeType>::SplitLeafNode(TreeType* tree,
std::vector<bool>& relevels)
{
// If we are splitting the root node, we need will do things differently so
// that the constructor and other methods don't confuse the end user by giving
@@ -103,7 +86,8 @@ void RTreeSplit<TreeType>::SplitLeafNode(TreeType *tree,std::vector<bool>& relev
* higher up the tree because they were already updated if necessary.
*/
template<typename TreeType>
bool RTreeSplit<TreeType>::SplitNonLeafNode(TreeType *tree,std::vector<bool>& relevels)
bool RTreeSplit<TreeType>::SplitNonLeafNode(TreeType* tree,
std::vector<bool>& relevels)
{
// If we are splitting the root node, we need will do things differently so
// that the constructor and other methods don't confuse the end user by giving
@@ -175,7 +159,9 @@ bool RTreeSplit<TreeType>::SplitNonLeafNode(TreeType *tree,std::vector<bool>& re
* The indices of these points will be stored in iRet and jRet.
*/
template<typename TreeType>
void RTreeSplit<TreeType>::GetPointSeeds(const TreeType *tree,int& iRet, int& jRet)
void RTreeSplit<TreeType>::GetPointSeeds(const TreeType* tree,
int& iRet,
int& jRet)
{
// Here we want to find the pair of points that it is worst to place in the
// same node. Because we are just using points, we will simply choose the two
@@ -203,7 +189,9 @@ void RTreeSplit<TreeType>::GetPointSeeds(const TreeType *tree,int& iRet, int& jR
* indices of the bounds will be stored in iRet and jRet.
*/
template<typename TreeType>
void RTreeSplit<TreeType>::GetBoundSeeds(const TreeType *tree,int& iRet, int& jRet)
void RTreeSplit<TreeType>::GetBoundSeeds(const TreeType* tree,
int& iRet,
int& jRet)
{
// Convenience typedef.
typedef typename TreeType::ElemType ElemType;
@@ -235,10 +223,10 @@ void RTreeSplit<TreeType>::GetBoundSeeds(const TreeType *tree,int& iRet, int& jR
template<typename TreeType>
void RTreeSplit<TreeType>::AssignPointDestNode(TreeType* oldTree,
TreeType* treeOne,
TreeType* treeTwo,
const int intI,
const int intJ)
TreeType* treeOne,
TreeType* treeTwo,
const int intI,
const int intJ)
{
// Convenience typedef.
typedef typename TreeType::ElemType ElemType;
@@ -376,10 +364,10 @@ void RTreeSplit<TreeType>::AssignPointDestNode(TreeType* oldTree,
template<typename TreeType>
void RTreeSplit<TreeType>::AssignNodeDestNode(TreeType* oldTree,
TreeType* treeOne,
TreeType* treeTwo,
const int intI,
const int intJ)
TreeType* treeOne,
TreeType* treeTwo,
const int intI,
const int intJ)
{
// Convenience typedef.
typedef typename TreeType::ElemType ElemType;
@@ -540,7 +528,8 @@ void RTreeSplit<TreeType>::AssignNodeDestNode(TreeType* oldTree,
* numberOfChildren.
*/
template<typename TreeType>
void RTreeSplit<TreeType>::InsertNodeIntoTree(TreeType* destTree, TreeType* srcNode)
void RTreeSplit<TreeType>::InsertNodeIntoTree(TreeType* destTree,
TreeType* srcNode)
{
destTree->Bound() |= srcNode->Bound();
destTree->Children()[destTree->NumChildren()++] = srcNode;
@@ -154,7 +154,7 @@ class RectangleTree
* @param numMaxChildren The max number of child nodes (used in x-trees).
*/
explicit RectangleTree(RectangleTree* parentNode,
const size_t numMaxChildren = 0);
const size_t numMaxChildren = 0);
/**
* Create a rectangle tree by copying the other tree. Be careful! This can
@@ -12,7 +12,6 @@
#include <mlpack/core/util/cli.hpp>
#include <mlpack/core/util/log.hpp>
#include <mlpack/core/util/string_util.hpp>
namespace mlpack {
namespace tree {
@@ -105,8 +104,9 @@ template<typename MetricType,
RectangleTree<MetricType, StatisticType, MatType, SplitType, DescentType>::
RectangleTree(
RectangleTree<MetricType, StatisticType, MatType, SplitType, DescentType>*
parentNode,const size_t numMaxChildren) :
maxNumChildren(numMaxChildren > 0 ? numMaxChildren : parentNode->MaxNumChildren()),
parentNode, const size_t numMaxChildren) :
maxNumChildren(numMaxChildren > 0 ? numMaxChildren :
parentNode->MaxNumChildren()),
minNumChildren(parentNode->MinNumChildren()),
numChildren(0),
children(maxNumChildren + 1),
@@ -36,23 +36,23 @@ class XTreeSplit
XTreeSplit();
//! Construct this with the specified node.
XTreeSplit(const TreeType *node);
XTreeSplit(const TreeType* node);
//! Create a copy of the other.split.
XTreeSplit(const TreeType &other);
XTreeSplit(const TreeType& other);
/**
* Split a leaf node using the algorithm described in "The R*-tree: An
* Efficient and Robust Access method for Points and Rectangles." If
* necessary, this split will propagate upwards through the tree.
*/
void SplitLeafNode(TreeType *tree,std::vector<bool>& relevels);
void SplitLeafNode(TreeType* tree, std::vector<bool>& relevels);
/**
* Split a non-leaf node using the "default" algorithm. If this is a root
* node, the tree increases in depth.
*/
bool SplitNonLeafNode(TreeType *tree,std::vector<bool>& relevels);
bool SplitNonLeafNode(TreeType* tree, std::vector<bool>& relevels);
/**
* The X tree requires that the tree records it's "split history". To make
@@ -98,7 +98,7 @@ class XTreeSplit
* Comparator for sorting with sortStruct.
*/
template<typename ElemType>
static bool structComp(const sortStruct<ElemType>& s1,
static bool structComp(const sortStruct<ElemType>& s1,
const sortStruct<ElemType>& s2)
{
return s1.d < s2.d;
@@ -119,7 +119,6 @@ class XTreeSplit
//! Modify the split history of the node assosiated with this object.
SplitHistoryStruct& SplitHistory() { return splitHistory; }
/**
* Serialize the split.
*/
@@ -23,10 +23,10 @@ XTreeSplit<TreeType>::XTreeSplit() :
}
template<typename TreeType>
XTreeSplit<TreeType>::XTreeSplit(const TreeType *node) :
normalNodeMaxNumChildren(node->Parent() ?
node->Parent()->Split().NormalNodeMaxNumChildren() :
node->MaxNumChildren()),
XTreeSplit<TreeType>::XTreeSplit(const TreeType*node) :
normalNodeMaxNumChildren(node->Parent() ?
node->Parent()->Split().NormalNodeMaxNumChildren() :
node->MaxNumChildren()),
splitHistory(node->Bound().Dim())
{
@@ -40,7 +40,6 @@ XTreeSplit<TreeType>::XTreeSplit(const TreeType &other) :
}
/**
* We call GetPointSeeds to get the two points which will be the initial points
* in the new nodes We then call AssignPointDestNode to assign the remaining
@@ -48,7 +47,8 @@ XTreeSplit<TreeType>::XTreeSplit(const TreeType &other) :
* new nodes into the tree, spliting the parent if necessary.
*/
template<typename TreeType>
void XTreeSplit<TreeType>::SplitLeafNode(TreeType *tree,std::vector<bool>& relevels)
void XTreeSplit<TreeType>::SplitLeafNode(TreeType* tree,
std::vector<bool>& relevels)
{
// Convenience typedef.
typedef typename TreeType::ElemType ElemType;
@@ -245,8 +245,8 @@ void XTreeSplit<TreeType>::SplitLeafNode(TreeType *tree,std::vector<bool>& relev
std::sort(sorted.begin(), sorted.end(), structComp<ElemType>);
TreeType* treeOne = new TreeType(tree->Parent(),NormalNodeMaxNumChildren());
TreeType* treeTwo = new TreeType(tree->Parent(),NormalNodeMaxNumChildren());
TreeType* treeOne = new TreeType(tree->Parent(), NormalNodeMaxNumChildren());
TreeType* treeTwo = new TreeType(tree->Parent(), NormalNodeMaxNumChildren());
// The leaf nodes should never have any overlap introduced by the above method
// since a split axis is chosen and then points are assigned based on their
@@ -319,7 +319,8 @@ void XTreeSplit<TreeType>::SplitLeafNode(TreeType *tree,std::vector<bool>& relev
* higher up the tree because they were already updated if necessary.
*/
template<typename TreeType>
bool XTreeSplit<TreeType>::SplitNonLeafNode(TreeType *tree,std::vector<bool>& relevels)
bool XTreeSplit<TreeType>::SplitNonLeafNode(TreeType* tree,
std::vector<bool>& relevels)
{
// Convenience typedef.
typedef typename TreeType::ElemType ElemType;
@@ -684,8 +685,8 @@ bool XTreeSplit<TreeType>::SplitNonLeafNode(TreeType *tree,std::vector<bool>& re
std::sort(sorted.begin(), sorted.end(), structComp<ElemType>);
TreeType* treeOne = new TreeType(tree->Parent(),tree->MaxNumChildren());
TreeType* treeTwo = new TreeType(tree->Parent(),tree->MaxNumChildren());
TreeType* treeOne = new TreeType(tree->Parent(), tree->MaxNumChildren());
TreeType* treeTwo = new TreeType(tree->Parent(), tree->MaxNumChildren());
// Now as per the X-tree paper, we ensure that this split was good enough.
bool useMinOverlapSplit = false;
@@ -770,7 +771,8 @@ bool XTreeSplit<TreeType>::SplitNonLeafNode(TreeType *tree,std::vector<bool>& re
(tree->Parent()->NumChildren() == 1))
{
// We make the root a supernode instead.
tree->Parent()->MaxNumChildren() = tree->MaxNumChildren() + NormalNodeMaxNumChildren();
tree->Parent()->MaxNumChildren() = tree->MaxNumChildren() +
NormalNodeMaxNumChildren();
tree->Parent()->Children().resize(tree->Parent()->MaxNumChildren() + 1);
tree->Parent()->NumChildren() = tree->NumChildren();
for (size_t i = 0; i < tree->NumChildren(); i++)
-2
View File
@@ -20,8 +20,6 @@ set(SOURCES
prefixedoutstream.cpp
prefixedoutstream_impl.hpp
sfinae_utility.hpp
string_util.hpp
string_util.cpp
timers.hpp
timers.cpp
version.hpp
@@ -19,7 +19,6 @@
#include <boost/type_traits.hpp>
#include <mlpack/core/util/sfinae_utility.hpp>
#include <mlpack/core/util/string_util.hpp>
namespace mlpack {
namespace util {
-47
View File
@@ -1,47 +0,0 @@
/**
* @file string_util.cpp
* @author Trironk Kiatkungwanglai
* @author Ryan Birmingham
*
* Defines methods useful for formatting output.
*/
#include "string_util.hpp"
using namespace mlpack;
using namespace mlpack::util;
using namespace std;
//! A utility function that replaces all all newlines with a number of spaces
//! depending on the indentation level.
string mlpack::util::Indent(string input, const size_t howManyTabs)
{
// For each declared...
string standardTab = " ";
string bigTab = "";
for (size_t ind = 0; ind < howManyTabs; ind++)
{
// Increase amount tabbed on later lines.
bigTab += standardTab;
// Add indentation to first line.
input.insert(0, 1, ' ');
input.insert(0, 1, ' ');
}
// Create the character sequence to replace all newline characters.
std::string tabbedNewline("\n" + bigTab);
// Replace all newline characters with the precomputed character sequence.
size_t startPos = 0;
while ((startPos = input.find("\n", startPos)) != string::npos)
{
// Don't replace the last newline.
if (startPos == input.length() - 1)
break;
input.replace(startPos, 1, tabbedNewline);
startPos += tabbedNewline.length();
}
return input;
}
-23
View File
@@ -1,23 +0,0 @@
/**
* @file string_util.hpp
* @author Trironk Kiatkungwanglai
* @author Ryan Birmingham
*
* Declares methods that are useful for writing formatting output.
*/
#ifndef MLPACK_CORE_STRING_UTIL_HPP
#define MLPACK_CORE_STRING_UTIL_HPP
#include <string>
namespace mlpack {
namespace util {
//! A utility function that replaces all all newlines with a number of spaces
//! depending on the indentation level.
std::string Indent(std::string input, const size_t howManyTabs = 1);
} // namespace util
} // namespace mlpack
#endif
-28
View File
@@ -37,20 +37,6 @@ class DTBRules
*/
double Score(const size_t queryIndex, TreeType& referenceNode);
/**
* Get the score for recursion order, passing the base case result (in the
* situation where it may be needed to calculate the recursion order). A low
* score indicates priority for recursion, while DBL_MAX indicates that the
* node should not be recursed into at all (it should be pruned).
*
* @param queryIndex Index of query point.
* @param referenceNode Candidate node to be recursed into.
* @param baseCaseResult Result of BaseCase(queryIndex, referenceNode).
*/
double Score(const size_t queryIndex,
TreeType& referenceNode,
const double baseCaseResult);
/**
* Re-evaluate the score for recursion order. A low score indicates priority
* for recursion, while DBL_MAX indicates that the node should not be recursed
@@ -76,20 +62,6 @@ class DTBRules
*/
double Score(TreeType& queryNode, TreeType& referenceNode);
/**
* Get the score for recursion order, passing the base case result (in the
* situation where it may be needed to calculate the recursion order). A low
* score indicates priority for recursion, while DBL_MAX indicates that the
* node should not be recursed into at all (it should be pruned).
*
* @param queryNode Candidate query node to recurse into.
* @param referenceNode Candidate reference node to recurse into.
* @param baseCaseResult Result of BaseCase(queryIndex, referenceNode).
*/
double Score(TreeType& queryNode,
TreeType& referenceNode,
const double baseCaseResult);
/**
* Re-evaluate the score for recursion order. A low score indicates priority
* for recursion, while DBL_MAX indicates that the node should not be recursed
@@ -92,31 +92,6 @@ double DTBRules<MetricType, TreeType>::Score(const size_t queryIndex,
? DBL_MAX : distance;
}
template<typename MetricType, typename TreeType>
double DTBRules<MetricType, TreeType>::Score(const size_t queryIndex,
TreeType& referenceNode,
const double baseCaseResult)
{
// I don't really understand the last argument here
// It just gets passed in the distance call, otherwise this function
// is the same as the one above.
size_t queryComponentIndex = connections.Find(queryIndex);
// If the query belongs to the same component as all of the references,
// then prune.
if (queryComponentIndex == referenceNode.Stat().ComponentMembership())
return DBL_MAX;
const arma::vec queryPoint = dataSet.unsafe_col(queryIndex);
const double distance = referenceNode.MinDistance(queryPoint,
baseCaseResult);
// If all the points in the reference node are farther than the candidate
// nearest neighbor for the query's component, we prune.
return (neighborsDistances[queryComponentIndex] < distance) ? DBL_MAX :
distance;
}
template<typename MetricType, typename TreeType>
double DTBRules<MetricType, TreeType>::Rescore(const size_t queryIndex,
TreeType& /* referenceNode */,
@@ -148,27 +123,6 @@ double DTBRules<MetricType, TreeType>::Score(TreeType& queryNode,
return (bound < distance) ? DBL_MAX : distance;
}
template<typename MetricType, typename TreeType>
double DTBRules<MetricType, TreeType>::Score(TreeType& queryNode,
TreeType& referenceNode,
const double baseCaseResult)
{
// If all the queries belong to the same component as all the references
// then we prune.
if ((queryNode.Stat().ComponentMembership() >= 0) &&
(queryNode.Stat().ComponentMembership() ==
referenceNode.Stat().ComponentMembership()))
return DBL_MAX;
++scores;
const double distance = queryNode.MinDistance(referenceNode, baseCaseResult);
const double bound = CalculateBound(queryNode);
// If all the points in the reference node are farther than the candidate
// nearest neighbor for all queries in the node, we prune.
return (bound < distance) ? DBL_MAX : distance;
}
template<typename MetricType, typename TreeType>
double DTBRules<MetricType, TreeType>::Rescore(TreeType& queryNode,
TreeType& /* referenceNode */,
+1
View File
@@ -11,6 +11,7 @@ set(SOURCES
elkan_kmeans_impl.hpp
hamerly_kmeans.hpp
hamerly_kmeans_impl.hpp
kill_empty_clusters.hpp
kmeans.hpp
kmeans_impl.hpp
max_variance_new_cluster.hpp
@@ -24,8 +24,8 @@ class AllowEmptyClusters
AllowEmptyClusters() { }
/**
* This function does nothing. It is called by K-Means when K-Means detects
* an empty cluster.
* This function allows empty clusters to persist simply by leaving the empty
* cluster in its last position.
*
* @tparam MatType Type of data (arma::mat or arma::spmat).
* @param data Dataset on which clustering is being performed.
@@ -43,15 +43,16 @@ class AllowEmptyClusters
template<typename MetricType, typename MatType>
static inline force_inline size_t EmptyCluster(
const MatType& /* data */,
const size_t /* emptyCluster */,
const arma::mat& /* oldCentroids */,
arma::mat& /* newCentroids */,
const size_t emptyCluster,
const arma::mat& oldCentroids,
arma::mat& newCentroids,
arma::Col<size_t>& /* clusterCounts */,
MetricType& /* metric */,
const size_t /* iteration */)
{
// Empty clusters are okay! Do nothing.
return 0;
// Take the last iteration's centroid.
newCentroids.col(emptyCluster) = oldCentroids.col(emptyCluster);
return 0; // No points were changed.
}
//! Serialize the empty cluster policy (nothing to do).
@@ -179,7 +179,6 @@ double DualTreeKMeans<MetricType, MatType, TreeType>::Iterate(
{
if (counts[c] == 0)
{
newCentroids.col(c).fill(DBL_MAX);
clusterDistances[c] = 0;
}
else
@@ -155,8 +155,6 @@ double ElkanKMeans<MetricType, MatType>::Iterate(const arma::mat& centroids,
{
if (counts[c] > 0)
newCentroids.col(c) /= counts[c];
else
newCentroids.col(c).fill(DBL_MAX); // Fill with invalid value.
moveDistances(c) = metric.Evaluate(newCentroids.col(c), centroids.col(c));
cNorm += std::pow(moveDistances(c), 2.0);
@@ -132,8 +132,6 @@ double HamerlyKMeans<MetricType, MatType>::Iterate(const arma::mat& centroids,
{
if (counts(c) > 0)
newCentroids.col(c) /= counts(c);
else
newCentroids.col(c).fill(DBL_MAX); // Empty cluster.
// Calculate movement.
const double movement = metric.Evaluate(centroids.col(c),
@@ -0,0 +1,66 @@
/**
* @file allow_empty_clusters.hpp
* @author Ryan Curtin
*
* This very simple policy is used when K-Means is allowed to return empty
* clusters.
*/
#ifndef __MLPACK_METHODS_KMEANS_KILL_EMPTY_CLUSTERS_HPP
#define __MLPACK_METHODS_KMEANS_KILL_EMPTY_CLUSTERS_HPP
#include <mlpack/core.hpp>
namespace mlpack {
namespace kmeans {
/**
* Policy which allows K-Means to "kill" empty clusters without any error being
* reported. This means the centroids will be filled with DBL_MAX.
*/
class KillEmptyClusters
{
public:
//! Default constructor required by EmptyClusterPolicy policy.
KillEmptyClusters() { }
/**
* This function sets an empty cluster found during k-means to all DBL_MAX
* (i.e. an invalid "dead" cluster).
*
* @tparam MatType Type of data (arma::mat or arma::spmat).
* @param data Dataset on which clustering is being performed.
* @param emptyCluster Index of cluster which is empty.
* @param oldCentroids Centroids of each cluster (one per column) at the start
* of the iteration.
* @param newCentroids Centroids of each cluster (one per column) at the end
* of the iteration.
* @param clusterCounts Number of points in each cluster.
* @param assignments Cluster assignments of each point.
* @param iteration Number of iteration.
*
* @return Number of points changed (0).
*/
template<typename MetricType, typename MatType>
static inline force_inline size_t EmptyCluster(
const MatType& /* data */,
const size_t emptyCluster,
const arma::mat& /* oldCentroids */,
arma::mat& newCentroids,
arma::Col<size_t>& /* clusterCounts */,
MetricType& /* metric */,
const size_t /* iteration */)
{
// Kill the empty cluster.
newCentroids.col(emptyCluster).fill(DBL_MAX);
return 0; // No points were changed.
}
//! Serialize the empty cluster policy (nothing to do).
template<typename Archive>
void Serialize(Archive& /* ar */, const unsigned int /* version */) { }
};
} // namespace kmeans
} // namespace mlpack
#endif
+23 -3
View File
@@ -8,6 +8,7 @@
#include "kmeans.hpp"
#include "allow_empty_clusters.hpp"
#include "kill_empty_clusters.hpp"
#include "refined_start.hpp"
#include "elkan_kmeans.hpp"
#include "hamerly_kmeans.hpp"
@@ -42,8 +43,19 @@ PROGRAM_INFO("K-Means Clustering", "This program performs K-Means clustering "
"('hamerly'), the dual-tree k-means algorithm ('dualtree'), and the "
"dual-tree k-means algorithm using the cover tree ('dualtree-covertree')."
"\n\n"
"The behavior for when an empty cluster is encountered can be modified with"
" the --allow_empty_clusters (-e) option. When this option is specified "
"and there is a cluster owning no points at the end of an iteration, that "
"cluster's centroid will simply remain in its position from the previous "
"iteration. If the --kill_empty_clusters (-E) option is specified, then "
"when a cluster owns no points at the end of an iteration, the cluster "
"centroid is simply filled with DBL_MAX, killing it and effectively "
"reducing k for the rest of the computation. Note that the default option "
"when neither empty cluster option is specified can be time-consuming to "
"calculate; therefore, specifying -e or -E will often accelerate runtime."
"\n\n"
"As of October 2014, the --overclustering option has been removed. If you "
"want this support back, let us know -- file a bug at "
"want this support back, let us know---file a bug at "
"https://github.com/mlpack/mlpack/ or get in touch through another means.");
// Required options.
@@ -61,7 +73,9 @@ PARAM_STRING("centroid_file", "If specified, the centroids of each cluster will"
" be written to the given file.", "C", "");
// k-means configuration options.
PARAM_FLAG("allow_empty_clusters", "Allow empty clusters to be created.", "e");
PARAM_FLAG("allow_empty_clusters", "Allow empty clusters to be persist.", "e");
PARAM_FLAG("kill_empty_clusters", "Remove empty clusters when they occur.",
"E");
PARAM_FLAG("labels_only", "Only output labels into output file.", "l");
PARAM_INT("max_iterations", "Maximum number of iterations before K-Means "
"terminates.", "m", 1000);
@@ -135,8 +149,14 @@ int main(int argc, char** argv)
template<typename InitialPartitionPolicy>
void FindEmptyClusterPolicy(const InitialPartitionPolicy& ipp)
{
if (CLI::HasParam("allow_empty_clusters"))
if (CLI::HasParam("allow_empty_clusters") &&
CLI::HasParam("kill_empty_clusters"))
Log::Fatal << "Only one of --allow_empty_clusters (-e) or "
<< "--kill_empty_clusters (-E) may be specified!" << endl;
else if (CLI::HasParam("allow_empty_clusters"))
FindLloydStepType<InitialPartitionPolicy, AllowEmptyClusters>(ipp);
else if (CLI::HasParam("kill_empty_clusters"))
FindLloydStepType<InitialPartitionPolicy, KillEmptyClusters>(ipp);
else
FindLloydStepType<InitialPartitionPolicy, MaxVarianceNewCluster>(ipp);
}
+4 -1
View File
@@ -35,10 +35,13 @@ class NaiveKMeans
/**
* Run a single iteration of the Lloyd algorithm, updating the given centroids
* into the newCentroids matrix.
* into the newCentroids matrix. If any cluster is empty (that is, if any
* cluster has no points assigned to it), then the centroid associated with
* that cluster may be filled with invalid data (it will be corrected later).
*
* @param centroids Current cluster centroids.
* @param newCentroids New cluster centroids.
* @param counts Number of points in each cluster at the end of the iteration.
*/
double Iterate(const arma::mat& centroids,
arma::mat& newCentroids,
@@ -61,8 +61,6 @@ double NaiveKMeans<MetricType, MatType>::Iterate(const arma::mat& centroids,
for (size_t i = 0; i < centroids.n_cols; ++i)
if (counts(i) != 0)
newCentroids.col(i) /= counts(i);
else
newCentroids.col(i).fill(DBL_MAX); // Invalid value.
distanceCalculations += centroids.n_cols * dataset.n_cols;
@@ -61,11 +61,7 @@ double PellegMooreKMeans<MetricType, MatType>::Iterate(
double residual = 0.0;
for (size_t c = 0; c < centroids.n_cols; ++c)
{
if (counts[c] == 0)
{
newCentroids.col(c).fill(DBL_MAX); // Should have happened anyway I think.
}
else
if (counts[c] > 0)
{
newCentroids.col(c) /= counts(c);
residual += std::pow(metric.Evaluate(centroids.col(c),
+2 -1
View File
@@ -65,7 +65,8 @@ PARAM_DOUBLE("hash_width", "The hash width for the first-level hashing in the "
"hash width for its use.", "H", 0.0);
PARAM_INT("second_hash_size", "The size of the second level hash table.", "S",
99901);
PARAM_INT("bucket_size", "The size of a bucket in the second level hash.", "B",
PARAM_INT("bucket_size", "The maximum size of a bucket in the second level "
"hash; 0 indicates no limit (so the table can be arbitrarily large!).", "B",
500);
PARAM_INT("seed", "Random seed. If 0, 'std::time(NULL)' is used.", "s", 0);
+41 -18
View File
@@ -50,10 +50,9 @@ class LSHSearch
* performing the hashing for details on how the hashing is done.
*
* @param referenceSet Set of reference points and the set of queries.
* @param numProj Number of projections in each hash table (anything between
* 10-50 might be a decent choice).
* @param numTables Total number of hash tables (anything between 10-20
* should suffice).
* @param projections Cube of projection tables. For a cube of size (a, b, c)
* we set numProj = a, numTables = c. b is the reference set
* dimensionality.
* @param hashWidth The width of hash for every table. If 0 (the default) is
* provided, then the hash width is automatically obtained by computing
* the average pairwise distance of 25 pairs. This should be a reasonable
@@ -61,8 +60,9 @@ class LSHSearch
* @param secondHashSize The size of the second hash table. This should be a
* large prime number.
* @param bucketSize The size of the bucket in the second hash table. This is
* the maximum number of points that can be hashed into single bucket.
* Default values are already provided here.
* the maximum number of points that can be hashed into single bucket. A
* value of 0 indicates that there is no limit (so the second hash table
* can be arbitrarily large---be careful!).
*/
LSHSearch(const arma::mat& referenceSet,
const arma::cube& projections,
@@ -76,9 +76,10 @@ class LSHSearch
* performing the hashing for details on how the hashing is done.
*
* @param referenceSet Set of reference points and the set of queries.
* @param projections Cube of projection tables. For a cube of size (a, b, c)
* we set numProj = a, numTables = c. b is the reference set
* dimensionality.
* @param numProj Number of projections in each hash table (anything between
* 10-50 might be a decent choice).
* @param numTables Total number of hash tables (anything between 10-20
* should suffice).
* @param hashWidth The width of hash for every table. If 0 (the default) is
* provided, then the hash width is automatically obtained by computing
* the average pairwise distance of 25 pairs. This should be a reasonable
@@ -86,8 +87,9 @@ class LSHSearch
* @param secondHashSize The size of the second hash table. This should be a
* large prime number.
* @param bucketSize The size of the bucket in the second hash table. This is
* the maximum number of points that can be hashed into single bucket.
* Default values are already provided here.
* the maximum number of points that can be hashed into single bucket. A
* value of 0 indicates that there is no limit (so the second hash table
* can be arbitrarily large---be careful!).
*/
LSHSearch(const arma::mat& referenceSet,
const size_t numProj,
@@ -108,9 +110,28 @@ class LSHSearch
~LSHSearch();
/**
* Train the LSH model on the given dataset. If a correct vector is not
* provided, this means building new hash tables. Otherwise, we use the ones
* provided by the user.
* Train the LSH model on the given dataset. If a correctly-sized projection
* cube is not provided, this means building new hash tables. Otherwise, we
* use the projections provided by the user.
*
* @param referenceSet Set of reference points and the set of queries.
* @param numProj Number of projections in each hash table (anything between
* 10-50 might be a decent choice).
* @param numTables Total number of hash tables (anything between 10-20
* should suffice).
* @param hashWidth The width of hash for every table. If 0 (the default) is
* provided, then the hash width is automatically obtained by computing
* the average pairwise distance of 25 pairs. This should be a reasonable
* upper bound on the nearest-neighbor distance in general.
* @param secondHashSize The size of the second hash table. This should be a
* large prime number.
* @param bucketSize The size of the bucket in the second hash table. This is
* the maximum number of points that can be hashed into single bucket. A
* value of 0 indicates that there is no limit (so the second hash table
* can be arbitrarily large---be careful!).
* @param projections Cube of projection tables. For a cube of size (a, b, c)
* we set numProj = a, numTables = c. b is the reference set
* dimensionality.
*/
void Train(const arma::mat& referenceSet,
const size_t numProj,
@@ -197,7 +218,8 @@ class LSHSearch
size_t BucketSize() const { return bucketSize; }
//! Get the second hash table.
const arma::Mat<size_t>& SecondHashTable() const { return secondHashTable; }
const std::vector<arma::Col<size_t>>& SecondHashTable() const
{ return secondHashTable; }
//! Get the projection tables.
const arma::cube& Projections() { return projections; }
@@ -314,15 +336,16 @@ class LSHSearch
//! The bucket size of the second hash.
size_t bucketSize;
//! The final hash table; should be (< secondHashSize) x bucketSize.
arma::Mat<size_t> secondHashTable;
//! The final hash table; should be (< secondHashSize) vectors each with
//! (<= bucketSize) elements.
std::vector<arma::Col<size_t>> secondHashTable;
//! The number of elements present in each hash bucket; should be
//! secondHashSize.
arma::Col<size_t> bucketContentSize;
//! For a particular hash value, points to the row in secondHashTable
//! corresponding to this value. Should be secondHashSize.
//! corresponding to this value. Length secondHashSize.
arma::Col<size_t> bucketRowInHashTable;
//! The number of distance evaluations.
+141 -86
View File
@@ -60,7 +60,7 @@ LSHSearch(const arma::mat& referenceSet,
// Empty constructor.
template<typename SortPolicy>
LSHSearch<SortPolicy>::LSHSearch() :
referenceSet(new arma::mat()), // empty dataset
referenceSet(new arma::mat()), // Use an empty dataset.
ownsSet(true),
numProj(0),
numTables(0),
@@ -131,21 +131,6 @@ void LSHSearch<SortPolicy>::Train(const arma::mat& referenceSet,
secondHashWeights = arma::floor(arma::randu(numProj) *
(double) secondHashSize);
// The 'secondHashTable' is initially an empty matrix of size
// ('secondHashSize' x 'bucketSize'). But by only filling the buckets as
// points land in them allows us to shrink the size of the 'secondHashTable'
// at the end of the hashing.
// Fill the second hash table n = referenceSet.n_cols. This is because no
// point has index 'n' so the presence of this in the bucket denotes that
// there are no more points in this bucket.
secondHashTable.set_size(secondHashSize, bucketSize);
secondHashTable.fill(referenceSet.n_cols);
// Keep track of the size of each bucket in the hash. At the end of hashing
// most buckets will be empty.
bucketContentSize.zeros(secondHashSize);
// Instead of putting the points in the row corresponding to the bucket, we
// chose the next empty row and keep track of the row in which the bucket
// lies. This allows us to stack together and slice out the empty buckets at
@@ -153,9 +138,6 @@ void LSHSearch<SortPolicy>::Train(const arma::mat& referenceSet,
bucketRowInHashTable.set_size(secondHashSize);
bucketRowInHashTable.fill(secondHashSize);
// Keep track of number of non-empty rows in the 'secondHashTable'.
size_t numRowsInTable = 0;
// Step II: The offsets for all projections in all tables.
// Since the 'offsets' are in [0, hashWidth], we obtain the 'offsets'
// as randu(numProj, numTables) * hashWidth.
@@ -183,6 +165,10 @@ void LSHSearch<SortPolicy>::Train(const arma::mat& referenceSet,
"tables provided must be equal to numProj");
}
// We will store the second hash vectors in this matrix; the second hash
// vector for table i will be held in row i.
arma::Mat<size_t> secondHashVectors(numTables, referenceSet.n_cols);
for (size_t i = 0; i < numTables; i++)
{
// Step IV: create the 'numProj'-dimensional key for each point in each
@@ -204,58 +190,64 @@ void LSHSearch<SortPolicy>::Train(const arma::mat& referenceSet,
// Step V: Putting the points in the 'secondHashTable' by hashing the key.
// Now we hash every key, point ID to its corresponding bucket.
arma::rowvec secondHashVec = secondHashWeights.t() * arma::floor(hashMat);
secondHashVectors.row(i) = arma::conv_to<arma::Row<size_t>>::from(
secondHashWeights.t() * arma::floor(hashMat));
}
// This gives us the bucket for the corresponding point ID.
for (size_t j = 0; j < secondHashVec.n_elem; j++)
secondHashVec[j] = (double) ((size_t) secondHashVec[j] % secondHashSize);
// Normalize hashes (take modulus with secondHashSize).
secondHashVectors.transform([secondHashSize](size_t val)
{ return val % secondHashSize; });
Log::Assert(secondHashVec.n_elem == referenceSet.n_cols);
// Now, using the hash vectors for each table, count the number of rows we
// have in the second hash table.
arma::Row<size_t> secondHashBinCounts(secondHashSize, arma::fill::zeros);
for (size_t i = 0; i < secondHashVectors.n_elem; ++i)
secondHashBinCounts[secondHashVectors[i]]++;
// Enforce the maximum bucket size.
const size_t effectiveBucketSize = (bucketSize == 0) ? SIZE_MAX : bucketSize;
secondHashBinCounts.transform([effectiveBucketSize](size_t val)
{ return std::min(val, effectiveBucketSize); });
const size_t numRowsInTable = arma::accu(secondHashBinCounts > 0);
bucketContentSize.zeros(numRowsInTable);
secondHashTable.resize(numRowsInTable);
// Next we must assign each point in each table to the right second hash
// table.
size_t currentRow = 0;
for (size_t i = 0; i < numTables; ++i)
{
// Insert the point in the corresponding row to its bucket in the
// 'secondHashTable'.
for (size_t j = 0; j < secondHashVec.n_elem; j++)
for (size_t j = 0; j < secondHashVectors.n_cols; j++)
{
// This is the bucket number.
size_t hashInd = (size_t) secondHashVec[j];
size_t hashInd = (size_t) secondHashVectors(i, j);
// The point ID is 'j'.
// If this is currently an empty bucket, start a new row keep track of
// which row corresponds to the bucket.
if (bucketContentSize[hashInd] == 0)
const size_t maxSize = secondHashBinCounts[hashInd];
if (bucketRowInHashTable[hashInd] == secondHashSize)
{
// Start a new row for hash.
bucketRowInHashTable[hashInd] = numRowsInTable;
secondHashTable(numRowsInTable, 0) = j;
numRowsInTable++;
bucketRowInHashTable[hashInd] = currentRow;
secondHashTable[currentRow].set_size(maxSize);
currentRow++;
}
else
{
// If bucket is already present in the 'secondHashTable', find the
// corresponding row and insert the point ID in this row unless the
// bucket is full, in which case, do nothing.
if (bucketContentSize[hashInd] < bucketSize)
secondHashTable(bucketRowInHashTable[hashInd],
bucketContentSize[hashInd]) = j;
}
// If this vector in the hash table is not full, add the point.
const size_t index = bucketRowInHashTable[hashInd];
if (bucketContentSize[index] < maxSize)
secondHashTable[index](bucketContentSize[index]++) = j;
// Increment the count of the points in this bucket.
if (bucketContentSize[hashInd] < bucketSize)
bucketContentSize[hashInd]++;
} // Loop over all points in the reference set.
} // Loop over tables.
// Step VI: Condensing the 'secondHashTable'.
size_t maxBucketSize = 0;
for (size_t i = 0; i < bucketContentSize.n_elem; i++)
if (bucketContentSize[i] > maxBucketSize)
maxBucketSize = bucketContentSize[i];
Log::Info << "Final hash table size: (" << numRowsInTable << " x "
<< maxBucketSize << ")" << std::endl;
secondHashTable.resize(numRowsInTable, maxBucketSize);
Log::Info << "Final hash table size: " << numRowsInTable << " rows, with a "
<< "maximum length of " << arma::max(secondHashBinCounts) << ", "
<< "totaling " << arma::accu(secondHashBinCounts) << " elements."
<< std::endl;
}
template<typename SortPolicy>
@@ -377,15 +369,16 @@ void LSHSearch<SortPolicy>::ReturnIndicesFromTable(
Log::Assert(hashVec.n_elem == numTablesToSearch);
// Count number of points hashed in the same bucket as the query
// Count number of points hashed in the same bucket as the query.
size_t maxNumPoints = 0;
for (size_t i = 0; i < numTablesToSearch; ++i) //For all tables
for (size_t i = 0; i < numTablesToSearch; ++i)
{
size_t hashInd = (size_t) hashVec[i]; //find query's bucket
maxNumPoints += bucketContentSize[hashInd]; //count bucket contents
const size_t hashInd = (size_t) hashVec[i];
const size_t tableRow = bucketRowInHashTable[hashInd];
if (tableRow != secondHashSize)
maxNumPoints += bucketContentSize[tableRow];
}
// There are two ways to proceed here:
// Either allocate a maxNumPoints-size vector, place all candidates, and run
// unique on the vector to discard duplicates.
@@ -407,18 +400,13 @@ void LSHSearch<SortPolicy>::ReturnIndicesFromTable(
for (size_t i = 0; i < hashVec.n_elem; ++i)
{
size_t hashInd = (size_t) hashVec[i];
const size_t hashInd = (size_t) hashVec[i];
const size_t tableRow = bucketRowInHashTable[hashInd];
if (bucketContentSize[hashInd] > 0)
{
// Pick the indices in the bucket corresponding to hashInd.
size_t tableRow = bucketRowInHashTable[hashInd];
assert(tableRow < secondHashSize);
assert(tableRow < secondHashTable.n_rows);
for (size_t j = 0; j < bucketContentSize[hashInd]; ++j)
refPointsConsidered[secondHashTable(tableRow, j)]++;
}
// Pick the indices in the bucket corresponding to 'hashInd'.
if (tableRow != secondHashSize)
for (size_t j = 0; j < bucketContentSize[tableRow]; j++)
refPointsConsidered[secondHashTable[tableRow](j)]++;
}
// Only keep reference points found in at least one bucket.
@@ -437,20 +425,13 @@ void LSHSearch<SortPolicy>::ReturnIndicesFromTable(
size_t start = 0;
for (size_t i = 0; i < numTablesToSearch; ++i) // For all tables
{
size_t hashInd = (size_t) hashVec[i]; // Find the query's bucket.
const size_t hashInd = (size_t) hashVec[i]; // Find the query's bucket.
const size_t tableRow = bucketRowInHashTable[hashInd];
if (bucketContentSize[hashInd] > 0)
{
// tableRow hash indices corresponding to query.
size_t tableRow = bucketRowInHashTable[hashInd];
assert(tableRow < secondHashSize);
assert(tableRow < secondHashTable.n_rows);
// This for-loop could be replaced with a vector slice (TODO).
// Store all secondHashTable points in the candidates set.
for (size_t j = 0; j < bucketContentSize[hashInd]; ++j)
refPointsConsideredSmall(start++) = secondHashTable(tableRow, j);
}
// Store all secondHashTable points in the candidates set.
if (tableRow != secondHashSize)
for (size_t j = 0; j < bucketContentSize[tableRow]; ++j)
refPointsConsideredSmall(start++) = secondHashTable[tableRow][j];
}
// Only keep unique candidates.
@@ -594,7 +575,7 @@ void LSHSearch<SortPolicy>::Serialize(Archive& ar,
if (Archive::is_loading::value)
projections.reset();
// Backward compatibility: older version of LSHSearch stored the projection
// Backward compatibility: older versions of LSHSearch stored the projection
// tables in a std::vector<arma::mat>.
if (version == 0)
{
@@ -615,9 +596,83 @@ void LSHSearch<SortPolicy>::Serialize(Archive& ar,
ar & CreateNVP(secondHashSize, "secondHashSize");
ar & CreateNVP(secondHashWeights, "secondHashWeights");
ar & CreateNVP(bucketSize, "bucketSize");
ar & CreateNVP(secondHashTable, "secondHashTable");
ar & CreateNVP(bucketContentSize, "bucketContentSize");
ar & CreateNVP(bucketRowInHashTable, "bucketRowInHashTable");
// needs specific handling for new version
// Backward compatibility: in older versions of LSHSearch, the secondHashTable
// was stored as an arma::Mat<size_t>. So we need to properly load that, then
// prune it down to size.
if (version == 0)
{
arma::Mat<size_t> tmpSecondHashTable;
ar & CreateNVP(tmpSecondHashTable, "secondHashTable");
// The old secondHashTable was stored in row-major format, so we transpose
// it.
tmpSecondHashTable = tmpSecondHashTable.t();
secondHashTable.resize(tmpSecondHashTable.n_cols);
for (size_t i = 0; i < tmpSecondHashTable.n_cols; ++i)
{
// Find length of each column. We know we are at the end of the list when
// the value referenceSet->n_cols is seen.
size_t len = 0;
for ( ; len < tmpSecondHashTable.n_rows; ++len)
if (tmpSecondHashTable(len, i) == referenceSet->n_cols)
break;
// Set the size of the new column correctly.
secondHashTable[i].set_size(len);
for (size_t j = 0; j < len; ++j)
secondHashTable[i](j) = tmpSecondHashTable(j, i);
}
}
else
{
size_t tables;
if (Archive::is_saving::value)
tables = secondHashTable.size();
ar & CreateNVP(tables, "numSecondHashTables");
// Set size of second hash table if needed.
if (Archive::is_loading::value)
{
secondHashTable.clear();
secondHashTable.resize(tables);
}
for (size_t i = 0; i < secondHashTable.size(); ++i)
{
std::ostringstream oss;
oss << "secondHashTable" << i;
ar & CreateNVP(secondHashTable[i], oss.str());
}
}
// Backward compatibility: old versions of LSHSearch held bucketContentSize
// for all possible buckets (of size secondHashSize), but now we hold a
// compressed representation.
if (version == 0)
{
// The vector was stored in the old uncompressed form. So we need to shrink
// it. But we can't do that until we have bucketRowInHashTable, so we also
// have to load that.
arma::Col<size_t> tmpBucketContentSize;
ar & CreateNVP(tmpBucketContentSize, "bucketContentSize");
ar & CreateNVP(bucketRowInHashTable, "bucketRowInHashTable");
// Compress into a smaller vector by just dropping all of the zeros.
bucketContentSize.set_size(secondHashTable.size());
for (size_t i = 0; i < tmpBucketContentSize.n_elem; ++i)
if (tmpBucketContentSize[i] > 0)
bucketContentSize[bucketRowInHashTable[i]] = tmpBucketContentSize[i];
}
else
{
ar & CreateNVP(bucketContentSize, "bucketContentSize");
ar & CreateNVP(bucketRowInHashTable, "bucketRowInHashTable");
}
ar & CreateNVP(distanceEvaluations, "distanceEvaluations");
}
@@ -638,9 +638,7 @@ Search(const size_t k,
nodes.pop();
// Reset bounds of this node.
node->Stat().FirstBound() = SortPolicy::WorstDistance();
node->Stat().SecondBound() = SortPolicy::WorstDistance();
node->Stat().LastDistance() = 0.0;
node->Stat().Reset();
// Then add the children.
for (size_t i = 0; i < node->NumChildren(); ++i)
@@ -57,6 +57,17 @@ class NeighborSearchStat
auxBound(SortPolicy::WorstDistance()),
lastDistance(0.0) { }
/**
* Reset statistic parameters to initial values.
*/
void Reset()
{
firstBound = SortPolicy::WorstDistance();
secondBound = SortPolicy::WorstDistance();
auxBound = SortPolicy::WorstDistance();
lastDistance = 0.0;
}
//! Get the first bound.
double FirstBound() const { return firstBound; }
//! Modify the first bound.
+1
View File
@@ -22,6 +22,7 @@ add_executable(mlpack_test
gmm_test.cpp
hmm_test.cpp
hoeffding_tree_test.cpp
ind2sub_test.cpp
init_rules_test.cpp
kernel_test.cpp
kernel_pca_test.cpp
+30
View File
@@ -0,0 +1,30 @@
/**
* @file ind2sub_test.cpp
* @author Nilay Jain
*
* Test the backported Armadillo ind2sub() and sub2ind() functions.
*/
#include <mlpack/core.hpp>
#include <boost/test/unit_test.hpp>
#include "old_boost_test_definitions.hpp"
BOOST_AUTO_TEST_SUITE(ind2subTest);
/**
* This test checks whether ind2sub and sub2ind are
* compiled successfully and that they function properly.
*/
BOOST_AUTO_TEST_CASE(ind2sub_test)
{
arma::mat A = arma::randu(4,5);
size_t index = 13;
arma::uvec u = arma::ind2sub(arma::size(A), index);
BOOST_REQUIRE_EQUAL(u(0), index % A.n_rows);
BOOST_REQUIRE_EQUAL(u(1), index / A.n_rows);
index = arma::sub2ind(arma::size(A), u(0), u(1));
BOOST_REQUIRE_EQUAL(index, u(0) + u(1) * A.n_rows);
}
BOOST_AUTO_TEST_SUITE_END();
+177
View File
@@ -15,6 +15,9 @@ using namespace std;
using namespace mlpack;
using namespace mlpack::neighbor;
/**
* Computes Recall (percent of neighbors found correctly).
*/
double ComputeRecall(
const arma::Mat<size_t>& lshNeighbors,
const arma::Mat<size_t>& groundTruth)
@@ -26,6 +29,68 @@ double ComputeRecall(
return same / (static_cast<double>(queries * neigh));
}
/**
* Generates a point set of four clusters around (0.5, 0.5),
* (3.5, 0.5), (0.5, 3.5), (3.5, 3.5).
*/
void GetPointset(const size_t N, arma::mat& rdata)
{
const size_t d = 2;
// Create four clusters of points.
arma::mat c1(d, N / 4, arma::fill::randu);
arma::mat c2(d, N / 4, arma::fill::randu);
arma::mat c3(d, N / 4, arma::fill::randu);
arma::mat c4(d, N / 4, arma::fill::randu);
arma::colvec offset1;
offset1 << 0 << arma::endr
<< 3 << arma::endr;
arma::colvec offset2;
offset2 << 3 << arma::endr
<< 3 << arma::endr;
arma::colvec offset4;
offset4 << 3 << arma::endr
<< 0 << arma::endr;
// Spread points in plane.
for (size_t p = 0; p < N / 4; ++p)
{
c1.col(p) += offset1;
c2.col(p) += offset2;
c4.col(p) += offset4;
}
rdata.set_size(d, N);
rdata.cols(0, (N / 4) - 1) = c1;
rdata.cols(N / 4, (N / 2) - 1) = c2;
rdata.cols(N / 2, (3 * N / 4) - 1) = c3;
rdata.cols(3 * N / 4, N - 1) = c4;
}
/**
* Generates two queries, one around (0.5, 0.5) and one around (3.5, 3.5).
*/
void GetQueries(arma::mat& qdata)
{
const size_t d = 2;
// Generate two queries inside two of the clusters.
// Put query 1 into cluster 3.
arma::colvec q1, q2;
q1.randu(d, 1);
// Offset second query to go into cluster 2.
q2.randu(d, 1);
q2.row(0) += 3;
q2.row(1) += 3;
qdata.set_size(d, 2);
qdata.col(0) = q1;
qdata.col(1) = q2;
}
BOOST_AUTO_TEST_SUITE(LSHTest);
/**
@@ -302,6 +367,118 @@ BOOST_AUTO_TEST_CASE(RecallTest)
BOOST_REQUIRE_LE(recallChp, recallThreshChp);
}
/**
* Test: This is a deterministic test that projects 2-dpoints to a known line
* (axis 2). The reference set contains 4 well-separated clusters that will
* merge into 2 clusters when projected on that axis.
*
* We create two queries, each one belonging in one cluster (q1 in cluster 3
* located around (0, 0) and q2 in cluster 2 located around (3, 3). After the
* projection, q1 should have neighbors in C3 and C4 and q2 in C1 and C2.
*/
BOOST_AUTO_TEST_CASE(DeterministicMerge)
{
const size_t N = 40; // Must be divisible by 4 to create 4 clusters properly.
arma::mat rdata;
arma::mat qdata;
GetPointset(N, rdata);
GetQueries(qdata);
const int k = N / 2;
const double hashWidth = 1;
const int secondHashSize = 99901;
const int bucketSize = 500;
// 1 table, with one projection to axis 1.
arma::cube projections(2, 1, 1);
projections(0, 0, 0) = 0;
projections(1, 0, 0) = 1;
LSHSearch<> lshTest(rdata, projections, hashWidth, secondHashSize,
bucketSize);
arma::Mat<size_t> neighbors;
arma::mat distances;
lshTest.Search(qdata, k, neighbors, distances);
// Test query 1.
size_t q;
for (size_t j = 0; j < k; ++j) // For each neighbor.
{
// If the neighbor is not found, ignore the point.
if (neighbors(j, 0) == N || neighbors(j, 1) == N)
continue;
// Query 1 is in cluster 3, which under this projection was merged with
// cluster 4. Clusters 3 and 4 have points 20:39, so only neighbors among
//those should be found.
q = 0;
BOOST_REQUIRE_GE(neighbors(j, q), N / 2);
// Query 2 is in cluster 2, which under this projection was merged with
// cluster 1. Clusters 1 and 2 have points 0:19, so only neighbors among
// those should be found.
q = 1;
BOOST_REQUIRE_LT(neighbors(j, q), N / 2);
}
}
/**
* Test: This is a deterministic test that projects 2-d points to the plane.
* The reference set contains 4 well-separated clusters that should not merge.
*
* We create two queries, each one belonging in one cluster (q1 in cluster 3
* located around (0, 0) and q2 in cluster 2 located around (3, 3). The test is
* a success if, after the projection, q1 should have neighbors in c3 and q2
* in c2.
*/
BOOST_AUTO_TEST_CASE(DeterministicNoMerge)
{
const size_t N = 40;
arma::mat rdata;
arma::mat qdata;
GetPointset(N, rdata);
GetQueries(qdata);
const int k = N / 2;
const double hashWidth = 1;
const int secondHashSize = 99901;
const int bucketSize = 500;
// 1 table, with one projection to axis 1.
arma::cube projections(2, 2, 1);
projections(0, 0, 0) = 0;
projections(1, 0, 0) = 1;
projections(0, 1, 0) = 1;
projections(1, 1, 0) = 0;
LSHSearch<> lshTest(rdata, projections, hashWidth, secondHashSize,
bucketSize);
arma::Mat<size_t> neighbors;
arma::mat distances;
lshTest.Search(qdata, k, neighbors, distances);
// Test query 1.
size_t q;
for (size_t j = 0; j < k; ++j) // For each neighbor.
{
// If the neighbor is not found, ignore the point.
if (neighbors(j, 0) == N || neighbors(j, 1) == N)
continue;
// Query 1 is in cluster 3, which is points 20:29.
q = 0;
BOOST_REQUIRE_LT(neighbors(j, q), 3 * N / 4);
BOOST_REQUIRE_GE(neighbors(j, q), N / 2);
// Query 2 is in cluster 2, which is points 10:19.
q = 1;
BOOST_REQUIRE_LT(neighbors(j, q), N / 2);
BOOST_REQUIRE_GE(neighbors(j, q), N / 4);
}
}
BOOST_AUTO_TEST_CASE(LSHTrainTest)
{
// This is a not very good test that simply checks that the re-trained LSH
+2 -2
View File
@@ -44,10 +44,10 @@ BOOST_AUTO_TEST_CASE(L2MetricTest)
arma::vec b1(5);
b1.randn();
arma::Col<size_t> a2(5);
arma::vec a2(5);
a2 << 1 << 2 << 1 << 0 << 5;
arma::Col<size_t> b2(5);
arma::vec b2(5);
b2 << 2 << 5 << 2 << 0 << 1;
EuclideanDistance lMetric;
-1
View File
@@ -614,7 +614,6 @@ BOOST_AUTO_TEST_CASE(XTreeTraverserTest)
}
}
// Test the tree splitting. We set MaxLeafSize and MaxNumChildren rather low
// to allow us to test by hand without adding hundreds of points.
BOOST_AUTO_TEST_CASE(RTreeSplitTest)
+10 -2
View File
@@ -1225,8 +1225,16 @@ BOOST_AUTO_TEST_CASE(LSHTest)
BOOST_REQUIRE_EQUAL(lsh.BucketSize(), textLsh.BucketSize());
BOOST_REQUIRE_EQUAL(lsh.BucketSize(), binaryLsh.BucketSize());
CheckMatrices(lsh.SecondHashTable(), xmlLsh.SecondHashTable(),
textLsh.SecondHashTable(), binaryLsh.SecondHashTable());
BOOST_REQUIRE_EQUAL(lsh.SecondHashTable().size(),
xmlLsh.SecondHashTable().size());
BOOST_REQUIRE_EQUAL(lsh.SecondHashTable().size(),
textLsh.SecondHashTable().size());
BOOST_REQUIRE_EQUAL(lsh.SecondHashTable().size(),
binaryLsh.SecondHashTable().size());
for (size_t i = 0; i < lsh.SecondHashTable().size(); ++i)
CheckMatrices(lsh.SecondHashTable()[i], xmlLsh.SecondHashTable()[i],
textLsh.SecondHashTable()[i], binaryLsh.SecondHashTable()[i]);
}
// Make sure serialization works for the decision stump.