Merge branch 'master' into markdown-bindings

This commit is contained in:
Ryan Curtin
2019-01-24 18:36:56 -05:00
48 changed files with 4864 additions and 270 deletions
+39 -24
View File
@@ -1,33 +1,48 @@
sudo: required
dist: trusty
language: cpp
env:
- CMAKE_OPTIONS="-DDEBUG=OFF -DPROFILE=OFF -DPYTHON=/usr/bin/python"
- CMAKE_OPTIONS="-DDEBUG=OFF -DPROFILE=OFF -DPYTHON=/usr/bin/python3"
- CMAKE_OPTIONS="-DDEBUG=ON -DPROFILE=OFF -DBUILD_PYTHON_BINDINGS=OFF"
matrix:
include:
- os: linux
dist: xenial
env: CMAKE_OPTIONS="-DDEBUG=OFF -DPROFILE=OFF -DPYTHON=/usr/bin/python"
before_install:
- sudo apt-get update
- sudo apt-get install -y --allow-unauthenticated libopenblas-dev liblapack-dev g++ libboost-all-dev python-pip cython python-numpy python-pandas
- sudo pip install --upgrade --ignore-installed setuptools
- curl https://ftp.fau.de/macports/distfiles/armadillo/armadillo-6.500.5.tar.gz | tar xvz && cd armadillo*
- cmake . && make && sudo make install && cd ..
- sudo cp .travis/config.hpp /usr/include/armadillo_bits/config.hpp
before_install:
# For the python bindings we need cython >= 0.24.
- sudo add-apt-repository -y ppa:imcode/s3ql-trusty-backport
# For the python bindings we need pandas >= 0.15.0.
- wget -O- http://neuro.debian.net/lists/trusty.us-ca.full | sudo tee /etc/apt/sources.list.d/neurodebian.sources.list
- sudo apt-key adv --recv-keys --keyserver hkp://ha.pool.sks-keyservers.net 0xA5D32F012649A5A9 ||
sudo apt-key adv --recv-keys --keyserver hkp://pgp.mit.edu 0xA5D32F012649A5A9 ||
sudo apt-key adv --recv-keys --keyserver hkp://keyserver.pgp.com 0xA5D32F012649A5A9
- sudo apt-get update
- sudo apt-get install -y --allow-unauthenticated libopenblas-dev liblapack-dev g++ libboost-all-dev python3-pip cython3 python3-numpy python3-pandas
# Install both python2 and python3 modules, and the build will decide which to
# use.
- sudo pip install cython numpy pandas
- sudo pip install --upgrade --ignore-installed setuptools
- sudo pip3 install --upgrade --ignore-installed setuptools
- curl https://ftp.fau.de/macports/distfiles/armadillo/armadillo-6.500.5.tar.gz | tar xvz && cd armadillo*
- cmake . && make && sudo make install && cd ..
- sudo cp .travis/config.hpp /usr/include/armadillo_bits/config.hpp
- os: linux
dist: xenial
env: CMAKE_OPTIONS="-DDEBUG=OFF -DPROFILE=OFF -DPYTHON=/usr/bin/python3"
before_install:
- sudo apt-get update
- sudo apt-get install -y --allow-unauthenticated libopenblas-dev liblapack-dev g++ libboost-all-dev python3-pip cython3 python3-numpy python3-pandas
- sudo pip3 install --upgrade --ignore-installed setuptools
- curl https://ftp.fau.de/macports/distfiles/armadillo/armadillo-6.500.5.tar.gz | tar xvz && cd armadillo*
- cmake . && make && sudo make install && cd ..
- sudo cp .travis/config.hpp /usr/include/armadillo_bits/config.hpp
- os: linux
dist: xenial
env: CMAKE_OPTIONS="-DDEBUG=ON -DPROFILE=OFF -DBUILD_PYTHON_BINDINGS=OFF"
before_install:
- sudo apt-get update
- sudo apt-get install -y --allow-unauthenticated libopenblas-dev liblapack-dev g++ libboost-all-dev
- curl https://ftp.fau.de/macports/distfiles/armadillo/armadillo-6.500.5.tar.gz | tar xvz && cd armadillo*
- cmake . && make && sudo make install && cd ..
- sudo cp .travis/config.hpp /usr/include/armadillo_bits/config.hpp
- os: osx
osx_image: xcode9.4 # Maybe we can try some different ones?
env: CMAKE_OPTIONS="-DDEBUG=ON -DPROFILE=OFF -DBUILD_PYTHON_BINDINGS=OFF"
before_install:
- brew install openblas armadillo || brew install openblas armadillo
install:
- mkdir build && cd build && cmake $CMAKE_OPTIONS .. && make -j2
- mkdir build && cd build && cmake $CMAKE_OPTIONS .. && travis_wait 60 make -j2
script:
- CTEST_OUTPUT_ON_FAILURE=1 travis_wait 30 ctest -j2
+9 -5
View File
@@ -3,16 +3,18 @@
#
# This module sets the following variables:
# ENSMALLEN_FOUND - set to true if the library is found
# ENSMALLEN_INCLUDE_DIRS - list of required include directories
# ENSMALLEN_INCLUDE_DIR - list of required include directories
# ENSMALLEN_VERSION_MAJOR - major version number
# ENSMALLEN_VERSION_MINOR - minor version number
# ENSMALLEN_VERSION_PATCH - patch version number
# ENSMALLEN_VERSION_STRING - version number as a string (ex: "1.0.4")
# ENSMALLEN_VERSION_NAME - name of the version (ex: "Antipodean Antileech")
file(GLOB ENSMALLEN_SEARCH_PATHS
${CMAKE_BINARY_DIR}/deps/ensmallen-[0-9]*.[0-9]*.[0-9]*)
find_path(ENSMALLEN_INCLUDE_DIR
NAMES ensmallen.hpp
PATHS "$ENV{ProgramFiles}/ensmallen/include")
PATHS ${ENSMALLEN_SEARCH_PATHS}/include)
if(ENSMALLEN_INCLUDE_DIR)
# ------------------------------------------------------------------------
@@ -26,15 +28,17 @@ if(ENSMALLEN_INCLUDE_DIR)
if(EXISTS "${ENSMALLEN_INCLUDE_DIR}/ensmallen_bits/ens_version.hpp")
set(ENSMALLEN_FOUND YES)
# Read and parse armdillo version header file for version number
file(READ "${ENSMALLEN_INCLUDE_DIR}/ensmallen_bits/ens_version.hpp"
_ensmallen_HEADER_CONTENTS)
string(REGEX REPLACE ".*#define ENS_VERSION_MAJOR ([0-9]+).*" "\\1"
ENSMALLEN_VERSION_MAJOR "${_armadillo_HEADER_CONTENTS}")
ENSMALLEN_VERSION_MAJOR "${_ensmallen_HEADER_CONTENTS}")
string(REGEX REPLACE ".*#define ENS_VERSION_MINOR ([0-9]+).*" "\\1"
ENSMALLEN_VERSION_MINOR "${_armadillo_HEADER_CONTENTS}")
ENSMALLEN_VERSION_MINOR "${_ensmallen_HEADER_CONTENTS}")
string(REGEX REPLACE ".*#define ENS_VERSION_PATCH ([0-9]+).*" "\\1"
ENSMALLEN_VERSION_PATCH "${_armadillo_HEADER_CONTENTS}")
ENSMALLEN_VERSION_PATCH "${_ensmallen_HEADER_CONTENTS}")
# WARNING: The number of spaces before the version name is not one.
string(REGEX REPLACE
+1 -1
View File
@@ -350,7 +350,7 @@ ${ENS_DOWNLOAD_ERROR}! Error log: ${ENS_DOWBLOAD_LOG}")
endif ()
endif ()
else ()
set(MLPACK_INCLUDE_DIRS ${MLPACK_INCLUDE_DIRS} ${ENSMALLEN_INCLUDE_DIRS})
set(MLPACK_INCLUDE_DIRS ${MLPACK_INCLUDE_DIRS} "${ENSMALLEN_INCLUDE_DIR}")
endif ()
# Unfortunately this configuration variable is necessary and will need to be
+9 -7
View File
@@ -11,7 +11,7 @@ interested in participating in Google Summer of Code, see
## Pull request process
Once a pull request is submitted, it must be reviewed by at least one member of
Once a pull request is submitted, it must be approved by at least one member of
mlpack's Contributors team, to ensure that (if applicable):
* the design meshes with the rest of mlpack
@@ -19,9 +19,11 @@ mlpack's Contributors team, to ensure that (if applicable):
[Style Guide](http://github.com/mlpack/mlpack/wiki/DesignGuidelines)
* any new functionality is tested and working
Once the pull request is approved by one member of the Contributors team, it may
be merged between 3 and 7 days after approval. This allows other contributors
and maintainers to have time to also review the PR. If a pull request has at
least two approvals from members of the Contributors team, then the pull
request may be immediately merged. This applies even if the submitter of the
PR is a member of the Contributors team.
The pull request can be merged as soon as it receives two approvals; 24 hours
after the first approval, mlpack-bot will provide a second approval. This is to
leave time for anyone to comment on the PR before it is merged.
Members of the Contributors team are encouraged to review pull requests that
have already been reviewed, and pull request contributors are encouraged to seek
multiple reviews. Reviews from anyone not on the Contributors team are always
appreciated and encouraged!
+2 -1
View File
@@ -93,7 +93,7 @@ Copyright:
Copyright 2018, B Kartheek Reddy <bkartheekreddy@gmail.com>
Copyright 2018, Atharva Khandait <akhandait45@gmail.com>
Copyright 2018, Wenhao Huang <wenhao.huang.work@gmail.com>
Copyright 2018, Roberto Hueso <robertohueso96@gmail.com>
Copyright 2018-2019, Roberto Hueso <robertohueso96@gmail.com>
Copyright 2018, Prabhat Sharma <prabhatsharma7298@gmail.com>
Copyright 2018, Tan Jun An <yamidarkxxx@gmail.com>
Copyright 2018, Moksh Jain <mokshjn00@gmail.com>
@@ -108,6 +108,7 @@ Copyright:
Copyright 2018, Ayush Chamoli
Copyright 2018, Tommi Laivamaa <tommi.laivamaa@protonmail.com>
Copyright 2019, Kim SangYeon <sy0814k@gmail.com>
Copyright 2019, Niteya Shah <niteya.56@gmail.com>
License: BSD-3-clause
All rights reserved.
+5
View File
@@ -1,3 +1,8 @@
### mlpack 3.1.0
###### ????-??-??
* Add kernel density estimation (KDE) implementation with bindings to other
languages (#1301).
### mlpack 3.0.5
###### ????-??-??
* Change DBSCAN to use PointSelectionPolicy and add OrderedPointSelection (#1625).
+1
View File
@@ -254,6 +254,7 @@
* - Ayush Chamoli
* - Tommi Laivamaa <tommi.laivamaa@protonmail.com>
* - Kim SangYeon <sy0814k@gmail.com>
* - Niteya Shah <niteya.56@gmail.com>
*/
// First, include all of the prerequisites.
+126 -34
View File
@@ -38,50 +38,142 @@ template<typename MatType,
bool NumClasses>
struct TrainForm;
template<typename PT, typename WT, typename... SignatureParams>
struct TrainFormBase
{
using PredictionsType = PT;
using WeightsType = WT;
#if _MSC_VER <= 1916 // Visual Studio 2017 version 15.9 or older.
// Due to an internal MSVC compiler bug (MSVC ) we can't use two parameter
// packs. So we have to write multiple TrainFormBase forms.
template<typename PT, typename WT, typename T1, typename T2>
struct TrainFormBase4
{
using PredictionsType = PT;
using WeightsType = WT;
/* A minimum number of parameters that should be inferred */
static const size_t MinNumberOfAdditionalArgs = 1;
/* A minimum number of parameters that should be inferred */
static const size_t MinNumberOfAdditionalArgs = 1;
template<typename Class, typename RT, typename... Ts>
using Type = RT(Class::*)(SignatureParams..., Ts...);
};
template<typename Class, typename RT, typename... Ts>
using Type = RT(Class::*)(T1, T2, Ts...);
};
template<typename MT, typename PT>
struct TrainForm<MT, PT, void, false, false> : public TrainFormBase<PT, void,
const MT&, const PT&> {};
template<typename PT, typename WT, typename T1, typename T2, typename T3>
struct TrainFormBase5
{
using PredictionsType = PT;
using WeightsType = WT;
template<typename MT, typename PT>
struct TrainForm<MT, PT, void, true, false> : public TrainFormBase<PT, void,
const MT&, const data::DatasetInfo&, const PT&> {};
/* A minimum number of parameters that should be inferred */
static const size_t MinNumberOfAdditionalArgs = 1;
template<typename MT, typename PT, typename WT>
struct TrainForm<MT, PT, WT, false, false> : public TrainFormBase<PT, WT,
const MT&, const PT&, const WT&> {};
template<typename Class, typename RT, typename... Ts>
using Type = RT(Class::*)(T1, T2, T3, Ts...);
};
template<typename MT, typename PT, typename WT>
struct TrainForm<MT, PT, WT, true, false> : public TrainFormBase<PT, WT,
const MT&, const data::DatasetInfo&, const PT&, const WT&> {};
template<typename PT, typename WT, typename T1, typename T2, typename T3,
typename T4>
struct TrainFormBase6
{
using PredictionsType = PT;
using WeightsType = WT;
template<typename MT, typename PT>
struct TrainForm<MT, PT, void, false, true> : public TrainFormBase<PT, void,
const MT&, const PT&, const size_t> {};
/* A minimum number of parameters that should be inferred */
static const size_t MinNumberOfAdditionalArgs = 1;
template<typename MT, typename PT>
struct TrainForm<MT, PT, void, true, true> : public TrainFormBase<PT, void,
const MT&, const data::DatasetInfo&, const PT&, const size_t> {};
template<typename Class, typename RT, typename... Ts>
using Type = RT(Class::*)(T1, T2, T3, T4, Ts...);
};
template<typename MT, typename PT, typename WT>
struct TrainForm<MT, PT, WT, false, true> : public TrainFormBase<PT, WT,
const MT&, const PT&, const size_t, const WT&> {};
template<typename PT, typename WT, typename T1, typename T2, typename T3,
typename T4, typename T5>
struct TrainFormBase7
{
using PredictionsType = PT;
using WeightsType = WT;
template<typename MT, typename PT, typename WT>
struct TrainForm<MT, PT, WT, true, true> : public TrainFormBase<PT, WT,
const MT&, const data::DatasetInfo&, const PT&, const size_t, const WT&> {};
/* A minimum number of parameters that should be inferred */
static const size_t MinNumberOfAdditionalArgs = 1;
template<typename Class, typename RT, typename... Ts>
using Type = RT(Class::*)(T1, T2, T3, T4, T5, Ts...);
};
template<typename MT, typename PT>
struct TrainForm<MT, PT, void, false, false> : public TrainFormBase4<PT, void,
const MT&, const PT&> {};
template<typename MT, typename PT>
struct TrainForm<MT, PT, void, true, false> : public TrainFormBase5<PT, void,
const MT&, const data::DatasetInfo&, const PT&> {};
template<typename MT, typename PT, typename WT>
struct TrainForm<MT, PT, WT, false, false> : public TrainFormBase5<PT, WT,
const MT&, const PT&, const WT&> {};
template<typename MT, typename PT, typename WT>
struct TrainForm<MT, PT, WT, true, false> : public TrainFormBase6<PT, WT,
const MT&, const data::DatasetInfo&, const PT&, const WT&> {};
template<typename MT, typename PT>
struct TrainForm<MT, PT, void, false, true> : public TrainFormBase5<PT, void,
const MT&, const PT&, const size_t> {};
template<typename MT, typename PT>
struct TrainForm<MT, PT, void, true, true> : public TrainFormBase6<PT, void,
const MT&, const data::DatasetInfo&, const PT&, const size_t> {};
template<typename MT, typename PT, typename WT>
struct TrainForm<MT, PT, WT, false, true> : public TrainFormBase6<PT, WT,
const MT&, const PT&, const size_t, const WT&> {};
template<typename MT, typename PT, typename WT>
struct TrainForm<MT, PT, WT, true, true> : public TrainFormBase7<PT, WT,
const MT&, const data::DatasetInfo&, const PT&,
const size_t, const WT&> {};
#else
template<typename PT, typename WT, typename... SignatureParams>
struct TrainFormBase
{
using PredictionsType = PT;
using WeightsType = WT;
/* A minimum number of parameters that should be inferred */
static const size_t MinNumberOfAdditionalArgs = 1;
template<typename Class, typename RT, typename... Ts>
using Type = RT(Class::*)(SignatureParams..., Ts...);
};
template<typename MT, typename PT>
struct TrainForm<MT, PT, void, false, false> : public TrainFormBase<PT, void,
const MT&, const PT&> {};
template<typename MT, typename PT>
struct TrainForm<MT, PT, void, true, false> : public TrainFormBase<PT, void,
const MT&, const data::DatasetInfo&, const PT&> {};
template<typename MT, typename PT, typename WT>
struct TrainForm<MT, PT, WT, false, false> : public TrainFormBase<PT, WT,
const MT&, const PT&, const WT&> {};
template<typename MT, typename PT, typename WT>
struct TrainForm<MT, PT, WT, true, false> : public TrainFormBase<PT, WT,
const MT&, const data::DatasetInfo&, const PT&, const WT&> {};
template<typename MT, typename PT>
struct TrainForm<MT, PT, void, false, true> : public TrainFormBase<PT, void,
const MT&, const PT&, const size_t> {};
template<typename MT, typename PT>
struct TrainForm<MT, PT, void, true, true> : public TrainFormBase<PT, void,
const MT&, const data::DatasetInfo&, const PT&, const size_t> {};
template<typename MT, typename PT, typename WT>
struct TrainForm<MT, PT, WT, false, true> : public TrainFormBase<PT, WT,
const MT&, const PT&, const size_t, const WT&> {};
template<typename MT, typename PT, typename WT>
struct TrainForm<MT, PT, WT, true, true> : public TrainFormBase<PT, WT,
const MT&, const data::DatasetInfo&, const PT&,
const size_t, const WT&> {};
#endif
/* A struct for indication that a right method form can't be found */
struct NotFoundMethodForm
+8
View File
@@ -55,6 +55,14 @@ class DatasetMapper
*/
explicit DatasetMapper(PolicyType& policy, const size_t dimensionality = 0);
/**
* Set the dimensionality of an existing DatasetMapper object. This resets
* all mappings (but not the PolicyType).
*
* @param dimensionality New dimensionality.
*/
void SetDimensionality(const size_t dimensionality);
/**
* Preprocessing: during a first pass of the data, pass the input on to the
* MapPolicy if they are needed.
@@ -37,6 +37,14 @@ inline DatasetMapper<PolicyType, InputType>::DatasetMapper(PolicyType& policy,
// Nothing to initialize here.
}
template<typename PolicyType, typename InputType>
inline void DatasetMapper<PolicyType, InputType>::SetDimensionality(
const size_t dimensionality)
{
types = std::vector<Datatype>(dimensionality, Datatype::numeric);
maps.clear();
}
// Utility helper function to call MapFirstPass.
template<typename PolicyType, typename InputType, typename T>
void CallMapFirstPass(
+1 -1
View File
@@ -180,7 +180,7 @@ class LoadCSV
stringRule[findRowSize] % delimiterRule);
// Now that we know the dimensionality, initialize the DatasetMapper.
info = DatasetMapper<MapPolicy>(rows);
info.SetDimensionality(rows);
}
// If we need to do a first pass for the DatasetMapper, do it.
+9
View File
@@ -30,6 +30,9 @@
#if (BINDING_TYPE == BINDING_TYPE_CLI) // This is a command-line executable.
// Matrices are transposed on load/save.
#define BINDING_MATRIX_TRANSPOSED true
#include <mlpack/bindings/cli/cli_option.hpp>
#include <mlpack/bindings/cli/print_doc_functions.hpp>
@@ -75,6 +78,9 @@ int main(int argc, char** argv)
#elif(BINDING_TYPE == BINDING_TYPE_TEST) // This is a unit test.
// Matrices are not transposed on load/save.
#define BINDING_MATRIX_TRANSPOSED false
#include <mlpack/bindings/tests/test_option.hpp>
#include <mlpack/bindings/tests/ignore_check.hpp>
#include <mlpack/bindings/tests/clean_memory.hpp>
@@ -106,6 +112,9 @@ using Option = mlpack::bindings::tests::TestOption<T>;
#elif(BINDING_TYPE == BINDING_TYPE_PYX) // This is a Python binding.
// Matrices are transposed on load/save.
#define BINDING_MATRIX_TRANSPOSED true
#include <mlpack/bindings/python/py_option.hpp>
#include <mlpack/bindings/python/print_doc_functions.hpp>
+1
View File
@@ -17,6 +17,7 @@ set(DIRS
gmm
hmm
hoeffding_trees
kde
kernel_pca
kmeans
lars
+21 -9
View File
@@ -42,7 +42,7 @@ namespace ann /** Artificial Neural Network. */ {
* f'(x) &=& \left\{
* \begin{array}{lr}
* 1 & : x > 0 \\
* y + \alpha & : x \le 0
* f(x) + \alpha & : x \le 0
* \end{array}
* \right.
* @f}
@@ -73,7 +73,7 @@ namespace ann /** Artificial Neural Network. */ {
* f'(x) &=& \left\{
* \begin{array}{lr}
* \lambda & : x > 0 \\
* \lambda * (y + \alpha) & : x \le 0
* f(x) + \lambda * \alpha & : x \le 0
* \end{array}
* \right.
* @f}
@@ -90,6 +90,10 @@ namespace ann /** Artificial Neural Network. */ {
* }
* @endcode
*
* In the deterministic mode, there is no computation of the derivative.
*
* @note During training deterministic should be set to false and during
* testing/inference deterministic should be set to true.
* @note Make sure to use SELU activation function with normalized inputs and
* weights initialized with Lecun Normal Initialization.
*
@@ -137,7 +141,7 @@ class ELU
* f(x) by propagating x backwards through f. Using the results from the feed
* forward pass.
*
* @param input The propagated input activation.
* @param input The propagated input activation f(x).
* @param gy The backpropagated error.
* @param g The calculated gradient.
*/
@@ -206,27 +210,29 @@ class ELU
* Computes the first derivative of the activation function.
*
* @param x Input data.
* @param y Propagated data f(x).
* @return f'(x)
*/
double Deriv(const double y)
double Deriv(const double x, const double y)
{
return (y > 0) ? lambda : lambda * (y + alpha);
return (x > 0) ? lambda : y + lambda * alpha;
}
/**
* Computes the first derivative of the activation function.
*
* @param y Input activations.
* @param x The resulting derivatives.
* @param x Input data.
* @param y Output activations f(x).
* @param z The resulting derivatives.
*/
template<typename InputType, typename OutputType>
void Deriv(const InputType& x, OutputType& y)
{
y = x;
derivative.set_size(arma::size(x));
for (size_t i = 0; i < x.n_elem; i++)
{
y(i) = Deriv(x(i));
derivative(i) = Deriv(x(i), y(i));
}
}
@@ -236,6 +242,9 @@ class ELU
//! Locally-stored output parameter object.
OutputDataType outputParameter;
//! Locally stored first derivative of the activation function.
arma::mat derivative;
//! ELU Hyperparameter (0 < alpha)
//! SELU parameter fixed to 1.6732632423543774 for normalized inputs.
double alpha;
@@ -245,6 +254,9 @@ class ELU
//! For SELU activation function, lambda = 1.0507009873554802 for normalized
//! inputs.
double lambda;
//! If true the derivative computation is disabled, see notes above.
bool deterministic;
}; // class ELU
// Template alias for SELU using ELU class.
+11 -5
View File
@@ -29,7 +29,8 @@ namespace ann /** Artificial Neural Network. */ {
template<typename InputDataType, typename OutputDataType>
ELU<InputDataType, OutputDataType>::ELU() :
alpha(1.6732632423543774),
lambda(1.0507009873554802)
lambda(1.0507009873554802),
deterministic(false)
{
// Nothing to do here.
}
@@ -38,7 +39,9 @@ ELU<InputDataType, OutputDataType>::ELU() :
// is fixed and equal to 1. 'alpha' is a hyperparameter.
template<typename InputDataType, typename OutputDataType>
ELU<InputDataType, OutputDataType>::ELU(const double alpha) :
alpha(alpha), lambda(1)
alpha(alpha),
lambda(1),
deterministic(false)
{
// Nothing to do here.
}
@@ -49,15 +52,18 @@ void ELU<InputDataType, OutputDataType>::Forward(
const InputType&& input, OutputType&& output)
{
Fn(input, output);
if (!deterministic)
{
Deriv(input, output);
}
}
template<typename InputDataType, typename OutputDataType>
template<typename DataType>
void ELU<InputDataType, OutputDataType>::Backward(
const DataType&& input, DataType&& gy, DataType&& g)
const DataType&& /* input */, DataType&& gy, DataType&& g)
{
DataType derivative;
Deriv(input, derivative);
g = gy % derivative;
}
@@ -18,7 +18,6 @@
#include "../visitor/delete_visitor.hpp"
#include "../visitor/delta_visitor.hpp"
#include "../visitor/output_parameter_visitor.hpp"
#include "../visitor/weight_size_visitor.hpp"
#include "layer_types.hpp"
#include "add_merge.hpp"
@@ -192,9 +191,6 @@ class Recurrent
//! Locally-stored merge module.
LayerTypes<CustomLayers...> mergeModule;
//! Locally-stored weight size visitor.
WeightSizeVisitor weightSizeVisitor;
//! Locally-stored delta visitor.
DeltaVisitor deltaVisitor;
@@ -86,11 +86,6 @@ Recurrent<InputDataType, OutputDataType, CustomLayers...>::Recurrent(
boost::apply_visitor(AddVisitor<CustomLayers...>(transferModule),
initialModule);
boost::apply_visitor(weightSizeVisitor, startModule);
boost::apply_visitor(weightSizeVisitor, inputModule);
boost::apply_visitor(weightSizeVisitor, feedbackModule);
boost::apply_visitor(weightSizeVisitor, transferModule);
boost::apply_visitor(AddVisitor<CustomLayers...>(inputModule), mergeModule);
boost::apply_visitor(AddVisitor<CustomLayers...>(feedbackModule),
mergeModule);
@@ -271,11 +266,6 @@ void Recurrent<InputDataType, OutputDataType, CustomLayers...>::serialize(
boost::apply_visitor(AddVisitor<CustomLayers...>(transferModule),
initialModule);
boost::apply_visitor(weightSizeVisitor, startModule);
boost::apply_visitor(weightSizeVisitor, inputModule);
boost::apply_visitor(weightSizeVisitor, feedbackModule);
boost::apply_visitor(weightSizeVisitor, transferModule);
boost::apply_visitor(AddVisitor<CustomLayers...>(inputModule),
mergeModule);
boost::apply_visitor(AddVisitor<CustomLayers...>(feedbackModule),
@@ -51,6 +51,8 @@ HoeffdingTree<
ownsInfo(true),
successProbability(successProbability),
splitDimension(size_t(-1)),
majorityClass(0),
majorityProbability(0.0),
categoricalSplit(0),
numericSplit()
{
@@ -107,6 +109,8 @@ HoeffdingTree<
ownsInfo(true),
successProbability(successProbability),
splitDimension(size_t(-1)),
majorityClass(0),
majorityProbability(0.0),
categoricalSplit(0),
numericSplit()
{
@@ -169,6 +173,8 @@ HoeffdingTree<
ownsInfo(true),
successProbability(0.95),
splitDimension(size_t(-1)),
majorityClass(0),
majorityProbability(0.0),
categoricalSplit(0),
numericSplit()
{
+23
View File
@@ -0,0 +1,23 @@
# Define the files we need to compile.
# Anything not in this list will not be compiled into mlpack.
set(SOURCES
kde.hpp
kde_impl.hpp
kde_rules.hpp
kde_rules_impl.hpp
kde_stat.hpp
kde_model.hpp
kde_model_impl.hpp
)
# Add directory name to sources.
set(DIR_SRCS)
foreach(file ${SOURCES})
set(DIR_SRCS ${DIR_SRCS} ${CMAKE_CURRENT_SOURCE_DIR}/${file})
endforeach()
# Append sources (with directory name) to list of all mlpack sources (used at
# the parent scope).
set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
add_cli_executable(kde)
add_python_binding(kde)
+263
View File
@@ -0,0 +1,263 @@
/**
* @file kde.hpp
* @author Roberto Hueso
*
* Kernel Density Estimation.
*
* mlpack is free software; you may redistribute it and/or modify it under the
* terms of the 3-clause BSD license. You should have received a copy of the
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*/
#ifndef MLPACK_METHODS_KDE_KDE_HPP
#define MLPACK_METHODS_KDE_KDE_HPP
#include <mlpack/prereqs.hpp>
#include <mlpack/core/tree/binary_space_tree.hpp>
#include "kde_stat.hpp"
namespace mlpack {
namespace kde /** Kernel Density Estimation. */ {
//! KDEMode represents the ways in which KDE algorithm can be executed.
enum KDEMode
{
DUAL_TREE_MODE,
SINGLE_TREE_MODE
};
/**
* The KDE class is a template class for performing Kernel Density Estimations.
* In statistics, kernel density estimation is a way to estimate the
* probability density function of a variable in a non parametric way.
* This implementation performs this estimation using a tree-independent
* dual-tree algorithm. Details about this algorithm are available in KDERules.
*
* @tparam KernelType Kernel function to use for KDE calculations.
* @tparam MetricType Metric to use for KDE calculations.
* @tparam MatType Type of data to use.
* @tparam TreeType Type of tree to use; must satisfy the TreeType policy API.
* @tparam DualTreeTraversalType Type of dual-tree traversal to use.
* @tparam SingleTreeTraversalType Type of single-tree traversal to use.
*/
template<typename KernelType = kernel::GaussianKernel,
typename MetricType = mlpack::metric::EuclideanDistance,
typename MatType = arma::mat,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType = tree::KDTree,
template<typename RuleType> class DualTreeTraversalType =
TreeType<MetricType,
kde::KDEStat,
MatType>::template DualTreeTraverser,
template<typename RuleType> class SingleTreeTraversalType =
TreeType<MetricType,
kde::KDEStat,
MatType>::template SingleTreeTraverser>
class KDE
{
public:
//! Convenience typedef.
typedef TreeType<MetricType, kde::KDEStat, MatType> Tree;
/**
* Initialize KDE object using custom instantiated Metric and Kernel objects.
*
* @param relError Relative error tolerance of the model.
* @param absError Absolute error tolerance of the model.
* @param kernel Instantiated kernel object.
* @param mode Mode for the algorithm.
* @param metric Instantiated metric object.
*/
KDE(const double relError = 0.05,
const double absError = 0,
KernelType kernel = KernelType(),
const KDEMode mode = DUAL_TREE_MODE,
MetricType metric = MetricType());
/**
* Construct KDE object as a copy of the given model. This may be
* computationally intensive!
*
* @param other KDE object to copy.
*/
KDE(const KDE& other);
/**
* Construct KDE object taking ownership of the given model.
*
* @param other KDE object to take ownership of.
*/
KDE(KDE&& other);
/**
* Copy a KDE model.
*
* Use std::move if the object to copy is no longer needed.
*
* @param other KDE model to copy.
*/
KDE& operator=(KDE other);
/**
* Destroy the KDE object. If this object created any trees, they will be
* deleted. If you created the trees then you have to delete them yourself.
*/
~KDE();
/**
* Trains the KDE model. It builds a tree using a reference set.
*
* Use std::move if the reference set is no longer needed.
*
* @param referenceSet Set of reference data.
*/
void Train(MatType referenceSet);
/**
* Trains the KDE model. Sets the reference tree to an already created tree.
*
* - If TreeTraits<TreeType>::RearrangesDataset is false then it is possible
* to use an empty oldFromNewReferences vector.
*
* @param referenceTree Built reference tree.
* @param oldFromNewReferences Permutations of reference points obtained
* during tree generation.
*/
void Train(Tree* referenceTree, std::vector<size_t>* oldFromNewReferences);
/**
* Estimate density of each point in the query set given the data of the
* reference set. The result is stored in an estimations vector.
* Estimations might not be normalized.
*
* - Dimension of each point in the query set must match the dimension of each
* point in the reference set.
*
* - Use std::move if the query set is no longer needed.
*
* @pre The model has to be previously trained.
* @param querySet Set of query points to get the density of.
* @param estimations Object which will hold the density of each query point.
*/
void Evaluate(MatType querySet, arma::vec& estimations);
/**
* Estimate density of each point in the query set given the data of an
* already created query tree. The result is stored in an estimations vector.
* Estimations might not be normalized.
*
* - Dimension of each point in the queryTree dataset must match the dimension
* of each point in the reference set.
*
* - Use std::move if the query tree is no longer needed.
*
* @pre The model has to be previously trained and mode has to be dual-tree.
* @param queryTree Tree of query points to get the density of.
* @param oldFromNewQueries Mappings of query points to the tree dataset.
* @param estimations Object which will hold the density of each query point.
*/
void Evaluate(Tree* queryTree,
const std::vector<size_t>& oldFromNewQueries,
arma::vec& estimations);
/**
* Estimate density of each point in the reference set given the data of the
* reference set. It does not compute the estimation of a point with itself.
* The result is stored in an estimations vector. Estimations might not be
* normalized.
*
* @pre The model has to be previously trained.
* @param estimations Object which will hold the density of each reference
* point.
*/
void Evaluate(arma::vec& estimations);
//! Get the kernel.
const KernelType& Kernel() const { return kernel; }
//! Modify the kernel.
KernelType& Kernel() { return kernel; }
//! Get the metric.
const MetricType& Metric() const { return metric; }
//! Modify the metric.
MetricType& Metric() { return metric; }
//! Get the reference tree.
Tree* ReferenceTree() { return referenceTree; }
//! Get relative error tolerance.
double RelativeError() const { return relError; }
//! Modify relative error tolerance (0 <= newError <= 1).
void RelativeError(const double newError);
//! Get absolute error tolerance.
double AbsoluteError() const { return absError; }
//! Modify absolute error tolerance (0 <= newError).
void AbsoluteError(const double newError);
//! Check whether reference tree is owned by the KDE model.
bool OwnsReferenceTree() const { return ownsReferenceTree; }
//! Check whether KDE model is trained or not.
bool IsTrained() const { return trained; }
//! Get the mode of KDE.
KDEMode Mode() const { return mode; }
//! Modify the mode of KDE.
KDEMode& Mode() { return mode; }
//! Serialize the model.
template<typename Archive>
void serialize(Archive& ar, const unsigned int /* version */);
private:
//! Kernel.
KernelType kernel;
//! Metric.
MetricType metric;
//! Reference tree.
Tree* referenceTree;
//! Permutations of reference points.
std::vector<size_t>* oldFromNewReferences;
//! Relative error tolerance.
double relError;
//! Absolute error tolerance.
double absError;
//! If true, the KDE object is responsible for deleting the reference tree.
bool ownsReferenceTree;
//! If true, the KDE object is trained.
bool trained;
//! Mode of the KDE algorithm.
KDEMode mode;
//! Check whether absolute and relative error values are compatible.
static void CheckErrorValues(const double relError, const double absError);
//! Rearrange estimations vector if required.
static void RearrangeEstimations(const std::vector<size_t>& oldFromNew,
arma::vec& estimations);
};
} // namespace kde
} // namespace mlpack
// Include implementation.
#include "kde_impl.hpp"
#endif // MLPACK_METHODS_KDE_KDE_HPP
+651
View File
@@ -0,0 +1,651 @@
/**
* @file kde_impl.hpp
* @author Roberto Hueso
*
* Implementation of Kernel Density Estimation.
*
* mlpack is free software; you may redistribute it and/or modify it under the
* terms of the 3-clause BSD license. You should have received a copy of the
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*/
#include "kde.hpp"
#include "kde_rules.hpp"
namespace mlpack {
namespace kde {
//! Construct tree that rearranges the dataset.
template<typename TreeType, typename MatType>
TreeType* BuildTree(
MatType&& dataset,
std::vector<size_t>& oldFromNew,
const typename std::enable_if<
tree::TreeTraits<TreeType>::RearrangesDataset>::type* = 0)
{
return new TreeType(std::forward<MatType>(dataset), oldFromNew);
}
//! Construct tree that doesn't rearrange the dataset.
template<typename TreeType, typename MatType>
TreeType* BuildTree(
MatType&& dataset,
const std::vector<size_t>& /* oldFromNew */,
const typename std::enable_if<
!tree::TreeTraits<TreeType>::RearrangesDataset>::type* = 0)
{
return new TreeType(std::forward<MatType>(dataset));
}
template<typename KernelType,
typename MetricType,
typename MatType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType,
template<typename> class DualTreeTraversalType,
template<typename> class SingleTreeTraversalType>
KDE<KernelType,
MetricType,
MatType,
TreeType,
DualTreeTraversalType,
SingleTreeTraversalType>::
KDE(const double relError,
const double absError,
KernelType kernel,
const KDEMode mode,
MetricType metric) :
kernel(kernel),
metric(metric),
referenceTree(nullptr),
oldFromNewReferences(nullptr),
relError(relError),
absError(absError),
ownsReferenceTree(false),
trained(false),
mode(mode)
{
CheckErrorValues(relError, absError);
}
template<typename KernelType,
typename MetricType,
typename MatType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType,
template<typename> class DualTreeTraversalType,
template<typename> class SingleTreeTraversalType>
KDE<KernelType,
MetricType,
MatType,
TreeType,
DualTreeTraversalType,
SingleTreeTraversalType>::
KDE(const KDE& other) :
kernel(KernelType(other.kernel)),
metric(MetricType(other.metric)),
relError(other.relError),
absError(other.absError),
ownsReferenceTree(other.ownsReferenceTree),
trained(other.trained),
mode(other.mode)
{
if (trained)
{
if (ownsReferenceTree)
{
oldFromNewReferences =
new std::vector<size_t>(*other.oldFromNewReferences);
referenceTree = new Tree(*other.referenceTree);
}
else
{
oldFromNewReferences = other.oldFromNewReferences;
referenceTree = other.referenceTree;
}
}
}
template<typename KernelType,
typename MetricType,
typename MatType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType,
template<typename> class DualTreeTraversalType,
template<typename> class SingleTreeTraversalType>
KDE<KernelType,
MetricType,
MatType,
TreeType,
DualTreeTraversalType,
SingleTreeTraversalType>::
KDE(KDE&& other) :
kernel(std::move(other.kernel)),
metric(std::move(other.metric)),
referenceTree(other.referenceTree),
oldFromNewReferences(other.oldFromNewReferences),
relError(other.relError),
absError(other.absError),
ownsReferenceTree(other.ownsReferenceTree),
trained(other.trained),
mode(other.mode)
{
other.kernel = std::move(KernelType());
other.metric = std::move(MetricType());
other.referenceTree = nullptr;
other.oldFromNewReferences = nullptr;
other.ownsReferenceTree = false;
other.trained = false;
}
template<typename KernelType,
typename MetricType,
typename MatType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType,
template<typename> class DualTreeTraversalType,
template<typename> class SingleTreeTraversalType>
KDE<KernelType,
MetricType,
MatType,
TreeType,
DualTreeTraversalType,
SingleTreeTraversalType>&
KDE<KernelType,
MetricType,
MatType,
TreeType,
DualTreeTraversalType,
SingleTreeTraversalType>::
operator=(KDE other)
{
// Clean memory.
if (ownsReferenceTree)
{
delete referenceTree;
delete oldFromNewReferences;
}
// Move the other object.
this->kernel = std::move(other.kernel);
this->metric = std::move(other.metric);
this->referenceTree = std::move(other.referenceTree);
this->oldFromNewReferences = std::move(other.oldFromNewReferences);
this->relError = other.relError;
this->absError = other.absError;
this->ownsReferenceTree = other.ownsReferenceTree;
this->trained = other.trained;
this->mode = other.mode;
return *this;
}
template<typename KernelType,
typename MetricType,
typename MatType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType,
template<typename> class DualTreeTraversalType,
template<typename> class SingleTreeTraversalType>
KDE<KernelType,
MetricType,
MatType,
TreeType,
DualTreeTraversalType,
SingleTreeTraversalType>::
~KDE()
{
if (ownsReferenceTree)
{
delete referenceTree;
delete oldFromNewReferences;
}
}
template<typename KernelType,
typename MetricType,
typename MatType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType,
template<typename> class DualTreeTraversalType,
template<typename> class SingleTreeTraversalType>
void KDE<KernelType,
MetricType,
MatType,
TreeType,
DualTreeTraversalType,
SingleTreeTraversalType>::
Train(MatType referenceSet)
{
// Check if referenceSet is not an empty set.
if (referenceSet.n_cols == 0)
{
throw std::invalid_argument("cannot train KDE model with an empty "
"reference set");
}
if (ownsReferenceTree)
{
delete referenceTree;
delete oldFromNewReferences;
}
this->ownsReferenceTree = true;
Timer::Start("building_reference_tree");
this->oldFromNewReferences = new std::vector<size_t>;
this->referenceTree = BuildTree<Tree>(std::move(referenceSet),
*oldFromNewReferences);
Timer::Stop("building_reference_tree");
this->trained = true;
}
template<typename KernelType,
typename MetricType,
typename MatType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType,
template<typename> class DualTreeTraversalType,
template<typename> class SingleTreeTraversalType>
void KDE<KernelType,
MetricType,
MatType,
TreeType,
DualTreeTraversalType,
SingleTreeTraversalType>::
Train(Tree* referenceTree, std::vector<size_t>* oldFromNewReferences)
{
// Check if referenceTree dataset is not an empty set.
if (referenceTree->Dataset().n_cols == 0)
{
throw std::invalid_argument("cannot train KDE model with an empty "
"reference set");
}
if (ownsReferenceTree == true)
{
delete this->referenceTree;
delete this->oldFromNewReferences;
}
this->ownsReferenceTree = false;
this->referenceTree = referenceTree;
this->oldFromNewReferences = oldFromNewReferences;
this->trained = true;
}
template<typename KernelType,
typename MetricType,
typename MatType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType,
template<typename> class DualTreeTraversalType,
template<typename> class SingleTreeTraversalType>
void KDE<KernelType,
MetricType,
MatType,
TreeType,
DualTreeTraversalType,
SingleTreeTraversalType>::
Evaluate(MatType querySet, arma::vec& estimations)
{
if (mode == DUAL_TREE_MODE)
{
Timer::Start("building_query_tree");
std::vector<size_t> oldFromNewQueries;
Tree* queryTree = BuildTree<Tree>(std::move(querySet), oldFromNewQueries);
Timer::Stop("building_query_tree");
this->Evaluate(queryTree, oldFromNewQueries, estimations);
delete queryTree;
}
else if (mode == SINGLE_TREE_MODE)
{
// Get estimations vector ready.
estimations.clear();
estimations.set_size(querySet.n_cols);
estimations.fill(arma::fill::zeros);
// Check whether has already been trained.
if (!trained)
{
throw std::runtime_error("cannot evaluate KDE model: model needs to be "
"trained before evaluation");
}
// Check querySet has at least 1 element to evaluate.
if (querySet.n_cols == 0)
{
Log::Warn << "KDE::Evaluate(): querySet is empty, no predictions will "
<< "be returned" << std::endl;
return;
}
// Check whether dimensions match.
if (querySet.n_rows != referenceTree->Dataset().n_rows)
{
throw std::invalid_argument("cannot evaluate KDE model: querySet and "
"referenceSet dimensions don't match");
}
Timer::Start("computing_kde");
// Evaluate.
typedef KDERules<MetricType, KernelType, Tree> RuleType;
RuleType rules = RuleType(referenceTree->Dataset(),
querySet,
estimations,
relError,
absError,
metric,
kernel,
false);
// Create traverser.
SingleTreeTraversalType<RuleType> traverser(rules);
// Traverse for each point.
for (size_t i = 0; i < querySet.n_cols; ++i)
traverser.Traverse(i, *referenceTree);
estimations /= referenceTree->Dataset().n_cols;
Timer::Stop("computing_kde");
Log::Info << rules.Scores() << " node combinations were scored."
<< std::endl;
Log::Info << rules.BaseCases() << " base cases were calculated."
<< std::endl;
}
}
template<typename KernelType,
typename MetricType,
typename MatType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType,
template<typename> class DualTreeTraversalType,
template<typename> class SingleTreeTraversalType>
void KDE<KernelType,
MetricType,
MatType,
TreeType,
DualTreeTraversalType,
SingleTreeTraversalType>::
Evaluate(Tree* queryTree,
const std::vector<size_t>& oldFromNewQueries,
arma::vec& estimations)
{
// Get estimations vector ready.
estimations.clear();
estimations.set_size(queryTree->Dataset().n_cols);
estimations.fill(arma::fill::zeros);
// Check whether has already been trained.
if (!trained)
{
throw std::runtime_error("cannot evaluate KDE model: model needs to be "
"trained before evaluation");
}
// Check querySet has at least 1 element to evaluate.
if (queryTree->Dataset().n_cols == 0)
{
Log::Warn << "KDE::Evaluate(): querySet is empty, no predictions will "
<< "be returned" << std::endl;
return;
}
// Check whether dimensions match.
if (queryTree->Dataset().n_rows != referenceTree->Dataset().n_rows)
{
throw std::invalid_argument("cannot evaluate KDE model: querySet and "
"referenceSet dimensions don't match");
}
// Check the mode is correct.
if (mode != DUAL_TREE_MODE)
{
throw std::invalid_argument("cannot evaluate KDE model: cannot use "
"a query tree when mode is different from "
"dual-tree");
}
Timer::Start("computing_kde");
// Evaluate.
typedef KDERules<MetricType, KernelType, Tree> RuleType;
RuleType rules = RuleType(referenceTree->Dataset(),
queryTree->Dataset(),
estimations,
relError,
absError,
metric,
kernel,
false);
// Create traverser.
DualTreeTraversalType<RuleType> traverser(rules);
traverser.Traverse(*queryTree, *referenceTree);
estimations /= referenceTree->Dataset().n_cols;
Timer::Stop("computing_kde");
// Rearrange if necessary.
RearrangeEstimations(oldFromNewQueries, estimations);
Log::Info << rules.Scores() << " node combinations were scored." << std::endl;
Log::Info << rules.BaseCases() << " base cases were calculated." << std::endl;
}
template<typename KernelType,
typename MetricType,
typename MatType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType,
template<typename> class DualTreeTraversalType,
template<typename> class SingleTreeTraversalType>
void KDE<KernelType,
MetricType,
MatType,
TreeType,
DualTreeTraversalType,
SingleTreeTraversalType>::
Evaluate(arma::vec& estimations)
{
// Check whether has already been trained.
if (!trained)
{
throw std::runtime_error("cannot evaluate KDE model: model needs to be "
"trained before evaluation");
}
// Get estimations vector ready.
estimations.clear();
estimations.set_size(referenceTree->Dataset().n_cols);
estimations.fill(arma::fill::zeros);
Timer::Start("computing_kde");
// Evaluate.
typedef KDERules<MetricType, KernelType, Tree> RuleType;
RuleType rules = RuleType(referenceTree->Dataset(),
referenceTree->Dataset(),
estimations,
relError,
absError,
metric,
kernel,
true);
if (mode == DUAL_TREE_MODE)
{
// Create traverser.
DualTreeTraversalType<RuleType> traverser(rules);
traverser.Traverse(*referenceTree, *referenceTree);
}
else if (mode == SINGLE_TREE_MODE)
{
SingleTreeTraversalType<RuleType> traverser(rules);
for (size_t i = 0; i < referenceTree->Dataset().n_cols; ++i)
traverser.Traverse(i, *referenceTree);
}
estimations /= referenceTree->Dataset().n_cols;
// Rearrange if necessary.
RearrangeEstimations(*oldFromNewReferences, estimations);
Timer::Stop("computing_kde");
Log::Info << rules.Scores() << " node combinations were scored." << std::endl;
Log::Info << rules.BaseCases() << " base cases were calculated." << std::endl;
}
template<typename KernelType,
typename MetricType,
typename MatType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType,
template<typename> class DualTreeTraversalType,
template<typename> class SingleTreeTraversalType>
void KDE<KernelType,
MetricType,
MatType,
TreeType,
DualTreeTraversalType,
SingleTreeTraversalType>::
RelativeError(const double newError)
{
CheckErrorValues(newError, absError);
relError = newError;
}
template<typename KernelType,
typename MetricType,
typename MatType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType,
template<typename> class DualTreeTraversalType,
template<typename> class SingleTreeTraversalType>
void KDE<KernelType,
MetricType,
MatType,
TreeType,
DualTreeTraversalType,
SingleTreeTraversalType>::
AbsoluteError(const double newError)
{
CheckErrorValues(relError, newError);
absError = newError;
}
template<typename KernelType,
typename MetricType,
typename MatType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType,
template<typename> class DualTreeTraversalType,
template<typename> class SingleTreeTraversalType>
template<typename Archive>
void KDE<KernelType,
MetricType,
MatType,
TreeType,
DualTreeTraversalType,
SingleTreeTraversalType>::
serialize(Archive& ar, const unsigned int /* version */)
{
// Serialize preferences.
ar & BOOST_SERIALIZATION_NVP(relError);
ar & BOOST_SERIALIZATION_NVP(absError);
ar & BOOST_SERIALIZATION_NVP(trained);
ar & BOOST_SERIALIZATION_NVP(mode);
// If we are loading, clean up memory if necessary.
if (Archive::is_loading::value)
{
if (ownsReferenceTree && referenceTree)
{
delete referenceTree;
delete oldFromNewReferences;
}
// After loading tree, we own it.
ownsReferenceTree = true;
}
// Serialize the rest of values.
ar & BOOST_SERIALIZATION_NVP(kernel);
ar & BOOST_SERIALIZATION_NVP(metric);
ar & BOOST_SERIALIZATION_NVP(referenceTree);
ar & BOOST_SERIALIZATION_NVP(oldFromNewReferences);
}
template<typename KernelType,
typename MetricType,
typename MatType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType,
template<typename> class DualTreeTraversalType,
template<typename> class SingleTreeTraversalType>
void KDE<KernelType,
MetricType,
MatType,
TreeType,
DualTreeTraversalType,
SingleTreeTraversalType>::
CheckErrorValues(const double relError, const double absError)
{
if (relError < 0 || relError > 1)
{
throw std::invalid_argument("Relative error tolerance must be a value "
"between 0 and 1");
}
if (absError < 0)
{
throw std::invalid_argument("Absolute error tolerance must be a value "
"greater or equal to 0");
}
}
template<typename KernelType,
typename MetricType,
typename MatType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType,
template<typename> class DualTreeTraversalType,
template<typename> class SingleTreeTraversalType>
void KDE<KernelType,
MetricType,
MatType,
TreeType,
DualTreeTraversalType,
SingleTreeTraversalType>::
RearrangeEstimations(const std::vector<size_t>& oldFromNew,
arma::vec& estimations)
{
if (tree::TreeTraits<Tree>::RearrangesDataset)
{
const size_t nQueries = oldFromNew.size();
arma::vec rearrangedEstimations(nQueries);
// Remap vector.
for (size_t i = 0; i < nQueries; ++i)
rearrangedEstimations(oldFromNew.at(i)) = estimations(i);
estimations = std::move(rearrangedEstimations);
}
}
} // namespace kde
} // namespace mlpack
+208
View File
@@ -0,0 +1,208 @@
/**
* @file kde_main.cpp
* @author Roberto Hueso
*
* Executable for running Kernel Density Estimation.
*
* mlpack is free software; you may redistribute it and/or modify it under the
* terms of the 3-clause BSD license. You should have received a copy of the
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*/
#include <mlpack/core/util/mlpack_main.hpp>
#include "kde.hpp"
#include "kde_model.hpp"
using namespace mlpack;
using namespace mlpack::kde;
using namespace mlpack::util;
using namespace std;
// Define parameters for the executable.
PROGRAM_INFO("Kernel Density Estimation",
"This program performs a Kernel Density Estimation. KDE is a "
"non-parametric way of estimating probability density function. "
"For each query point the program will estimate its probability density "
"by applying a kernel function to each reference point. The computational "
"complexity of this is O(N^2) where there are N query points and N "
"reference points, but this implementation will typically see better "
"performance as it uses an approximate dual or single tree algorithm for "
"acceleration."
"\n\n"
"Dual or single tree optimization allows to avoid lots of barely relevant "
"calculations (as kernel function values decrease with distance), so it is "
"an approximate computation. You can specify the maximum relative error "
"tolerance for each query value with " + PRINT_PARAM_STRING("rel_error") +
" as well as the maximum absolute error tolerance with the parameter " +
PRINT_PARAM_STRING("abs_error") + ". This program runs using an Euclidean "
"metric. Kernel function can be selected using the " +
PRINT_PARAM_STRING("kernel") + " option. You can also choose what which "
"type of tree to use for the dual-tree algorithm with " +
PRINT_PARAM_STRING("tree") + ". It is also possible to select whether to "
"use dual-tree algorithm or single-tree algorithm using the " +
PRINT_PARAM_STRING("algorithm") + " option."
"\n\n"
"For example, the following will run KDE using the data in " +
PRINT_DATASET("ref_data") + " for training and the data in " +
PRINT_DATASET("qu_data") + " as query data. It will apply an Epanechnikov "
"kernel with a 0.2 bandwidth to each reference point and use a KD-Tree for "
"the dual-tree optimization. The returned predictions will be within 5% of "
"the real KDE value for each query point."
"\n\n" +
PRINT_CALL("kde", "reference", "ref_data", "query", "qu_data", "bandwidth",
0.2, "kernel", "epanechnikov", "tree", "kd-tree", "rel_error",
0.05, "predictions", "out_data") +
"\n\n"
"the predicted density estimations will be stored in " +
PRINT_DATASET("out_data") + "."
"\n"
"If no " + PRINT_PARAM_STRING("query") + " is provided, then KDE will be "
"computed on the " + PRINT_PARAM_STRING("reference") + " dataset."
"\n"
"It is possible to select either a reference dataset or an input model "
"but not both at the same time.");
// Required options.
PARAM_MATRIX_IN("reference", "Input reference dataset use for KDE.", "r");
PARAM_MATRIX_IN("query", "Query dataset to KDE on.", "q");
PARAM_DOUBLE_IN("bandwidth", "Bandwidth of the kernel.", "b", 1.0);
// Load or save models.
PARAM_MODEL_IN(KDEModel,
"input_model",
"Contains pre-trained KDE model.",
"m");
PARAM_MODEL_OUT(KDEModel,
"output_model",
"If specified, the KDE model will be saved here.",
"M");
// Configuration options.
PARAM_STRING_IN("kernel", "Kernel to use for the prediction."
"('gaussian', 'epanechnikov', 'laplacian', 'spherical', 'triangular').",
"k", "gaussian");
PARAM_STRING_IN("tree", "Tree to use for the prediction."
"('kd-tree', 'ball-tree', 'cover-tree', 'octree', 'r-tree').",
"t", "kd-tree");
PARAM_STRING_IN("algorithm", "Algorithm to use for the prediction."
"('dual-tree', 'single-tree').",
"a", "dual-tree");
PARAM_DOUBLE_IN("rel_error",
"Relative error tolerance for the prediction.",
"e",
0.05);
PARAM_DOUBLE_IN("abs_error",
"Relative error tolerance for the prediction.",
"E",
0.0);
// Output predictions options.
PARAM_COL_OUT("predictions", "Vector to store density predictions.",
"p");
// Maybe, in the future, it could be interesting to implement different metrics.
static void mlpackMain()
{
// Get some parameters.
const double bandwidth = CLI::GetParam<double>("bandwidth");
const std::string kernelStr = CLI::GetParam<std::string>("kernel");
const std::string treeStr = CLI::GetParam<std::string>("tree");
const std::string modeStr = CLI::GetParam<std::string>("algorithm");
const double relError = CLI::GetParam<double>("rel_error");
const double absError = CLI::GetParam<double>("abs_error");
// Initialize results vector.
arma::vec estimations;
// You can only specify reference data or a pre-trained model.
RequireOnlyOnePassed({ "reference", "input_model" }, true);
ReportIgnoredParam({{ "input_model", true }}, "tree");
ReportIgnoredParam({{ "input_model", true }}, "kernel");
ReportIgnoredParam({{ "input_model", true }}, "rel_error");
ReportIgnoredParam({{ "input_model", true }}, "abs_error");
// Requirements for parameter values.
RequireParamInSet<string>("kernel", { "gaussian", "epanechnikov",
"laplacian", "spherical", "triangular" }, true, "unknown kernel type");
RequireParamInSet<string>("tree", { "kd-tree", "ball-tree", "cover-tree",
"octree", "r-tree"}, true, "unknown tree type");
RequireParamInSet<string>("algorithm", { "dual-tree", "single-tree"},
true, "unknown algorithm");
RequireParamValue<double>("rel_error", [](double x){return x >= 0 && x <= 1;},
true, "relative error must be between 0 and 1");
RequireParamValue<double>("abs_error", [](double x){return x >= 0;},
true, "absolute error must be equal or greater than 0");
KDEModel* kde;
if (CLI::HasParam("reference"))
{
arma::mat reference = std::move(CLI::GetParam<arma::mat>("reference"));
kde = new KDEModel();
// Set parameters.
kde->Bandwidth() = bandwidth;
kde->RelativeError() = relError;
kde->AbsoluteError() = absError;
// Set KernelType.
if (kernelStr == "gaussian")
kde->KernelType() = KDEModel::GAUSSIAN_KERNEL;
else if (kernelStr == "epanechnikov")
kde->KernelType() = KDEModel::EPANECHNIKOV_KERNEL;
else if (kernelStr == "laplacian")
kde->KernelType() = KDEModel::LAPLACIAN_KERNEL;
else if (kernelStr == "spherical")
kde->KernelType() = KDEModel::SPHERICAL_KERNEL;
else if (kernelStr == "triangular")
kde->KernelType() = KDEModel::TRIANGULAR_KERNEL;
// Set TreeType.
if (treeStr == "kd-tree")
kde->TreeType() = KDEModel::KD_TREE;
else if (treeStr == "ball-tree")
kde->TreeType() = KDEModel::BALL_TREE;
else if (treeStr == "cover-tree")
kde->TreeType() = KDEModel::COVER_TREE;
else if (treeStr == "octree")
kde->TreeType() = KDEModel::OCTREE;
else if (treeStr == "r-tree")
kde->TreeType() = KDEModel::R_TREE;
// Build model.
kde->BuildModel(std::move(reference));
// Set Mode.
if (modeStr == "dual-tree")
kde->Mode() = KDEMode::DUAL_TREE_MODE;
else if (modeStr == "single-tree")
kde->Mode() = KDEMode::SINGLE_TREE_MODE;
}
else
{
// Load model.
kde = CLI::GetParam<KDEModel*>("input_model");
}
// Evaluation.
if (CLI::HasParam("query"))
{
arma::mat query = std::move(CLI::GetParam<arma::mat>("query"));
kde->Evaluate(std::move(query), estimations);
}
else
{
kde->Evaluate(estimations);
}
// Output predictions if needed.
if (CLI::HasParam("predictions"))
CLI::GetParam<arma::vec>("predictions") = std::move(estimations);
// Save model.
if (CLI::HasParam("output_model"))
CLI::GetParam<KDEModel*>("output_model") = kde;
}
+382
View File
@@ -0,0 +1,382 @@
/**
* @file kde_model.hpp
* @author Roberto Hueso
*
* Model for KDE. It abstracts different types of tree, kernels, etc.
*
* mlpack is free software; you may redistribute it and/or modify it under the
* terms of the 3-clause BSD license. You should have received a copy of the
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*/
#ifndef MLPACK_METHODS_KDE_MODEL_HPP
#define MLPACK_METHODS_KDE_MODEL_HPP
// Include trees.
#include <mlpack/core/tree/binary_space_tree.hpp>
#include <mlpack/core/tree/octree.hpp>
#include <mlpack/core/tree/cover_tree.hpp>
#include <mlpack/core/tree/rectangle_tree.hpp>
// Include core.
#include <mlpack/core.hpp>
// Remaining includes.
#include <boost/variant.hpp>
#include "kde.hpp"
namespace mlpack {
namespace kde {
//! Alias template.
template<typename KernelType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType>
using KDEType = KDE<KernelType,
metric::EuclideanDistance,
arma::mat,
TreeType,
TreeType<metric::EuclideanDistance,
kde::KDEStat,
arma::mat>::template DualTreeTraverser,
TreeType<metric::EuclideanDistance,
kde::KDEStat,
arma::mat>::template SingleTreeTraverser>;
/**
* KernelNormalizer holds a set of methods to normalize estimations applying
* in each case the appropiate kernel normalizer function.
*/
class KernelNormalizer
{
private:
// SFINAE check if Normalizer function is present.
HAS_MEM_FUNC(Normalizer, HasNormalizer);
public:
//! Normalization not needed.
template<typename KernelType>
static void ApplyNormalizer(
KernelType& /* kernel */,
const size_t /* dimension */,
arma::vec& /* estimations */,
const typename std::enable_if<
!HasNormalizer<KernelType, double(KernelType::*)(size_t)>::value>::
type* = 0)
{ return; }
//! Normalize kernels that have normalizer.
template<typename KernelType>
static void ApplyNormalizer(
KernelType& kernel,
const size_t dimension,
arma::vec& estimations,
const typename std::enable_if<
HasNormalizer<KernelType, double(KernelType::*)(size_t)>::value>::
type* = 0)
{
estimations /= kernel.Normalizer(dimension);
}
};
/**
* DualMonoKDE computes a Kernel Density Estimation on the given KDEType.
* It performs a monochromatic KDE.
*/
class DualMonoKDE : public boost::static_visitor<void>
{
private:
//! Vector to store the KDE results.
arma::vec& estimations;
public:
//! Alias template necessary for Visual C++ compiler.
template<typename KernelType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType>
using KDETypeT = KDEType<KernelType, TreeType>;
//! Default DualMonoKDE on some KDEType.
template<typename KernelType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType>
void operator()(KDETypeT<KernelType, TreeType>* kde) const;
// TODO Implement specific cases where a leaf size can be selected.
//! DualMonoKDE constructor.
DualMonoKDE(arma::vec& estimations);
};
/**
* DualBiKDE computes a Kernel Density Estimation on the given KDEType.
* It performs a bichromatic KDE.
*/
class DualBiKDE : public boost::static_visitor<void>
{
private:
//! Query set dimensionality.
const size_t dimension;
//! The query set for the KDE.
const arma::mat& querySet;
//! Vector to store the KDE results.
arma::vec& estimations;
public:
//! Alias template necessary for Visual C++ compiler.
template<typename KernelType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType>
using KDETypeT = KDEType<KernelType, TreeType>;
//! Default DualBiKDE on some KDEType.
template<typename KernelType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType>
void operator()(KDETypeT<KernelType, TreeType>* kde) const;
// TODO Implement specific cases where a leaf size can be selected.
//! DualBiKDE constructor. Takes ownership of the given querySet.
DualBiKDE(arma::mat&& querySet, arma::vec& estimations);
};
/**
* TrainVisitor trains a given KDEType using a reference set.
*/
class TrainVisitor : public boost::static_visitor<void>
{
private:
//! The reference set used for training.
arma::mat&& referenceSet;
public:
//! Default TrainVisitor on some KDEType.
template<typename KernelType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType>
void operator()(KDEType<KernelType, TreeType>* kde) const;
// TODO Implement specific cases where a leaf size can be selected.
//! TrainVisitor constructor. Takes ownership of the given referenceSet.
TrainVisitor(arma::mat&& referenceSet);
};
/**
* ModeVisitor exposes the Mode() method of the KDEType.
*/
class ModeVisitor : public boost::static_visitor<KDEMode&>
{
public:
//! Return mode of KDEType instance.
template<typename KDEType>
KDEMode& operator()(KDEType* kde) const;
};
class DeleteVisitor : public boost::static_visitor<void>
{
public:
//! Delete KDEType instance.
template<typename KDEType>
void operator()(KDEType* kde) const;
};
class KDEModel
{
public:
enum TreeTypes
{
KD_TREE,
BALL_TREE,
COVER_TREE,
OCTREE,
R_TREE
};
enum KernelTypes
{
GAUSSIAN_KERNEL,
EPANECHNIKOV_KERNEL,
LAPLACIAN_KERNEL,
SPHERICAL_KERNEL,
TRIANGULAR_KERNEL
};
private:
//! Bandwidth of the kernel.
double bandwidth;
//! Relative error tolerance.
double relError;
//! Absolute error tolerance.
double absError;
//! Type of kernel.
KernelTypes kernelType;
//! Type of tree.
TreeTypes treeType;
/**
* kdeModel holds an instance of each possible combination of KernelType and
* TreeType. It is initialized using BuildModel.
*/
boost::variant<KDEType<kernel::GaussianKernel, tree::KDTree>*,
KDEType<kernel::GaussianKernel, tree::BallTree>*,
KDEType<kernel::GaussianKernel, tree::StandardCoverTree>*,
KDEType<kernel::GaussianKernel, tree::Octree>*,
KDEType<kernel::GaussianKernel, tree::RTree>*,
KDEType<kernel::EpanechnikovKernel, tree::KDTree>*,
KDEType<kernel::EpanechnikovKernel, tree::BallTree>*,
KDEType<kernel::EpanechnikovKernel, tree::StandardCoverTree>*,
KDEType<kernel::EpanechnikovKernel, tree::Octree>*,
KDEType<kernel::EpanechnikovKernel, tree::RTree>*,
KDEType<kernel::LaplacianKernel, tree::KDTree>*,
KDEType<kernel::LaplacianKernel, tree::BallTree>*,
KDEType<kernel::LaplacianKernel, tree::StandardCoverTree>*,
KDEType<kernel::LaplacianKernel, tree::Octree>*,
KDEType<kernel::LaplacianKernel, tree::RTree>*,
KDEType<kernel::SphericalKernel, tree::KDTree>*,
KDEType<kernel::SphericalKernel, tree::BallTree>*,
KDEType<kernel::SphericalKernel, tree::StandardCoverTree>*,
KDEType<kernel::SphericalKernel, tree::Octree>*,
KDEType<kernel::SphericalKernel, tree::RTree>*,
KDEType<kernel::TriangularKernel, tree::KDTree>*,
KDEType<kernel::TriangularKernel, tree::BallTree>*,
KDEType<kernel::TriangularKernel, tree::StandardCoverTree>*,
KDEType<kernel::TriangularKernel, tree::Octree>*,
KDEType<kernel::TriangularKernel, tree::RTree>*> kdeModel;
public:
/**
* Initialize KDEModel.
*
* @param bandwidth Bandwidth to use for the kernel.
* @param relError Maximum relative error tolerance for each point in the
* model. For example, 0.05 means that each value must be
* within 5% of the true KDE value.
* @param absError Maximum absolute error tolerance for each point in the
* model. For example, 0.1 means that for each point the
* value can have a maximum error of 0.1 units.
* @param kernelType Type of kernel to use.
* @param treeType Type of tree to use.
*/
KDEModel(const double bandwidth = 1.0,
const double relError = 0.05,
const double absError = 0,
const KernelTypes kernelType = KernelTypes::GAUSSIAN_KERNEL,
const TreeTypes treeType = TreeTypes::KD_TREE);
//! Copy constructor of the given model.
KDEModel(const KDEModel& other);
//! Move constructor of the given model. Takes ownership of the model.
KDEModel(KDEModel&& other);
/**
* Copy the given model.
*
* Use std::move if the object to copy is no longer needed.
*
* @param other KDEModel to copy.
*/
KDEModel& operator=(KDEModel other);
//! Destroy the KDEModel object.
~KDEModel();
//! Serialize the KDE model.
template<typename Archive>
void serialize(Archive& ar, const unsigned int /* version */);
//! Get the bandwidth of the kernel.
double Bandwidth() const { return bandwidth; }
//! Modify the bandwidth of the kernel.
double& Bandwidth() { return bandwidth; }
//! Get the relative error tolerance.
double RelativeError() const { return relError; }
//! Modify the relative error tolerance.
double& RelativeError() { return relError; }
//! Get the absolute error tolerance.
double AbsoluteError() const { return absError; }
//! Modify the absolute error tolerance.
double& AbsoluteError() { return absError; }
//! Get the tree type of the model.
TreeTypes TreeType() const { return treeType; }
//! Modify the tree type of the model.
TreeTypes& TreeType() { return treeType; }
//! Get the kernel type of the model.
KernelTypes KernelType() const { return kernelType; }
//! Modify the kernel type of the model.
KernelTypes& KernelType() { return kernelType; }
//! Get the mode of the model.
KDEMode Mode() const;
//! Modify the mode of the model.
KDEMode& Mode();
/**
* Build the KDE model with the given parameters and then trains it with the
* given reference data.
* Takes possession of the reference set to avoid a copy, so the reference set
* will not be usable after this.
*
* @param referenceSet Set of reference points.
*/
void BuildModel(arma::mat&& referenceSet);
/**
* Perform kernel density estimation on the given query set.
* Takes possession of the query set to avoid a copy, so the query set
* will not be usable after this. If possible, it returns normalized
* estimations.
*
* @pre The model has to be previously created with BuildModel.
* @param querySet Set of query points.
* @param estimations Vector where the results will be stored in the same
* order as the query points.
*/
void Evaluate(arma::mat&& querySet, arma::vec& estimations);
/**
* Perform kernel density estimation on the reference set.
* If possible, it returns normalized estimations.
*
* @pre The model has to be previously created with BuildModel.
* @param estimations Vector where the results will be stored in the same
* order as the query points.
*/
void Evaluate(arma::vec& estimations);
private:
//! Clean memory.
void CleanMemory();
};
} // namespace kde
} // namespace mlpack
#include "kde_model_impl.hpp"
#endif
+365
View File
@@ -0,0 +1,365 @@
/**
* @file kde_model_impl.hpp
* @author Roberto Hueso
*
* Implementation of KDE Model.
*
* mlpack is free software; you may redistribute it and/or modify it under the
* terms of the 3-clause BSD license. You should have received a copy of the
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*/
#ifndef MLPACK_METHODS_KDE_MODEL_IMPL_HPP
#define MLPACK_METHODS_KDE_MODEL_IMPL_HPP
// In case it hasn't been included yet.
#include "kde_model.hpp"
#include <boost/serialization/variant.hpp>
namespace mlpack {
namespace kde {
//! Initialize the KDEModel with the given parameters.
inline KDEModel::KDEModel(const double bandwidth,
const double relError,
const double absError,
const KernelTypes kernelType,
const TreeTypes treeType) :
bandwidth(bandwidth),
relError(relError),
absError(absError),
kernelType(kernelType),
treeType(treeType)
{
// Nothing to do.
}
// Copy constructor.
inline KDEModel::KDEModel(const KDEModel& other) :
bandwidth(other.bandwidth),
relError(other.relError),
absError(other.absError),
kernelType(other.kernelType),
treeType(other.treeType)
{
// Nothing to do.
}
// Move constructor.
inline KDEModel::KDEModel(KDEModel&& other) :
bandwidth(other.bandwidth),
relError(other.relError),
absError(other.absError),
kernelType(other.kernelType),
treeType(other.treeType),
kdeModel(std::move(other.kdeModel))
{
// Reset other model.
other.bandwidth = 1.0;
other.relError = 0.05;
other.absError = 0;
other.kernelType = KernelTypes::GAUSSIAN_KERNEL;
other.treeType = TreeTypes::KD_TREE;
other.kdeModel = decltype(other.kdeModel)();
}
inline KDEModel& KDEModel::operator=(KDEModel other)
{
boost::apply_visitor(DeleteVisitor(), kdeModel);
bandwidth = other.bandwidth;
relError = other.relError;
absError = other.absError;
kernelType = other.kernelType;
treeType = other.treeType;
kdeModel = std::move(other.kdeModel);
return *this;
}
// Clean memory.
inline KDEModel::~KDEModel()
{
boost::apply_visitor(DeleteVisitor(), kdeModel);
}
inline void KDEModel::BuildModel(arma::mat&& referenceSet)
{
// Clean memory, if necessary.
boost::apply_visitor(DeleteVisitor(), kdeModel);
// Build the actual model.
if (kernelType == GAUSSIAN_KERNEL && treeType == KD_TREE)
{
kdeModel = new KDEType<kernel::GaussianKernel, tree::KDTree>
(relError, absError, kernel::GaussianKernel(bandwidth));
}
else if (kernelType == GAUSSIAN_KERNEL && treeType == BALL_TREE)
{
kdeModel = new KDEType<kernel::GaussianKernel, tree::BallTree>
(relError, absError, kernel::GaussianKernel(bandwidth));
}
else if (kernelType == GAUSSIAN_KERNEL && treeType == COVER_TREE)
{
kdeModel = new KDEType<kernel::GaussianKernel, tree::StandardCoverTree>
(relError, absError, kernel::GaussianKernel(bandwidth));
}
else if (kernelType == GAUSSIAN_KERNEL && treeType == OCTREE)
{
kdeModel = new KDEType<kernel::GaussianKernel, tree::Octree>
(relError, absError, kernel::GaussianKernel(bandwidth));
}
else if (kernelType == GAUSSIAN_KERNEL && treeType == R_TREE)
{
kdeModel = new KDEType<kernel::GaussianKernel, tree::RTree>
(relError, absError, kernel::GaussianKernel(bandwidth));
}
else if (kernelType == EPANECHNIKOV_KERNEL && treeType == KD_TREE)
{
kdeModel = new KDEType<kernel::EpanechnikovKernel, tree::KDTree>
(relError, absError, kernel::EpanechnikovKernel(bandwidth));
}
else if (kernelType == EPANECHNIKOV_KERNEL && treeType == BALL_TREE)
{
kdeModel = new KDEType<kernel::EpanechnikovKernel, tree::BallTree>
(relError, absError, kernel::EpanechnikovKernel(bandwidth));
}
else if (kernelType == EPANECHNIKOV_KERNEL && treeType == COVER_TREE)
{
kdeModel = new KDEType<kernel::EpanechnikovKernel, tree::StandardCoverTree>
(relError, absError, kernel::EpanechnikovKernel(bandwidth));
}
else if (kernelType == EPANECHNIKOV_KERNEL && treeType == OCTREE)
{
kdeModel = new KDEType<kernel::EpanechnikovKernel, tree::Octree>
(relError, absError, kernel::EpanechnikovKernel(bandwidth));
}
else if (kernelType == EPANECHNIKOV_KERNEL && treeType == R_TREE)
{
kdeModel = new KDEType<kernel::EpanechnikovKernel, tree::RTree>
(relError, absError, kernel::EpanechnikovKernel(bandwidth));
}
else if (kernelType == LAPLACIAN_KERNEL && treeType == KD_TREE)
{
kdeModel = new KDEType<kernel::LaplacianKernel, tree::KDTree>
(relError, absError, kernel::LaplacianKernel(bandwidth));
}
else if (kernelType == LAPLACIAN_KERNEL && treeType == BALL_TREE)
{
kdeModel = new KDEType<kernel::LaplacianKernel, tree::BallTree>
(relError, absError, kernel::LaplacianKernel(bandwidth));
}
else if (kernelType == LAPLACIAN_KERNEL && treeType == COVER_TREE)
{
kdeModel = new KDEType<kernel::LaplacianKernel, tree::StandardCoverTree>
(relError, absError, kernel::LaplacianKernel(bandwidth));
}
else if (kernelType == LAPLACIAN_KERNEL && treeType == OCTREE)
{
kdeModel = new KDEType<kernel::LaplacianKernel, tree::Octree>
(relError, absError, kernel::LaplacianKernel(bandwidth));
}
else if (kernelType == LAPLACIAN_KERNEL && treeType == R_TREE)
{
kdeModel = new KDEType<kernel::LaplacianKernel, tree::RTree>
(relError, absError, kernel::LaplacianKernel(bandwidth));
}
else if (kernelType == SPHERICAL_KERNEL && treeType == KD_TREE)
{
kdeModel = new KDEType<kernel::SphericalKernel, tree::KDTree>
(relError, absError, kernel::SphericalKernel(bandwidth));
}
else if (kernelType == SPHERICAL_KERNEL && treeType == BALL_TREE)
{
kdeModel = new KDEType<kernel::SphericalKernel, tree::BallTree>
(relError, absError, kernel::SphericalKernel(bandwidth));
}
else if (kernelType == SPHERICAL_KERNEL && treeType == COVER_TREE)
{
kdeModel = new KDEType<kernel::SphericalKernel, tree::StandardCoverTree>
(relError, absError, kernel::SphericalKernel(bandwidth));
}
else if (kernelType == SPHERICAL_KERNEL && treeType == OCTREE)
{
kdeModel = new KDEType<kernel::SphericalKernel, tree::Octree>
(relError, absError, kernel::SphericalKernel(bandwidth));
}
else if (kernelType == SPHERICAL_KERNEL && treeType == R_TREE)
{
kdeModel = new KDEType<kernel::SphericalKernel, tree::RTree>
(relError, absError, kernel::SphericalKernel(bandwidth));
}
else if (kernelType == TRIANGULAR_KERNEL && treeType == KD_TREE)
{
kdeModel = new KDEType<kernel::TriangularKernel, tree::KDTree>
(relError, absError, kernel::TriangularKernel(bandwidth));
}
else if (kernelType == TRIANGULAR_KERNEL && treeType == BALL_TREE)
{
kdeModel = new KDEType<kernel::TriangularKernel, tree::BallTree>
(relError, absError, kernel::TriangularKernel(bandwidth));
}
else if (kernelType == TRIANGULAR_KERNEL && treeType == COVER_TREE)
{
kdeModel = new KDEType<kernel::TriangularKernel, tree::StandardCoverTree>
(relError, absError, kernel::TriangularKernel(bandwidth));
}
else if (kernelType == TRIANGULAR_KERNEL && treeType == OCTREE)
{
kdeModel = new KDEType<kernel::TriangularKernel, tree::Octree>
(relError, absError, kernel::TriangularKernel(bandwidth));
}
else if (kernelType == TRIANGULAR_KERNEL && treeType == R_TREE)
{
kdeModel = new KDEType<kernel::TriangularKernel, tree::RTree>
(relError, absError, kernel::TriangularKernel(bandwidth));
}
// Train the model.
TrainVisitor train(std::move(referenceSet));
boost::apply_visitor(train, kdeModel);
}
// Perform bichromatic evaluation.
inline void KDEModel::Evaluate(arma::mat&& querySet, arma::vec& estimations)
{
Log::Info << "Evaluating KDE..." << std::endl;
DualBiKDE eval(std::move(querySet), estimations);
boost::apply_visitor(eval, kdeModel);
}
// Perform monochromatic evaluation.
inline void KDEModel::Evaluate(arma::vec& estimations)
{
Log::Info << "Evaluating KDE..." << std::endl;
DualMonoKDE eval(estimations);
boost::apply_visitor(eval, kdeModel);
}
// Clean memory.
inline void KDEModel::CleanMemory()
{
boost::apply_visitor(DeleteVisitor(), kdeModel);
}
// Parameters for KDE evaluation.
DualMonoKDE::DualMonoKDE(arma::vec& estimations):
estimations(estimations)
{}
// Default KDE evaluation.
template<typename KernelType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType>
void DualMonoKDE::operator()(KDETypeT<KernelType, TreeType>* kde) const
{
if (kde)
{
kde->Evaluate(estimations);
const size_t dimension = (kde->ReferenceTree())->Dataset().n_rows;
KernelNormalizer::ApplyNormalizer<KernelType>(kde->Kernel(),
dimension,
estimations);
}
else
{
throw std::runtime_error("no KDE model initialized");
}
}
// Parameters for KDE evaluation.
DualBiKDE::DualBiKDE(arma::mat&& querySet, arma::vec& estimations):
dimension(querySet.n_rows),
querySet(std::move(querySet)),
estimations(estimations)
{}
// Default KDE evaluation.
template<typename KernelType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType>
void DualBiKDE::operator()(KDETypeT<KernelType, TreeType>* kde) const
{
if (kde)
{
kde->Evaluate(std::move(querySet), estimations);
KernelNormalizer::ApplyNormalizer<KernelType>(kde->Kernel(),
dimension,
estimations);
}
else
{
throw std::runtime_error("no KDE model initialized");
}
}
// Parameters for Train.
TrainVisitor::TrainVisitor(arma::mat&& referenceSet) :
referenceSet(std::move(referenceSet))
{}
// Default Train.
template<typename KernelType,
template<typename TreeMetricType,
typename TreeStatType,
typename TreeMatType> class TreeType>
void TrainVisitor::operator()(KDEType<KernelType, TreeType>* kde) const
{
Log::Info << "Training KDE model..." << std::endl;
if (kde)
kde->Train(std::move(referenceSet));
else
throw std::runtime_error("no KDE model initialized");
}
// Delete model.
template<typename KDEType>
void DeleteVisitor::operator()(KDEType* kde) const
{
if (kde)
delete kde;
}
// Mode of model.
template<typename KDEType>
KDEMode& ModeVisitor::operator()(KDEType* kde) const
{
if (kde)
return kde->Mode();
else
throw std::runtime_error("no KDE model initialized");
}
// Get mode of model.
KDEMode KDEModel::Mode() const
{
return boost::apply_visitor(ModeVisitor(), kdeModel);
}
// Modify mode of model.
KDEMode& KDEModel::Mode()
{
return boost::apply_visitor(ModeVisitor(), kdeModel);
}
// Serialize the model.
template<typename Archive>
void KDEModel::serialize(Archive& ar, const unsigned int /* version */)
{
ar & BOOST_SERIALIZATION_NVP(bandwidth);
ar & BOOST_SERIALIZATION_NVP(relError);
ar & BOOST_SERIALIZATION_NVP(absError);
ar & BOOST_SERIALIZATION_NVP(kernelType);
ar & BOOST_SERIALIZATION_NVP(treeType);
if (Archive::is_loading::value)
boost::apply_visitor(DeleteVisitor(), kdeModel);
ar & BOOST_SERIALIZATION_NVP(kdeModel);
}
} // namespace kde
} // namespace mlpack
#endif
+139
View File
@@ -0,0 +1,139 @@
/**
* @file kde_rules.hpp
* @author Roberto Hueso
*
* Rules for Kernel Density Estimation, so that it can be done with arbitrary
* tree types.
*
* mlpack is free software; you may redistribute it and/or modify it under the
* terms of the 3-clause BSD license. You should have received a copy of the
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*/
#ifndef MLPACK_METHODS_KDE_RULES_HPP
#define MLPACK_METHODS_KDE_RULES_HPP
#include <mlpack/core/tree/traversal_info.hpp>
namespace mlpack {
namespace kde {
/**
* A dual-tree traversal Rules class for kernel density estimation. This
* contains the Score() and BaseCase() implementations.
*/
template<typename MetricType, typename KernelType, typename TreeType>
class KDERules
{
public:
/**
* Construct KDERules.
*
* @param referenceSet Reference set data.
* @param querySet Query set data.
* @param densities Vector where estimations will be written.
* @param relError Relative error tolerance.
* @param absError Absolute error tolerance.
* @param metric Instantiated metric.
* @param kernel Instantiated kernel.
* @param sameSet True if query and reference sets are the same
* (monochromatic evaluation).
*/
KDERules(const arma::mat& referenceSet,
const arma::mat& querySet,
arma::vec& densities,
const double relError,
const double absError,
MetricType& metric,
KernelType& kernel,
const bool sameSet);
//! Base Case.
double BaseCase(const size_t queryIndex, const size_t referenceIndex);
//! SingleTree Rescore.
double Score(const size_t queryIndex, TreeType& referenceNode);
//! SingleTree Score.
double Rescore(const size_t queryIndex,
TreeType& referenceNode,
const double oldScore) const;
//! DoubleTree Score.
double Score(TreeType& queryNode, TreeType& referenceNode);
//! DoubleTree Rescore.
double Rescore(TreeType& queryNode,
TreeType& referenceNode,
const double oldScore) const;
typedef typename tree::TraversalInfo<TreeType> TraversalInfoType;
//! Get traversal information.
const TraversalInfoType& TraversalInfo() const { return traversalInfo; }
//! Modify traversal information.
TraversalInfoType& TraversalInfo() { return traversalInfo; }
//! Get the number of base cases.
size_t BaseCases() const { return baseCases; }
//! Get the number of scores.
size_t Scores() const { return scores; }
private:
//! Evaluate kernel value of 2 points given their indexes.
double EvaluateKernel(const size_t queryIndex,
const size_t referenceIndex) const;
//! Evaluate kernel value of 2 points.
double EvaluateKernel(const arma::vec& query,
const arma::vec& reference) const;
//! The reference set.
const arma::mat& referenceSet;
//! The query set.
const arma::mat& querySet;
//! Density values.
arma::vec& densities;
//! Absolute error tolerance.
const double absError;
//! Relatve error tolerance.
const double relError;
//! Instantiated metric.
MetricType& metric;
//! Instantiated kernel.
KernelType& kernel;
//! Whether reference and query sets are the same.
const bool sameSet;
//! The last query index.
size_t lastQueryIndex;
//! The last reference index.
size_t lastReferenceIndex;
//! Traversal information.
TraversalInfoType traversalInfo;
//! The number of base cases.
size_t baseCases;
//! The number of scores.
size_t scores;
};
} // namespace kde
} // namespace mlpack
// Include implementation.
#include "kde_rules_impl.hpp"
#endif
+246
View File
@@ -0,0 +1,246 @@
/**
* @file kde_rules_impl.hpp
* @author Roberto Hueso
*
* Implementation of rules for Kernel Density Estimation with generic trees.
*
* mlpack is free software; you may redistribute it and/or modify it under the
* terms of the 3-clause BSD license. You should have received a copy of the
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*/
#ifndef MLPACK_METHODS_KDE_RULES_IMPL_HPP
#define MLPACK_METHODS_KDE_RULES_IMPL_HPP
// In case it hasn't been included yet.
#include "kde_rules.hpp"
namespace mlpack {
namespace kde {
template<typename MetricType, typename KernelType, typename TreeType>
KDERules<MetricType, KernelType, TreeType>::KDERules(
const arma::mat& referenceSet,
const arma::mat& querySet,
arma::vec& densities,
const double relError,
const double absError,
MetricType& metric,
KernelType& kernel,
const bool sameSet) :
referenceSet(referenceSet),
querySet(querySet),
densities(densities),
absError(absError),
relError(relError),
metric(metric),
kernel(kernel),
sameSet(sameSet),
lastQueryIndex(querySet.n_cols),
lastReferenceIndex(referenceSet.n_cols),
baseCases(0),
scores(0)
{
// Nothing to do.
}
//! The base case.
template<typename MetricType, typename KernelType, typename TreeType>
inline force_inline
double KDERules<MetricType, KernelType, TreeType>::BaseCase(
const size_t queryIndex,
const size_t referenceIndex)
{
// If reference and query sets are the same we don't want to compute the
// estimation of a point with itself.
if (sameSet && (queryIndex == referenceIndex))
return 0.0;
// Avoid duplicated calculations.
if ((lastQueryIndex == queryIndex) && (lastReferenceIndex == referenceIndex))
return 0.0;
// Calculations.
const double distance = metric.Evaluate(querySet.col(queryIndex),
referenceSet.col(referenceIndex));
densities(queryIndex) += kernel.Evaluate(distance);
++baseCases;
lastQueryIndex = queryIndex;
lastReferenceIndex = referenceIndex;
return distance;
}
//! Single-tree scoring function.
template<typename MetricType, typename KernelType, typename TreeType>
inline double KDERules<MetricType, KernelType, TreeType>::
Score(const size_t queryIndex, TreeType& referenceNode)
{
double score, maxKernel, minKernel, bound;
const arma::vec& queryPoint = querySet.unsafe_col(queryIndex);
const double minDistance = referenceNode.MinDistance(queryPoint);
bool newCalculations = true;
if (tree::TreeTraits<TreeType>::FirstPointIsCentroid &&
lastQueryIndex == queryIndex &&
traversalInfo.LastReferenceNode() != NULL &&
traversalInfo.LastReferenceNode()->Point(0) == referenceNode.Point(0))
{
// Don't duplicate calculations.
newCalculations = false;
lastQueryIndex = queryIndex;
lastReferenceIndex = referenceNode.Point(0);
}
else
{
// Calculations are new.
maxKernel = kernel.Evaluate(minDistance);
minKernel = kernel.Evaluate(referenceNode.MaxDistance(queryPoint));
bound = maxKernel - minKernel;
}
if (newCalculations &&
bound <= (absError + relError * minKernel) / referenceSet.n_cols)
{
// Estimate values.
double kernelValue;
// Calculate kernel value based on reference node centroid.
if (tree::TreeTraits<TreeType>::FirstPointIsCentroid)
{
kernelValue = EvaluateKernel(queryIndex, referenceNode.Point(0));
}
else
{
kde::KDEStat& referenceStat = referenceNode.Stat();
kernelValue = EvaluateKernel(queryPoint, referenceStat.Centroid());
}
densities(queryIndex) += referenceNode.NumDescendants() * kernelValue;
// Don't explore this tree branch.
score = DBL_MAX;
}
else
{
score = minDistance;
}
++scores;
traversalInfo.LastReferenceNode() = &referenceNode;
traversalInfo.LastScore() = score;
return score;
}
template<typename MetricType, typename KernelType, typename TreeType>
inline double KDERules<MetricType, KernelType, TreeType>::Rescore(
const size_t /* queryIndex */,
TreeType& /* referenceNode */,
const double oldScore) const
{
// If it's pruned it continues to be pruned.
return oldScore;
}
//! Double-tree scoring function.
template<typename MetricType, typename KernelType, typename TreeType>
inline double KDERules<MetricType, KernelType, TreeType>::
Score(TreeType& queryNode, TreeType& referenceNode)
{
double score, maxKernel, minKernel, bound;
const double minDistance = queryNode.MinDistance(referenceNode);
// Calculations are not duplicated.
bool newCalculations = true;
if (tree::TreeTraits<TreeType>::FirstPointIsCentroid &&
(traversalInfo.LastQueryNode() != NULL) &&
(traversalInfo.LastReferenceNode() != NULL) &&
(traversalInfo.LastQueryNode()->Point(0) == queryNode.Point(0)) &&
(traversalInfo.LastReferenceNode()->Point(0) == referenceNode.Point(0)))
{
// Don't duplicate calculations.
newCalculations = false;
lastQueryIndex = queryNode.Point(0);
lastReferenceIndex = referenceNode.Point(0);
}
else
{
// Calculations are new.
maxKernel = kernel.Evaluate(minDistance);
minKernel = kernel.Evaluate(queryNode.MaxDistance(referenceNode));
bound = maxKernel - minKernel;
}
// If possible, avoid some calculations because of the error tolerance.
if (newCalculations &&
bound <= (absError + relError * minKernel) / referenceSet.n_cols)
{
// Auxiliary variables.
double kernelValue;
kde::KDEStat& referenceStat = referenceNode.Stat();
kde::KDEStat& queryStat = queryNode.Stat();
// If calculating a center is not required.
if (tree::TreeTraits<TreeType>::FirstPointIsCentroid)
{
kernelValue = EvaluateKernel(queryNode.Point(0), referenceNode.Point(0));
}
// Sadly, we have no choice but to calculate the center.
else
{
kernelValue = EvaluateKernel(queryStat.Centroid(),
referenceStat.Centroid());
}
// Sum up estimations.
for (size_t i = 0; i < queryNode.NumDescendants(); ++i)
{
densities(queryNode.Descendant(i)) +=
referenceNode.NumDescendants() * kernelValue;
}
score = DBL_MAX;
}
else
{
score = minDistance;
}
++scores;
traversalInfo.LastQueryNode() = &queryNode;
traversalInfo.LastReferenceNode() = &referenceNode;
traversalInfo.LastScore() = score;
return score;
}
//! Double-tree rescore.
template<typename MetricType, typename KernelType, typename TreeType>
inline double KDERules<MetricType, KernelType, TreeType>::
Rescore(TreeType& /*queryNode*/,
TreeType& /*referenceNode*/,
const double oldScore) const
{
// If a branch is pruned then it continues to be pruned.
return oldScore;
}
template<typename MetricType, typename KernelType, typename TreeType>
inline force_inline double KDERules<MetricType, KernelType, TreeType>::
EvaluateKernel(const size_t queryIndex,
const size_t referenceIndex) const
{
return EvaluateKernel(querySet.unsafe_col(queryIndex),
referenceSet.unsafe_col(referenceIndex));
}
template<typename MetricType, typename KernelType, typename TreeType>
inline force_inline double KDERules<MetricType, KernelType, TreeType>::
EvaluateKernel(const arma::vec& query, const arma::vec& reference) const
{
return kernel.Evaluate(metric.Evaluate(query, reference));
}
} // namespace kde
} // namespace mlpack
#endif
+84
View File
@@ -0,0 +1,84 @@
/**
* @file kde_stat.hpp
* @author Roberto Hueso
*
* Defines TreeStatType for KDE.
*
* mlpack is free software; you may redistribute it and/or modify it under the
* terms of the 3-clause BSD license. You should have received a copy of the
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*/
#ifndef MLPACK_METHODS_KDE_STAT_HPP
#define MLPACK_METHODS_KDE_STAT_HPP
#include <mlpack/prereqs.hpp>
namespace mlpack {
namespace kde {
/**
* Extra data for each node in the tree for the task of kernel density
* estimation.
*/
class KDEStat
{
public:
//! Initialize the statistic.
KDEStat() : validCentroid(false) { }
//! Initialization for a fully initialized node.
template<typename TreeType>
KDEStat(TreeType& node)
{
// Calculate centroid if necessary.
if (!tree::TreeTraits<TreeType>::FirstPointIsCentroid)
{
node.Center(centroid);
validCentroid = true;
}
else
{
validCentroid = false;
}
}
//! Get the centroid of the node.
inline const arma::vec& Centroid() const
{
if (validCentroid)
return centroid;
throw std::logic_error("Centroid must be assigned before requesting its "
"value");
}
//! Modify the centroid of the node.
void SetCentroid(arma::vec newCentroid)
{
validCentroid = true;
centroid = std::move(newCentroid);
}
//! Get whether the centroid is valid.
inline bool ValidCentroid() const { return validCentroid; }
//! Serialize the statistic to/from an archive.
template<typename Archive>
void serialize(Archive& ar, const unsigned int /* version */)
{
ar & BOOST_SERIALIZATION_NVP(centroid);
ar & BOOST_SERIALIZATION_NVP(validCentroid);
}
private:
//! Node centroid.
arma::vec centroid;
//! Whether the centroid is updated or is junk.
bool validCentroid;
};
} // namespace kde
} // namespace mlpack
#endif
+52 -15
View File
@@ -95,6 +95,41 @@ PARAM_STRING_IN("update_rules", "Update rules for each iteration; ( multdist | "
PARAM_MATRIX_IN("initial_w", "Initial W matrix.", "p");
PARAM_MATRIX_IN("initial_h", "Initial H matrix.", "q");
void LoadInitialWH(const bool bindingTransposed, arma::mat& w, arma::mat& h)
{
// Note that these datasets will typically be transposed on load, since we are
// likely receiving it from a row-major language, but we get it in a
// column-major form. Therefore, we're actually decomposing V^T = W^T * H^T.
// Effectively this means we are solving, for the user, V = H*W. Therefore,
// we actually have to switch what we are saving, so we will save the W we get
// from amf.Apply() as H, and vice versa.
if (bindingTransposed)
{
w = CLI::GetParam<arma::mat>("initial_h");
h = CLI::GetParam<arma::mat>("initial_w");
}
else
{
h = CLI::GetParam<arma::mat>("initial_h");
w = CLI::GetParam<arma::mat>("initial_w");
}
}
void SaveWH(const bool bindingTransposed, arma::mat&& w, arma::mat&& h)
{
// The same transposition applies when saving.
if (bindingTransposed)
{
CLI::GetParam<arma::mat>("w") = std::move(h);
CLI::GetParam<arma::mat>("h") = std::move(w);
}
else
{
CLI::GetParam<arma::mat>("h") = std::move(h);
CLI::GetParam<arma::mat>("w") = std::move(w);
}
}
static void mlpackMain()
{
// Initialize random seed.
@@ -120,7 +155,8 @@ static void mlpackMain()
RequireAtLeastOnePassed({ "h", "w" }, false, "no output will be saved");
RequireNoneOrAllPassed({"initial_w", "initial_h"}, true);
// Load input dataset.
// Load input dataset. We know if the data is transposed based on the
// BINDING_MATRIX_TRANSPOSED macro, which will be 'true' or 'false'.
arma::mat V = std::move(CLI::GetParam<arma::mat>("input"));
arma::mat W;
@@ -136,9 +172,10 @@ static void mlpackMain()
if (CLI::HasParam("initial_w"))
{
// Initialization with given W, H matrices.
GivenInitialization ginit = GivenInitialization(
std::move(CLI::GetParam<arma::mat>("initial_w")),
std::move(CLI::GetParam<arma::mat>("initial_h")));
arma::mat initialW, initialH;
LoadInitialWH(BINDING_MATRIX_TRANSPOSED, initialW, initialH);
GivenInitialization ginit = GivenInitialization(initialW, initialH);
AMF<SimpleResidueTermination,
GivenInitialization> amf(srt, ginit);
amf.Apply(V, r, W, H);
@@ -158,9 +195,10 @@ static void mlpackMain()
if (CLI::HasParam("initial_w"))
{
// Initialization with given W, H matrices.
GivenInitialization ginit = GivenInitialization(
std::move(CLI::GetParam<arma::mat>("initial_w")),
std::move(CLI::GetParam<arma::mat>("initial_h")));
arma::mat initialW, initialH;
LoadInitialWH(BINDING_MATRIX_TRANSPOSED, initialW, initialH);
GivenInitialization ginit = GivenInitialization(initialW, initialH);
AMF<SimpleResidueTermination,
GivenInitialization,
NMFMultiplicativeDivergenceUpdate> amf(srt, ginit);
@@ -183,9 +221,10 @@ static void mlpackMain()
if (CLI::HasParam("initial_w"))
{
// Initialization with given W, H matrices.
GivenInitialization ginit = GivenInitialization(
std::move(CLI::GetParam<arma::mat>("initial_w")),
std::move(CLI::GetParam<arma::mat>("initial_h")));
arma::mat initialW, initialH;
LoadInitialWH(BINDING_MATRIX_TRANSPOSED, initialW, initialH);
GivenInitialization ginit = GivenInitialization(initialW, initialH);
AMF<SimpleResidueTermination,
GivenInitialization,
NMFALSUpdate> amf(srt, ginit);
@@ -200,9 +239,7 @@ static void mlpackMain()
}
}
// Save results.
if (CLI::HasParam("w"))
CLI::GetParam<arma::mat>("w") = std::move(W);
if (CLI::HasParam("h"))
CLI::GetParam<arma::mat>("h") = std::move(H);
// Save results. Remember from our discussion in the comments earlier that we
// may need to switch the names of the outputs.
SaveWH(BINDING_MATRIX_TRANSPOSED, std::move(W), std::move(H));
}
@@ -59,9 +59,13 @@ void NystroemMethod<KernelType, PointSelectionPolicy>::GetKernelMatrix(
{
// Assemble mini-kernel matrix.
for (size_t i = 0; i < rank; ++i)
{
for (size_t j = 0; j < rank; ++j)
{
miniKernel(i, j) = kernel.Evaluate(data.col(selectedPoints(i)),
data.col(selectedPoints(j)));
}
}
// Construct semi-kernel matrix with interactions between selected points and
// all points.
@@ -85,8 +89,13 @@ void NystroemMethod<KernelType, PointSelectionPolicy>::Apply(arma::mat& output)
arma::vec s;
arma::svd(U, s, V, miniKernel);
// Construct the output matrix.
// Construct the output matrix. We need to have special handling when
// miniKernel ended up being low-rank.
arma::mat normalization = arma::diagmat(1.0 / sqrt(s));
for (size_t i = 0; i < s.n_elem; ++i)
if (std::abs(s[i]) <= 1e-20)
normalization(i, i) = 0.0;
output = semiKernel * U * normalization * V;
}
@@ -213,7 +213,7 @@ static void mlpackMain()
rs = CLI::GetParam<RSModel*>("input_model");
Log::Info << "Using range search model from '"
<< CLI::GetPrintableParam<RSModel>("input_model") << "' ("
<< CLI::GetPrintableParam<RSModel*>("input_model") << "' ("
<< "trained on " << rs->Dataset().n_rows << "x" << rs->Dataset().n_cols
<< " dataset)." << endl;
@@ -271,11 +271,11 @@ void MonoSearchVisitor::operator()(RSType* rs) const
}
//! Save parameters for bichromatic range search.
BiSearchVisitor::BiSearchVisitor(const arma::mat& querySet,
const math::Range& range,
std::vector<std::vector<size_t>>& neighbors,
std::vector<std::vector<double>>& distances,
const size_t leafSize):
inline BiSearchVisitor::BiSearchVisitor(const arma::mat& querySet,
const math::Range& range,
std::vector<std::vector<size_t>>& neighbors,
std::vector<std::vector<double>>& distances,
const size_t leafSize):
querySet(querySet),
range(range),
neighbors(neighbors),
@@ -295,7 +295,7 @@ void BiSearchVisitor::operator()(RSTypeT<TreeType>* rs) const
}
//! Bichromatic range search on the given RSType specialized for KDTrees.
void BiSearchVisitor::operator()(RSTypeT<tree::KDTree>* rs) const
inline void BiSearchVisitor::operator()(RSTypeT<tree::KDTree>* rs) const
{
if (rs)
return SearchLeaf(rs);
@@ -303,7 +303,7 @@ void BiSearchVisitor::operator()(RSTypeT<tree::KDTree>* rs) const
}
//! Bichromatic range search on the given RSType specialized for BallTrees.
void BiSearchVisitor::operator()(RSTypeT<tree::BallTree>* rs) const
inline void BiSearchVisitor::operator()(RSTypeT<tree::BallTree>* rs) const
{
if (rs)
return SearchLeaf(rs);
@@ -311,7 +311,7 @@ void BiSearchVisitor::operator()(RSTypeT<tree::BallTree>* rs) const
}
//! Bichromatic range search specialized for Ocrees.
void BiSearchVisitor::operator()(RSTypeT<tree::Octree>* rs) const
inline void BiSearchVisitor::operator()(RSTypeT<tree::Octree>* rs) const
{
if (rs)
return SearchLeaf(rs);
@@ -351,8 +351,8 @@ void BiSearchVisitor::SearchLeaf(RSType* rs) const
}
//! Save parameters for Train.
TrainVisitor::TrainVisitor(arma::mat&& referenceSet,
const size_t leafSize) :
inline TrainVisitor::TrainVisitor(arma::mat&& referenceSet,
const size_t leafSize) :
referenceSet(std::move(referenceSet)),
leafSize(leafSize)
{}
@@ -369,7 +369,7 @@ void TrainVisitor::operator()(RSTypeT<TreeType>* rs) const
}
//! Train on the given RSType specialized for KDTrees.
void TrainVisitor::operator()(RSTypeT<tree::KDTree>* rs) const
inline void TrainVisitor::operator()(RSTypeT<tree::KDTree>* rs) const
{
if (rs)
return TrainLeaf(rs);
@@ -377,7 +377,7 @@ void TrainVisitor::operator()(RSTypeT<tree::KDTree>* rs) const
}
//! Train on the given RSType specialized for BallTrees.
void TrainVisitor::operator()(RSTypeT<tree::BallTree>* rs) const
inline void TrainVisitor::operator()(RSTypeT<tree::BallTree>* rs) const
{
if (rs)
return TrainLeaf(rs);
@@ -385,7 +385,7 @@ void TrainVisitor::operator()(RSTypeT<tree::BallTree>* rs) const
}
//! Train specialized for Octrees.
void TrainVisitor::operator()(RSTypeT<tree::Octree>* rs) const
inline void TrainVisitor::operator()(RSTypeT<tree::Octree>* rs) const
{
if (rs)
return TrainLeaf(rs);
@@ -4,7 +4,7 @@ set(SOURCES
mountain_car.hpp
cart_pole.hpp
continuous_mountain_car.hpp
acrobat.hpp
acrobot.hpp
pendulum.hpp
reward_clipping.hpp
)
@@ -1,8 +1,8 @@
/**
* @file acrobat.hpp
* @file acrobot.hpp
* @author Rohan Raj
*
* This file is an implementation of Acrobat task:
* This file is an implementation of Acrobot task:
* https://gym.openai.com/envs/Acrobot-v1/
*
* mlpack is free software; you may redistribute it and/or modify it under the
@@ -10,8 +10,8 @@
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*/
#ifndef MLPACK_METHODS_RL_ENVIRONMENT_ACROBAT_HPP
#define MLPACK_METHODS_RL_ENVIRONMENT_ACROBAT_HPP
#ifndef MLPACK_METHODS_RL_ENVIRONMENT_ACROBOT_HPP
#define MLPACK_METHODS_RL_ENVIRONMENT_ACROBOT_HPP
#include <mlpack/core.hpp>
@@ -19,17 +19,17 @@ namespace mlpack{
namespace rl{
/**
* Implementation of Acrobat game. Acrobot is a 2-link pendulum with only the
* Implementation of Acrobot game. Acrobot is a 2-link pendulum with only the
* second joint actuated. Intitially, both links point downwards. The goal is
* to swing the end-effector at a height at least the length of one link above
* the base. Both links can swing freely and can pass by each other, i.e.,
* they don't collide when they have the same angle.
*/
class Acrobat
class Acrobot
{
public:
/*
* Implementation of Acrobat State. Each State is a tuple vector
* Implementation of Acrobot State. Each State is a tuple vector
* (theta1, thetha2, angular velocity 1, angular velocity 2).
*/
class State
@@ -83,7 +83,7 @@ class Acrobat
};
/*
* Implementation of action for Acrobat
* Implementation of action for Acrobot
*/
enum Action
{
@@ -96,7 +96,7 @@ class Acrobat
};
/**
* Construct a Acrobat instance using the given constants.
* Construct a Acrobot instance using the given constants.
*
* @param gravity The gravity parameter.
* @param linkLength1 The length of link 1.
@@ -110,7 +110,7 @@ class Acrobat
* @param maxVel2 The max angular velocity of link2.
* @param dt The differential value.
*/
Acrobat(const double gravity = 9.81,
Acrobot(const double gravity = 9.81,
const double linkLength1 = 1.0,
const double linkLength2 = 1.0,
const double linkMass1 = 1.0,
@@ -137,7 +137,7 @@ class Acrobat
{ /* Nothing to do here */ }
/**
* Dynamics of the Acrobat System. To get reward and next state based on
* Dynamics of the Acrobot System. To get reward and next state based on
* current state and current action. Always return -1 reward.
*
* @param state The current State.
@@ -165,7 +165,7 @@ class Acrobat
nextState.AngularVelocity2() = std::min(
std::max(currentNextState[3], -maxVel2), maxVel2);
/**
* If the acrobat reaches a terminal state, it should be given a positive
* If the acrobot reaches a terminal state, it should be given a positive
* reward. This will ensure that the agent learns the goal of the game.
*/
bool done = IsTerminal(nextState);
@@ -175,7 +175,7 @@ class Acrobat
};
/**
* Dynamics of the Acrobat System. To get reward and next state based on
* Dynamics of the Acrobot System. To get reward and next state based on
* current state and current action. This function calls the Sample function
* to estimate the next state return reward for taking a particular action.
*
@@ -198,7 +198,7 @@ class Acrobat
}
/**
* This function checks if the acrobat has reached the terminal state.
* This function checks if the acrobot has reached the terminal state.
*
* @param state The current State.
*/
@@ -349,7 +349,12 @@ class Acrobat
//! Locally-stored done reward.
double doneReward;
}; // class Acrobat
}; // class Acrobot
/**
* Add an alias for backward compatibility.
*/
typedef Acrobot Acrobat;
} // namespace rl
} // namespace mlpack
@@ -184,7 +184,7 @@ class ContinuousMountainCar
*/
bool IsTerminal(const State& state) const
{
return bool(state.Position() >= positionGoal);
return state.Position() >= positionGoal;
}
private:
@@ -90,16 +90,19 @@ class MountainCar
*
* @param positionMin Minimum legal position.
* @param positionMax Maximum legal position.
* @param positionGoal Final target position.
* @param velocityMin Minimum legal velocity.
* @param velocityMax Maximum legal velocity.
*/
MountainCar(const double positionMin = -1.2,
const double positionMax = 0.5,
const double positionMax = 0.6,
const double positionGoal = 0.5,
const double velocityMin = -0.07,
const double velocityMax = 0.07,
const double doneReward = 0) :
positionMin(positionMin),
positionMax(positionMax),
positionGoal(positionGoal),
velocityMin(velocityMin),
velocityMax(velocityMax),
doneReward(doneReward)
@@ -130,10 +133,9 @@ class MountainCar
nextState.Position() = std::min(
std::max(nextState.Position(), positionMin), positionMax);
if (std::abs(nextState.Position() - positionMin) <= 1e-5)
{
if (nextState.Position() == positionMin && nextState.Velocity() < 0)
nextState.Velocity() = 0.0;
}
bool done = IsTerminal(nextState);
/**
* If done is true , it means that car has reached its goal.
@@ -183,7 +185,7 @@ class MountainCar
*/
bool IsTerminal(const State& state) const
{
return std::abs(state.Position() - positionMax) <= 1e-5;
return state.Position() >= positionGoal;
}
private:
@@ -193,6 +195,9 @@ class MountainCar
//! Locally-stored maximum legal position.
double positionMax;
//! Locally-stored goal position.
double positionGoal;
//! Locally-stored minimum legal velocity.
double velocityMin;
+3
View File
@@ -39,6 +39,7 @@ add_executable(mlpack_test
hyperplane_test.cpp
imputation_test.cpp
init_rules_test.cpp
kde_test.cpp
kernel_pca_test.cpp
kernel_test.cpp
kernel_traits_test.cpp
@@ -119,6 +120,7 @@ add_executable(mlpack_test
main_tests/det_test.cpp
main_tests/decision_tree_test.cpp
main_tests/decision_stump_test.cpp
main_tests/kde_test.cpp
main_tests/linear_regression_test.cpp
main_tests/logistic_regression_test.cpp
main_tests/lmnn_test.cpp
@@ -144,6 +146,7 @@ add_executable(mlpack_test
main_tests/radical_test.cpp
main_tests/hmm_test_utils.hpp
main_tests/kernel_pca_test.cpp
main_tests/range_search_test.cpp
)
# Link dependencies of test executable.
+10 -7
View File
@@ -243,11 +243,12 @@ void CheckELUDerivativeCorrect(const arma::colvec input,
ELU<> lrf(1.0);
// Test the calculation of the derivatives using the entire vector as input.
arma::colvec derivatives;
arma::colvec derivatives, activations;
// This error vector will be set to 1 to get the derivatives.
arma::colvec error = arma::ones<arma::colvec>(input.n_elem);
lrf.Backward(std::move(input), std::move(error), std::move(derivatives));
lrf.Forward(std::move(input), std::move(activations));
lrf.Backward(std::move(activations), std::move(error), std::move(derivatives));
for (size_t i = 0; i < derivatives.n_elem; i++)
{
BOOST_REQUIRE_CLOSE(derivatives.at(i), target.at(i), 1e-3);
@@ -381,21 +382,23 @@ BOOST_AUTO_TEST_CASE(SELUFunctionDerivativeTest)
arma::mat error = arma::ones<arma::mat>(input.n_elem, 1);
arma::mat derivatives;
arma::mat derivatives, activations;
SELU selu;
selu.Backward(std::move(input), std::move(error), std::move(derivatives));
selu.Forward(std::move(input), activations);
selu.Backward(std::move(activations), std::move(error), std::move(derivatives));
BOOST_REQUIRE_LE(arma::as_scalar(arma::abs(arma::mean(derivatives) -
selu.Lambda())), 10e-4);
input.fill(-1);
selu.Backward(std::move(input), std::move(error), std::move(derivatives));
selu.Forward(std::move(input), activations);
selu.Backward(std::move(activations), std::move(error), std::move(derivatives));
BOOST_REQUIRE_LE(arma::as_scalar(arma::abs(arma::mean(derivatives) -
selu.Lambda() * (selu.Alpha() - 1))), 10e-4);
selu.Lambda() * selu.Alpha() - arma::mean(activations))), 10e-4);
}
/**
@@ -519,7 +522,7 @@ BOOST_AUTO_TEST_CASE(ELUFunctionTest)
1 0.36787945 1 1");
CheckELUActivationCorrect(activationData, desiredActivations);
CheckELUDerivativeCorrect(desiredActivations, desiredDerivatives);
CheckELUDerivativeCorrect(activationData, desiredDerivatives);
}
/**
+3 -1
View File
@@ -1626,7 +1626,9 @@ BOOST_AUTO_TEST_CASE(GradientAtrousConvolutionLayerTest)
arma::mat input, target;
} function;
BOOST_REQUIRE_LE(CheckGradient(function), 1e-3);
// TODO: this tolerance seems far higher than necessary. The implementation
// should be checked.
BOOST_REQUIRE_LE(CheckGradient(function), 0.2);
}
/**
+115 -77
View File
@@ -111,10 +111,11 @@ BOOST_AUTO_TEST_CASE(GMMTrainEMOneGaussian)
arma::mat actualCovar = ccov(data, 1 /* biased estimator */);
// Check the model to see that it is correct.
CheckMatrices(gmm.Component(0).Mean(), actualMean);
CheckMatrices(gmm.Component(0).Covariance(), actualCovar);
BOOST_REQUIRE_LT(arma::norm(gmm.Component(0).Mean() - actualMean), 1e-5);
BOOST_REQUIRE_LT(arma::norm(gmm.Component(0).Covariance() - actualCovar),
1e-4);
BOOST_REQUIRE_CLOSE(gmm.Weights()[0], 1.0, 1e-5);
BOOST_REQUIRE_CLOSE(gmm.Weights()[0], 1.0, 1e-4);
}
}
@@ -130,83 +131,120 @@ BOOST_AUTO_TEST_CASE(GMMTrainEMMultipleGaussians)
size_t dims = 8;
size_t gaussians = 3;
// Generate dataset.
arma::mat data;
data.zeros(dims, 500);
std::vector<arma::vec> means(gaussians);
std::vector<arma::mat> covars(gaussians);
arma::vec weights(gaussians);
arma::Col<size_t> counts(gaussians);
// Choose weights randomly.
weights.zeros();
while (weights.min() < 0.02)
// We'll run three trials, and it needs to pass during at least one trial.
bool success = false;
for (size_t trial = 0; trial < 3; ++trial)
{
weights.randu(gaussians);
weights /= accu(weights);
// Generate dataset.
arma::mat data;
data.zeros(dims, 500);
std::vector<arma::vec> means(gaussians);
std::vector<arma::mat> covars(gaussians);
arma::vec weights(gaussians);
arma::Col<size_t> counts(gaussians);
// Choose weights randomly. We want each component to have somewhat
// significant weight, but we also need to make sure that no weights are too
// close.
double minDiff = DBL_MAX;
do
{
weights.zeros();
weights.randu(gaussians);
weights /= accu(weights);
weights *= 0.4;
weights += (0.6 / double(gaussians));
weights /= accu(weights); // Paranoia, just to be sure they sum to 1.
// Compute minimum element difference.
minDiff = DBL_MAX;
for (size_t i = 0; i < weights.n_elem; ++i)
for (size_t j = (i + 1); j < weights.n_elem; ++j)
if (std::abs(weights[i] - weights[j]) < minDiff)
minDiff = std::abs(weights[i] - weights[j]);
} while (minDiff < 0.02);
for (size_t i = 0; i < gaussians; i++)
counts[i] = round(weights[i] * (data.n_cols - gaussians));
// Ensure one point minimum in each.
counts += 1;
// Account for rounding errors (possibly necessary).
counts[gaussians - 1] += (data.n_cols - arma::accu(counts));
// Build each Gaussian individually.
size_t point = 0;
for (size_t i = 0; i < gaussians; i++)
{
arma::mat gaussian;
gaussian.randn(dims, counts[i]);
// Randomly generate mean and covariance.
means[i].randu(dims);
means[i] -= 0.5;
means[i] *= 50;
// We need to make sure the covariance is positive definite. We will take
// a random matrix C and then set our covariance to 4 * C * C', which will
// be positive semidefinite.
covars[i].randu(dims, dims);
covars[i] *= 4 * trans(covars[i]);
data.cols(point, point + counts[i] - 1) = (covars[i] * gaussian + means[i]
* arma::ones<arma::rowvec>(counts[i]));
// Calculate the actual means and covariances because they will probably
// be different (this is easier to do before we shuffle the points).
means[i] = arma::mean(data.cols(point, point + counts[i] - 1), 1);
covars[i] = ccov(data.cols(point, point + counts[i] - 1), 1 /* biased */);
point += counts[i];
}
// Calculate actual weights.
for (size_t i = 0; i < gaussians; i++)
weights[i] = (double) counts[i] / data.n_cols;
// Now train the model.
GMM gmm(gaussians, dims);
gmm.Train(data, 10);
arma::uvec sortRef = sort_index(weights);
arma::uvec sortTry = sort_index(gmm.Weights());
// If it's a bad match, try training again with a different seed. We
// probably just fell into some bad local minimum or had a bad starting
// point.
gmm = GMM(gaussians, dims);
gmm.Train(data, 10);
sortTry = sort_index(gmm.Weights());
if (arma::norm(weights.elem(sortRef) - gmm.Weights().elem(sortTry)) > 0.1)
continue;
// Check the model to see that it is correct.
for (size_t i = 0; i < gaussians; i++)
{
// Check the mean.
BOOST_REQUIRE_LT(
arma::norm(gmm.Component(sortTry[i]).Mean() - means[sortRef[i]]),
0.05);
// Check the covariance.
BOOST_REQUIRE_LT(
arma::norm(gmm.Component(sortTry[i]).Covariance() -
covars[sortRef[i]]), 0.2);
// Check the weight.
BOOST_REQUIRE_CLOSE(gmm.Weights()[sortTry[i]], weights[sortRef[i]],
0.005);
}
success = true;
break; // No need for multiple iterations.
}
for (size_t i = 0; i < gaussians; i++)
counts[i] = round(weights[i] * (data.n_cols - gaussians));
// Ensure one point minimum in each.
counts += 1;
// Account for rounding errors (possibly necessary).
counts[gaussians - 1] += (data.n_cols - arma::accu(counts));
// Build each Gaussian individually.
size_t point = 0;
for (size_t i = 0; i < gaussians; i++)
{
arma::mat gaussian;
gaussian.randn(dims, counts[i]);
// Randomly generate mean and covariance.
means[i].randu(dims);
means[i] -= 0.5;
means[i] *= 50;
// We need to make sure the covariance is positive definite. We will take a
// random matrix C and then set our covariance to 4 * C * C', which will be
// positive semidefinite.
covars[i].randu(dims, dims);
covars[i] *= 4 * trans(covars[i]);
data.cols(point, point + counts[i] - 1) = (covars[i] * gaussian + means[i]
* arma::ones<arma::rowvec>(counts[i]));
// Calculate the actual means and covariances because they will probably
// be different (this is easier to do before we shuffle the points).
means[i] = arma::mean(data.cols(point, point + counts[i] - 1), 1);
covars[i] = ccov(data.cols(point, point + counts[i] - 1), 1 /* biased */);
point += counts[i];
}
// Calculate actual weights.
for (size_t i = 0; i < gaussians; i++)
weights[i] = (double) counts[i] / data.n_cols;
// Now train the model.
GMM gmm(gaussians, dims);
gmm.Train(data, 10);
arma::uvec sortRef = sort_index(weights);
arma::uvec sortTry = sort_index(gmm.Weights());
// Check the model to see that it is correct.
for (size_t i = 0; i < gaussians; i++)
{
// Check the mean.
CheckMatrices(gmm.Component(sortTry[i]).Mean(), means[sortRef[i]], 1e-3);
// Check the covariance.
CheckMatrices(gmm.Component(sortTry[i]).Covariance(), covars[sortRef[i]],
0.15);
// Check the weight.
BOOST_REQUIRE_CLOSE(gmm.Weights()[sortTry[i]], weights[sortRef[i]],
0.005);
}
BOOST_REQUIRE_EQUAL(success, true);
}
/**
+821
View File
@@ -0,0 +1,821 @@
/**
* @file kde_test.cpp
* @author Roberto Hueso
*
* mlpack is free software; you may redistribute it and/or modify it under the
* terms of the 3-clause BSD license. You should have received a copy of the
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*/
#include <mlpack/core.hpp>
#include <mlpack/methods/kde/kde.hpp>
#include <mlpack/core/tree/binary_space_tree.hpp>
#include <mlpack/core/tree/octree.hpp>
#include <mlpack/core/tree/cover_tree.hpp>
#include <mlpack/core/tree/rectangle_tree.hpp>
#include <boost/test/unit_test.hpp>
#include "test_tools.hpp"
#include "serialization.hpp"
using namespace mlpack;
using namespace mlpack::kde;
using namespace mlpack::metric;
using namespace mlpack::tree;
using namespace mlpack::kernel;
using namespace boost::serialization;
BOOST_AUTO_TEST_SUITE(KDETest);
// Brute force gaussian KDE.
template <typename KernelType>
void BruteForceKDE(const arma::mat& reference,
const arma::mat& query,
arma::vec& densities,
KernelType& kernel)
{
metric::EuclideanDistance metric;
for (size_t i = 0; i < query.n_cols; ++i)
{
for (size_t j = 0; j < reference.n_cols; ++j)
{
double distance = metric.Evaluate(query.col(i),reference.col(j));
densities(i) += kernel.Evaluate(distance);
}
}
densities /= reference.n_cols;
}
/**
* Test if simple case is correct according to manually calculated results.
*/
BOOST_AUTO_TEST_CASE(KDESimpleTest)
{
// Transposed reference and query sets because it's easier to read.
arma::mat reference = { {-1.0, -1.0},
{-2.0, -1.0},
{-3.0, -2.0},
{ 1.0, 1.0},
{ 2.0, 1.0},
{ 3.0, 2.0} };
arma::mat query = { { 0.0, 0.5},
{ 0.4, -3.0},
{ 0.0, 0.0},
{-2.1, 1.0} };
arma::inplace_trans(reference);
arma::inplace_trans(query);
arma::vec estimations;
// Manually calculated results.
arma::vec estimationsResult = {0.08323668699564207296148765,
0.00167470061366603324010116,
0.07658867126520703394465527,
0.01028120384800740999553525};
KDE<GaussianKernel,
EuclideanDistance,
arma::mat,
KDTree>
kde(0.0, 0.01, GaussianKernel(0.8));
kde.Train(reference);
kde.Evaluate(query, estimations);
for (size_t i = 0; i < query.n_cols; ++i)
BOOST_REQUIRE_CLOSE(estimations[i], estimationsResult[i], 0.01);
}
/**
* Test Train(Tree...) and Evaluate(Tree...).
*/
BOOST_AUTO_TEST_CASE(KDETreeAsArguments)
{
// Transposed reference and query sets because it's easier to read.
arma::mat reference = { {-1.0, -1.0},
{-2.0, -1.0},
{-3.0, -2.0},
{ 1.0, 1.0},
{ 2.0, 1.0},
{ 3.0, 2.0} };
arma::mat query = { { 0.0, 0.5},
{ 0.4, -3.0},
{ 0.0, 0.0},
{-2.1, 1.0} };
arma::inplace_trans(reference);
arma::inplace_trans(query);
arma::vec estimations = arma::vec(query.n_cols, arma::fill::zeros);
arma::vec estimationsResult = arma::vec(query.n_cols, arma::fill::zeros);
const double kernelBandwidth = 0.8;
// Get brute force results.
GaussianKernel kernel(kernelBandwidth);
BruteForceKDE<GaussianKernel>(reference,
query,
estimationsResult,
kernel);
// Get dual-tree results.
typedef KDTree<EuclideanDistance, kde::KDEStat, arma::mat> Tree;
std::vector<size_t> oldFromNewQueries, oldFromNewReferences;
Tree* queryTree = new Tree(query, oldFromNewQueries, 2);
Tree* referenceTree = new Tree(reference, oldFromNewReferences, 2);
KDE<GaussianKernel,
EuclideanDistance,
arma::mat,
KDTree>
kde(0.0, 1e-6, GaussianKernel(kernelBandwidth));
kde.Train(referenceTree, &oldFromNewReferences);
kde.Evaluate(queryTree, std::move(oldFromNewQueries), estimations);
for (size_t i = 0; i < query.n_cols; ++i)
BOOST_REQUIRE_CLOSE(estimations[i], estimationsResult[i], 0.01);
delete queryTree;
delete referenceTree;
}
/**
* Test dual-tree implementation results against brute force results.
*/
BOOST_AUTO_TEST_CASE(GaussianKDEBruteForceTest)
{
arma::mat reference = arma::randu(2, 200);
arma::mat query = arma::randu(2, 60);
arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros);
arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros);
const double kernelBandwidth = 0.3;
const double relError = 0.01;
// Brute force KDE.
GaussianKernel kernel(kernelBandwidth);
BruteForceKDE<GaussianKernel>(reference,
query,
bfEstimations,
kernel);
// Optimized KDE.
metric::EuclideanDistance metric;
KDE<GaussianKernel,
metric::EuclideanDistance,
arma::mat,
tree::KDTree>
kde(relError, 0.0, kernel, KDEMode::DUAL_TREE_MODE, metric);
kde.Train(reference);
kde.Evaluate(query, treeEstimations);
// Check whether results are equal.
for (size_t i = 0; i < query.n_cols; ++i)
BOOST_REQUIRE_CLOSE(bfEstimations[i], treeEstimations[i], relError*100);
}
/**
* Test single-tree implementation results against brute force results.
*/
BOOST_AUTO_TEST_CASE(GaussianSingleKDEBruteForceTest)
{
arma::mat reference = arma::randu(2, 300);
arma::mat query = arma::randu(2, 100);
arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros);
arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros);
const double kernelBandwidth = 0.3;
const double relError = 0.01;
// Brute force KDE.
GaussianKernel kernel(kernelBandwidth);
BruteForceKDE<GaussianKernel>(reference,
query,
bfEstimations,
kernel);
// Optimized KDE.
metric::EuclideanDistance metric;
KDE<GaussianKernel,
metric::EuclideanDistance,
arma::mat,
tree::KDTree>
kde(relError, 0.0, kernel, KDEMode::SINGLE_TREE_MODE, metric);
kde.Train(reference);
kde.Evaluate(query, treeEstimations);
// Check whether results are equal.
for (size_t i = 0; i < query.n_cols; ++i)
BOOST_REQUIRE_CLOSE(bfEstimations[i], treeEstimations[i], relError*100);
}
/**
* Test single-tree implementation results against brute force results using
* a cover-tree and Epanechnikov kernel.
*/
BOOST_AUTO_TEST_CASE(EpanechnikovCoverSingleKDETest)
{
arma::mat reference = arma::randu(2, 300);
arma::mat query = arma::randu(2, 100);
arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros);
arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros);
const double kernelBandwidth = 1.1;
const double relError = 0.08;
// Brute force KDE.
EpanechnikovKernel kernel(kernelBandwidth);
BruteForceKDE<EpanechnikovKernel>(reference,
query,
bfEstimations,
kernel);
// Optimized KDE.
metric::EuclideanDistance metric;
KDE<EpanechnikovKernel,
metric::EuclideanDistance,
arma::mat,
tree::StandardCoverTree>
kde(relError, 0.0, kernel, KDEMode::SINGLE_TREE_MODE, metric);
kde.Train(reference);
kde.Evaluate(query, treeEstimations);
// Check whether results are equal.
for (size_t i = 0; i < query.n_cols; ++i)
BOOST_REQUIRE_CLOSE(bfEstimations[i], treeEstimations[i], relError*100);
}
/**
* Test single-tree implementation results against brute force results using
* an octree and Epanechnikov kernel.
*/
BOOST_AUTO_TEST_CASE(EpanechnikovOctreeSingleKDETest)
{
arma::mat reference = arma::randu(2, 300);
arma::mat query = arma::randu(2, 100);
arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros);
arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros);
const double kernelBandwidth = 1.0;
const double relError = 0.05;
// Brute force KDE.
EpanechnikovKernel kernel(kernelBandwidth);
BruteForceKDE<EpanechnikovKernel>(reference,
query,
bfEstimations,
kernel);
// Optimized KDE.
metric::EuclideanDistance metric;
KDE<EpanechnikovKernel,
metric::EuclideanDistance,
arma::mat,
tree::Octree>
kde(relError, 0.0, kernel, KDEMode::SINGLE_TREE_MODE, metric);
kde.Train(reference);
kde.Evaluate(query, treeEstimations);
// Check whether results are equal.
for (size_t i = 0; i < query.n_cols; ++i)
BOOST_REQUIRE_CLOSE(bfEstimations[i], treeEstimations[i], relError*100);
}
/**
* Test BallTree dual-tree implementation results against brute force results.
*/
BOOST_AUTO_TEST_CASE(BallTreeGaussianKDETest)
{
arma::mat reference = arma::randu(2, 200);
arma::mat query = arma::randu(2, 60);
arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros);
arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros);
const double kernelBandwidth = 0.4;
const double relError = 0.05;
// Brute force KDE.
GaussianKernel kernel(kernelBandwidth);
BruteForceKDE<GaussianKernel>(reference,
query,
bfEstimations,
kernel);
// BallTree KDE.
typedef BallTree<EuclideanDistance, kde::KDEStat, arma::mat> Tree;
std::vector<size_t> oldFromNewQueries, oldFromNewReferences;
Tree* queryTree = new Tree(query, oldFromNewQueries, 2);
Tree* referenceTree = new Tree(reference, oldFromNewReferences, 2);
KDE<GaussianKernel,
EuclideanDistance,
arma::mat,
BallTree>
kde(relError, 0.0, GaussianKernel(kernelBandwidth));
kde.Train(referenceTree, &oldFromNewReferences);
kde.Evaluate(queryTree, std::move(oldFromNewQueries), treeEstimations);
// Check whether results are equal.
for (size_t i = 0; i < query.n_cols; ++i)
BOOST_REQUIRE_CLOSE(bfEstimations[i], treeEstimations[i], relError*100);
delete queryTree;
delete referenceTree;
}
/**
* Test Octree dual-tree implementation results against brute force results.
*/
BOOST_AUTO_TEST_CASE(OctreeGaussianKDETest)
{
arma::mat reference = arma::randu(2, 500);
arma::mat query = arma::randu(2, 200);
arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros);
arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros);
const double kernelBandwidth = 0.3;
const double relError = 0.01;
// Brute force KDE.
GaussianKernel kernel(kernelBandwidth);
BruteForceKDE<GaussianKernel>(reference,
query,
bfEstimations,
kernel);
// Optimized KDE.
metric::EuclideanDistance metric;
KDE<GaussianKernel,
metric::EuclideanDistance,
arma::mat,
tree::Octree>
kde(relError, 0.0, kernel, KDEMode::DUAL_TREE_MODE, metric);
kde.Train(reference);
kde.Evaluate(query, treeEstimations);
// Check whether results are equal.
for (size_t i = 0; i < query.n_cols; ++i)
BOOST_REQUIRE_CLOSE(bfEstimations[i], treeEstimations[i], relError*100);
}
/**
* Test RTree dual-tree implementation results against brute force results.
*/
BOOST_AUTO_TEST_CASE(RTreeGaussianKDETest)
{
arma::mat reference = arma::randu(2, 500);
arma::mat query = arma::randu(2, 200);
arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros);
arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros);
const double kernelBandwidth = 0.3;
const double relError = 0.01;
// Brute force KDE.
GaussianKernel kernel(kernelBandwidth);
BruteForceKDE<GaussianKernel>(reference,
query,
bfEstimations,
kernel);
// Optimized KDE.
metric::EuclideanDistance metric;
KDE<GaussianKernel,
metric::EuclideanDistance,
arma::mat,
tree::RTree>
kde(relError, 0.0, kernel, KDEMode::DUAL_TREE_MODE, metric);
kde.Train(reference);
kde.Evaluate(query, treeEstimations);
// Check whether results are equal.
for (size_t i = 0; i < query.n_cols; ++i)
BOOST_REQUIRE_CLOSE(bfEstimations[i], treeEstimations[i], relError*100);
}
/**
* Test Standard Cover Tree dual-tree implementation results against brute
* force results.
*/
BOOST_AUTO_TEST_CASE(StandardCoverTreeGaussianKDETest)
{
arma::mat reference = arma::randu(2, 500);
arma::mat query = arma::randu(2, 200);
arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros);
arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros);
const double kernelBandwidth = 0.3;
const double relError = 0.01;
// Brute force KDE.
GaussianKernel kernel(kernelBandwidth);
BruteForceKDE<GaussianKernel>(reference,
query,
bfEstimations,
kernel);
// Optimized KDE.
metric::EuclideanDistance metric;
KDE<GaussianKernel,
metric::EuclideanDistance,
arma::mat,
tree::StandardCoverTree>
kde(relError, 0.0, kernel, KDEMode::DUAL_TREE_MODE, metric);
kde.Train(reference);
kde.Evaluate(query, treeEstimations);
// Check whether results are equal.
for (size_t i = 0; i < query.n_cols; ++i)
BOOST_REQUIRE_CLOSE(bfEstimations[i], treeEstimations[i], relError*100);
}
/**
* Test duplicated value in reference matrix.
*/
BOOST_AUTO_TEST_CASE(DuplicatedReferenceSampleKDETest)
{
arma::mat reference = arma::randu(2, 30);
arma::mat query = arma::randu(2, 10);
arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros);
arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros);
const double kernelBandwidth = 0.4;
const double relError = 0.05;
// Duplicate value.
reference.col(2) = reference.col(3);
// Brute force KDE.
GaussianKernel kernel(kernelBandwidth);
BruteForceKDE<GaussianKernel>(reference,
query,
bfEstimations,
kernel);
// Dual-tree KDE.
typedef KDTree<EuclideanDistance, kde::KDEStat, arma::mat> Tree;
std::vector<size_t> oldFromNewQueries, oldFromNewReferences;
Tree* queryTree = new Tree(query, oldFromNewQueries, 2);
Tree* referenceTree = new Tree(reference, oldFromNewReferences, 2);
KDE<GaussianKernel,
EuclideanDistance,
arma::mat,
KDTree>
kde(relError, 0.0, GaussianKernel(kernelBandwidth));
kde.Train(referenceTree, &oldFromNewReferences);
kde.Evaluate(queryTree, oldFromNewQueries, treeEstimations);
// Check whether results are equal.
for (size_t i = 0; i < query.n_cols; ++i)
BOOST_REQUIRE_CLOSE(bfEstimations[i], treeEstimations[i], relError*100);
delete queryTree;
delete referenceTree;
}
/**
* Test duplicated value in query matrix.
*/
BOOST_AUTO_TEST_CASE(DuplicatedQuerySampleKDETest)
{
arma::mat reference = arma::randu(2, 30);
arma::mat query = arma::randu(2, 10);
arma::vec estimations = arma::vec(query.n_cols, arma::fill::zeros);
const double kernelBandwidth = 0.4;
const double relError = 0.05;
// Duplicate value.
query.col(2) = query.col(3);
// Dual-tree KDE.
typedef KDTree<EuclideanDistance, kde::KDEStat, arma::mat> Tree;
std::vector<size_t> oldFromNewQueries, oldFromNewReferences;
Tree* queryTree = new Tree(query, oldFromNewQueries, 2);
Tree* referenceTree = new Tree(reference, oldFromNewReferences, 2);
KDE<GaussianKernel,
EuclideanDistance,
arma::mat,
KDTree>
kde(relError, 0.0, GaussianKernel(kernelBandwidth));
kde.Train(referenceTree, &oldFromNewReferences);
kde.Evaluate(queryTree, oldFromNewQueries, estimations);
// Check whether results are equal.
BOOST_REQUIRE_CLOSE(estimations[2], estimations[3], relError*100);
delete queryTree;
delete referenceTree;
}
/**
* Test dual-tree breadth-first implementation results against brute force
* results.
*/
BOOST_AUTO_TEST_CASE(BreadthFirstKDETest)
{
arma::mat reference = arma::randu(2, 200);
arma::mat query = arma::randu(2, 60);
arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros);
arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros);
const double kernelBandwidth = 0.8;
const double relError = 0.01;
// Brute force KDE.
GaussianKernel kernel(kernelBandwidth);
BruteForceKDE<GaussianKernel>(reference,
query,
bfEstimations,
kernel);
// Breadth-First KDE.
metric::EuclideanDistance metric;
KDE<GaussianKernel,
metric::EuclideanDistance,
arma::mat,
tree::KDTree,
tree::KDTree<metric::EuclideanDistance,
kde::KDEStat,
arma::mat>::template BreadthFirstDualTreeTraverser>
kde(relError, 0.0, kernel, KDEMode::DUAL_TREE_MODE, metric);
kde.Train(reference);
kde.Evaluate(query, treeEstimations);
// Check whether results are equal.
for (size_t i = 0; i < query.n_cols; ++i)
BOOST_REQUIRE_CLOSE(bfEstimations[i], treeEstimations[i], relError*100);
}
/**
* Test 1-dimensional implementation results against brute force results.
*/
BOOST_AUTO_TEST_CASE(OneDimensionalTest)
{
arma::mat reference = arma::randu(1, 200);
arma::mat query = arma::randu(1, 60);
arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros);
arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros);
const double kernelBandwidth = 0.7;
const double relError = 0.01;
// Brute force KDE.
GaussianKernel kernel(kernelBandwidth);
BruteForceKDE<GaussianKernel>(reference,
query,
bfEstimations,
kernel);
// Optimized KDE.
metric::EuclideanDistance metric;
KDE<GaussianKernel,
metric::EuclideanDistance,
arma::mat,
tree::KDTree>
kde(relError, 0.0, kernel, KDEMode::DUAL_TREE_MODE, metric);
kde.Train(reference);
kde.Evaluate(query, treeEstimations);
// Check whether results are equal.
for (size_t i = 0; i < query.n_cols; ++i)
BOOST_REQUIRE_CLOSE(bfEstimations[i], treeEstimations[i], relError*100);
}
/**
* Test a case where an empty reference set is given to train the model.
*/
BOOST_AUTO_TEST_CASE(EmptyReferenceTest)
{
arma::mat reference;
arma::mat query = arma::randu(1, 10);
arma::vec estimations = arma::vec(query.n_cols, arma::fill::zeros);
const double kernelBandwidth = 0.7;
const double relError = 0.01;
// KDE.
metric::EuclideanDistance metric;
GaussianKernel kernel(kernelBandwidth);
KDE<GaussianKernel,
metric::EuclideanDistance,
arma::mat,
tree::KDTree>
kde(relError, 0.0, kernel, KDEMode::DUAL_TREE_MODE, metric);
// When training using the dataset matrix.
BOOST_REQUIRE_THROW(kde.Train(reference), std::invalid_argument);
// When training using a tree.
std::vector<size_t> oldFromNewReferences;
typedef KDTree<EuclideanDistance, kde::KDEStat, arma::mat> Tree;
Tree* referenceTree = new Tree(reference, oldFromNewReferences, 2);
BOOST_REQUIRE_THROW(
kde.Train(referenceTree, &oldFromNewReferences), std::invalid_argument);
delete referenceTree;
}
/**
* Tests when reference set values and query set values dimensions don't match.
*/
BOOST_AUTO_TEST_CASE(EvaluationMatchDimensionsTest)
{
arma::mat reference = arma::randu(3, 10);
arma::mat query = arma::randu(1, 10);
arma::vec estimations = arma::vec(query.n_cols, arma::fill::zeros);
const double kernelBandwidth = 0.7;
const double relError = 0.01;
// KDE.
metric::EuclideanDistance metric;
GaussianKernel kernel(kernelBandwidth);
KDE<GaussianKernel,
metric::EuclideanDistance,
arma::mat,
tree::KDTree>
kde(relError, 0.0, kernel, KDEMode::DUAL_TREE_MODE, metric);
kde.Train(reference);
// When evaluating using the query dataset matrix.
BOOST_REQUIRE_THROW(kde.Evaluate(query, estimations),
std::invalid_argument);
// When evaluating using a query tree.
typedef KDTree<EuclideanDistance, kde::KDEStat, arma::mat> Tree;
std::vector<size_t> oldFromNewQueries;
Tree* queryTree = new Tree(query, oldFromNewQueries, 3);
BOOST_REQUIRE_THROW(kde.Evaluate(queryTree, oldFromNewQueries, estimations),
std::invalid_argument);
delete queryTree;
}
/**
* Tests when an empty query set is given to be evaluated.
*/
BOOST_AUTO_TEST_CASE(EmptyQuerySetTest)
{
arma::mat reference = arma::randu(1, 10);
arma::mat query;
// Set estimations to the wrong size.
arma::vec estimations(33, arma::fill::zeros);
const double kernelBandwidth = 0.7;
const double relError = 0.01;
// KDE.
metric::EuclideanDistance metric;
GaussianKernel kernel(kernelBandwidth);
KDE<GaussianKernel,
metric::EuclideanDistance,
arma::mat,
tree::KDTree>
kde(relError, 0.0, kernel, KDEMode::DUAL_TREE_MODE, metric);
kde.Train(reference);
// The query set must be empty.
BOOST_REQUIRE_EQUAL(query.n_cols, 0);
// When evaluating using the query dataset matrix.
BOOST_REQUIRE_NO_THROW(kde.Evaluate(query, estimations));
// When evaluating using a query tree.
typedef KDTree<EuclideanDistance, kde::KDEStat, arma::mat> Tree;
std::vector<size_t> oldFromNewQueries;
Tree* queryTree = new Tree(query, oldFromNewQueries, 3);
BOOST_REQUIRE_NO_THROW(
kde.Evaluate(queryTree, oldFromNewQueries, estimations));
delete queryTree;
// Estimations must be empty.
BOOST_REQUIRE_EQUAL(estimations.size(), 0);
}
/**
* Tests serialiation of KDE models.
*/
BOOST_AUTO_TEST_CASE(SerializationTest)
{
// Initial KDE model to me serialized.
const double relError = 0.25;
const double absError = 0.0;
arma::mat reference = arma::randu(4, 800);
KDE<GaussianKernel,
metric::EuclideanDistance,
arma::mat,
tree::KDTree>
kde(relError, absError, GaussianKernel(0.25));
kde.Train(reference);
// Get estimations to compare.
arma::mat query = arma::randu(4, 100);;
arma::vec estimations = arma::vec(query.n_cols, arma::fill::zeros);
kde.Evaluate(query, estimations);
// Initialize serialized objects.
KDE<GaussianKernel,
metric::EuclideanDistance,
arma::mat,
tree::KDTree> kdeXml, kdeText, kdeBinary;
SerializeObjectAll(kde, kdeXml, kdeText, kdeBinary);
// Check everything is correct.
BOOST_REQUIRE_CLOSE(kde.RelativeError(), relError, 1e-8);
BOOST_REQUIRE_CLOSE(kdeXml.RelativeError(), relError, 1e-8);
BOOST_REQUIRE_CLOSE(kdeText.RelativeError(), relError, 1e-8);
BOOST_REQUIRE_CLOSE(kdeBinary.RelativeError(), relError, 1e-8);
BOOST_REQUIRE_CLOSE(kde.AbsoluteError(), absError, 1e-8);
BOOST_REQUIRE_CLOSE(kdeXml.AbsoluteError(), absError, 1e-8);
BOOST_REQUIRE_CLOSE(kdeText.AbsoluteError(), absError, 1e-8);
BOOST_REQUIRE_CLOSE(kdeBinary.AbsoluteError(), absError, 1e-8);
BOOST_REQUIRE_EQUAL(kde.IsTrained(), true);
BOOST_REQUIRE_EQUAL(kdeXml.IsTrained(), true);
BOOST_REQUIRE_EQUAL(kdeText.IsTrained(), true);
BOOST_REQUIRE_EQUAL(kdeBinary.IsTrained(), true);
const KDEMode mode = KDEMode::DUAL_TREE_MODE;
BOOST_REQUIRE_EQUAL(kde.Mode(), mode);
BOOST_REQUIRE_EQUAL(kdeXml.Mode(), mode);
BOOST_REQUIRE_EQUAL(kdeText.Mode(), mode);
BOOST_REQUIRE_EQUAL(kdeBinary.Mode(), mode);
// Test if execution gives the same result.
arma::vec xmlEstimations = arma::vec(query.n_cols, arma::fill::zeros);
arma::vec textEstimations = arma::vec(query.n_cols, arma::fill::zeros);
arma::vec binEstimations = arma::vec(query.n_cols, arma::fill::zeros);
kdeXml.Evaluate(query, xmlEstimations);
kdeText.Evaluate(query, textEstimations);
kdeBinary.Evaluate(query, binEstimations);
for (size_t i = 0; i < query.n_cols; ++i)
{
BOOST_REQUIRE_CLOSE(estimations[i], xmlEstimations[i], relError*100);
BOOST_REQUIRE_CLOSE(estimations[i], textEstimations[i], relError*100);
BOOST_REQUIRE_CLOSE(estimations[i], binEstimations[i], relError*100);
}
}
/**
* Test if the copy constructor and copy operator works properly.
*/
BOOST_AUTO_TEST_CASE(CopyConstructor)
{
arma::mat reference = arma::randu(2, 300);
arma::mat query = arma::randu(2, 100);
arma::vec estimations1, estimations2, estimations3;
const double kernelBandwidth = 1.5;
const double relError = 0.05;
typedef KDE<GaussianKernel, metric::EuclideanDistance, arma::mat>
KDEType;
// KDE.
KDEType kde(relError, 0, kernel::GaussianKernel(kernelBandwidth));
kde.Train(std::move(reference));
// Copy constructor KDE.
KDEType constructor(kde);
// Copy operator KDE.
KDEType oper = kde;
// Evaluations.
kde.Evaluate(query, estimations1);
constructor.Evaluate(query, estimations2);
oper.Evaluate(query, estimations3);
// Check results.
for (size_t i = 0; i < query.n_cols; ++i)
{
BOOST_REQUIRE_CLOSE(estimations1[i], estimations2[i], 1e-10);
BOOST_REQUIRE_CLOSE(estimations2[i], estimations3[i], 1e-10);
}
}
/**
* Test if the move constructor works properly.
*/
BOOST_AUTO_TEST_CASE(MoveConstructor)
{
arma::mat reference = arma::randu(2, 300);
arma::mat query = arma::randu(2, 100);
arma::vec estimations1, estimations2, estimations3;
const double kernelBandwidth = 1.2;
const double relError = 0.05;
typedef KDE<EpanechnikovKernel, metric::EuclideanDistance, arma::mat>
KDEType;
// KDE.
KDEType kde(relError, 0, kernel::EpanechnikovKernel(kernelBandwidth));
kde.Train(std::move(reference));
kde.Evaluate(query, estimations1);
// Move constructor KDE.
KDEType constructor(std::move(kde));
constructor.Evaluate(query, estimations2);
// Check results.
BOOST_REQUIRE_THROW(kde.Evaluate(query, estimations3), std::runtime_error);
for (size_t i = 0; i < query.n_cols; ++i)
BOOST_REQUIRE_CLOSE(estimations1[i], estimations2[i], 1e-10);
}
/**
* Test if an untrained KDE works properly.
*/
BOOST_AUTO_TEST_CASE(NotTrained)
{
arma::mat query = arma::randu(1, 10);
std::vector<size_t> oldFromNew;
arma::vec estimations;
KDE<> kde;
KDE<>::Tree queryTree(query, oldFromNew);
// Check results.
BOOST_REQUIRE_THROW(kde.Evaluate(query, estimations), std::runtime_error);
BOOST_REQUIRE_THROW(kde.Evaluate(&queryTree, oldFromNew, estimations),
std::runtime_error);
BOOST_REQUIRE_THROW(kde.Evaluate(estimations), std::runtime_error);
}
BOOST_AUTO_TEST_SUITE_END();
+408
View File
@@ -0,0 +1,408 @@
/**
* @file kde_test.cpp
* @author Roberto Hueso
*
* Test mlpackMain() of kde_main.cpp
*
* mlpack is free software; you may redistribute it and/or modify it under the
* terms of the 3-clause BSD license. You should have received a copy of the
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*/
#include <string>
#define BINDING_TYPE BINDING_TYPE_TEST
static const std::string testName = "KDE";
#include <mlpack/core.hpp>
#include <mlpack/core/util/mlpack_main.hpp>
#include "test_helper.hpp"
#include <mlpack/methods/kde/kde_main.cpp>
#include <boost/test/unit_test.hpp>
#include "../test_tools.hpp"
using namespace mlpack;
struct KDETestFixture
{
public:
KDETestFixture()
{
// Cache in the options for this program.
CLI::RestoreSettings(testName);
}
~KDETestFixture()
{
// Clear the settings.
CLI::ClearSettings();
}
};
void ResetKDESettings()
{
CLI::ClearSettings();
CLI::RestoreSettings(testName);
}
BOOST_FIXTURE_TEST_SUITE(KDEMainTest, KDETestFixture);
/**
* Ensure that the estimations we get for KDEMain, are the same as the ones we
* get from the KDE class without any wrappers. Requires normalization.
**/
BOOST_AUTO_TEST_CASE(KDEGaussianRTreeResultsMain)
{
// Datasets.
arma::mat reference = arma::randu(3, 500);
arma::mat query = arma::randu(3, 100);
arma::vec kdeEstimations, mainEstimations;
double kernelBandwidth = 1.5;
double relError = 0.05;
kernel::GaussianKernel kernel(kernelBandwidth);
metric::EuclideanDistance metric;
KDE<kernel::GaussianKernel,
metric::EuclideanDistance,
arma::mat,
tree::RTree>
kde(relError, 0.0, kernel, KDEMode::DUAL_TREE_MODE, metric);
kde.Train(reference);
kde.Evaluate(query, kdeEstimations);
// Normalize estimations.
kdeEstimations /= kernel.Normalizer(reference.n_rows);
// Main estimations.
SetInputParam("reference", reference);
SetInputParam("query", query);
SetInputParam("kernel", std::string("gaussian"));
SetInputParam("tree", std::string("r-tree"));
SetInputParam("rel_error", relError);
SetInputParam("bandwidth", kernelBandwidth);
mlpackMain();
mainEstimations = std::move(CLI::GetParam<arma::vec>("predictions"));
// Check whether results are equal.
for (size_t i = 0; i < query.n_cols; ++i)
BOOST_REQUIRE_CLOSE(kdeEstimations[i], mainEstimations[i], relError);
}
/**
* Ensure that the estimations we get for KDEMain, are the same as the ones we
* get from the KDE class without any wrappers. Doesn't require normalization.
**/
BOOST_AUTO_TEST_CASE(KDETriangularBallTreeResultsMain)
{
// Datasets.
arma::mat reference = arma::randu(3, 300);
arma::mat query = arma::randu(3, 100);
arma::vec kdeEstimations, mainEstimations;
double kernelBandwidth = 3.0;
double relError = 0.06;
kernel::TriangularKernel kernel(kernelBandwidth);
metric::EuclideanDistance metric;
KDE<kernel::TriangularKernel,
metric::EuclideanDistance,
arma::mat,
tree::BallTree>
kde(relError, 0.0, kernel, KDEMode::DUAL_TREE_MODE, metric);
kde.Train(reference);
kde.Evaluate(query, kdeEstimations);
// Main estimations.
SetInputParam("reference", reference);
SetInputParam("query", query);
SetInputParam("kernel", std::string("triangular"));
SetInputParam("tree", std::string("ball-tree"));
SetInputParam("rel_error", relError);
SetInputParam("bandwidth", kernelBandwidth);
mlpackMain();
mainEstimations = std::move(CLI::GetParam<arma::vec>("predictions"));
// Check whether results are equal.
for (size_t i = 0; i < query.n_cols; ++i)
BOOST_REQUIRE_CLOSE(kdeEstimations[i], mainEstimations[i], relError);
}
/**
* Ensure that the estimations we get for KDEMain, are the same as the ones we
* get from the KDE class without any wrappers in the monochromatic case.
**/
BOOST_AUTO_TEST_CASE(KDEMonoResultsMain)
{
// Datasets.
arma::mat reference = arma::randu(2, 300);
arma::vec kdeEstimations, mainEstimations;
double kernelBandwidth = 2.3;
double relError = 0.05;
kernel::EpanechnikovKernel kernel(kernelBandwidth);
metric::EuclideanDistance metric;
KDE<kernel::EpanechnikovKernel,
metric::EuclideanDistance,
arma::mat,
tree::StandardCoverTree>
kde(relError, 0.0, kernel, KDEMode::DUAL_TREE_MODE, metric);
kde.Train(reference);
// Perform monochromatic KDE.
kde.Evaluate(kdeEstimations);
// Normalize.
kdeEstimations /= kernel.Normalizer(reference.n_rows);
// Main estimations.
SetInputParam("reference", reference);
SetInputParam("kernel", std::string("epanechnikov"));
SetInputParam("tree", std::string("cover-tree"));
SetInputParam("rel_error", relError);
SetInputParam("bandwidth", kernelBandwidth);
mlpackMain();
mainEstimations = std::move(CLI::GetParam<arma::vec>("predictions"));
// Check whether results are equal.
for (size_t i = 0; i < reference.n_cols; ++i)
BOOST_REQUIRE_CLOSE(kdeEstimations[i], mainEstimations[i], relError);
}
/**
* Ensuring that absence of input data is checked.
**/
BOOST_AUTO_TEST_CASE(KDENoInputData)
{
// No input data is not provided. Should throw a runtime error.
Log::Fatal.ignoreInput = true;
BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
Log::Fatal.ignoreInput = false;
}
/**
* Check result has as many densities as query points.
**/
BOOST_AUTO_TEST_CASE(KDEOutputSize)
{
const size_t dim = 3;
const size_t samples = 110;
arma::mat reference = arma::randu<arma::mat>(dim, 325);
arma::mat query = arma::randu<arma::mat>(dim, samples);
// Main params.
SetInputParam("reference", reference);
SetInputParam("query", query);
mlpackMain();
// Check number of output elements.
BOOST_REQUIRE_EQUAL(CLI::GetParam<arma::vec>("predictions").size(), samples);
}
/**
* Check that saved model can be reused.
**/
BOOST_AUTO_TEST_CASE(KDEModelReuse)
{
const size_t dim = 3;
const size_t samples = 100;
const double relError = 0.05;
arma::mat reference = arma::randu<arma::mat>(dim, 300);
arma::mat query = arma::randu<arma::mat>(dim, samples);
// Main params.
SetInputParam("reference", reference);
SetInputParam("query", query);
SetInputParam("bandwidth", 2.4);
SetInputParam("rel_error", 0.05);
mlpackMain();
arma::vec oldEstimations = std::move(CLI::GetParam<arma::vec>("predictions"));
// Change parameters and load model.
CLI::GetSingleton().Parameters()["reference"].wasPassed = false;
SetInputParam("bandwidth", 0.5);
SetInputParam("query", query);
SetInputParam("input_model",
std::move(CLI::GetParam<KDEModel*>("output_model")));
mlpackMain();
arma::vec newEstimations = std::move(CLI::GetParam<arma::vec>("predictions"));
// Check estimations are the same.
for (size_t i = 0; i < samples; ++i)
BOOST_REQUIRE_CLOSE(oldEstimations[i], newEstimations[i], relError);
}
/**
* Ensure that the estimations we get for KDEMain, are the same as the ones we
* get from the KDE class without any wrappers using single-tree mode.
**/
BOOST_AUTO_TEST_CASE(KDEGaussianSingleKDTreeResultsMain)
{
// Datasets.
arma::mat reference = arma::randu(3, 400);
arma::mat query = arma::randu(3, 400);
arma::vec kdeEstimations, mainEstimations;
double kernelBandwidth = 3.0;
double relError = 0.06;
kernel::GaussianKernel kernel(kernelBandwidth);
metric::EuclideanDistance metric;
KDE<kernel::GaussianKernel,
metric::EuclideanDistance,
arma::mat,
tree::BallTree>
kde(relError, 0.0, kernel, KDEMode::SINGLE_TREE_MODE, metric);
kde.Train(reference);
kde.Evaluate(query, kdeEstimations);
kdeEstimations /= kernel.Normalizer(reference.n_rows);
// Main estimations.
SetInputParam("reference", reference);
SetInputParam("query", query);
SetInputParam("kernel", std::string("gaussian"));
SetInputParam("tree", std::string("kd-tree"));
SetInputParam("algorithm", std::string("single-tree"));
SetInputParam("rel_error", relError);
SetInputParam("bandwidth", kernelBandwidth);
mlpackMain();
mainEstimations = std::move(CLI::GetParam<arma::vec>("predictions"));
// Check whether results are equal.
for (size_t i = 0; i < query.n_cols; ++i)
BOOST_REQUIRE_CLOSE(kdeEstimations[i], mainEstimations[i], relError);
}
/**
* Ensure we get an exception when an invalid kernel is specified.
**/
BOOST_AUTO_TEST_CASE(KDEMainInvalidKernel)
{
arma::mat reference = arma::randu<arma::mat>(2, 10);
arma::mat query = arma::randu<arma::mat>(2, 5);
// Main params.
SetInputParam("reference", reference);
SetInputParam("query", query);
SetInputParam("kernel", std::string("linux"));
Log::Fatal.ignoreInput = true;
BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
Log::Fatal.ignoreInput = false;
}
/**
* Ensure we get an exception when an invalid tree is specified.
**/
BOOST_AUTO_TEST_CASE(KDEMainInvalidTree)
{
arma::mat reference = arma::randu<arma::mat>(2, 10);
arma::mat query = arma::randu<arma::mat>(2, 5);
// Main params.
SetInputParam("reference", reference);
SetInputParam("query", query);
SetInputParam("tree", std::string("olive"));
Log::Fatal.ignoreInput = true;
BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
Log::Fatal.ignoreInput = false;
}
/**
* Ensure we get an exception when an invalid algorithm is specified.
**/
BOOST_AUTO_TEST_CASE(KDEMainInvalidAlgorithm)
{
arma::mat reference = arma::randu<arma::mat>(2, 10);
arma::mat query = arma::randu<arma::mat>(2, 5);
// Main params.
SetInputParam("reference", reference);
SetInputParam("query", query);
SetInputParam("algorithm", std::string("bogosort"));
Log::Fatal.ignoreInput = true;
BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
Log::Fatal.ignoreInput = false;
}
/**
* Ensure we get an exception when both reference and input_model are
* specified.
**/
BOOST_AUTO_TEST_CASE(KDEMainReferenceAndModel)
{
arma::mat reference = arma::randu<arma::mat>(2, 10);
arma::mat query = arma::randu<arma::mat>(2, 5);
KDEModel* model = new KDEModel();
// Main params.
SetInputParam("reference", reference);
SetInputParam("query", query);
SetInputParam("input_model", model);
Log::Fatal.ignoreInput = true;
BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
Log::Fatal.ignoreInput = false;
}
/**
* Ensure we get an exception when an invalid absolute error is specified.
**/
BOOST_AUTO_TEST_CASE(KDEMainInvalidAbsoluteError)
{
arma::mat reference = arma::randu<arma::mat>(1, 10);
arma::mat query = arma::randu<arma::mat>(1, 5);
// Main params.
SetInputParam("reference", reference);
SetInputParam("query", query);
Log::Fatal.ignoreInput = true;
// Invalid value.
SetInputParam("abs_error", -0.1);
BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
// Valid value.
SetInputParam("abs_error", 5.8);
BOOST_REQUIRE_NO_THROW(mlpackMain());
Log::Fatal.ignoreInput = false;
}
/**
* Ensure we get an exception when an invalid relative error is specified.
**/
BOOST_AUTO_TEST_CASE(KDEMainInvalidRelativeError)
{
arma::mat reference = arma::randu<arma::mat>(1, 10);
arma::mat query = arma::randu<arma::mat>(1, 5);
// Main params.
SetInputParam("reference", reference);
SetInputParam("query", query);
Log::Fatal.ignoreInput = true;
// Invalid under 0.
SetInputParam("rel_error", -0.1);
BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
// Invalid over 1.
SetInputParam("rel_error", 1.1);
BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
// Valid value.
SetInputParam("rel_error", 0.3);
BOOST_REQUIRE_NO_THROW(mlpackMain());
Log::Fatal.ignoreInput = false;
}
BOOST_AUTO_TEST_SUITE_END();
+6 -6
View File
@@ -58,7 +58,7 @@ BOOST_FIXTURE_TEST_SUITE(NMFMainTest, NMFTestFixture);
*/
BOOST_AUTO_TEST_CASE(NMFMultdistShapeTest)
{
mat v = randu<mat>(10, 10);
mat v = randu<mat>(8, 10);
int r = 5;
SetInputParam("update_rules", std::string("multdist"));
@@ -73,7 +73,7 @@ BOOST_AUTO_TEST_CASE(NMFMultdistShapeTest)
const mat& h = CLI::GetParam<mat>("h");
// Check the shapes of W and H.
BOOST_REQUIRE_EQUAL(w.n_rows, 10);
BOOST_REQUIRE_EQUAL(w.n_rows, 8);
BOOST_REQUIRE_EQUAL(w.n_cols, 5);
BOOST_REQUIRE_EQUAL(h.n_rows, 5);
BOOST_REQUIRE_EQUAL(h.n_cols, 10);
@@ -85,7 +85,7 @@ BOOST_AUTO_TEST_CASE(NMFMultdistShapeTest)
*/
BOOST_AUTO_TEST_CASE(NMFMultdivShapeTest)
{
mat v = randu<mat>(10, 10);
mat v = randu<mat>(8, 10);
int r = 5;
SetInputParam("update_rules", std::string("multdiv"));
@@ -100,7 +100,7 @@ BOOST_AUTO_TEST_CASE(NMFMultdivShapeTest)
const mat& h = CLI::GetParam<mat>("h");
// Check the shapes of W and H.
BOOST_REQUIRE_EQUAL(w.n_rows, 10);
BOOST_REQUIRE_EQUAL(w.n_rows, 8);
BOOST_REQUIRE_EQUAL(w.n_cols, 5);
BOOST_REQUIRE_EQUAL(h.n_rows, 5);
BOOST_REQUIRE_EQUAL(h.n_cols, 10);
@@ -112,7 +112,7 @@ BOOST_AUTO_TEST_CASE(NMFMultdivShapeTest)
*/
BOOST_AUTO_TEST_CASE(NMFAlsShapeTest)
{
mat v = randu<mat>(10, 10);
mat v = randu<mat>(8, 10);
int r = 5;
SetInputParam("update_rules", std::string("als"));
@@ -127,7 +127,7 @@ BOOST_AUTO_TEST_CASE(NMFAlsShapeTest)
const mat& h = CLI::GetParam<mat>("h");
// Check the shapes of W and H.
BOOST_REQUIRE_EQUAL(w.n_rows, 10);
BOOST_REQUIRE_EQUAL(w.n_rows, 8);
BOOST_REQUIRE_EQUAL(w.n_cols, 5);
BOOST_REQUIRE_EQUAL(h.n_rows, 5);
BOOST_REQUIRE_EQUAL(h.n_cols, 10);
@@ -0,0 +1,574 @@
/**
* @file range_search_test.cpp
* @author Niteya Shah
*
* Test mlpackMain() of range_search_main.cpp.
*
* mlpack is free software; you may redistribute it and/or modify it under the
* terms of the 3-clause BSD license. You should have received a copy of the
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*/
#define BINDING_TYPE BINDING_TYPE_TEST
static const std::string testName = "RangeSearchMain";
#include <mlpack/core.hpp>
#include <mlpack/core/util/mlpack_main.hpp>
#include "test_helper.hpp"
#include <mlpack/methods/range_search/range_search_main.cpp>
#include "range_search_utils.hpp"
#include <boost/test/unit_test.hpp>
using namespace mlpack;
struct RangeSearchTestFixture
{
public:
RangeSearchTestFixture()
{
// Cache in the options for this program.
CLI::RestoreSettings(testName);
}
~RangeSearchTestFixture()
{
// Clear the settings.
bindings::tests::CleanMemory();
CLI::ClearSettings();
}
};
BOOST_FIXTURE_TEST_SUITE(RangeSearchMainTest, RangeSearchTestFixture);
/**
* Check that we have to specify a reference set or input model.
*/
BOOST_AUTO_TEST_CASE(RangeSearchNoReference)
{
Log::Fatal.ignoreInput = true;
BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
Log::Fatal.ignoreInput = false;
}
/**
* Check that we cannot pass an incorrect parameter.
*/
BOOST_AUTO_TEST_CASE(RangeSearchWrongParameter)
{
string wrongString = "abc";
Log::Fatal.ignoreInput = true;
BOOST_REQUIRE_THROW(SetInputParam("RST", wrongString), std::runtime_error);
Log::Fatal.ignoreInput = false;
}
/**
* Check that we have to specify a query if an input model is specified.
*/
BOOST_AUTO_TEST_CASE(RangeSearchInputModelNoQuery)
{
arma::mat inputData;
double minVal = 0, maxVal = 3;
string distanceFile = "distances.csv";
string neighborsFile = "neighbors.csv";
if (!data::Load("iris.csv", inputData))
BOOST_FAIL("Unable to load dataset iris.csv!");
SetInputParam("reference", move(inputData));
SetInputParam("min", minVal);
SetInputParam("max", maxVal);
SetInputParam("distances_file", distanceFile);
SetInputParam("neighbors_file", neighborsFile);
mlpackMain();
CLI::GetSingleton().Parameters()["reference"].wasPassed = false;
SetInputParam("input_model", move(CLI::GetParam<RSModel*>("output_model")));
Log::Fatal.ignoreInput = true;
BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
Log::Fatal.ignoreInput = false;
remove(neighborsFile.c_str());
remove(distanceFile.c_str());
}
/**
* Check that we cannot specify a tree type which is not available or wrong.
*/
BOOST_AUTO_TEST_CASE(RangeSearchDifferentTree)
{
arma::mat inputData;
double minVal = 0, maxVal = 3;
string distanceFile = "distances.csv";
string neighborsFile = "neighbors.csv";
string wrongTreeType = "RST";
if (!data::Load("iris.csv", inputData))
BOOST_FAIL("Unable to load dataset iris.csv!");
SetInputParam("reference", move(inputData));
SetInputParam("min", minVal);
SetInputParam("max", maxVal);
SetInputParam("distances_file", distanceFile);
SetInputParam("neighbors_file", neighborsFile);
SetInputParam("tree_type", wrongTreeType);
Log::Fatal.ignoreInput = true;
BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
Log::Fatal.ignoreInput = false;
remove(neighborsFile.c_str());
remove(distanceFile.c_str());
}
/**
* Check that we cannot specify both a reference set and input model.
*/
BOOST_AUTO_TEST_CASE(RangeSearchBothReferenceAndModel)
{
arma::mat inputData, queryData;
double minVal = 0, maxVal = 3;
string distanceFile = "distances.csv";
string neighborsFile = "neighbors.csv";
if (!data::Load("iris.csv", inputData))
BOOST_FAIL("Unable to load dataset iris.csv!");
if (!data::Load("iris_test.csv", queryData))
BOOST_FAIL("Unable to load dataset iris_test.csv!");
SetInputParam("reference", move(inputData));
SetInputParam("min", minVal);
SetInputParam("max", maxVal);
SetInputParam("distances_file", distanceFile);
SetInputParam("neighbors_file", neighborsFile);
SetInputParam("query", queryData);
mlpackMain();
SetInputParam("input_model", move(CLI::GetParam<RSModel*>("output_model")));
SetInputParam("query", move(queryData));
Log::Fatal.ignoreInput = true;
BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
Log::Fatal.ignoreInput = false;
remove(neighborsFile.c_str());
remove(distanceFile.c_str());
}
/**
* Check that the correct output is returned for a small synthetic input case,
* by comparing with pre-calculated neighbor and distance values, when no query
* set is specified.
*/
BOOST_AUTO_TEST_CASE(RangeSearchTest)
{
arma::mat x = {{0, 3, 3, 4, 3, 1},
{4, 4, 4, 5, 5, 2},
{0, 1, 2, 2, 3, 3}};
string distanceFile = "distances.csv";
string neighborsFile = "neighbors.csv";
double minVal = 0, maxVal = 3;
vector<vector<size_t>> neighborVal = {{},
{2, 3, 4},
{1, 3, 4, 5},
{1, 2, 4},
{1, 2, 3},
{2}};
vector<vector<double>> distanceVal = {{},
{1, 1.73205, 2.23607},
{1, 1.41421, 1.41421, 3},
{1.73205, 1.41421, 1.41421},
{2.23607, 1.41421, 1.41421},
{3}};
vector<vector<size_t>> neighbors;
vector<vector<double>> distances;
SetInputParam("reference", move(x));
SetInputParam("min", minVal);
SetInputParam("max", maxVal);
SetInputParam("distances_file", distanceFile);
SetInputParam("neighbors_file", neighborsFile);
mlpackMain();
neighbors = ReadData<size_t>(neighborsFile);
distances = ReadData<double>(distanceFile);
CheckMatrices(neighbors, neighborVal);
CheckMatrices(distances, distanceVal);
remove(neighborsFile.c_str());
remove(distanceFile.c_str());
}
/**
* Check that the correct output is returned for a small synthetic input case,
* when a query set is provided.
*/
BOOST_AUTO_TEST_CASE(RangeSeachTestwithQuery)
{
arma::mat queryData = {{5, 3, 1}, {4, 2, 4}, {3, 1, 7}};
arma::mat x = {{0, 3, 3, 4, 3, 1},
{4, 4, 4, 5, 5, 2},
{0, 1, 2, 2, 3, 3}};
vector<vector<double>> distanceVal = {
{2.82843, 2.23607, 1.73205, 2.23607, 4.47214},
{3.74166, 2, 2.23607, 3.31662, 3.60555, 2.82843},
{4.58258, 4.47214}};
vector<vector<size_t>> neighborVal = {{1, 2, 3, 4, 5},
{0, 1, 2, 3, 4, 5},
{4, 5}};
vector<vector<size_t>> neighbors;
vector<vector<double>> distances;
string distanceFile = "distances.csv";
string neighborsFile = "neighbors.csv";
double minVal = 0, maxVal = 5;
SetInputParam("query", queryData);
SetInputParam("reference", move(x));
SetInputParam("min", minVal);
SetInputParam("max", maxVal);
SetInputParam("distances_file", distanceFile);
SetInputParam("neighbors_file", neighborsFile);
mlpackMain();
neighbors = ReadData<size_t>(neighborsFile);
distances = ReadData<double>(distanceFile);
CheckMatrices(neighbors, neighborVal);
CheckMatrices(distances, distanceVal);
remove(neighborsFile.c_str());
remove(distanceFile.c_str());
}
/**
* Train a model using a synthetic dataset and then output the model, and ensure
* it can be used again.
*/
BOOST_AUTO_TEST_CASE(ModelCheck)
{
arma::mat inputData, queryData;
double minVal = 0, maxVal = 3;
string distanceFile = "distances.csv";
string neighborsFile = "neighbors.csv";
vector<vector<size_t>> neighbors, neighborsTemp;
vector<vector<double>> distances, distancetemp;
if (!data::Load("iris.csv", inputData))
BOOST_FAIL("Unable to load dataset iris.csv!");
if (!data::Load("iris_test.csv", queryData))
BOOST_FAIL("Unable to load dataset iris_test.csv!");
SetInputParam("reference", move(inputData));
SetInputParam("min", minVal);
SetInputParam("max", maxVal);
SetInputParam("distances_file", distanceFile);
SetInputParam("neighbors_file", neighborsFile);
SetInputParam("query", queryData);
mlpackMain();
neighbors = ReadData<size_t>(neighborsFile);
distances = ReadData<double>(distanceFile);
RSModel* outputModel = move(CLI::GetParam<RSModel*>("output_model"));
CLI::GetSingleton().Parameters()["reference"].wasPassed = false;
SetInputParam("input_model", outputModel);
SetInputParam("query", move(queryData));
mlpackMain();
neighborsTemp = ReadData<size_t>(neighborsFile);
distancetemp = ReadData<double>(distanceFile);
CheckMatrices(neighbors, neighborsTemp);
CheckMatrices(distances, distancetemp);
BOOST_REQUIRE_EQUAL(ModelToString(outputModel),
ModelToString(CLI::GetParam<RSModel*>("output_model")));
remove(neighborsFile.c_str());
remove(distanceFile.c_str());
}
/**
* Check that the models are different but the results are the same for three
* different leaf size parameters.
*/
BOOST_AUTO_TEST_CASE(LeafValueTesting)
{
arma::mat inputData;
if (!data::Load("iris.csv", inputData))
BOOST_FAIL("Unable to load dataset iris.csv!");
string distanceFile = "distances.csv";
string neighborsFile = "neighbors.csv";
double minVal = 0, maxVal = 3;
vector<vector<size_t>> neighbors, neighborsTemp;
vector<vector<double>> distances, distancestemp;
vector<int> leafSizes {20, 15, 25};
SetInputParam("reference", inputData);
SetInputParam("min", minVal);
SetInputParam("max", maxVal);
SetInputParam("distances_file", distanceFile);
SetInputParam("neighbors_file", neighborsFile);
SetInputParam("leaf_size", leafSizes[0]);
// The default leaf size is 20.
mlpackMain();
RSModel* outputModel1 = CLI::GetParam<RSModel*>("output_model");
neighbors = ReadData<size_t>(neighborsFile);
distances = ReadData<double>(distanceFile);
for (size_t i = 1; i < leafSizes.size(); i++)
{
SetInputParam("leaf_size", leafSizes[i]);
SetInputParam("reference", inputData);
SetInputParam("min", minVal);
SetInputParam("max", maxVal);
SetInputParam("distances_file", distanceFile);
SetInputParam("neighbors_file", neighborsFile);
mlpackMain();
neighborsTemp = ReadData<size_t>(neighborsFile);
distancestemp = ReadData<double>(distanceFile);
CheckMatrices(neighbors, neighborsTemp);
CheckMatrices(distances, distancestemp);
BOOST_REQUIRE_NE(ModelToString(outputModel1),
ModelToString(CLI::GetParam<RSModel*>("output_model")));
}
remove(neighborsFile.c_str());
remove(distanceFile.c_str());
}
/**
* Make sure that the models are different but the results are the same for
* different tree types. We use the default kd-tree as the base model to
* compare against.
*/
BOOST_AUTO_TEST_CASE(TreeTypeTesting)
{
string distanceFile = "distances.csv";
string neighborsFile = "neighbors.csv";
double minVal = 0, maxVal = 3;
arma::mat queryData, inputData;
vector<vector<size_t>> neighbors, neighborsTemp;
vector<vector<double>> distances, distancestemp;
vector<string> trees = {"kd", "cover", "r", "r-star", "ball", "x",
"hilbert-r", "r-plus", "r-plus-plus", "vp","rp",
"max-rp", "ub", "oct"};
if (!data::Load("iris.csv", inputData))
BOOST_FAIL("Unable to load dataset iris.csv!");
if (!data::Load("iris_test.csv", queryData))
BOOST_FAIL("Unable to load dataset iris_test.csv!");
// Define base parameters with the kd-tree.
SetInputParam("tree_type", trees[0]);
SetInputParam("min", minVal);
SetInputParam("max", maxVal);
SetInputParam("distances_file", distanceFile);
SetInputParam("neighbors_file", neighborsFile);
SetInputParam("reference", inputData);
SetInputParam("query", queryData);
mlpackMain();
neighbors = ReadData<size_t>(neighborsFile);
distances = ReadData<double>(distanceFile);
RSModel* outputModel1=CLI::GetParam<RSModel*>("output_model");
for (size_t i = 1;i < trees.size(); i++)
{
if (!data::Load("iris.csv", inputData))
BOOST_FAIL("Unable to load dataset iris.csv!");
if (!data::Load("iris_test.csv", queryData))
BOOST_FAIL("Unable to load dataset iris_test.csv!");
SetInputParam("min", minVal);
SetInputParam("max", maxVal);
SetInputParam("distances_file", distanceFile);
SetInputParam("neighbors_file", neighborsFile);
SetInputParam("query", queryData);
SetInputParam("reference", inputData);
SetInputParam("tree_type", trees[i]);
mlpackMain();
neighborsTemp = ReadData<size_t>(neighborsFile);
distancestemp = ReadData<double>(distanceFile);
CheckMatrices(neighbors, neighborsTemp);
CheckMatrices(distances, distancestemp);
BOOST_REQUIRE_NE(ModelToString(outputModel1),
ModelToString(CLI::GetParam<RSModel*>("output_model")));
}
remove(neighborsFile.c_str());
remove(distanceFile.c_str());
}
/**
* Project the data onto a random basis and ensure that this gives identical
* results to non-projected data but different models.
*/
BOOST_AUTO_TEST_CASE(RandomBasisTesting)
{
string distanceFile = "distances.csv";
string neighborsFile = "neighbors.csv";
double minVal = 0, maxVal = 3;
arma::mat queryData, inputData;
if (!data::Load("iris.csv", inputData))
BOOST_FAIL("Unable to load dataset iris.csv!");
if (!data::Load("iris_test.csv", queryData))
BOOST_FAIL("Unable to load dataset iris_test.csv!");
SetInputParam("min", minVal);
SetInputParam("max", maxVal);
SetInputParam("distances_file", distanceFile);
SetInputParam("neighbors_file", neighborsFile);
SetInputParam("reference", inputData);
mlpackMain();
RSModel* outputModel = move(CLI::GetParam<RSModel*>("output_model"));
SetInputParam("min", minVal);
SetInputParam("max", maxVal);
SetInputParam("distances_file", distanceFile);
SetInputParam("neighbors_file", neighborsFile);
SetInputParam("reference", inputData);
SetInputParam("random_basis",true);
mlpackMain();
BOOST_REQUIRE_NE(ModelToString(outputModel),
ModelToString(CLI::GetParam<RSModel*>("output_model")));
remove(neighborsFile.c_str());
remove(distanceFile.c_str());
}
/**
* Ensure that naive mode gives the same result, but different models.
*/
BOOST_AUTO_TEST_CASE(NaiveModeTest)
{
string distanceFile = "distances.csv";
string neighborsFile = "neighbors.csv";
double minVal = 0, maxVal = 3;
arma::mat queryData, inputData;
vector<vector<size_t>> neighbors, neighborsTemp;
vector<vector<double>> distances, distancestemp;
if (!data::Load("iris.csv", inputData))
BOOST_FAIL("Unable to load dataset iris.csv!");
if (!data::Load("iris_test.csv", queryData))
BOOST_FAIL("Unable to load dataset iris_test.csv!");
SetInputParam("min", minVal);
SetInputParam("max", maxVal);
SetInputParam("distances_file", distanceFile);
SetInputParam("neighbors_file", neighborsFile);
SetInputParam("reference", inputData);
mlpackMain();
neighbors = ReadData<size_t>(neighborsFile);
distances = ReadData<double>(distanceFile);
RSModel* outputModel = move(CLI::GetParam<RSModel*>("output_model"));
SetInputParam("min", minVal);
SetInputParam("max", maxVal);
SetInputParam("distances_file", distanceFile);
SetInputParam("neighbors_file", neighborsFile);
SetInputParam("reference", inputData);
SetInputParam("naive", true);
mlpackMain();
neighborsTemp = ReadData<size_t>(neighborsFile);
distancestemp = ReadData<double>(distanceFile);
CheckMatrices(neighbors, neighborsTemp);
CheckMatrices(distances, distancestemp);
BOOST_REQUIRE_NE(ModelToString(outputModel),
ModelToString(CLI::GetParam<RSModel*>("output_model")));
remove(neighborsFile.c_str());
remove(distanceFile.c_str());
}
/**
* Ensure that single-tree mode gives the same result but different models.
*/
BOOST_AUTO_TEST_CASE(SingleModeTest)
{
string distanceFile = "distances.csv";
string neighborsFile = "neighbors.csv";
double minVal = 0, maxVal = 3;
arma::mat queryData, inputData;
vector<vector<size_t>> neighbors, neighborsTemp;
vector<vector<double>> distances, distancestemp;
if (!data::Load("iris.csv", inputData))
BOOST_FAIL("Unable to load dataset iris.csv!");
if (!data::Load("iris_test.csv", queryData))
BOOST_FAIL("Unable to load dataset iris_test.csv!");
SetInputParam("min", minVal);
SetInputParam("max", maxVal);
SetInputParam("distances_file", distanceFile);
SetInputParam("neighbors_file", neighborsFile);
SetInputParam("reference", inputData);
mlpackMain();
neighbors = ReadData<size_t>(neighborsFile);
distances = ReadData<double>(distanceFile);
RSModel* outputModel = move(CLI::GetParam<RSModel*>("output_model"));
SetInputParam("min", minVal);
SetInputParam("max", maxVal);
SetInputParam("distances_file", distanceFile);
SetInputParam("neighbors_file", neighborsFile);
SetInputParam("reference", inputData);
SetInputParam("single_mode", true);
mlpackMain();
neighborsTemp = ReadData<size_t>(neighborsFile);
distancestemp = ReadData<double>(distanceFile);
CheckMatrices(neighbors, neighborsTemp);
CheckMatrices(distances, distancestemp);
BOOST_REQUIRE_NE(ModelToString(outputModel),
ModelToString(CLI::GetParam<RSModel*>("output_model")));
remove(neighborsFile.c_str());
remove(distanceFile.c_str());
}
BOOST_AUTO_TEST_SUITE_END();
@@ -0,0 +1,107 @@
/**
* @file range_search_utils.hpp
* @author Niteya Shah
*
* Helper functions used in the execution of the Range Search test.
*
* mlpack is free software; you may redistribute it and/or modify it under the
* terms of the 3-clause BSD license. You should have received a copy of the
* 3-clause BSD license along with mlpack. If not, see
* http://www.opensource.org/licenses/BSD-3-Clause for more information.
*/
#ifndef MLPACK_TESTS_MAIN_TESTS_RANGE_SEARCH_TEST_UTILS_HPP
#define MLPACK_TESTS_MAIN_TESTS_RANGE_SEARCH_TEST_UTILS_HPP
#include <boost/test/unit_test.hpp>
#include <mlpack/methods/range_search/rs_model.hpp>
#include <mlpack/core.hpp>
#include <mlpack/core/util/mlpack_main.hpp>
/**
* Convert a model to a string using the text_oarchive of boost::serialization.
*
* @param model RSModel to be converted to string.
*/
inline std::string ModelToString(RSModel* model)
{
std::ostringstream oss;
boost::archive::text_oarchive oa(oss);
oa << model;
return oss.str();
}
/**
* Check that 2 matrices of type vector<vector<double>> are close to equal,
* using the given tolerance.
*
* @param vec1 First vector to compare.
* @param vec2 Second vector to compare.
* @param tolerance Allowed tolerance for values.
*/
inline void CheckMatrices(std::vector<std::vector<double>>& vec1,
std::vector<std::vector<double>>& vec2,
const double tolerance = 1e-3)
{
BOOST_REQUIRE_EQUAL(vec1.size() , vec2.size());
for (size_t i = 0; i < vec1.size(); i++)
{
BOOST_REQUIRE_EQUAL(vec1[i].size(), vec2[i].size());
std::sort(vec1[i].begin(), vec1[i].end());
std::sort(vec2[i].begin(), vec2[i].end());
for (size_t j = 0 ; j < vec1[i].size(); j++)
{
BOOST_REQUIRE_CLOSE(vec1[i][j], vec2[i][j], tolerance);
}
}
}
/**
* Check that 2 matrices of type vector<vector<size_t>> are equal.
*
* @param vec1 First vector to compare.
* @param vec2 Second vector to compare.
*/
inline void CheckMatrices(std::vector<std::vector<size_t>>& vec1,
std::vector<std::vector<size_t>>& vec2)
{
BOOST_REQUIRE_EQUAL(vec1.size() , vec2.size());
for (size_t i = 0; i < vec1.size(); i++)
{
BOOST_REQUIRE_EQUAL(vec1[i].size(), vec2[i].size());
std::sort(vec1[i].begin(), vec1[i].end());
std::sort(vec2[i].begin(), vec2[i].end());
for (size_t j = 0; j < vec1[i].size(); j++)
{
BOOST_REQUIRE_EQUAL(vec1[i][j], vec2[i][j]);
}
}
}
/**
* Load a CSV file into a vector of vector with a templated datatype. Any ','
* characters are stripped from the input; lines are split on '\n' and elements
* of each line are split on spaces.
*
* @param filename Name of the file to load.
*/
template<typename T>
std::vector<std::vector<T>> ReadData(const std::string& filename)
{
std::ifstream ifs(filename);
std::vector<std::vector<T>> table;
std::string line;
while (std::getline(ifs, line))
{
std::vector<T> numbers;
T n;
std::replace(line.begin(), line.end(), ',', ' ');
std::istringstream stm(line);
while (stm >> n)
numbers.push_back(n);
table.push_back(numbers);
}
return table;
}
#endif
+73 -8
View File
@@ -19,7 +19,7 @@
#include <mlpack/methods/ann/loss_functions/mean_squared_error.hpp>
#include <mlpack/methods/reinforcement_learning/q_learning.hpp>
#include <mlpack/methods/reinforcement_learning/environment/mountain_car.hpp>
#include <mlpack/methods/reinforcement_learning/environment/acrobat.hpp>
#include <mlpack/methods/reinforcement_learning/environment/acrobot.hpp>
#include <mlpack/methods/reinforcement_learning/environment/cart_pole.hpp>
#include <mlpack/methods/reinforcement_learning/policy/greedy_policy.hpp>
#include <mlpack/methods/reinforcement_learning/training_config.hpp>
@@ -173,8 +173,8 @@ BOOST_AUTO_TEST_CASE(CartPoleWithDoubleDQN)
BOOST_REQUIRE(converged);
}
//! Test DQN in Acrobat task.
BOOST_AUTO_TEST_CASE(AcrobatWithDQN)
//! Test DQN in Acrobot task.
BOOST_AUTO_TEST_CASE(AcrobotWithDQN)
{
// We will allow three trials, although it would be very uncommon for the test
// to use more than one.
@@ -191,8 +191,8 @@ BOOST_AUTO_TEST_CASE(AcrobatWithDQN)
model.Add<Linear<>>(32, 3);
// Set up the policy and replay method.
GreedyPolicy<Acrobat> policy(1.0, 1000, 0.1);
RandomReplay<Acrobat> replayMethod(20, 10000);
GreedyPolicy<Acrobot> policy(1.0, 1000, 0.1);
RandomReplay<Acrobot> replayMethod(20, 10000);
TrainingConfig config;
config.StepSize() = 0.01;
@@ -203,7 +203,7 @@ BOOST_AUTO_TEST_CASE(AcrobatWithDQN)
config.StepLimit() = 400;
// Set up DQN agent.
QLearning<Acrobat, decltype(model), AdamUpdate, decltype(policy)>
QLearning<Acrobot, decltype(model), AdamUpdate, decltype(policy)>
agent(std::move(config), std::move(model), std::move(policy),
std::move(replayMethod));
@@ -218,13 +218,13 @@ BOOST_AUTO_TEST_CASE(AcrobatWithDQN)
if (episodes > 1000)
{
Log::Debug << "Acrobat with DQN failed." << std::endl;
Log::Debug << "Acrobot with DQN failed." << std::endl;
converged = false;
break;
}
/**
* I am using a thresold of -380 to check convegence.
* I am using a threshold of -380 to check convergence.
*/
Log::Debug << "Average return: " << averageReturn.mean()
<< " Episode return: " << episodeReturn << std::endl;
@@ -251,4 +251,69 @@ BOOST_AUTO_TEST_CASE(AcrobatWithDQN)
BOOST_REQUIRE_EQUAL(success, true);
}
//! Test DQN in Mountain Car task.
BOOST_AUTO_TEST_CASE(MountainCarWithDQN)
{
// Set up the network.
FFN<MeanSquaredError<>, GaussianInitialization> model(MeanSquaredError<>(),
GaussianInitialization(0, 0.001));
model.Add<Linear<>>(2, 64);
model.Add<ReLULayer<>>();
model.Add<Linear<>>(64, 32);
model.Add<ReLULayer<>>();
model.Add<Linear<>>(32, 3);
// Set up the policy and replay method.
GreedyPolicy<MountainCar> policy(1.0, 1000, 0.1);
RandomReplay<MountainCar> replayMethod(20, 10000);
TrainingConfig config;
config.StepSize() = 0.0001;
config.Discount() = 0.9;
config.TargetNetworkSyncInterval() = 100;
config.ExplorationSteps() = 100;
config.DoubleQLearning() = false;
config.StepLimit() = 400;
// Set up DQN agent.
QLearning<MountainCar, decltype(model), AdamUpdate, decltype(policy)>
agent(std::move(config), std::move(model), std::move(policy),
std::move(replayMethod));
arma::running_stat<double> averageReturn;
size_t episodes = 0;
bool converged = true;
while (true)
{
double episodeReturn = agent.Episode();
averageReturn(episodeReturn);
episodes += 1;
if (episodes > 1000)
{
Log::Debug << "Mountain Car with DQN failed." << std::endl;
converged = false;
break;
}
/**
* Set a threshold of -370 to check convergence.
*/
Log::Debug << "Average return: " << averageReturn.mean()
<< " Episode return: " << episodeReturn << std::endl;
if (averageReturn.mean() > -370)
{
agent.Deterministic() = true;
arma::running_stat<double> testReturn;
for (size_t i = 0; i < 10; ++i)
testReturn(agent.Episode());
Log::Debug << "Average return in deterministic test: "
<< testReturn.mean() << std::endl;
break;
}
}
BOOST_REQUIRE(converged);
}
BOOST_AUTO_TEST_SUITE_END();
+2 -2
View File
@@ -238,7 +238,7 @@ BOOST_AUTO_TEST_CASE(UnweightedCategoricalLearningTest)
size_t rfCorrect = arma::accu(rfPredictions == testLabels);
size_t dtCorrect = arma::accu(dtPredictions == testLabels);
BOOST_REQUIRE_GE(rfCorrect, dtCorrect - 30);
BOOST_REQUIRE_GE(rfCorrect, dtCorrect - 50);
BOOST_REQUIRE_GE(rfCorrect, size_t(0.7 * testData.n_cols));
}
@@ -295,7 +295,7 @@ BOOST_AUTO_TEST_CASE(WeightedCategoricalLearningTest)
size_t rfCorrect = arma::accu(rfPredictions == testLabels);
size_t dtCorrect = arma::accu(dtPredictions == testLabels);
BOOST_REQUIRE_GE(rfCorrect, dtCorrect - 30);
BOOST_REQUIRE_GE(rfCorrect, dtCorrect - 50);
BOOST_REQUIRE_GE(rfCorrect, size_t(0.7 * testData.n_cols));
}
+9 -9
View File
@@ -15,7 +15,7 @@
#include <mlpack/methods/reinforcement_learning/environment/mountain_car.hpp>
#include <mlpack/methods/reinforcement_learning/environment/continuous_mountain_car.hpp>
#include <mlpack/methods/reinforcement_learning/environment/cart_pole.hpp>
#include <mlpack/methods/reinforcement_learning/environment/acrobat.hpp>
#include <mlpack/methods/reinforcement_learning/environment/acrobot.hpp>
#include <mlpack/methods/reinforcement_learning/environment/pendulum.hpp>
#include <mlpack/methods/reinforcement_learning/environment/reward_clipping.hpp>
@@ -54,8 +54,8 @@ BOOST_AUTO_TEST_CASE(ClippedRewardTest)
BOOST_REQUIRE(reward >= -2.0);
}
//! Test DQN in Acrobat task.
BOOST_AUTO_TEST_CASE(RewardClippedAcrobatWithDQN)
//! Test DQN in Acrobot task.
BOOST_AUTO_TEST_CASE(RewardClippedAcrobotWithDQN)
{
// Set up the network.
FFN<MeanSquaredError<>, GaussianInitialization> model(MeanSquaredError<>(),
@@ -67,12 +67,12 @@ BOOST_AUTO_TEST_CASE(RewardClippedAcrobatWithDQN)
model.Add<Linear<>>(32, 3);
// Set up the policy and replay method.
GreedyPolicy<RewardClipping<Acrobat>> policy(1.0, 1000, 0.1);
RandomReplay<RewardClipping<Acrobat>> replayMethod(20, 10000);
GreedyPolicy<RewardClipping<Acrobot>> policy(1.0, 1000, 0.1);
RandomReplay<RewardClipping<Acrobot>> replayMethod(20, 10000);
// Set up Acrobat task and reward clipping wrapper
Acrobat task;
RewardClipping<Acrobat> rewardClipping(task, -2.0, +2.0);
// Set up Acrobot task and reward clipping wrapper
Acrobot task;
RewardClipping<Acrobot> rewardClipping(task, -2.0, +2.0);
// Set up update rule
AdamUpdate update;
@@ -102,7 +102,7 @@ BOOST_AUTO_TEST_CASE(RewardClippedAcrobatWithDQN)
if (episodes > 1000)
{
Log::Debug << "Acrobat with DQN failed." << std::endl;
Log::Debug << "Acrobot with DQN failed." << std::endl;
converged = false;
break;
}
+7 -7
View File
@@ -16,7 +16,7 @@
#include <mlpack/methods/reinforcement_learning/environment/mountain_car.hpp>
#include <mlpack/methods/reinforcement_learning/environment/continuous_mountain_car.hpp>
#include <mlpack/methods/reinforcement_learning/environment/cart_pole.hpp>
#include <mlpack/methods/reinforcement_learning/environment/acrobat.hpp>
#include <mlpack/methods/reinforcement_learning/environment/acrobot.hpp>
#include <mlpack/methods/reinforcement_learning/environment/pendulum.hpp>
#include <mlpack/methods/reinforcement_learning/replay/random_replay.hpp>
#include <mlpack/methods/reinforcement_learning/policy/greedy_policy.hpp>
@@ -68,20 +68,20 @@ BOOST_AUTO_TEST_CASE(SimpleContinuousMountainCarTest)
}
/**
* Constructs a Acrobat instance and check if the main rountine works as
* Constructs a Acrobot instance and check if the main rountine works as
* it should be.
*/
BOOST_AUTO_TEST_CASE(SimpleAcrobatTest)
BOOST_AUTO_TEST_CASE(SimpleAcrobotTest)
{
const Acrobat task = Acrobat();
const Acrobot task = Acrobot();
Acrobat::State state = task.InitialSample();
Acrobat::Action action = Acrobat::Action::negativeTorque;
Acrobot::State state = task.InitialSample();
Acrobot::Action action = Acrobot::Action::negativeTorque;
double reward = task.Sample(state, action);
BOOST_REQUIRE_EQUAL(reward, -1.0);
BOOST_REQUIRE(!task.IsTerminal(state));
BOOST_REQUIRE_EQUAL(3, Acrobat::Action::size);
BOOST_REQUIRE_EQUAL(3, Acrobot::Action::size);
}
/**