Merge pull request #3603 from rcurtin/main-docs

Add main mlpack documentation framework
This commit is contained in:
Ryan Curtin
2024-02-22 09:59:04 -05:00
committed by GitHub
12 changed files with 1920 additions and 240 deletions
+52
View File
@@ -0,0 +1,52 @@
# Citation details
If you use mlpack in your research or software, please cite mlpack using the
citation for
[the paper below](https://joss.theoj.org/papers/10.21105/joss.05026):
* ***mlpack 4: a fast, header-only C++ machine learning library***. R.R.
Curtin, M. Edel, O. Shrit, S. Agrawal, S. Basak, J.J. Balamuta, R.
Birmingham, K. Dutt, D. Eddelbuettel, R. Garg, S. Jaiswal, A. Kaushik, S.
Kim, A. Mukherjee, N.G. Sai, N. Sharma, Y.S. Parihar, R. Swain, C. Sanderson.
_Journal of Open Source Software_ 8:82, p. 5026, 2023.
In BibTeX format:
```
@article{mlpack2023,
title = {mlpack 4: a fast, header-only C++ machine learning library},
author = {Ryan R. Curtin and Marcus Edel and Omar Shrit and
Shubham Agrawal and Suryoday Basak and James J. Balamuta and
Ryan Birmingham and Kartik Dutt and Dirk Eddelbuettel and
Rishabh Garg and Shikhar Jaiswal and Aakash Kaushik and
Sangyeon Kim and Anjishnu Mukherjee and Nanubala Gnana Sai and
Nippun Sharma and Yashwant Singh Parihar and Roshan Swain and
Conrad Sanderson},
journal = {Journal of Open Source Software},
volume = {8},
number = {82},
pages = {5026},
year = {2023},
doi = {10.21105/joss.05026},
url = {https://doi.org/10.21105/joss.05026}
}
```
Citations are beneficial for the growth and improvement of mlpack.
### Older papers
See also the following older papers concerning mlpack, its community, and its
internal design.
* [mlpack 3: a fast, flexible machine learning
library](https://joss.theoj.org/papers/10.21105/joss.00726) (2018)
* [mlpack open-source machine learning library and
community](http://kurg.org/pub/pdf/2018mlossmlpack.pdf) (2018)
* [Designing and building the mlpack open-source machine learning
library](https://arxiv.org/abs/1708.05279) (2017)
* [mlpack: a scalable C++ machine learning
library](https://www.jmlr.org/papers/volume14/curtin13a/curtin13a.pdf) (2013)
-25
View File
@@ -1,25 +0,0 @@
# mlpack versions in code
mlpack provides a couple of convenience macros and functions to get the version
of mlpack. More information (and straightforward code) can be found in
`src/mlpack/core/util/version.hpp`.
The following three macros provide major, minor, and patch versions of mlpack
(i.e. for `mlpack-x.y.z`, `x` is the major version, `y` is the minor version,
and `z` is the patch version):
```c++
MLPACK_VERSION_MAJOR
MLPACK_VERSION_MINOR
MLPACK_VERSION_PATCH
```
In addition, the function `mlpack::util::GetVersion()` returns the mlpack
version as a string (for instance, `"mlpack 1.0.8"`).
## mlpack command-line program versions
Each mlpack command-line program supports the `--version` (or `-V`) option,
which will print the version of mlpack used. If the version is not an official
release but instead from git, the version will be `mlpack git` (and will have a
git revision SHA appended to `git`).
+156
View File
@@ -0,0 +1,156 @@
# Documentation for mlpack
## A fast, flexible machine learning library
mlpack is an intuitive, fast, and flexible header-only C++ machine learning
library with bindings to other languages. It aims to provide fast, lightweight
implementations of both common and cutting-edge machine learning algorithms.
mlpack's lightweight C++ implementation makes it ideal for deployment, and it
can also be used for interactive prototyping via C++ notebooks (these can be
seen in action on mlpack's [homepage](https://www.mlpack.org/)).
In addition to its [powerful C++ interface](quickstart/cpp.md), mlpack also
provides [command-line programs](quickstart/cli.md), and bindings to the
[Python](quickstart/python.md), [R](quickstart/r.md),
[Julia](quickstart/julia.md), and [Go](quickstart/go.md) languages.
_If you use mlpack, please [cite the software](citation.md)._
## mlpack basics
Installing mlpack can be done using the
[instructions in the README](README.md#3-installing-and-using-mlpack-in-c);
or the [Windows build guide](user/build_windows.md).
The following basic guides are *highly recommended* before using mlpack.
* ***First steps***:
- [mlpack C++ quickstart](quickstart/cpp.md): create a couple simple C++
programs that use mlpack
- [Sample Windows mlpack C++ application](user/sample_ml_app.md): create a
working mlpack Windows program using Visual Studio
* ***Basics of matrices and data in mlpack***:
- [Matrices and data in mlpack](user/matrices.md)
- [Loading and saving mlpack objects](user/load_save.md)
* ***Reference for mlpack core classes***:
- [Core mlpack documentation](user/core.md)
* ***Using mlpack natively with our extensions in Python, R, CLI, Julia, and Go***:
- [Links to quickstarts and references](#bindings-to-other-languages)
## mlpack algorithm documentation
Documentation for each machine learning algorithm that mlpack implements is
detailed in the sections below.
* [Classification algorithms](#classification-algorithms): classify points as
discrete labels (`0`, `1`, `2`, ...).
* [Regression algorithms](#regression-algorithms): predict continuous values.
* [Clustering algorithms](#clustering-algorithms): group points into clusters.
* [Geometric algorithms](#geometric-algorithms): computations based on distance
metrics (nearest neighbors, kernel density estimation, etc.).
* [Preprocessing utilities](#preprocessing-utilities): prepare data for machine
learning algorithms.
* [Transformations](#transformations): transform data from one space to
another (principal components analysis, etc.).
* [Modeling utilities](#modeling-utilities): cross-validation, hyperparameter
tuning, etc.
### Classification algorithms
Classify points as discrete labels (`0`, `1`, `2`, ...).
* [`AdaBoost`](user/methods/adaboost.md): Adaptive Boosting
* [`DecisionTree`](user/methods/decision_tree.md): ID3-style decision tree
classifier
* [`LogisticRegression`](user/methods/logistic_regression.md): L2-regularized
logistic regression (two-class only)
* [`NaiveBayesClassifier`](user/methods/naive_bayes_classifier.md): simple
multi-class naive Bayes classifier
* [`Perceptron`](user/methods/perceptron.md): simple Perceptron classifier
* [`SoftmaxRegression`](user/methods/softmax_regression.md): L2-regularized
softmax regression (i.e. multi-class logistic regression)
### Regression algorithms
Predict continuous values.
* [`DecisionTreeRegressor`](user/methods/decision_tree_regressor.md): ID3-style
decision tree regressor
* [`LARS`](user/methods/lars.md): Least Angle Regression (LARS), L1-regularized
and L2-regularized
* [`LinearRegression`](user/methods/linear_regression.md): L2-regularized
linear regression (ridge regression)
### Clustering algorithms
Group points into clusters.
<!-- TODO: add some -->
### Geometric algorithms
Computations based on distance metrics.
<!-- TODO: add some -->
### Preprocessing utilities
Prepare data for machine learning algorithms.
<!-- TODO: add some -->
### Transformations
Transform data from one space to another.
<!-- TODO: add some -->
### Modeling utilities
Cross-validation, hyperparameter tuning, etc.
<!-- TODO: add some -->
## Bindings to other languages
mlpack's bindings to other languages have less complete functionality than
mlpack in C++, but almost all the same algorithms are available.
| ***Python*** | -- | [quickstart](quickstart/python.md) | -- | [reference](https://www.mlpack.org/doc/python_documentation.html) |
| ***Julia*** | -- | [quickstart](quickstart/julia.md) | -- | [reference](https://www.mlpack.org/doc/julia_documentation.html) |
| ***R*** | -- | [quickstart](quickstart/r.md) | -- | [reference](https://www.mlpack.org/doc/r_documentation.html)
| ***Command-line programs*** | -- | [quickstart](quickstart/cli.md) | -- | [reference](https://www.mlpack.org/doc/cli_documentation.html) |
| ***Go*** | -- | [quickstart](quickstart/go.md) | -- | [reference](https://www.mlpack.org/doc/go_documentation.html) |
## Examples and further documentation
* [mlpack examples repository](https://github.com/mlpack/examples/): numerous
fully-working example applications of mlpack, in C++ and other languages.
* [mlpack models repository](https://github.com/mlpack/models/): complex models
in C++ built with mlpack
For additional documentation beyond what is covered in all the resources above,
the source code should be consulted. Each method is fully documented.
## Developer documentation
Throughout the codebase, mlpack uses some common template parameter policies.
These are documented below.
* [The `ElemType` policy](developer/elemtype.md): element types for data
* [The `MetricType` policy](developer/metrics.md): distance metrics
* [The `KernelType` policy](developer/kernels.md): kernel functions
* [The `TreeType` policy](developer/trees.md): space trees (ball trees,
KD-trees, etc.)
In addition, the following documentation may be useful when developing bindings
for other languages:
* [Timers](developer/timer.md): timing parts of bindings
* [Writing an mlpack binding](developer/iodoc.md): simple examples of mlpack
bindings
* [Automatic bindings](developer/bindings.md): details on mlpack's automatic
binding generator system.
+598
View File
@@ -0,0 +1,598 @@
# mlpack core class documentation
Underlying the implementations of [mlpack's machine learning
algorithms](index.md#mlpack-algorithm-documentation) are mlpack core support
classes, each of which are documented on this page.
* [Core math utilities](#core-math-utilities): utility classes for mathematical
purposes
* [Distributions](#distributions): probability distributions
* [Metrics](#metrics): distance metrics for geometric algorithms
* [Kernels](#kernels): Mercer kernels for kernel-based algorithms
## Core math utilities
Utilities in the `mlpack::math::` namespace are meant to provide additional
mathematical support on top of Armadillo.
* [`math::Range`](#mathrange): simple mathematical range (i.e. `[0, 3]`)
---
### `math::Range`
The `math::Range` class represents a simple mathematical range (i.e. `[0, 3]`),
with the bounds represented as `double`s.
---
#### Constructors
* `r = math::Range()`
- Construct an empty range.
* `r = math::Range(p)`
- Construct the range `[p, p]`.
* `r = math::Range(lo, hi)`
- Construct the range `[lo, hi]`.
---
#### Accessing and modifying range properties
* `r.Lo()` and `r.Hi()` return the lower and upper bounds of the range as
`double`s.
- A range is considered empty if `r.Lo() > r.Hi()`.
- These can be used to modify the bounds, e.g., `r.Lo() = 3.0`.
* `r.Width()` returns the span of the range (i.e. `r.Hi() - r.Lo()`) as a
`double`.
* `r.Mid()` returns the midpoint of the range as a `double`.
---
#### Working with ranges
* Given two ranges `r1` and `r2`,
- `r1 | r2` returns the union of the ranges,
- `r1 |= r2` expands `r1` to include the range `r2`,
- `r1 & r2` returns the intersection of the ranges (possibly an empty range),
- `r1 &= r2` shrinks `r1` to the intersection of `r1` and `r2`,
- `r1 == r2` returns `true` if the two ranges are strictly equal (i.e. lower
and upper bounds are equal),
- `r1 != r2` returns `true` if the two ranges are not strictly equal,
- `r1 < r2` returns `true` if `r1.Hi() < r2.Lo()`,
- `r1 > r2` returns `true` if `r1.Lo() > r2.Hi()`, and
- `r1.Contains(r2)` returns `true` if the ranges overlap at all.
* Given a range `r` and a `double` scalar `d`,
- `r * d` returns a new range `[d * r.Lo(), d * r.Hi()]`,
- `r *= d` scales `r.Lo()` and `r.Hi()` by `d`, and
- `r.Contains(d)` returns `true` if `d` is contained in the range.
---
* To use ranges with different element types (e.g. `float`), use the type
`math::RangeType<float>` or similar.
---
Example:
```c++
mlpack::math::Range r1(5.0, 6.0); // [5, 6]
mlpack::math::Range r2(7.0, 8.0); // [7, 8]
mlpack::math::Range r3 = r1 | r2; // [5, 8]
mlpack::math::Range r4 = r1 & r2; // empty range
bool b1 = r1.Contains(r2); // false
bool b2 = r1.Contains(5.5); // true
bool b3 = r1.Contains(r3); // true
bool b4 = r3.Contains(r4); // false
// Create a range of `float`s and a range of `int`s.
mlpack::math::RangeType<float> r5(1.0f, 1.5f); // [1.0, 1.5]
mlpack::math::RangeType<int> r6(3, 4); // [3, 4]
```
---
`math::Range` is used by:
* [`RangeSearch`](range_search.md)
* [mlpack trees](#trees)
---
## Distributions
mlpack has support for a number of different distributions, each supporting the
same API. These can be used with, for instance, the [`HMM`](hmm.md) class.
* [`DiscreteDistribution`](#discretedistribution): multidimensional categorical
distribution (generalized Bernoulli distribution)
* [`GaussianDistribution`](#gaussiandistribution): multidimensional Gaussian
distribution
### `DiscreteDistribution`
`DiscreteDistribution` represents a multidimensional categorical distribution
(or generalized Bernoulli distribution) where integer-valued vectors (e.g.
`[0, 3, 4]`) are associated with specific probabilities in each dimension.
*Example:* a 3-dimensional `DiscreteDistribution` will have a specific
probability value associated with each integer value in each dimension. So, for
the vector `[0, 3, 4]`, `P(0)` in dimension 0 could be, e.g., `0.3`, `P(3)` in
dimension 1 could be, e.g., `0.4`, and `P(4)` in dimension 2 could be, e.g.,
`0.6`. Then, `P([0, 3, 4])` would be `0.3 * 0.4 * 0.6 = 0.072`.
---
#### Constructors
* `d = DiscreteDistribution(numObservations)`
- Create a one-dimensional discrete distribution with `numObservations`
different observations in the one and only dimension. `numObservations` is
of type `size_t`.
* `d = DiscreteDistribution(numObservationsVec)`
- Create a multidimensional discrete distribution with
`numObservationsVec.n_elem` dimensions and `numObservationsVec[i]`
different observations in dimension `i`.
- `numObservationsVec` is of type `arma::Col<size_t>`.
* `d = DiscreteDistribution(probabilities)`
- Create a multidimensional discrete distribution with the given
probabilities.
- `probabilities` should have type `std::vector<arma::vec>`, and
`probabilities.size()` should be equal to the dimensionality of the
distribution.
- `probabilities[i]` is a vector such that `probabilities[i][j]` contains the
probability of `j` in dimension `i`.
---
#### Access and modify properties of distribution
* `d.Dimensionality()` returns a `size_t` indicating the number of dimensions
in the multidimensional discrete distribution.
* `d.Probabilities(i)` returns an `arma::vec&` containing the probabilities of
each observation in dimension `i`.
- `d.Probabilities(i)[j]` is the probability of `j` in dimension `i`.
- This can be used to modify probabilities: `d.Probabilities(0)[1] = 0.7`
sets the probability of observing the value `1` in dimension `0` to `0.7`.
- *Note:* when setting probabilities manually, be sure that the sum of
probabilities in a dimension is 1!
---
#### Compute probabilities of points
* `d.Probability(observation)` returns the probability of the given
observation as a `double`.
- `observation` should be an `arma::vec` of size `d.Dimensionality()`.
- `observation[i]` should take integer values between `0` and
`d.Probabilities(i).n_elem - 1`.
* `d.Probability(observations, probabilities)` computes the probabilities of
many observations.
- `observations` should be an `arma::mat` with number of rows equal to
`d.Dimensionality()`; `observations.n_cols` is the number of observations.
- `probabilities` will be set to size `observations.n_cols`.
- `probabilities[i]` will be set to `d.Probability(observations.col(i))`.
* `d.LogProbability(observation)` returns the log-probability of the given
observation as a `double`.
* `d.LogProbability(observations, probabilities)` computes the
log-probabilities of many observations.
---
#### Sample from the distribution
* `d.Random()` returns an `arma::vec` with a random sample from the
multidimensional discrete distribution.
---
#### Fit the distribution to observations
* `d.Train(observations)`
- Fit the distribution to the given observations.
- `observations` should be an `arma::mat` with number of rows equal to
`d.Dimensionality()`; `observations.n_cols` is the number of observations.
- `observations(j, i)` should be an integer value between `0` and the number
of observations for dimension `i`.
* `d.Train(observations, observationProbabilities)`
- Fit the distribution to the given observations, as above, but also provide
probabilities that each observation is from this distribution.
- `observationProbabilities` should be an `arma::vec` of length
`observations.n_cols`.
- `observationProbabilities[i]` should be equal to the probability that
`observations.col(i)` is from `d`.
---
*Example usage:*
```c++
// Create a single-dimension Bernoulli distribution: P([0]) = 0.3, P([1]) = 0.7.
mlpack::DiscreteDistribution bernoulli(2);
bernoulli.Probabilities(0)[0] = 0.3;
bernoulli.Probabilities(0)[1] = 0.7;
const double p1 = bernoulli.Probability(arma::vec("0")); // p1 = 0.3.
const double p2 = bernoulli.Probability(arma::vec("1")); // p2 = 0.7.
// Create a 3-dimensional discrete distribution by specifying the probabilities
// manually.
arma::vec probDim0 = arma::vec("0.1 0.3 0.5 0.1"); // 4 possible values.
arma::vec probDim1 = arma::vec("0.7 0.3"); // 2 possible values.
arma::vec probDim2 = arma::vec("0.4 0.4 0.2"); // 3 possible values.
std::vector<arma::vec> probs { probDim0, probDim1, probDim2 };
mlpack::DiscreteDistribution d(probs);
arma::vec obs("2 0 1");
const double p3 = d.Probability(obs); // p3 = 0.5 * 0.7 * 0.4 = 0.14.
// Estimate a 10-dimensional discrete distribution.
// Each dimension takes values between 0 and 9.
arma::mat observations = arma::randi<arma::mat>(10, 1000,
arma::distr_param(0, 9));
// Create a distribution with 10 observations in each of the 10 dimensions.
mlpack::DiscreteDistribution d2(
arma::Col<size_t>("10 10 10 10 10 10 10 10 10 10"));
d2.Train(observations);
// Compute the probabilities of each point.
arma::vec probabilities;
d2.Probability(observations, probabilities);
std::cout << "Average probability: " << arma::mean(probabilities) << "."
<< std::endl;
```
---
### `GaussianDistribution`
`GaussianDistribution` is a standard multivariate Gaussian distribution with
parameterized mean and covariance.
---
#### Constructors
* `g = GaussianDistribution(dimensionality)`
- Create the distribution with the given dimensionality.
- The distribution will have a zero mean and unit diagonal covariance matrix.
* `g = GaussianDistribution(mean, covariance)`
- Create the distribution with the given mean and covariance.
- `mean` is of type `arma::vec` and should have length equal to the
dimensionality of the distribution.
- `covariance` is of type `arma::mat`, and should be symmetric and square,
with rows and columns equal to the dimensionality of the distribution.
---
#### Access and modify properties of distribution
* `g.Dimensionality()` returns the dimensionality of the distribution as a
`size_t`.
* `g.Mean()` returns an `arma::vec&` holding the mean of the distribution.
This can be modified.
* `g.Covariance()` returns a `const arma::mat&` holding the covariance of the
distribution. To set a new covariance, use `g.Covariance(newCov)` or
`g.Covariance(std::move(newCov))`.
* `g.InvCov()` returns a `const arma::mat&` holding the precomputed inverse of
the covariance.
* `g.LogDetCov()` returns a `double` holding the log-determinant of the
covariance.
---
#### Compute probabilities of points
* `g.Probability(observation)` returns the probability of the given
observation as a `double`.
- `observation` should be an `arma::vec` of size `d.Dimensionality()`.
* `g.Probability(observations, probabilities)` computes the probabilities of
many observations.
- `observations` should be an `arma::mat` with number of rows equal to
`d.Dimensionality()`; `observations.n_cols` is the number of observations.
- `probabilities` will be set to size `observations.n_cols`.
- `probabilities[i]` will be set to `g.Probability(observations.col(i))`.
* `g.LogProbability(observation)` returns the log-probability of the given
observation as a `double`.
* `g.LogProbability(observations, probabilities)` computes the
log-probabilities of many observations.
---
#### Sample from the distribution
* `g.Random()` returns an `arma::vec` with a random sample from the
multidimensional discrete distribution.
---
#### Fit the distribution to observations
* `g.Train(observations)`
- Fit the distribution to the given observations.
- `observations` should be an `arma::mat` with number of rows equal to
`d.Dimensionality()`; `observations.n_cols` is the number of observations.
* `g.Train(observations, observationProbabilities)`
- Fit the distribution to the given observations, as above, but also provide
probabilities that each observation is from this distribution.
- `observationProbabilities` should be an `arma::vec` of length
`observations.n_cols`.
- `observationProbabilities[i]` should be equal to the probability that
`observations.col(i)` is from `d`.
---
*Example usage:*
```c++
// Create a Gaussian distribution in 3 dimensions with zero mean and unit
// covariance.
mlpack::GaussianDistribution g(3);
// Compute the probability of the point [0, 0.5, 0.25].
const double p = g.Probability(arma::vec("0 0.5 0.25"));
// Modify the mean in dimension 0.
g.Mean()[0] = 0.5;
// Set a random covariance.
arma::mat newCov(3, 3, arma::fill::randu);
newCov *= newCov.t(); // Ensure covariance is positive semidefinite.
g.Covariance(std::move(newCov)); // Set new covariance.
// Compute the probability of the same point [0, 0.5, 0.25].
const double p2 = g.Probability(arma::vec("0 0.5 0.25"));
// Create a Gaussian distribution that is estimated from random samples in 50
// dimensions.
arma::mat samples(50, 10000, arma::fill::randn); // Normally distributed.
mlpack::GaussianDistribution g2(50);
g2.Train(samples);
// Compute the probability of all of the samples.
arma::vec probabilities;
g2.Probability(samples, probabilities);
std::cout << "Average probability is: " << arma::mean(probabilities) << "."
<< std::endl;
```
## Metrics
mlpack includes a number of distance metrics for its distance-based techniques.
These all implement the [same API](../developer/metrics.md), providing one
`Evaluate()` method, and can be used with a variety of different techniques,
including:
<!-- TODO: better names for each link -->
* [`NeighborSearch`](neighbor_search.md)
* [`RangeSearch`](range_search.md)
* [`LMNN`](lmnn.md)
* [`EMST`](emst.md)
* [`NCA`](nca.md)
* [`RANN`](rann.md)
* [`KMeans`](kmeans.md)
Supported metrics:
* [`LMetric`](#lmetric): generalized L-metric/Lp-metric, including
Manhattan/Euclidean/Chebyshev distances
* [Implement a custom metric](../developer/metrics.md)
### `LMetric`
The `LMetric` template class implements a [generalized
L-metric](https://en.wikipedia.org/wiki/Lp_space#Definition)
(L1-metric, L2-metric, etc.). The class has two template parameters:
```c++
LMetric<Power, TakeRoot>
```
* `Power` is an `int` representing the type of the metric; e.g., `2` would
represent the L2-metric (Euclidean distance).
- `Power` must be `1` or greater.
- If `Power` is `INT_MAX`, the metric is the L-infinity distance (Chebyshev
distance).
* `TakeRoot` is a `bool` (default `true`) indicating whether the root of the
distance should be taken.
- If set to `false`, the metric will no longer satisfy the triangle
inequality.
---
Several convenient typedefs are available:
* `ManhattanDistance` (defined as `LMetric<1>`)
* `EuclideanDistance` (defined as `LMetric<2>`)
* `SquaredEuclideanDistance` (defined as `LMetric<2, false>`)
* `ChebyshevDistance` (defined as `LMetric<INT_MAX>`)
---
The static `Evaluate()` method can be used to compute the distance between two
vectors.
*Note:* The vectors given to `Evaluate()` can have any type so long as the type
implements the Armadillo API (e.g. `arma::fvec`, `arma::sp_fvec`, etc.).
---
*Example usage:*
```c++
// Create two vectors: [0, 1.0, 5.0] and [1.0, 3.0, 5.0].
arma::vec a("0.0 1.0 5.0");
arma::vec b("1.0 3.0 5.0");
const double d1 = mlpack::ManhattanDistance::Evaluate(a, b); // d1 = 3.0
const double d2 = mlpack::EuclideanDistance::Evaluate(a, b); // d2 = 2.24
const double d3 = mlpack::SquaredEuclideanDistance::Evaluate(a, b); // d3 = 5.0
const double d4 = mlpack::ChebyshevDistance::Evaluate(a, b); // d4 = 2.0
const double d5 = mlpack::LMetric<4>::Evaluate(a, b); // d5 = 2.03
const double d6 = mlpack::LMetric<3, false>::Evaluate(a, b); // d6 = 9.0
std::cout << "Manhattan distance: " << d1 << "." << std::endl;
std::cout << "Euclidean distance: " << d2 << "." << std::endl;
std::cout << "Squared Euclidean distance: " << d3 << "." << std::endl;
std::cout << "Chebyshev distance: " << d4 << "." << std::endl;
std::cout << "L4-distance: " << d5 << "." << std::endl;
std::cout << "Cubed L3-distance: " << d6 << "." << std::endl;
// Compute the distance between two random 10-dimensional vectors in a matrix.
arma::mat m(10, 100, arma::fill::randu);
const double d7 = mlpack::EuclideanDistance::Evaluate(m.col(0), m.col(7));
std::cout << std::endl;
std::cout << "Distance between two random vectors: " << d7 << "." << std::endl;
std::cout << std::endl;
// Compute the distance between two 32-bit precision `float` vectors.
arma::fvec fa("0.0 1.0 5.0");
arma::fvec fb("1.0 3.0 5.0");
const double d8 = mlpack::EuclideanDistance::Evaluate(fa, fb); // d8 = 2.236
std::cout << "Euclidean distance (fvec): " << d8 << "." << std::endl;
```
## Kernels
mlpack includes a number of Mercer kernels for its kernel-based techniques.
These all implement the [same API](../developer/kernels.md), providing one
`Evaluate()` method, and can be used with a variety of different techniques,
including:
<!-- TODO: better names for links below -->
* [`KDE`](kde.md)
* [`MeanShift`](mean_shift.md)
* [`KernelPCA`](kernel_pca.md)
* [`FastMKS`](fastmks.md)
* [`NystroemMethod`](nystroem_method.md)
Supported kernels:
* [`GaussianKernel`](#gaussiankernel): standard Gaussian/radial basis
function/RBF kernel
* [Implement a custom kernel](../developer/kernels.md)
### `GaussianKernel`
The `GaussianKernel` class implements the standard [Gaussian
kernel](https://en.wikipedia.org/wiki/Radial_basis_function_kernel) (also called
the _radial basis function kernel_ or _RBF kernel_).
The Gaussian kernel is defined as:
`k(x1, x2) = exp(-|| x1 - x2 ||^2 / (2 * bw^2))`
where `bw` is the bandwidth parameter of the kernel.
---
#### Constructors and properties
* `g = GaussianKernel(bw=1.0)`
- Create a `GaussianKernel` with the given bandwidth `bw`.
* `g.Bandwidth()` returns the bandwidth of the kernel as a `double`.
- To set the bandwidth, use `g.Bandwidth(newBandwidth)`.
---
#### Kernel evaluation
* `g.Evaluate(x1, x2)`
- Compute the kernel value between two vectors `x1` and `x2`.
- `x1` and `x2` should be vector types that implement the Armadillo API
(e.g., `arma::vec`).
* `g.Evaluate(distance)`
- Compute the kernel value between two vectors, given that the distance
between those two vectors (`distance`) is already known.
- `distance` should have type `double`.
---
#### Other utilities
* `g.Gradient(distance)`
- Compute the (one-dimensional) gradient of the kernel function with respect
to the distance between two points, evaluated at `distance`.
* `g.Normalizer(dimensionality)`
- Return the [normalizing
constant](https://en.wikipedia.org/wiki/Radial_basis_function_kernel) of
the Gaussian kernel for points in the given dimensionality as a `double`.
---
*Example usage:*
```c++
// Create a Gaussian kernel with default bandwidth.
mlpack::GaussianKernel g;
// Create a Gaussian kernel with bandwidth 5.0.
mlpack::GaussianKernel g2(5.0);
// Evaluate the kernel value between two 3-dimensional points.
arma::vec x1("0.5 1.0 1.5");
arma::vec x2("1.5 1.0 0.5");
const double k1 = g.Evaluate(x1, x2);
const double k2 = g2.Evaluate(x1, x2);
std::cout << "Kernel values: " << k1 << " (bw=1.0), " << k2 << " (bw=5.0)."
<< std::endl;
// Evaluate the kernel value when the distance between two points is already
// computed.
const double distance = 1.5;
const double k3 = g.Evaluate(distance);
// Change the bandwidth of the kernel to 2.5.
g.Bandwidth(2.5);
const double k4 = g.Evaluate(x1, x2);
std::cout << "Kernel value with bw=2.5: " << k4 << "." << std::endl;
// Evaluate the kernel value between x1 and all points in a random matrix.
arma::mat r(3, 100, arma::fill::randu);
arma::vec kernelValues(100);
for (size_t i = 0; i < r.n_cols; ++i)
kernelValues[i] = g.Evaluate(x1, r.col(i));
std::cout << "Average kernel value for random points: "
<< arma::mean(kernelValues) << "." << std::endl;
// Compute the kernel value between two 32-bit floating-point vectors.
arma::fvec fx1("0.5 1.0 1.5");
arma::fvec fx2("1.5 1.0 0.5");
const double k5 = g.Evaluate(fx1, fx2);
const double k6 = g2.Evaluate(fx1, fx2);
```
+758
View File
@@ -0,0 +1,758 @@
# Loading and saving mlpack objects
mlpack provides the `data::Load()` and `data::Save()` functions to load and save
[Armadillo matrices](matrices.md) (e.g. numeric and categorical datasets) and
any mlpack object via the [cereal](https://uscilab.github.io/cereal/)
serialization toolkit. A number of other utilities related to loading and
saving data and objects are also available.
* [Numeric data](#numeric-data)
* [Mixed categorical data](#mixed-categorical-data)
- [`data::DatasetInfo`](#datadatasetinfo)
- [Loading categorical data](#loading-categorical-data)
* [Image data](#image-data)
- [`data::ImageInfo`](#dataimageinfo)
- [Loading images](#loading-images)
* [mlpack objects](#mlpack-objects): load or save any mlpack object
* [Normalizing labels](#normalizing-labels): convert labels to ranges required
by mlpack classifiers
* [Formats](#formats): supported formats for each load/save variant
## Numeric data
Numeric data or general numeric matrices can be loaded or saved with the
following functions.
- `data::Load(filename, matrix, fatal=false, transpose=true, format=FileType::AutoDetect)`
- `data::Save(filename, matrix, fatal=false, transpose=true, format=FileType::AutoDetect)`
* `filename` is a `std::string` with a path to the file to be loaded.
* By default the format is auto-detected based on the file extension, but can
be explicitly specified with `format`; see [Formats](#formats).
* `matrix` is an `arma::mat&`, `arma::Mat<size_t>&`, or similar (e.g., a
reference to an Armadillo object that data will be loaded into or saved
from).
* If `fatal` is `true`, a `std::runtime_error` will be thrown on failure.
* If `transpose` is `true`, then for plaintext formats (CSV/TSV/ASCII), the
matrix will be transposed on save. (Keep this `true` if you want a
column-major matrix to be saved with points as rows and dimensions as
columns; that is generally what is desired.)
* A `bool` is returned indicating whether the operation was successful.
---
Example usage:
```c++
// See https://datasets.mlpack.org/satellite.train.csv.
arma::mat dataset;
mlpack::data::Load("satellite.train.csv", dataset, true);
// See https://datasets.mlpack.org/satellite.train.labels.csv.
arma::Row<size_t> labels;
mlpack::data::Load("satellite.train.labels.csv", labels, true);
// Print information about the data.
std::cout << "The data in 'satellite.train.csv' has: " << std::endl;
std::cout << " - " << dataset.n_cols << " points." << std::endl;
std::cout << " - " << dataset.n_rows << " dimensions." << std::endl;
std::cout << "The labels in 'satellite.train.labels.csv' have: " << std::endl;
std::cout << " - " << labels.n_elem << " labels." << std::endl;
std::cout << " - A maximum label of " << labels.max() << "." << std::endl;
std::cout << " - A minimum label of " << labels.min() << "." << std::endl;
// Modify and save the data. Add 2 to the data and drop the last column.
dataset += 2;
dataset.shed_col(dataset.n_cols - 1);
labels.shed_col(labels.n_cols - 1);
mlpack::data::Save("satellite.train.mod.csv", dataset);
mlpack::data::Save("satellite.train.labels.mod.csv", labels);
```
---
## Mixed categorical data
Some mlpack techniques support mixed categorical data, e.g., data where some
dimensions take only categorical values (e.g. `0`, `1`, `2`, etc.). When using
mlpack, string data and other non-numerical data must be mapped to categorical
values and represented as part of an `arma::mat`. Category information is
stored in an auxiliary `data::DatasetInfo` object.
### `data::DatasetInfo`
<!-- TODO: also document in core.md? -->
mlpack represents categorical data via the use of the auxiliary
`data::DatasetInfo` object, which stores information about which dimensions are
numeric or categorical and allows conversion from the original category values
to the numeric values used to represent those categories.
---
#### Constructors
- `info = data::DatasetInfo()`
* Create an empty `data::DatasetInfo` object.
* Use this constructor if you intend to populate the `data::DatasetInfo` via
a `data::Load()` call.
- `info = data::DatasetInfo(dimensionality)`
* Create a `data::DatasetInfo` object with the given dimensionality
* All dimensions are assumed to be numeric (not categorical).
---
#### Accessing and setting properties
- `info.Type(d)`
* Get the type (categorical or numeric) of dimension `d`.
* Returns a `data::Datatype`, either `data::Datatype::numeric` or
`data::Datatype::categorical`.
* Calling `info.Type(d) = t` will set a dimension to type `t`, but this
should only be done before `info` is used with `data::Load()` or
`data::Save()`.
- `info.NumMappings(d)`
* Get the number of categories in dimension `d` as a `size_t`.
* Returns `0` if dimension `d` is numeric.
- `info.Dimensionality()`
* Return the dimensionality of the object as a `size_t`.
---
#### Map to and from numeric values
- `info.MapString<double>(value, d)`
* Given `value` (a `std::string`), return the `double` representing the
categorical mapping (an integer value) of `value` in dimension `d`.
* If a mapping for `value` does not exist in dimension `d`, a new mapping is
created, and `info.NumMappings(d)` is increased by one.
* If dimension `d` is numeric and `value` cannot be parsed as a numeric
value, then dimension `d` is changed to categorical and a new mapping is
returned.
- `info.UnmapString(mappedValue, d)`
* Given `mappedValue` (a `size_t`), return the `std::string` containing the
original category that mapped to the value `mappedValue` in dimension `d`.
* If dimension `d` is not categorical, a `std::invalid_argument` is thrown.
---
### Loading categorical data
With a `data::DatasetInfo` object, categorical data can be loaded:
- `data::Load(filename, matrix, info, fatal=false, transpose=true)`
* `filename` is a `std::string` with a path to the file to be loaded.
* The format is auto-detected based on the extension of the filename and the
contents of the file:
- `.csv`, `.tsv`, or `.txt` for CSV/TSV (tab-separated)/ASCII
(space-separated)
- `.arff` for [ARFF](https://www.cs.waikato.ac.nz/~ml/weka/arff.html)
* `matrix` is an `arma::mat&`, `arma::Mat<size_t>&`, or similar (e.g., a
reference to an Armadillo object that data will be loaded into or saved
from).
* `info` is a `data::DatasetInfo&` object. This will be populated with the
category information of the file when loading, and used to unmap values
when saving.
* If `fatal` is `true`, a `std::runtime_error` will be thrown on failure.
* If `transpose` is `true`, then for plaintext formats (CSV/TSV/ASCII), the
matrix will be transposed on save. (Keep this `true` if you want a
column-major matrix to be saved with points as rows and dimensions as
columns; that is generally what is desired.)
* A `bool` is returned indicating whether the operation was successful.
Saving should be performed with the [numeric](#numeric-data) `data::Load()`
variant.
---
Example usage to load and manipulate an ARFF file.
```c++
// Load a categorical dataset.
arma::mat dataset;
mlpack::data::DatasetInfo info;
// See https://datasets.mlpack.org/covertype.train.arff.
mlpack::data::Load("covertype.train.arff", dataset, info, true);
arma::Row<size_t> labels;
// See https://datasets.mlpack.org/covertype.train.labels.csv.
mlpack::data::Load("covertype.train.labels.csv", labels, true);
// Print information about the data.
std::cout << "The data in 'covertype.train.arff' has: " << std::endl;
std::cout << " - " << dataset.n_cols << " points." << std::endl;
std::cout << " - " << info.Dimensionality() << " dimensions." << std::endl;
// Print information about each dimension.
for (size_t d = 0; d < info.Dimensionality(); ++d)
{
if (info.Type(d) == mlpack::data::Datatype::categorical)
{
std::cout << " - Dimension " << d << " is categorical with "
<< info.NumMappings(d) << " categories." << std::endl;
}
else
{
std::cout << " - Dimension " << d << " is numeric." << std::endl;
}
}
// Modify the 5th point. Increment any numeric values, and set any categorical
// values to the string "hooray!".
for (size_t d = 0; d < info.Dimensionality(); ++d)
{
if (info.Type(d) == mlpack::data::Datatype::categorical)
{
// This will create a new mapping if the string "hooray!" does not already
// exist as a category for dimension d..
dataset(d, 4) = info.MapString<double>("hooray!", d);
}
else
{
dataset(d, 4) += 1.0;
}
}
```
---
Example usage to manually create a `data::DatasetInfo` object.
```c++
// This will manually create the following data matrix (shown as it would appear
// in a CSV):
//
// 1, TRUE, "good", 7.0, 4
// 2, FALSE, "good", 5.6, 3
// 3, FALSE, "bad", 6.1, 4
// 4, TRUE, "bad", 6.1, 1
// 5, TRUE, "unknown", 6.3, 0
// 6, FALSE, "unknown", 5.1, 2
//
// Although the last dimension is numeric, we will take it as a categorical
// dimension.
arma::mat dataset(5, 6); // 6 data points in 5 dimensions.
mlpack::data::DatasetInfo info(5);
// Set types of dimensions. By default they are numeric so we only set
// categorical dimensions.
info.Type(1) = mlpack::data::Datatype::categorical;
info.Type(2) = mlpack::data::Datatype::categorical;
info.Type(4) = mlpack::data::Datatype::categorical;
// The first dimension is numeric.
dataset(0, 0) = 1;
dataset(0, 1) = 2;
dataset(0, 2) = 3;
dataset(0, 3) = 4;
dataset(0, 4) = 5;
dataset(0, 5) = 6;
// The second dimension is categorical.
dataset(1, 0) = info.MapString<double>("TRUE", 1);
dataset(1, 1) = info.MapString<double>("FALSE", 1);
dataset(1, 2) = info.MapString<double>("FALSE", 1);
dataset(1, 3) = info.MapString<double>("TRUE", 1);
dataset(1, 4) = info.MapString<double>("TRUE", 1);
dataset(1, 5) = info.MapString<double>("FALSE", 1);
// The third dimension is categorical.
dataset(2, 0) = info.MapString<double>("good", 2);
dataset(2, 1) = info.MapString<double>("good", 2);
dataset(2, 2) = info.MapString<double>("bad", 2);
dataset(2, 3) = info.MapString<double>("bad", 2);
dataset(2, 4) = info.MapString<double>("unknown", 2);
dataset(2, 5) = info.MapString<double>("unknown", 2);
// The fourth dimension is numeric.
dataset(3, 0) = 7.0;
dataset(3, 1) = 5.6;
dataset(3, 2) = 6.1;
dataset(3, 3) = 6.1;
dataset(3, 4) = 6.3;
dataset(3, 5) = 5.1;
// The fifth dimension is categorical. Note that `info` will choose to assign
// category values in the order they are seen, even if the category can be
// parsed as a number. So, here, the value '4' will be assigned category '0',
// since it is seen first.
dataset(4, 0) = info.MapString<double>("4", 4);
dataset(4, 1) = info.MapString<double>("3", 4);
dataset(4, 2) = info.MapString<double>("4", 4);
dataset(4, 3) = info.MapString<double>("1", 4);
dataset(4, 4) = info.MapString<double>("0", 4);
dataset(4, 5) = info.MapString<double>("2", 4);
// Print the dataset with mapped categories.
dataset.print("Dataset with mapped categories");
// Print the mappings for the third dimension.
std::cout << "Mappings for dimension 3: " << std::endl;
for (size_t i = 0; i < info.NumMappings(2); ++i)
{
std::cout << " - \"" << info.UnmapString(i, 2) << "\" maps to " << i << "."
<< std::endl;
}
// Now `dataset` is ready for use with an mlpack algorithm that supports
// categorical data.
```
---
## Image data
If the STB image library is available on the system (`stb_image.h` and
`stb_image_write.h` must be available on the compiler's include search path),
then mlpack will define the `MLPACK_HAS_STB` macro, and support for loading
individual images or sets of images will be available.
Supported formats for loading are `jpg`, `png`, `tga`, `bmp`, `psd`, `gif`, `hdr`, `pic`, and `pnm`.
Supported formats for saving are `jpg`, `png`, `tga`, `bmp`, and `hdr`.
When loading images, each image is represented as a flattened single column
vector in a data matrix; each row of the resulting vector will correspond to a
single pixel value in a single channel. An auxiliary `data::ImageInfo` class is
used to store information about the images.
### `data::ImageInfo`
The `data::ImageInfo` class contains the metadata of the images.
---
#### Constructors
- `info = data::ImageInfo()`
* Create a `data::ImageInfo` object with no data.
* Use this constructor if you intend to populate the `data::ImageInfo` via a
`data::Load()` call.
- `info = data::ImageInfo(width, height, channels)`
* Create a `data::ImageInfo` object with the given image specifications.
* `width` and `height` are specified as pixels.
---
#### Accessing and modifying image metadata
- `info.Quality() = q` will set the compression quality (e.g. for saving JPEGs)
to `q`.
* `q` should take values between `0` and `100`.
* The quality value is ignored unless calling `data::Save()` with `info`.
- Calling `info.Channels() = 1` before loading will cause images to be loaded
in grayscale.
- Metadata stored in the `data::ImageInfo` can be accessed with the following
members:
* `info.Width()` returns the image width in pixels.
* `info.Height()` returns the image height in pixels.
* `info.Channels()` returns the number of color channels in the image.
* `info.Quality()` returns the compression quality that will be used to save
images (between 0 and 100).
---
### Loading images
With a `data::ImageInfo` object, image data can be loaded or saved, handling
either one or multiple images at a time:
<!-- TODO: add parameter to force use of what's in `info` -->
- `data::Load(filename, matrix, info, fatal=false)`
* Load a ***single image*** from `filename` into `matrix`.
- Format is chosen by extension (e.g. `image.png` will load as PNG).
* `matrix` will have one column representing the image as a flattened vector.
* `info` will be populated with information from the image in `filename`.
* If `fatal` is `true`, a `std::runtime_error` will be thrown upon load
failure.
* Returns a `bool` indicating the success of the operation.
---
- `data::Load(files, matrix, info, fatal=false)`
* Load ***multiple images*** from `files` into `matrix`.
- `files` is of type `std::vector<std::string>` and should contain the list
of images to be loaded.
- `matrix` will have `files.size()` columns, each representing the
corresponding image as a flattened vector.
* `info` will be populated with information from the images in `files`.
* If `fatal` is `true`, a `std::runtime_error` will be thrown if any files
fail to load.
* Returns a `bool` indicating the success of the operation.
---
- `data::Save(filename, matrix, info, fatal=false)`
* Save a ***single image*** from `matrix` into the file `filename`.
- Format is chosen by extension (e.g. `image.png` will save as PNG).
* `matrix` is expected to have only one column representing the image as a
flattened vector.
* If `fatal` is `true`, a `std::runtime_error` will be thrown in the event of
save failure.
* Returns a `bool` indicating the success of the operation.
---
- `data::Save(files, matrix, info, fatal=false)`
* Save ***multiple images*** from `matrix` into `files`.
- `files` is of type `std::vector<std::string>` and should contain the list
of files to save to.
- The format of each file is chosen by extension (e.g. `image.png` will
save as PNG); it is allowed for filenames in `files` to have different
extensions.
* `matrix` is expected to have `files.size()` columns representing images as
flattened vectors.
* If `fatal` is `true`, a `std::runtime_error` will be thrown if any images
fail to save.
* Returns a `bool` indicating the success of the operation.
---
Images are flattened along rows, with channel values interleaved, starting from
the top left. Thus, the value of the pixel at position `(x, y)` in channel `c`
will be contained in element/row `y * (width * channels) + x * (channels) + c`
of the flattened vector.
Pixels take values between 0 and 255.
---
Example of loading and saving a single image:
```c++
// See https://www.mlpack.org/static/img/numfocus-logo.png.
mlpack::data::ImageInfo info;
arma::mat matrix;
mlpack::data::Load("numfocus-logo.png", matrix, info, true);
// `matrix` should now contain one column.
// Print information about the image.
std::cout << "Information about the image in 'numfocus-logo.png': "
<< std::endl;
std::cout << " - " << info.Width() << " pixels in width." << std::endl;
std::cout << " - " << info.Height() << " pixels in height." << std::endl;
std::cout << " - " << info.Channels() << " color channels." << std::endl;
std::cout << "Value at pixel (x=3, y=4) in the first channel: ";
const size_t index = (4 * info.Width() * info.Channels()) +
(3 * info.Channels());
std::cout << matrix[index] << "." << std::endl;
// Increment each pixel value, but make sure they are still within the bounds.
matrix += 1;
matrix = arma::clamp(matrix, 0, 255);
mlpack::data::Save("numfocus-logo-mod.png", matrix, info);
```
---
Example of loading and saving multiple images:
```c++
// Load some favicons from websites associated with mlpack.
std::vector<std::string> images;
// See the following files:
// - https://datasets.mlpack.org/images/mlpack-favicon.png
// - https://datasets.mlpack.org/images/ensmallen-favicon.png
// - https://datasets.mlpack.org/images/armadillo-favicon.png
// - https://datasets.mlpack.org/images/bandicoot-favicon.png
images.push_back("mlpack-favicon.png");
images.push_back("ensmallen-favicon.png");
images.push_back("armadillo-favicon.png");
images.push_back("bandicoot-favicon.png");
mlpack::data::ImageInfo info;
info.Channels(1); // Force loading in grayscale.
arma::mat matrix;
mlpack::data::Load(images, matrix, info, true);
// Print information about what we loaded.
std::cout << "Loaded " << matrix.n_cols << " images. Images are of size "
<< info.Width() << " x " << info.Height() << " with " << info.Channels()
<< " color channel." << std::endl;
// Invert images.
matrix = (255.0 - matrix);
// Save as compressed JPEGs with low quality.
info.Quality() = 75;
std::vector<std::string> outImages;
outImages.push_back("mlpack-favicon-inv.jpeg");
outImages.push_back("ensmallen-favicon-inv.jpeg");
outImages.push_back("armadillo-favicon-inv.jpeg");
outImages.push_back("bandicoot-favicon-inv.jpeg");
mlpack::data::Save(outImages, matrix, info);
```
## mlpack objects
All mlpack objects can be saved with `data::Save()` and loaded with
`data::Load()`. Serialization is performed using the
[cereal](https://uscilab.github.io/cereal/) serialization toolkit.
Each object must be given a logical name.
- `data::Load(filename, name, object, fatal=false, format=data::format::autodetect)`
- `data::Save(filename, name, object, fatal=false, format=data::format::autodetect)`
* Load/save `object` to/from `filename` with the logical name `name`.
* If `fatal` is `true`, a `std::runtime_error` will be thrown in the event of
load or save failure.
* The format is autodetected based on extension (`.bin`, `.json`, or `.xml`),
but can be manually specified:
- `data::format::binary`: binary blob (smallest and fastest). No checks;
assumes all data is correct.
- `data::format::json`: JSON.
- `data::format::xml`: XML (largest and slowest).
* For JSON and XML types, when loading, `name` must match the name used to
save the object.
* Returns a `bool` indicating the success of the operation.
***Note:*** when loading an object that was saved as a binary blob, the C++ type
of the object must be ***exactly the same*** (including template parameters) as
the type used to save the object. If not, undefined behavior will occur---most
likely a crash.
---
Simple example: create a `math::Range` object, then save and load it.
```c++
mlpack::math::Range r(3.0, 6.0);
// Save the Range to 'range.bin', using the name "range".
mlpack::data::Save("range.bin", "range", r, true);
// Load the range into a new object.
mlpack::math::Range r2;
mlpack::data::Load("range.bin", "range", r2, true);
std::cout << "Loaded range: [" << r2.Lo() << ", " << r2.Hi() << "]."
<< std::endl;
// Modify and save the range as JSON.
r2.Lo() = 4.0;
mlpack::data::Save("range.json", "range", r2, true);
// Now 'range.json' will contain the following:
//
// {
// "range": {
// "cereal_class_version": 0,
// "hi": 6.0,
// "lo": 4.0
// }
// }
```
---
## Normalizing labels
mlpack classifiers and other algorithms require labels to be in the range `0` to
`numClasses - 1`. A vector of labels with arbitrary (`size_t`) values can be
normalized to the required range with the `NormalizeLabels()` function.
---
* `data::NormalizeLabels(labelsIn, labelsOut, mappings)`
- Map vector `labelsIn` into the range `0` to `numClasses - 1`, storing as
`labelsOut` (of type `arma::Row<size_t>`).
* `numClasses` is automatically detected using the number of unique values
in `labelsIn`.
- The column vector `mappings` will be filled with the reverse mappings to
convert back to the old labels; this can be used by `RevertLabels()`.
- `mappings[i]` contains the original class label for the mapped label `i`.
---
* `data::RevertLabels(labelsIn, mappings, labelsOut)`
- Unmap normalized labels `labelsIn` using `mappings` into `labelsOut`.
- Performs the reverse operation of `NormalizeLabels()`; `mappings` should
be the same vector output by `NormalizeLabels()`.
---
Simple example: convert labels into `0`, `1`, `2`, learn a model, then convert
predictions back to the original label values.
```c++
// Create a random dataset with 5 points in 10 dimensions.
arma::mat dataset(10, 5, arma::fill::randu);
// Manually assemble labels vector: [3, 7, 3, 3, 5]
arma::Row<size_t> labels = { 3, 7, 3, 3, 5 };
// Note that these labels are not in the range `0` to `2`, and thus cannot be
// used directly by mlpack classifiers!
// We will map them to that range using NormalizeLabels().
arma::Row<size_t> mappedLabels;
arma::Col<size_t> mappings;
mlpack::data::NormalizeLabels(labels, mappedLabels, mappings);
const size_t numClasses = mappedLabels.max() + 1;
// Print the mapped values:
// [3, 7, 3, 3, 5] maps to [0, 1, 0, 0, 2].
// The `mappings` vector will be [3, 7, 5].
std::cout << "Original labels: " << labels;
std::cout << "Mapped labels: " << mappedLabels;
std::cout << "Mappings: " << mappings;
// Learn a model with the mapped labels.
mlpack::DecisionTree d(dataset, mappedLabels, numClasses, 1 /* leaf size */);
// Make predictions on the training dataset.
arma::Row<size_t> mappedPredictions;
d.Classify(dataset, mappedPredictions);
// The predictions use mapped labels (0, 1, 2), which we will need to map back
// to the original labels using RevertLabels().
arma::Row<size_t> predictions;
mlpack::data::RevertLabels(mappedPredictions, mappings, predictions);
// Print the predictions before and after unmapping.
// The mapped predictions will take values 0, 1, or 2; the predictions will take
// values 3, 7, or 5 (like the original data).
std::cout << "Mapped predictions: " << mappedPredictions;
std::cout << "Predictions: " << predictions;
```
## Formats
mlpack's `data::Load()` and `data::Save()` functions support a variety of
different formats in different contexts.
---
#### [Numeric data](#numeric-data)
By default, load/save format is ***autodetected***, but can be manually
specified with the `format` parameter using one of the options below:
- `FileType::AutoDetect` (default): auto-detects the format as one of the
formats below using the extension of the filename and inspecting the file
contents.
- `FileType::CSVASCII` (autodetect extensions `.csv`, `.tsv`): CSV format
with no header.
- `FileType::RawASCII` (autodetect extensions `.csv`, `.txt`):
space-separated values or tab-separated values (TSV) with no header.
- `FileType::ArmaASCII` (autodetect extension `.txt`): space-separated
values as saved by Armadillo with the
[`arma_ascii`](https://arma.sourceforge.net/docs.html#save_load_mat)
format.
- `FileType::CoordASCII` (not autodetected, must be manually specified):
coordinate list format for sparse data (see
[`coord_ascii`](https://arma.sourceforge.net/docs.html#save_load_mat)).
- `FileType::ArmaBinary` (autodetect extension `.bin`): Armadillo's
efficient binary matrix format
([`arma_binary`](https://arma.sourceforge.net/docs.html#save_load_mat)).
- `FileType::HDF5Binary` (autodetect extensions `.h5`, `.hdf5`, `.hdf`,
`.he5`): [HDF5](https://en.wikipedia.org/wiki/Hierarchical_Data_Format)
binary format; only available if Armadillo is configured with
[HDF5 support](https://arma.sourceforge.net/docs.html#config_hpp).
- `FileType::RawBinary` (autodetect extension `.bin`): packed binary data
with no header and no size information; data will be loaded as a single
column vector _(not recommended)_.
- `FileType::PGMBinary` (autodetect extension `.pgm`): PGM image format
***Notes:***
- ASCII formats (`CSVASCII`, `RawASCII`, `ArmaASCII`) are human-readable but
large; to reduce dataset size, consider a binary format such as
`ArmaBinary` or `HDF5Binary`.
- Sparse data (`arma::sp_mat`, `arma::sp_fmat`, etc.) should be saved in a
binary format (`ArmaBinary` or `HDF5Binary`) or as a coordinate list
(`CoordASCII`).
---
#### [Mixed categorical data](#mixed-categorical-data)
The format of mixed categorical data is detected automatically based on the
file extension and inspecting the file contents:
- `.csv`, `.txt`, or `.tsv` indicates CSV/TSV/ASCII format
- `.arff` indicates [ARFF](https://www.cs.waikato.ac.nz/~ml/weka/arff.html)
---
#### [Image data](#image-data)
The format of images are detected automatically based on the file extension.
- The following formats are supported for loading: `.jpg`, `.jpeg`, `.png`,
`.tga`, `.bmp`, `.psd`, `.gif`, `.hdr`, `.pic`, `.pnm`
- The following formats are supported for saving: `.jpg`, `.png`, `.tga`,
`.bmp`, `.hdr`
---
#### [mlpack objects](#mlpack-objects)
By default, load/save format for mlpack objects is autodetected, but can be
manually specified with the `format` parameter using one of the options below:
- `format::autodetect` (default): auto-detects the format as one of the
formats below using the extension of the filename
- `format::json` (autodetect extension `.json`)
- `format::xml` (autodetect extension `.xml`)
- `format::binary` (autodetect extension `.bin`)
***Notes:***
- `format::json` (`.json`) and `format::xml` (`.xml`) produce human-readable
files, but they may be quite large.
- `format::binary` (`.bin`) is recommended for the sake of size; objects in
binary format may be an order of magnitude or more smaller than JSON!
+355 -34
View File
@@ -1,50 +1,95 @@
# Matrices in mlpack
mlpack uses Armadillo matrices for matrix support. Armadillo is a fast C++
matrix library which makes use of advanced template techniques to provide the
fastest possible matrix operations.
mlpack uses Armadillo matrices for linear algebra support. Armadillo is a fast
C++ matrix library which uses advanced template metaprogramming techniques to
provide the fastest possible linear algebra operations.
Documentation on Armadillo can be found on [the Armadillo
<center><p><img src="https://arma.sourceforge.net/img/armadillo_logo2.png" alt="Armadillo logo"></p></center>
Detailed documentation on Armadillo can be found on [the Armadillo
website](http://arma.sourceforge.net/docs.html).
Nonetheless, there are a few further caveats for mlpack Armadillo usage.
## Column-major matrices
* [An Armadillo primer](#an-armadillo-primer)
* [Representing data in mlpack](#representing-data-in-mlpack)
* [Loading data](#loading-data)
* [Loading and using categorical data](#loading-and-using-categorical-data)
* [Alternate matrix types](#alternate-matrix-types)
* [Adapting from other toolkits (Eigen, etc.)](#adapting-from-other-toolkits-eigen-etc)
Armadillo matrices are stored in a column-major format; this means that on disk,
each column is located in contiguous memory.
## An Armadillo primer
The Armadillo syntax is straightforward and is aimed at ease-of-use and
readability. To give a flavor of what a linear algebra program using Armadillo
looks like, see the trivial (contrived) program below that performs some basic
matrix operations.
```c++
// Create a 10x15 matrix with random elements.
arma::mat m(10, 15, arma::fill::randu);
std::cout << "Size of m: " << m.n_rows << " x " << m.n_cols << "." << std::endl;
// Sum all elements in the matrix.
const double sumVal = arma::accu(m);
std::cout << "Sum of all elements: " << sumVal << "." << std::endl;
// Sum the elements in each column.
arma::rowvec sums = arma::sum(m, 0);
std::cout << "Sums in each column: " << sums;
// Add 1 to all elements.
m += 1;
// Subtract sums from each row.
m.each_row() -= sums;
// Print an individual element.
std::cout << "m(3, 4) is: " << m(3, 4) << "." << std::endl;
```
For more information on Armadillo, see the following resources:
* [Armadillo documentation](https://arma.sourceforge.net/docs.html)
* [Armadillo example
program](https://arma.sourceforge.net/docs.html#example_prog)
* [Armadillo/MATLAB syntax conversion
table](https://arma.sourceforge.net/docs.html#syntax)
## Representing data in mlpack
Armadillo matrices, unlike numpy and some other toolkits, store data in a
***column-major*** format. This means that each column is located in contiguous
memory; i.e., `x(0, 0)` is adjacent to `x(1, 0)` in memory.
This means that, for the vast majority of machine learning methods, it is faster
to store observations as columns and dimensions as rows. This is counter to
most standard machine learning texts!
to store ***observations as columns*** and ***dimensions as rows***. This is
counter to most standard machine learning texts! It also has some implications
for linear algebra operations; for instance, computing the Gram matrix of a
matrix `X` is typically expressed as `X^T X`, but when using column-major
matrices, the expression must be `X X^T`.
Major implications of this are for linear algebra. For instance, the covariance
of a matrix is typically
In general, the following Armadillo types are commonly used inside mlpack:
```
C = X^T X
```
* `arma::mat`: datasets and general-purpose matrices
* `arma::Row<size_t>`: integer response data, e.g., labels for classification
datasets
* `arma::rowvec`: floating-point response data, e.g., responses for regression
datasets
* `arma::vec`: general-purpose column vectors
* `arma::sp_mat`, `arma::fmat`: alternate types for representing data; see
[Alternate matrix types](#alternate-matrix-types)
but for a column-wise matrix, it is
## Loading data
```
C = X X^T
```
mlpack provides two simple functions for loading and saving data matrices in a
column-major form:
and this is very important to keep in mind! If your mlpack code is not working,
this may be a factor in why.
* `data::Load(filename, matrix, fatal=false, transpose=true, type=FileType::AutoDetect)` ([full documentation](load_save.md#numeric_data))
* `data::Save(filename, matrix, fatal=false, transpose=true, type=FileType::AutoDetect)` ([full documentation](load_save.md#numeric_data))
## Loading matrices
mlpack provides a `data::Load()` and `data::Save()` function, which should be
used instead of Armadillo's loading and saving functions.
Most machine learning data is stored in row-major format; a CSV, for example,
will generally have one observation per line and each column will correspond to
a dimension.
The `data::Load()` and `data::Save()` functions transpose the matrix upon
loading, meaning that the following CSV:
As an example, consider the following CSV file:
```sh
$ cat data.csv
@@ -63,6 +108,282 @@ $ cat data.csv
2,4,4,2,0
```
is actually loaded with 5 rows and 13 columns, not 13 rows and 5 columns like
the CSV is written. More information on mlpack's loading functionality can be
found in [the formats tutorial](formats.md).
The following program will load the data, print information about it, and save a
modified dataset to disk.
```c++
// Load data from `data.csv` into `m`. Throw an exception on failure (i.e. set
// `fatal` to `true`).
arma::mat m;
mlpack::data::Load("data.csv", m, true);
// Since mlpack uses column-major data,
//
// - each column corresponds to a data point!
// - each row corresponds to a dimension!
//
std::cout << "The matrix in 'data.csv' has: " << std::endl;
std::cout << " - " << m.n_cols << " points." << std::endl;
std::cout << " - " << m.n_rows << " dimensions." << std::endl;
std::cout << "The second point in the dataset: " << std::endl;
std::cout << m.col(1).t();
// Now modify the matrix and save to a different format (space-separated
// values).
m += 3;
mlpack::data::Save("data-mod.txt", m);
```
Although Armadillo does provide a `.load()` and `.save()` member function for
matrices, the `data::Load()` and `data::Save()` functions offer additional
flexibility, and ensure that data is saved and loaded in a column-major format.
## Loading and using categorical data
Some mlpack techniques support mixed categorical data, e.g., data where some
dimensions take only categorical values (e.g. `0`, `1`, `2`, etc.). String data
and other non-numerical data can be represented as categorical values, and
mlpack has support to load mixed categorical data:
* The `data::DatasetInfo` auxiliary class stores information about whether each
dimension is numeric or categorical. ([full
documentation](load_save.md#dataset_info))
* `data::Load(filename, matrix, info, fatal=false, transpose=true)` ([full
documentation](load_save.md#load_categorical))
For example, consider the following CSV file that contains strings:
```sh
$ cat mixed_string_data.csv
3,"hello",3,"f",0
3,"goodbye",4,"f",0
3,"goodbye",4,"e",0
3,"hello",4,"d",0
3,"hello",4,"d",0
2,"hello",4,"d",0
2,"hello",4,"d",0
3,"goodbye",3,"f",0
3,"goodbye",4,"f",0
3,"hello",4,"f",0
3,"hello",4,"c",0
3,"hello",4,"f",0
2,"hello",4,"c",0
```
The following program will load the data file, print information about
categorical dimensions, and prepare the data for use with an mlpack algorithm
that supports mixed categorical data.
```c++
// Load data from `mixed_string_data.csv` into `m`. Throw an exception on
// failure (i.e. set `fatal` to `true`). This populates the `info` object.
arma::mat m;
mlpack::data::DatasetInfo info;
mlpack::data::Load("mixed_string_data.csv", m, info, true);
// Print information about the data.
std::cout << "The matrix in 'mixed_string_data.csv' has: " << std::endl;
std::cout << " - " << m.n_cols << " points." << std::endl;
std::cout << " - " << info.Dimensionality() << " dimensions." << std::endl;
// Print which dimensions are categorical.
for (size_t d = 0; d < info.Dimensionality(); ++d)
{
if (info.Type(d) == mlpack::data::Datatype::categorical)
{
std::cout << " - Dimension " << d << " is categorical with "
<< info.NumMappings(d) << " distinct categories." << std::endl;
}
}
// Modify the third point to be 4,"wonderful",1,"c",0.
// Note that we manually map the string values; MapString() returns the category
// for a given value.
m(0, 2) = 4;
m(1, 2) = info.MapString<double>("wonderful", 1); // Create new third category.
m(2, 2) = 1;
m(3, 2) = info.MapString<double>("c", 1);
m(4, 2) = 0;
// `m` can now be used with any mlpack algorithm that supports categorical data.
```
Not every mlpack method supports categorical data. Below are the list of
methods that do have categorical data support:
* [`DecisionTree`](decision_tree.md)
* [`DecisionTreeRegressor`](decision_tree_regressor.md)
* [`RandomForest`](random_forest.md)
* [`HoeffdingTree`](hoeffding_tree.md)
## Alternate matrix types
mlpack's documentation focuses on the use of the `arma::mat`, `arma::vec`, and
`arma::rowvec` types, with an underlying `double` numeric type, e.g., 64-bit
floating-point. But, many mlpack algorithms and support utilities have support
for alternate matrix types and element types:
* Many methods, such as
[`LogisticRegression`](logistic_regression.md#advanced-functionality-different-element-types),
allow specifying the matrix types as a template parameter.
* Some methods, such as
[`DecisionTree`](decision_tree.md#using-different-element-types),
accept different matrix types to `Train()`, `Classify()`, or `Predict()`,
without needing to specify an explicit template parameter.
* In general, any matrix type that supports the Armadillo API can be used; this
includes:
- Single-precision floating point matrices (`arma::fmat`, `arma::frowvec`,
`arma::fvec`)
- Sparse matrices (`arma::sp_mat`, `arma::sp_fmat`)
- GPU matrices via [Bandicoot](https://coot.sourceforge.io) (`coot::mat`,
`coot::fmat`) --- ***(note: support is under development and still
experimental)***
---
A simple example of using single-precision floating point data to train an
[`AdaBoost`](adaboost.md) model is below.
```c++
// 1000 random points in 10 dimensions, using 32-bit precision (float).
arma::fmat dataset(10, 1000, arma::fill::randu);
// Random labels for each point, totaling 5 classes.
arma::Row<size_t> labels =
arma::randi<arma::Row<size_t>>(1000, arma::distr_param(0, 4));
// Train in the constructor, using floating-point data.
// The weak learner type is now a floating-point Perceptron.
typedef mlpack::Perceptron<
mlpack::SimpleWeightUpdate,
mlpack::ZeroInitialization,
arma::fmat> PerceptronType;
mlpack::AdaBoost<PerceptronType, arma::fmat> ab(dataset, labels, 5);
// Create test data (500 points).
arma::fmat testDataset(10, 500, arma::fill::randu);
arma::Row<size_t> predictions;
ab.Classify(testDataset, predictions);
// Now `predictions` holds predictions for the test dataset.
// Print some information about the test predictions.
std::cout << arma::accu(predictions == 3) << " test points classified as class "
<< "3." << std::endl;
```
---
A simple example of using sparse 32-bit floating point data to train a
[`LogisticRegression`](logistic_regression.md) model is below.
```c++
// Create random, sparse 100-dimensional data.
arma::sp_fmat dataset;
dataset.sprandu(100, 5000, 0.3);
arma::Row<size_t> labels =
arma::randi<arma::Row<size_t>>(5000, arma::distr_param(0, 1));
// Train with L2 regularization penalty parameter of 0.1.
mlpack::LogisticRegression<arma::sp_fmat> lr(dataset, labels, 0.1);
// Now classify a test point.
arma::sp_fvec point;
point.sprandu(100, 1, 0.3);
size_t prediction;
arma::fvec probabilitiesVec;
lr.Classify(point, prediction, probabilitiesVec);
std::cout << "Prediction for random test point: " << prediction << "."
<< std::endl;
std::cout << "Class probabilities for random test point: "
<< probabilitiesVec.t();
```
<!-- TODO: a simple Bandicoot example! -->
## Adapting from other toolkits (Eigen, etc.)
In general, C++ linear algebra toolkits store data in a column-major
representation, and transitioning between toolkits is a matter of getting access
to the underlying memory.
---
Copy an [Eigen](https://eigen.tuxfamily.org) matrix into an Armadillo matrix.
```c++
// Note: this will only work if the Eigen matrix is stored in column-major
// order. See https://eigen.tuxfamily.org/dox/group__TopicStorageOrders.html
// for more details.
Eigen::MatrixXd m;
const size_t rows = 10;
const size_t cols = 20;
m.setRandom(rows, cols); // 10x20 random matrix.
// Copy into an Armadillo matrix.
arma::mat mCopy(&m(0, 0), rows, cols);
```
---
Copy an [XTensor](https://xtensor.readthedocs.io/en/latest/) matrix into an
Armadillo matrix.
```c++
// Note: this will only work correctly if the layout_type of the XTensor matrix
// is column-major (i.e. xt::layout_type::column_major). See
// https://xtensor.readthedocs.io/en/latest/container.html for more details.
const size_t rows = 10;
const size_t cols = 20;
// Create a random 10 x 20 matrix with normally distributed values.
// Note that we must ensure that the matrix is laid out in column-major form.
xt::xarray<double, xt::layout_type::column_major> m =
xt::random::randn<double>({ rows, cols });
// Copy into an Armadillo matrix.
arma::mat mCopy(m.data(), rows, cols);
```
---
Make an Armadillo matrix that is an alias of an
[Eigen](https://eigen.tuxfamily.org) matrix. Note that changes to the Eigen
matrix will be reflected in the Armadillo matrix, and vice versa.
*If the Eigen matrix is deallocated, the Armadillo matrix will become invalid.
Be careful! [More details
here](https://arma.sourceforge.net/docs.html#adv_constructors_mat).*
```c++
// Note: this will only work if the Eigen matrix is stored in column-major
// order. See https://eigen.tuxfamily.org/dox/group__TopicStorageOrders.html
// for more details.
Eigen::MatrixXd m;
const size_t rows = 10;
const size_t cols = 20;
m.setRandom(rows, cols); // 10x20 random matrix.
// Make an Armadillo matrix that is an alias of the Eigen matrix. This avoids
// the copy, but is potentially dangerous: be careful that the Eigen matrix is
// not deleted while the Armadillo matrix is in use!
//
// See https://arma.sourceforge.net/docs.html#adv_constructors_mat
arma::mat mAlias(&m(0, 0), rows, cols, false, true);
```
---
Copy an Armadillo matrix to an Eigen matrix.
```c++
const size_t rows = 10;
const size_t cols = 20;
arma::mat m(10, 20, arma::fill::randu);
// Construct the Eigen matrix by using a map of the Armadillo memory.
Eigen::MatrixXd eigenM(Eigen::Map<Eigen::MatrixXd>(m.memptr(), rows, cols));
```
@@ -63,24 +63,6 @@ class EpanechnikovKernel
*/
inline double Gradient(const double distance) const;
/**
* Evaluate the Gradient of Epanechnikov kernel
* given that the squared distance between the two
* input points is known.
*/
inline double GradientForSquaredDistance(const double distanceSquared) const;
/**
* Obtains the convolution integral [integral of K(||x-a||) K(||b-x||) dx]
* for the two vectors.
*
* @tparam VecType Type of vector (arma::vec, arma::spvec should be expected).
* @param a First vector.
* @param b Second vector.
* @return the convolution integral value.
*/
template<typename VecTypeA, typename VecTypeB>
double ConvolutionIntegral(const VecTypeA& a, const VecTypeB& b);
/**
* Compute the normalizer of this Epanechnikov kernel for the given dimension.
*
@@ -28,50 +28,6 @@ inline double EpanechnikovKernel::Evaluate(const VecTypeA& a, const VecTypeB& b)
* inverseBandwidthSquared);
}
/**
* Obtains the convolution integral [integral of K(||x-a||) K(||b-x||) dx]
* for the two vectors.
*
* @tparam VecTypeA Type of first vector (arma::vec, arma::sp_vec should be
* expected).
* @tparam VecTypeB Type of second vector (arma::vec, arma::sp_vec).
* @param a First vector.
* @param b Second vector.
* @return the convolution integral value.
*/
template<typename VecTypeA, typename VecTypeB>
inline double EpanechnikovKernel::ConvolutionIntegral(const VecTypeA& a,
const VecTypeB& b)
{
double distance = sqrt(SquaredEuclideanDistance::Evaluate(a, b));
if (distance >= 2.0 * bandwidth)
return 0.0;
double volumeSquared = std::pow(Normalizer(a.n_rows), 2.0);
switch (a.n_rows)
{
case 1:
return 1.0 / volumeSquared *
(16.0 / 15.0 * bandwidth - 4.0 * distance * distance /
(3.0 * bandwidth) + 2.0 * distance * distance * distance /
(3.0 * bandwidth * bandwidth) -
std::pow(distance, 5.0) / (30.0 * std::pow(bandwidth, 4.0)));
case 2:
return 1.0 / volumeSquared *
((2.0 / 3.0 * bandwidth * bandwidth - distance * distance) *
asin(sqrt(1.0 - std::pow(distance / (2.0 * bandwidth), 2.0))) +
sqrt(4.0 * bandwidth * bandwidth - distance * distance) *
(distance / 6.0 + 2.0 / 9.0 * distance *
std::pow(distance / bandwidth, 2.0) - distance / 72.0 *
std::pow(distance / bandwidth, 4.0)));
default:
Log::Fatal << "EpanechnikovKernel::ConvolutionIntegral(): dimension "
<< a.n_rows << " not supported.";
return -1.0; // This line will not execute.
}
}
/**
* Compute the normalizer of this Epanechnikov kernel for the given dimension.
*
@@ -113,29 +69,6 @@ inline double EpanechnikovKernel::Gradient(const double distance) const
}
}
/**
* Evaluate gradient of the kernel not for two points
* but for a numerical value.
*/
inline double EpanechnikovKernel::GradientForSquaredDistance(
const double distanceSquared) const
{
double bandwidthSquared = bandwidth * bandwidth;
if (distanceSquared < bandwidthSquared)
{
return -1 * inverseBandwidthSquared;
}
else if (distanceSquared > bandwidthSquared &&
distanceSquared >= 0)
{
return 0;
}
else
{
// The gradient doesn't exist.
return arma::datum::nan;
}
}
//! Serialize the kernel.
template<typename Archive>
void EpanechnikovKernel::serialize(Archive& ar,
@@ -89,24 +89,6 @@ class ExampleKernel
template<typename Archive>
void serialize(Archive& /* ar */, const uint32_t /* version */) { }
/**
* Obtains the convolution integral [integral K(||x-a||)K(||b-x||)dx]
* for the two vectors. In this case, because
* our simple example kernel has no internal parameters, we can declare the
* function static. For a more complex example which cannot be declared
* static, see the GaussianKernel, which stores an internal parameter.
*
* @tparam VecTypeA Type of first vector (arma::vec, arma::sp_vec should be
* expected).
* @tparam VecTypeB Type of second vector (arma::vec, arma::sp_vec).
* @param * (a) First vector.
* @param * (b) Second vector.
* @return the convolution integral value.
*/
template<typename VecTypeA, typename VecTypeB>
static double ConvolutionIntegral(const VecTypeA& /* a */,
const VecTypeB& /* b */) { return 0; }
/**
* Obtains the normalizing volume for the kernel with dimension $dimension$.
* In this case, because our simple example kernel has no internal parameters,
@@ -92,18 +92,6 @@ class GaussianKernel
return 2 * t * gamma * exp(gamma * std::pow(t, 2.0));
}
/**
* Evaluation of the gradient of Gaussian kernel
* given the squared distance between two points.
*
* @param t The squared distance between the two points
* @return K(t) using the bandwidth (@f$\mu@f$) specified in the
* constructor.
*/
double GradientForSquaredDistance(const double t) const {
return gamma * exp(gamma * t);
}
/**
* Obtain the normalization constant of the Gaussian kernel.
*
@@ -115,21 +103,6 @@ class GaussianKernel
return pow(sqrt(2.0 * M_PI) * bandwidth, (double) dimension);
}
/**
* Obtain a convolution integral of the Gaussian kernel.
*
* @param a First vector.
* @param b Second vector.
* @return The convolution integral.
*/
template<typename VecTypeA, typename VecTypeB>
double ConvolutionIntegral(const VecTypeA& a, const VecTypeB& b)
{
return Evaluate(sqrt(SquaredEuclideanDistance::Evaluate(a, b) /
2.0)) / (Normalizer(a.n_rows) * pow(2.0, (double) a.n_rows / 2.0));
}
//! Get the bandwidth.
double Bandwidth() const { return bandwidth; }
+1 -34
View File
@@ -44,41 +44,7 @@ class SphericalKernel
return (SquaredEuclideanDistance::Evaluate(a, b) <= bandwidthSquared) ?
1.0 : 0.0;
}
/**
* Obtains the convolution integral [integral K(||x-a||)K(||b-x||)dx]
* for the two vectors.
*
* @tparam VecTypeA Type of first vector (arma::vec, arma::sp_vec should be
* expected).
* @tparam VecTypeB Type of second vector.
* @param a First vector.
* @param b Second vector.
* @return The convolution integral value.
*/
template<typename VecTypeA, typename VecTypeB>
double ConvolutionIntegral(const VecTypeA& a, const VecTypeB& b) const
{
double distance = sqrt(SquaredEuclideanDistance::Evaluate(a, b));
if (distance >= 2.0 * bandwidth)
{
return 0.0;
}
double volumeSquared = pow(Normalizer(a.n_rows), 2.0);
switch (a.n_rows)
{
case 1:
return 1.0 / volumeSquared * (2.0 * bandwidth - distance);
case 2:
return 1.0 / volumeSquared *
(2.0 * bandwidth * bandwidth * acos(distance/(2.0 * bandwidth)) -
distance / 4.0 * sqrt(4.0*bandwidth*bandwidth-distance*distance));
default:
Log::Fatal << "The spherical kernel does not support convolution\
integrals above dimension two, yet..." << std::endl;
return -1.0;
}
}
double Normalizer(size_t dimension) const
{
return pow(bandwidth, (double) dimension) * pow(M_PI, dimension / 2.0) /
@@ -94,6 +60,7 @@ class SphericalKernel
{
return (t <= bandwidth) ? 1.0 : 0.0;
}
double Gradient(double t)
{
return t == bandwidth ? arma::datum::nan : 0.0;
-17
View File
@@ -286,13 +286,6 @@ TEST_CASE("GaussianKernelTest", "[KernelTest]")
REQUIRE(gk.Normalizer(2) == Approx(1.5707963267948963).epsilon(1e-7));
REQUIRE(gk.Normalizer(3) == Approx(1.9687012432153019).epsilon(1e-7));
REQUIRE(gk.Normalizer(4) == Approx(2.4674011002723386).epsilon(1e-7));
/* check the convolution integral */
REQUIRE(gk.ConvolutionIntegral(a, b) ==
Approx(0.024304474038457577).epsilon(1e-7));
REQUIRE(gk.ConvolutionIntegral(a, c) ==
Approx(0.024304474038457577).epsilon(1e-7));
REQUIRE(gk.ConvolutionIntegral(b, c) ==
Approx(0.024304474038457577).epsilon(1e-7));
}
TEST_CASE("GaussianKernelSerializationTest", "[KernelTest]")
@@ -329,11 +322,6 @@ TEST_CASE("SphericalKernelTest", "[KernelTest]")
REQUIRE(sk.Normalizer(2) == Approx(0.78539816339744828).epsilon(1e-7));
REQUIRE(sk.Normalizer(3) == Approx(0.52359877559829893).epsilon(1e-7));
REQUIRE(sk.Normalizer(4) == Approx(0.30842513753404244).epsilon(1e-7));
/* check the convolution integral */
REQUIRE(sk.ConvolutionIntegral(a, b) == Approx(0.0).epsilon(1e-7));
REQUIRE(sk.ConvolutionIntegral(a, c) == Approx(0.0).epsilon(1e-7));
REQUIRE(sk.ConvolutionIntegral(b, c) ==
Approx(1.0021155029652784).epsilon(1e-7));
}
TEST_CASE("EpanechnikovKernelTest", "[KernelTest]")
@@ -356,11 +344,6 @@ TEST_CASE("EpanechnikovKernelTest", "[KernelTest]")
REQUIRE(ek.Normalizer(2) == Approx(0.39269908169872414).epsilon(1e-7));
REQUIRE(ek.Normalizer(3) == Approx(0.20943951023931956).epsilon(1e-7));
REQUIRE(ek.Normalizer(4) == Approx(0.10280837917801415).epsilon(1e-7));
/* check the convolution integral */
REQUIRE(ek.ConvolutionIntegral(a, b) == Approx(0.0).epsilon(1e-7));
REQUIRE(ek.ConvolutionIntegral(a, c) == Approx(0.0).epsilon(1e-7));
REQUIRE(ek.ConvolutionIntegral(b, c) ==
Approx(1.5263455690698258).epsilon(1e-7));
}
TEST_CASE("PolynomialKernelTest", "[KernelTest]")