diff --git a/doc/index.md b/doc/index.md
index 607a2ae214..cc71edfed4 100644
--- a/doc/index.md
+++ b/doc/index.md
@@ -123,6 +123,10 @@ Transform data from one space to another.
* [`AMF`](user/methods/amf.md): alternating matrix factorization
* [`LocalCoordinateCoding`](user/methods/local_coordinate_coding.md): local
coordinate coding with dictionary learning
+ * [`LMNN`](user/methods/lmnn.md): large margin nearest neighbor (distance
+ metric learning)
+ * [`NCA`](user/methods/nca.md): neighborhood components analysis (distance
+ metric learning)
* [`NMF`](user/methods/nmf.md): non-negative matrix factorization
* [`PCA`](user/methods/pca.md): principal components analysis
* [`SparseCoding`](user/methods/sparse_coding.md): sparse coding with
diff --git a/doc/sidebar.html b/doc/sidebar.html
index 568496310e..86bedf3e6d 100644
--- a/doc/sidebar.html
+++ b/doc/sidebar.html
@@ -170,6 +170,16 @@ when the sidebar is built for each page.
LocalCoordinateCoding
+
+
+ LMNN
+
+
+
+
+ NCA
+
+
NMF
diff --git a/doc/user/bindings/cli.md b/doc/user/bindings/cli.md
index bc02653831..adfd721f22 100644
--- a/doc/user/bindings/cli.md
+++ b/doc/user/bindings/cli.md
@@ -1677,9 +1677,9 @@ $ mlpack_linear_svm --input_model_file lsvm_model.bin --test_file test.csv
$ mlpack_lmnn [--batch_size 50] [--center] [--distance_file ]
[--help] [--info ] --input_file [--k 1] [--labels_file
] [--linear_scan] [--max_iterations 100000] [--normalize]
- [--optimizer 'amsgrad'] [--passes 50] [--print_accuracy] [--range 1]
- [--rank 0] [--regularization 0.5] [--seed 0] [--step_size 0.01]
- [--tolerance 1e-07] [--verbose] [--version] [--centered_data_file
+ [--optimizer 'amsgrad'] [--passes 50] [--print_accuracy] [--rank 0]
+ [--regularization 0.5] [--seed 0] [--step_size 0.01] [--tolerance 1e-07]
+ [--update_interval 1] [--verbose] [--version] [--centered_data_file
] [--output_file ] [--transformed_data_file ]
```
@@ -1706,12 +1706,12 @@ An implementation of Large Margin Nearest Neighbors (LMNN), a distance learning
| `--optimizer (-O)` | [`string`](#doc_string) | Optimizer to use; 'amsgrad', 'bbsgd', 'sgd', or 'lbfgs'. | `'amsgrad'` |
| `--passes (-p)` | [`int`](#doc_int) | Maximum number of full passes over dataset for AMSGrad, BB_SGD and SGD. | `50` |
| `--print_accuracy (-P)` | [`flag`](#doc_flag) | Print accuracies on initial and transformed dataset | |
-| `--range (-R)` | [`int`](#doc_int) | Number of iterations after which impostors needs to be recalculated | `1` |
| `--rank (-A)` | [`int`](#doc_int) | Rank of distance matrix to be optimized. | `0` |
| `--regularization (-r)` | [`double`](#doc_double) | Regularization for LMNN objective function | `0.5` |
| `--seed (-s)` | [`int`](#doc_int) | Random seed. If 0, 'std::time(NULL)' is used. | `0` |
| `--step_size (-a)` | [`double`](#doc_double) | Step size for AMSGrad, BB_SGD and SGD (alpha). | `0.01` |
| `--tolerance (-t)` | [`double`](#doc_double) | Maximum tolerance for termination of AMSGrad, BB_SGD, SGD or L-BFGS. | `1e-07` |
+| `--update_interval (-R)` | [`int`](#doc_int) | Number of iterations after which impostors need to be recalculated. | `1` |
| `--verbose (-v)` | [`flag`](#doc_flag) | Display informational messages and the full list of parameters and timers at the end of execution. | |
| `--version (-V)` | [`flag`](#doc_flag) | Display the version of mlpack. Only exists in CLI binding. | |
@@ -1731,7 +1731,7 @@ This program implements Large Margin Nearest Neighbors, a distance learning tech
To work, this algorithm needs labeled data. It can be given as the last row of the input dataset (specified with `--input_file (-i)`), or alternatively as a separate matrix (specified with `--labels_file (-l)`). Additionally, a starting point for optimization (specified with `--distance_file (-d)`can be given, having (r x d) dimensionality. Here r should satisfy 1 <= r <= d, Consequently a Low-Rank matrix will be optimized. Alternatively, Low-Rank distance can be learned by specifying the `--rank (-A)`parameter (A Low-Rank matrix with uniformly distributed values will be used as initial learning point).
-The program also requires number of targets neighbors to work with ( specified with `--k (-k)`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `--regularization (-r)`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `--range (-R)`).
+The program also requires number of targets neighbors to work with ( specified with `--k (-k)`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `--regularization (-r)`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `--update_interval (-R)`).
Output can either be the learned distance matrix (specified with `--output_file (-o)`), or the transformed dataset (specified with `--transformed_data_file (-D)`), or both. Additionally mean-centered dataset (specified with `--centered_data_file (-c)`) can be accessed given mean-centering (specified with `--center (-C)`) is performed on the dataset. Accuracy on initial dataset and final transformed dataset can be printed by specifying the `--print_accuracy (-P)`parameter.
@@ -1755,10 +1755,10 @@ $ mlpack_lmnn --input_file iris.csv --labels_file iris_labels.csv --k 3
--optimizer bbsgd --output_file output.csv
```
-An another program call making use of range & regularization parameter with dataset having labels as last column can be made as:
+Another program call making use of update interval & regularization parameter with dataset having labels as last column can be made as:
```bash
-$ mlpack_lmnn --input_file letter_recognition.csv --k 5 --range 10
+$ mlpack_lmnn --input_file letter_recognition.csv --k 5 --update_interval 10
--regularization 0.4 --output_file output.csv
```
diff --git a/doc/user/bindings/go.md b/doc/user/bindings/go.md
index c9baae764f..71878e2430 100644
--- a/doc/user/bindings/go.md
+++ b/doc/user/bindings/go.md
@@ -2069,12 +2069,12 @@ param.Normalize = false
param.Optimizer = "amsgrad"
param.Passes = 50
param.PrintAccuracy = false
-param.Range = 1
param.Rank = 0
param.Regularization = 0.5
param.Seed = 0
param.StepSize = 0.01
param.Tolerance = 1e-07
+param.UpdateInterval = 1
param.Verbose = false
centered_data, output, transformed_data := mlpack.Lmnn(input, param)
@@ -2102,12 +2102,12 @@ There are two types of input options: required options, which are passed directl
| `Optimizer` | [`string`](#doc_string) | Optimizer to use; 'amsgrad', 'bbsgd', 'sgd', or 'lbfgs'. | `"amsgrad"` |
| `Passes` | [`int`](#doc_int) | Maximum number of full passes over dataset for AMSGrad, BB_SGD and SGD. | `50` |
| `PrintAccuracy` | [`bool`](#doc_bool) | Print accuracies on initial and transformed dataset | `false` |
-| `Range` | [`int`](#doc_int) | Number of iterations after which impostors needs to be recalculated | `1` |
| `Rank` | [`int`](#doc_int) | Rank of distance matrix to be optimized. | `0` |
| `Regularization` | [`float64`](#doc_float64) | Regularization for LMNN objective function | `0.5` |
| `Seed` | [`int`](#doc_int) | Random seed. If 0, 'std::time(NULL)' is used. | `0` |
| `StepSize` | [`float64`](#doc_float64) | Step size for AMSGrad, BB_SGD and SGD (alpha). | `0.01` |
| `Tolerance` | [`float64`](#doc_float64) | Maximum tolerance for termination of AMSGrad, BB_SGD, SGD or L-BFGS. | `1e-07` |
+| `UpdateInterval` | [`int`](#doc_int) | Number of iterations after which impostors need to be recalculated. | `1` |
| `Verbose` | [`bool`](#doc_bool) | Display informational messages and the full list of parameters and timers at the end of execution. | `false` |
### Output options
@@ -2127,7 +2127,7 @@ This program implements Large Margin Nearest Neighbors, a distance learning tech
To work, this algorithm needs labeled data. It can be given as the last row of the input dataset (specified with `Input`), or alternatively as a separate matrix (specified with `Labels`). Additionally, a starting point for optimization (specified with `Distance`can be given, having (r x d) dimensionality. Here r should satisfy 1 <= r <= d, Consequently a Low-Rank matrix will be optimized. Alternatively, Low-Rank distance can be learned by specifying the `Rank`parameter (A Low-Rank matrix with uniformly distributed values will be used as initial learning point).
-The program also requires number of targets neighbors to work with ( specified with `K`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `Regularization`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `Range`).
+The program also requires number of targets neighbors to work with ( specified with `K`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `Regularization`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `UpdateInterval`).
Output can either be the learned distance matrix (specified with `Output`), or the transformed dataset (specified with `TransformedData`), or both. Additionally mean-centered dataset (specified with `CenteredData`) can be accessed given mean-centering (specified with `Center`) is performed on the dataset. Accuracy on initial dataset and final transformed dataset can be printed by specifying the `PrintAccuracy`parameter.
@@ -2156,13 +2156,13 @@ param.Optimizer = "bbsgd"
_, output, _ := mlpack.Lmnn(iris, param)
```
-An another program call making use of range & regularization parameter with dataset having labels as last column can be made as:
+Another program call making use of update interval & regularization parameter with dataset having labels as last column can be made as:
```go
// Initialize optional parameters for Lmnn().
param := mlpack.LmnnOptions()
param.K = 5
-param.Range = 10
+param.UpdateInterval = 10
param.Regularization = 0.4
_, output, _ := mlpack.Lmnn(letter_recognition, param)
diff --git a/doc/user/bindings/julia.md b/doc/user/bindings/julia.md
index 5d9e699354..6b81648246 100644
--- a/doc/user/bindings/julia.md
+++ b/doc/user/bindings/julia.md
@@ -1696,9 +1696,9 @@ julia> using mlpack: lmnn
julia> centered_data, output, transformed_data = lmnn(input;
batch_size=50, center=false, distance=zeros(0, 0), k=1, labels=Int[],
linear_scan=false, max_iterations=100000, normalize=false,
- optimizer="amsgrad", passes=50, print_accuracy=false, range=1, rank=0,
+ optimizer="amsgrad", passes=50, print_accuracy=false, rank=0,
regularization=0.5, seed=0, step_size=0.01, tolerance=1e-07,
- verbose=false)
+ update_interval=1, verbose=false)
```
An implementation of Large Margin Nearest Neighbors (LMNN), a distance learning technique. Given a labeled dataset, this learns a transformation of the data that improves k-nearest-neighbor performance; this can be useful as a preprocessing step. [Detailed documentation](#lmnn_detailed-documentation).
@@ -1722,12 +1722,12 @@ An implementation of Large Margin Nearest Neighbors (LMNN), a distance learning
| `optimizer` | [`String`](#doc_String) | Optimizer to use; 'amsgrad', 'bbsgd', 'sgd', or 'lbfgs'. | `"amsgrad"` |
| `passes` | [`Int`](#doc_Int) | Maximum number of full passes over dataset for AMSGrad, BB_SGD and SGD. | `50` |
| `print_accuracy` | [`Bool`](#doc_Bool) | Print accuracies on initial and transformed dataset | `false` |
-| `range` | [`Int`](#doc_Int) | Number of iterations after which impostors needs to be recalculated | `1` |
| `rank` | [`Int`](#doc_Int) | Rank of distance matrix to be optimized. | `0` |
| `regularization` | [`Float64`](#doc_Float64) | Regularization for LMNN objective function | `0.5` |
| `seed` | [`Int`](#doc_Int) | Random seed. If 0, 'std::time(NULL)' is used. | `0` |
| `step_size` | [`Float64`](#doc_Float64) | Step size for AMSGrad, BB_SGD and SGD (alpha). | `0.01` |
| `tolerance` | [`Float64`](#doc_Float64) | Maximum tolerance for termination of AMSGrad, BB_SGD, SGD or L-BFGS. | `1e-07` |
+| `update_interval` | [`Int`](#doc_Int) | Number of iterations after which impostors need to be recalculated. | `1` |
| `verbose` | [`Bool`](#doc_Bool) | Display informational messages and the full list of parameters and timers at the end of execution. | `false` |
### Output options
@@ -1747,7 +1747,7 @@ This program implements Large Margin Nearest Neighbors, a distance learning tech
To work, this algorithm needs labeled data. It can be given as the last row of the input dataset (specified with `input`), or alternatively as a separate matrix (specified with `labels`). Additionally, a starting point for optimization (specified with `distance`can be given, having (r x d) dimensionality. Here r should satisfy 1 <= r <= d, Consequently a Low-Rank matrix will be optimized. Alternatively, Low-Rank distance can be learned by specifying the `rank`parameter (A Low-Rank matrix with uniformly distributed values will be used as initial learning point).
-The program also requires number of targets neighbors to work with ( specified with `k`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `regularization`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `range`).
+The program also requires number of targets neighbors to work with ( specified with `k`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `regularization`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `update_interval`).
Output can either be the learned distance matrix (specified with `output`), or the transformed dataset (specified with `transformed_data`), or both. Additionally mean-centered dataset (specified with `centered_data`) can be accessed given mean-centering (specified with `center`) is performed on the dataset. Accuracy on initial dataset and final transformed dataset can be printed by specifying the `print_accuracy`parameter.
@@ -1774,13 +1774,13 @@ julia> _, output, _ = lmnn(iris; k=3, labels=iris_labels,
optimizer="bbsgd")
```
-An another program call making use of range & regularization parameter with dataset having labels as last column can be made as:
+Another program call making use of update interval & regularization parameter with dataset having labels as last column can be made as:
```julia
julia> using CSV
julia> letter_recognition = CSV.read("letter_recognition.csv")
-julia> _, output, _ = lmnn(letter_recognition; k=5, range=10,
- regularization=0.4)
+julia> _, output, _ = lmnn(letter_recognition; k=5,
+ regularization=0.4, update_interval=10)
```
### See also
diff --git a/doc/user/bindings/python.md b/doc/user/bindings/python.md
index a0a777fc72..8399b13531 100644
--- a/doc/user/bindings/python.md
+++ b/doc/user/bindings/python.md
@@ -1715,8 +1715,8 @@ Then, to use that model to predict classes for the dataset '`'test'`', storing t
copy_all_inputs=False, distance=np.empty([0, 0]), input_=np.empty([0,
0]), k=1, labels=np.empty([0], dtype=np.uint64), linear_scan=False,
max_iterations=100000, normalize=False, optimizer='amsgrad', passes=50,
- print_accuracy=False, range=1, rank=0, regularization=0.5, seed=0,
- step_size=0.01, tolerance=1e-07, verbose=False)
+ print_accuracy=False, rank=0, regularization=0.5, seed=0,
+ step_size=0.01, tolerance=1e-07, update_interval=1, verbose=False)
>>> centered_data = d['centered_data']
>>> output = d['output']
>>> transformed_data = d['transformed_data']
@@ -1744,12 +1744,12 @@ An implementation of Large Margin Nearest Neighbors (LMNN), a distance learning
| `optimizer` | [`str`](#doc_str) | Optimizer to use; 'amsgrad', 'bbsgd', 'sgd', or 'lbfgs'. | `'amsgrad'` |
| `passes` | [`int`](#doc_int) | Maximum number of full passes over dataset for AMSGrad, BB_SGD and SGD. | `50` |
| `print_accuracy` | [`bool`](#doc_bool) | Print accuracies on initial and transformed dataset | `False` |
-| `range` | [`int`](#doc_int) | Number of iterations after which impostors needs to be recalculated | `1` |
| `rank` | [`int`](#doc_int) | Rank of distance matrix to be optimized. | `0` |
| `regularization` | [`float`](#doc_float) | Regularization for LMNN objective function | `0.5` |
| `seed` | [`int`](#doc_int) | Random seed. If 0, 'std::time(NULL)' is used. | `0` |
| `step_size` | [`float`](#doc_float) | Step size for AMSGrad, BB_SGD and SGD (alpha). | `0.01` |
| `tolerance` | [`float`](#doc_float) | Maximum tolerance for termination of AMSGrad, BB_SGD, SGD or L-BFGS. | `1e-07` |
+| `update_interval` | [`int`](#doc_int) | Number of iterations after which impostors need to be recalculated. | `1` |
| `verbose` | [`bool`](#doc_bool) | Display informational messages and the full list of parameters and timers at the end of execution. | `False` |
### Output options
@@ -1769,7 +1769,7 @@ This program implements Large Margin Nearest Neighbors, a distance learning tech
To work, this algorithm needs labeled data. It can be given as the last row of the input dataset (specified with `input_`), or alternatively as a separate matrix (specified with `labels`). Additionally, a starting point for optimization (specified with `distance`can be given, having (r x d) dimensionality. Here r should satisfy 1 <= r <= d, Consequently a Low-Rank matrix will be optimized. Alternatively, Low-Rank distance can be learned by specifying the `rank`parameter (A Low-Rank matrix with uniformly distributed values will be used as initial learning point).
-The program also requires number of targets neighbors to work with ( specified with `k`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `regularization`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `range`).
+The program also requires number of targets neighbors to work with ( specified with `k`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `regularization`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `update_interval`).
Output can either be the learned distance matrix (specified with `output`), or the transformed dataset (specified with `transformed_data`), or both. Additionally mean-centered dataset (specified with `centered_data`) can be accessed given mean-centering (specified with `center`) is performed on the dataset. Accuracy on initial dataset and final transformed dataset can be printed by specifying the `print_accuracy`parameter.
@@ -1793,10 +1793,10 @@ Example - Let's say we want to learn distance on iris dataset with number of tar
>>> output = output['output']
```
-An another program call making use of range & regularization parameter with dataset having labels as last column can be made as:
+Another program call making use of update interval & regularization parameter with dataset having labels as last column can be made as:
```python
->>> output = lmnn(input_=letter_recognition, k=5, range=10,
+>>> output = lmnn(input_=letter_recognition, k=5, update_interval=10,
regularization=0.4)
>>> output = output['output']
```
diff --git a/doc/user/bindings/r.md b/doc/user/bindings/r.md
index 2aa0037959..b6a242b521 100644
--- a/doc/user/bindings/r.md
+++ b/doc/user/bindings/r.md
@@ -1689,9 +1689,9 @@ R> library(mlpack)
R> d <- lmnn(batch_size=50, center=FALSE, distance=matrix(numeric(), 0,
0), input=matrix(numeric(), 0, 0), k=1, labels=matrix(integer(), 0, 0),
linear_scan=FALSE, max_iterations=100000, normalize=FALSE,
- optimizer="amsgrad", passes=50, print_accuracy=FALSE, range=1, rank=0,
+ optimizer="amsgrad", passes=50, print_accuracy=FALSE, rank=0,
regularization=0.5, seed=0, step_size=0.01, tolerance=1e-07,
- verbose=getOption("mlpack.verbose", FALSE))
+ update_interval=1, verbose=getOption("mlpack.verbose", FALSE))
R> centered_data <- d$centered_data
R> output <- d$output
R> transformed_data <- d$transformed_data
@@ -1718,12 +1718,12 @@ An implementation of Large Margin Nearest Neighbors (LMNN), a distance learning
| `optimizer` | [`character`](#doc_character) | Optimizer to use; 'amsgrad', 'bbsgd', 'sgd', or 'lbfgs'. | `"amsgrad"` |
| `passes` | [`integer`](#doc_integer) | Maximum number of full passes over dataset for AMSGrad, BB_SGD and SGD. | `50` |
| `print_accuracy` | [`logical`](#doc_logical) | Print accuracies on initial and transformed dataset | `FALSE` |
-| `range` | [`integer`](#doc_integer) | Number of iterations after which impostors needs to be recalculated | `1` |
| `rank` | [`integer`](#doc_integer) | Rank of distance matrix to be optimized. | `0` |
| `regularization` | [`numeric`](#doc_numeric) | Regularization for LMNN objective function | `0.5` |
| `seed` | [`integer`](#doc_integer) | Random seed. If 0, 'std::time(NULL)' is used. | `0` |
| `step_size` | [`numeric`](#doc_numeric) | Step size for AMSGrad, BB_SGD and SGD (alpha). | `0.01` |
| `tolerance` | [`numeric`](#doc_numeric) | Maximum tolerance for termination of AMSGrad, BB_SGD, SGD or L-BFGS. | `1e-07` |
+| `update_interval` | [`integer`](#doc_integer) | Number of iterations after which impostors need to be recalculated. | `1` |
| `verbose` | [`logical`](#doc_logical) | Display informational messages and the full list of parameters and timers at the end of execution. | `getOption("mlpack.verbose", FALSE)` |
### Output options
@@ -1743,7 +1743,7 @@ This program implements Large Margin Nearest Neighbors, a distance learning tech
To work, this algorithm needs labeled data. It can be given as the last row of the input dataset (specified with `input`), or alternatively as a separate matrix (specified with `labels`). Additionally, a starting point for optimization (specified with `distance`can be given, having (r x d) dimensionality. Here r should satisfy 1 <= r <= d, Consequently a Low-Rank matrix will be optimized. Alternatively, Low-Rank distance can be learned by specifying the `rank`parameter (A Low-Rank matrix with uniformly distributed values will be used as initial learning point).
-The program also requires number of targets neighbors to work with ( specified with `k`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `regularization`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `range`).
+The program also requires number of targets neighbors to work with ( specified with `k`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `regularization`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `update_interval`).
Output can either be the learned distance matrix (specified with `output`), or the transformed dataset (specified with `transformed_data`), or both. Additionally mean-centered dataset (specified with `centered_data`) can be accessed given mean-centering (specified with `center`) is performed on the dataset. Accuracy on initial dataset and final transformed dataset can be printed by specifying the `print_accuracy`parameter.
@@ -1767,10 +1767,10 @@ R> output <- lmnn(input=iris, labels=iris_labels, k=3, optimizer="bbsgd")
R> output <- output$output
```
-An another program call making use of range & regularization parameter with dataset having labels as last column can be made as:
+Another program call making use of update interval & regularization parameter with dataset having labels as last column can be made as:
```R
-R> output <- lmnn(input=letter_recognition, k=5, range=10,
+R> output <- lmnn(input=letter_recognition, k=5, update_interval=10,
regularization=0.4)
R> output <- output$output
```
diff --git a/doc/user/core.md b/doc/user/core.md
index 0caaea7459..c482688d39 100644
--- a/doc/user/core.md
+++ b/doc/user/core.md
@@ -862,9 +862,9 @@ including:
* [`NeighborSearch`](/src/mlpack/methods/neighbor_search/neighbor_search.hpp)
* [`RangeSearch`](/src/mlpack/methods/range_search/range_search.hpp)
- * [`LMNN`](/src/mlpack/methods/lmnn/lmnn.hpp)
+ * [`LMNN`](methods/lmnn.md)
* [`EMST`](/src/mlpack/methods/emst/emst.hpp)
- * [`NCA`](/src/mlpack/methods/nca/nca.hpp)
+ * [`NCA`](methods/nca.md)
* [`RANN`](/src/mlpack/methods/rann/rann.hpp)
* [`KMeans`](/src/mlpack/methods/kmeans/kmeans.hpp)
@@ -1281,6 +1281,11 @@ std::cout << "Squared Mahalanobis distance on 32-bit floating point data:"
<< std::endl;
std::cout << " - Points 3 and 5: " << d1 << "." << std::endl;
std::cout << " - Points 11 and 31: " << d2 << "." << std::endl;
+
+// Note that an equivalent transformation matrix can be recovered from Q with
+// an upper Cholesky decomposition (Q -> R.t() * R).
+arma::mat recoveredW = arma::chol(md.Q(), "lower");
+// A transformed dataset can be created with `(recoveredW * dataset)`.
```
---
diff --git a/doc/user/methods/lmnn.md b/doc/user/methods/lmnn.md
new file mode 100644
index 0000000000..322a5f683e
--- /dev/null
+++ b/doc/user/methods/lmnn.md
@@ -0,0 +1,454 @@
+## LMNN
+
+The `LMNN` class implements large margin nearest neighbor, which can be used
+as both a linear dimensionality reduction technique and a distance learning
+technique (also called metric learning). LMNN finds a linear transformation of
+the dataset that improves `k`-nearest-neighbor classification performance.
+
+#### Simple usage example:
+
+```c++
+// Learn a distance metric that improves kNN classification performance.
+
+// All data and labels are uniform random; 10 dimensional data, 5 classes.
+// Replace with a data::Load() call or similar for a real application.
+arma::mat dataset(10, 1000, arma::fill::randu); // 1000 points.
+arma::Row labels =
+ arma::randi>(1000, arma::distr_param(0, 4));
+
+mlpack::LMNN lmnn(3 /* neighbors to consider */); // Step 1: create object.
+arma::mat distance;
+lmnn.LearnDistance(dataset, labels, distance); // Step 2: learn distance.
+
+// `distance` can now be used as a transformation matrix for the data.
+arma::mat transformedData = distance * dataset;
+// Or, you can create a MahalanobisDistance to evaluate points in the
+// transformed dataset space.
+arma::mat q = distance.t() * distance;
+mlpack::MahalanobisDistance d(std::move(q));
+
+std::cout << "Distance between points 0 and 1:" << std::endl;
+std::cout << " - Before LMNN: "
+ << mlpack::EuclideanDistance::Evaluate(dataset.col(0), dataset.col(1))
+ << "." << std::endl;
+std::cout << " - After LMNN: "
+ << d.Evaluate(dataset.col(0), dataset.col(1)) << "." << std::endl;
+```
+More examples...
+
+#### Quick links:
+
+ * [Constructors](#constructors): create `LMNN` objects.
+ * [`LearnDistance()`](#learning-distances): learn distance metrics.
+ * [Other functionality](#other-functionality) for loading and saving.
+ * [Examples](#simple-examples) of simple usage and integration with other
+ techniques.
+
+#### See also:
+
+
+
+ * [mlpack distance metrics](../core.md#distances)
+ * [`NCA`](nca.md)
+ * [Metric learning on Wikipedia](https://en.wikipedia.org/wiki/Similarity_learning#Metric_learning)
+ * [Large margin nearest neighbor on Wikipedia](https://en.wikipedia.org/wiki/Large_margin_nearest_neighbor)
+ * [Distance metric learning for Large Margin Nearest Neighbor Classification (pdf)](https://proceedings.neurips.cc/paper_files/paper/2005/file/a7f592cef8b130a6967a90617db5681b-Paper.pdf)
+
+### Constructors
+
+ * `lmnn = LMNN(k, regularization=0.5, updateInterval=1)`
+ - Create an `LMNN` object considering the specified number `k` of neighbors.
+ - Optionally, specify the regularization to be applied to the LMNN cost
+ function (a `double`), and the number of iterations between recomputation
+ of neighbors (`updateInterval`, a `size_t`).
+
+---
+
+ * `lmnn = LMNN(k, regularization=0.5, updateInterval=1)`
+ * `lmnn = LMNN(k, regularization, updateInterval, distance)`
+ - Create an `LMNN` object using a custom
+ [`DistanceType`](../core.md#distances).
+ - `k` specifies the number of neighbors to consider.
+ - `regularization` specifies the regularization penalty to be applied to the
+ LMNN cost function (a `double`).
+ - `updateInterval` specifies the number of iterations between recomputation
+ of neighbors (a `size_t`).
+ - An instantiated `DistanceType` can optionally be passed with the `distance`
+ parameter.
+ - Using a custom `DistanceType` means that `LearnDistance()` will learn a
+ linear transformation for the data *in the metric space of the custom
+ `DistanceType`*.
+ * This means any learned distance may not necessarily improve
+ classification performance with the
+ [Euclidean distance](../core.md#lmetric).
+ * Instead, classification performance will be improved when the learned
+ distance is used with the given `DistanceType` only.
+ - Any mlpack `DistanceType` can be used as a drop-in replacement, or a
+ [custom `DistanceType`](../../developer/distances.md).
+ * A list of mlpack's provided distance metrics can be found
+ [here](../core.md#distances).
+ - ***Note: be sure that you understand the implications of a custom
+ `DistanceType` before using this version.***
+
+---
+
+***Notes***:
+
+ - A larger `k` will cause `LearnDistance()` to take longer to compute, but will
+ give more accurate results. It is generally suggested to keep `k` in roughly
+ the `3` to `5` range, depending on the dataset. Using `k = 1` can provide
+ fast convergence, but the learned distance metric may be of lower quality.
+
+ - `regularization` controls the balance between encouraging small distances for
+ points of the same class and penalizing small distances for points of
+ different classes. When `regularization` is increased, small distances for
+ points of different classes are further penalized.
+
+ - Setting `updateInterval` greater than `1` will allow the LMNN algorithm to
+ take multiple steps without the expensive recomputation of neighbors, but
+ this means that subsequent optimization steps may not be using the true
+ nearest neighbors.
+ * If using an SGD-like algorithm (i.e. an optimizer for a
+ [differentiable separable function](https://www.ensmallen.org/docs.html#differentiable-separable-functions)),
+ this can often be set to a relatively high value (100 is not unreasonable).
+ * If using an optimizer like L-BFGS (i.e. a full-batch optimizer for
+ [differentiable functions](https://www.ensmallen.org/docs.html#differentiable-functions)),
+ this should be kept relatively low (going above 10 is not advised).
+ * It is worth cross-validating different values of the parameter to see what
+ works for your dataset.
+
+---
+
+### Learning Distances
+
+Once an `LMNN` object has been created, the `LearnDistance()` method can be used
+to learn a distance.
+
+ * `lmnn.LearnDistance(data, labels, distance, [callbacks...])`
+ * `lmnn.LearnDistance(data, labels, distance, optimizer, [callbacks...])`
+ - Learn a distance metric on the given `data` and `labels`, filling
+ `distance` with a transformation matrix that can be used to map the data
+ into the space of the learned distance.
+ - Optionally, pass an instantiated
+ [ensmallen optimizer](https://www.ensmallen.org) and/or
+ [ensmallen callbacks](https://www.ensmallen.org/docs.html#callback-documentation)
+ to be used for the learning process.
+ - If no optimizer is passed,
+ [`ens::AMSGrad`](https://www.ensmallen.org/docs.html#amsgrad) is used.
+ - If `distance` already has size `r` x `data.n_rows` for some `r` less than
+ or equal to `data.n_rows`, it will be used as the starting point for
+ optimization. Otherwise, the identity matrix with size `data.n_rows` x
+ `data.n_rows` will be used.
+ - When optimization is complete, `distance` will have size `r` x
+ `data.n_rows`, where `r` is less than or equal to `data.n_rows`.
+ * *Note*: If `r < data.n_rows`, then LMNN has learned a distance metric
+ that also reduces the dimensionality of the data. See the
+ [last example](#simple-examples).
+
+To use `distance`, either:
+
+ * Compute a new transformed dataset as `distance * data`, or
+ * Use an instantiated [`MahalanobisDistance`](../core.md#mahalanobisdistance)
+ with `distance.t() * distance` as the `Q` matrix.
+
+See the [examples section](#simple-examples) for more details.
+
+#### `LearnDistance()` Parameters:
+
+| **name** | **type** | **description** |
+|----------|----------|-----------------|
+| `data` | [`arma::mat`](../matrices.md) | [Column-major](../matrices.md#representing-data-in-mlpack) training matrix. |
+| `labels` | [`arma::Row`](../matrices.md) | Training labels, [between `0` and `numClasses - 1`](../load_save.md#normalizing-labels) (inclusive). Should have length `data.n_cols`. |
+| `distance` | [`arma::mat`](../matrices.md) | Output matrix to store transformation matrix representing learned distance. |
+| `optimizer` | [any ensmallen optimizer](https://www.ensmallen.org) | Instantiated ensmallen optimizer for [differentiable functions](https://www.ensmallen.org/docs.html#differentiable-functions) or [differentiable separable functions](https://www.ensmallen.org/docs.html#differentiable-separable-functions). | `ens::AMSGrad()` |
+| `callbacks...` | [any set of ensmallen callbacks](https://www.ensmallen.org/docs.html#callback-documentation) | Optional callbacks for the ensmallen optimizer, such as e.g. `ens::ProgressBar()`, `ens::Report()`, or others. | _(N/A)_ |
+
+***Note***: any matrix type can be used for `data` and `distance`, so long as
+that type implements the Armadillo API. So, e.g., `arma::fmat` can be used.
+
+### Other Functionality
+
+ * An `LMNN` object can be serialized with
+ [`data::Save()` and `data::Load()`](../load_save.md#mlpack-objects).
+ Note that this is only meaningful if a custom `DistanceType` is being used,
+ and that custom `DistanceType` has state to be saved.
+
+ * `lmnn.K()` returns the number of neighbors used by LMNN, and `lmnn.K() = k`
+ will set the number of neighbors to use to `k`.
+
+ * `lmnn.Regularization()` returns the current regularization value of the LMNN
+ object (as a `double`), and `lmnn.Regularization() = r` can be used to set
+ the regularization value to `r`.
+
+ * `lmnn.UpdateInterval()` returns the current number of iterations between
+ neighbor recomputation (as a `size_t`), and `lmnn.UpdateInterval() = i` sets
+ the number of iterations between neighbor recomputation to `i`.
+
+ * `lmnn.Distance()` will return the `DistanceType` being used for learning.
+ Unless a custom `DistanceType` was specified in the constructor,
+ this simply returns a [`SquaredEuclideanDistance`](../core.md#lmetric)
+ object.
+
+### Simple Examples
+
+Learn a distance metric to improve classification performance on the iris
+dataset, and show improved performance when using
+[`NaiveBayesClassifier`](naive_bayes_classifier.md).
+
+```c++
+// See https://datasets.mlpack.org/satellite.test.csv.
+// (We are using the test set here just because it is a little smaller and
+// we want this example to run quickly.)
+arma::mat dataset;
+mlpack::data::Load("satellite.test.csv", dataset, true);
+// See https://datasets.mlpack.org/satellite.test.labels.csv.
+arma::Row labels;
+mlpack::data::Load("satellite.test.labels.csv", labels, true);
+
+// Create an LMNN object using 5 nearest neighbors and learn a distance.
+arma::mat distance;
+mlpack::LMNN lmnn(5);
+lmnn.LearnDistance(dataset, labels, distance);
+
+// The distance matrix has size equal to the dimensionality of the data.
+std::cout << "Learned distance size: " << distance.n_rows << " x "
+ << distance.n_cols << "." << std::endl;
+
+// Learn a NaiveBayesClassifier model on the data and print the performance.
+mlpack::NaiveBayesClassifier nbc1(dataset, labels, 2);
+arma::Row predictions;
+nbc1.Classify(dataset, predictions);
+std::cout << "Naive Bayes Classifier without LMNN: "
+ << arma::accu(labels == predictions) << " of " << labels.n_elem
+ << " correct." << std::endl;
+
+// Now transform the data and learn another NaiveBayesClassifier.
+arma::mat transformedDataset = distance * dataset;
+mlpack::NaiveBayesClassifier nbc2(transformedDataset, labels, 2);
+nbc2.Classify(transformedDataset, predictions);
+std::cout << "Naive Bayes Classifier with LMNN: "
+ << arma::accu(labels == predictions) << " of " << labels.n_elem
+ << " correct." << std::endl;
+```
+
+---
+
+Learn a distance metric on the vehicle dataset, using 32-bit floating point to
+represent the data and metric.
+
+```c++
+// See https://datasets.mlpack.org/vehicle.csv.
+arma::fmat dataset;
+mlpack::data::Load("vehicle.csv", dataset, true);
+
+// The labels are contained as the last row of the dataset.
+arma::Row labels =
+ arma::conv_to>::from(dataset.row(dataset.n_rows - 1));
+dataset.shed_row(dataset.n_rows - 1);
+
+// Create an LMNN object with k=1 and learn distance on float32 data.
+// Set updateInterval to a large value (100) because we are using the default
+// AMSGrad optimizer (which will take very many small steps).
+arma::fmat distance;
+mlpack::LMNN lmnn(1, 0.5, 100);
+
+lmnn.LearnDistance(dataset, labels, distance, ens::ProgressBar());
+
+// We want to compute six quantities:
+//
+// - Average distance to points of the same class before LMNN.
+// - Average distance to points of the same class after LMNN, using
+// MahalanobisDistance.
+// - Average distance to points of the same class after LMNN, using the
+// transformed dataset.
+//
+// - The same three quantities above, but for points of the other class.
+//
+// LMNN should reduce the average distance to points in the same class, while
+// increasing the average distance to points in other classes.
+float distSums[6] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
+size_t sameCount = 0;
+arma::fmat q = distance.t() * distance;
+mlpack::MahalanobisDistance md(std::move(q));
+arma::fmat transformedDataset = distance * dataset;
+for (size_t i = 1; i < dataset.n_cols; ++i)
+{
+ const double d1 = mlpack::EuclideanDistance::Evaluate(
+ dataset.col(0), dataset.col(i));
+ const double d2 = md.Evaluate(dataset.col(0), dataset.col(i));
+ const double d3 = mlpack::EuclideanDistance::Evaluate(
+ transformedDataset.col(0), transformedDataset.col(i));
+
+ // Determine whether the point has the same label as point 0.
+ if (labels[i] == labels[0])
+ {
+ distSums[0] += d1;
+ distSums[1] += d2;
+ distSums[2] += d3;
+ ++sameCount;
+ }
+ else
+ {
+ distSums[3] += d1;
+ distSums[4] += d2;
+ distSums[5] += d3;
+ }
+}
+
+// Turn the results into average distances across the class.
+distSums[0] /= sameCount;
+distSums[1] /= sameCount;
+distSums[2] /= sameCount;
+distSums[3] /= (dataset.n_cols - sameCount);
+distSums[4] /= (dataset.n_cols - sameCount);
+distSums[5] /= (dataset.n_cols - sameCount);
+
+// Print the results.
+std::cout << "Average distance between point 0 and other points of the same "
+ << "class:" << std::endl;
+std::cout << " - Before LMNN: " << distSums[0] << "."
+ << std::endl;
+std::cout << " - After LMNN (with MahalanobisDistance): " << distSums[1] << "."
+ << std::endl;
+std::cout << " - After LMNN (with transformed dataset): " << distSums[2] << "."
+ << std::endl;
+std::cout << std::endl;
+
+std::cout << "Average distance between point 0 and points of other classes: "
+ << std::endl;
+std::cout << " - Before LMNN: " << distSums[3] << "."
+ << std::endl;
+std::cout << " - After LMNN (with MahalanobisDistance): " << distSums[4] << "."
+ << std::endl;
+std::cout << " - After LMNN (with transformed dataset): " << distSums[5] << "."
+ << std::endl;
+std::cout << std::endl;
+
+std::cout << "Ratio of other-class to same-class distances:" << std::endl;
+std::cout << "(We expect this to go up.)" << std::endl;
+std::cout << " - Before LMNN: " << (distSums[3] / distSums[0]) << "."
+ << std::endl;
+std::cout << " - After LMNN: " << (distSums[5] / distSums[2]) << "."
+ << std::endl;
+```
+
+---
+
+Learn a distance metric on the iris dataset, using the L-BFGS optimizer with
+callbacks.
+
+```c++
+// See https://datasets.mlpack.org/iris.csv.
+arma::mat dataset;
+mlpack::data::Load("iris.csv", dataset, true);
+// See https://datasets.mlpack.org/iris.labels.csv.
+arma::Row labels;
+mlpack::data::Load("iris.labels.csv", labels, true);
+
+// Learn a distance with ensmallen's L-BFGS optimizer.
+ens::L_BFGS lbfgs;
+lbfgs.NumBasis() = 5;
+lbfgs.MaxIterations() = 1000;
+
+// Use 5 neighbors for LMNN, and leave updateInterval at the default of 1,
+// because we are using L-BFGS (a full-back optimizer).
+mlpack::LMNN lmnn(5);
+
+// Use a callback that prints a final optimization report.
+arma::mat distance;
+lmnn.LearnDistance(dataset, labels, distance, lbfgs, ens::Report());
+```
+
+---
+
+Learn a distance metric on the vehicle dataset, but instead of using the
+Euclidean distance as the underlying metric, use the Manhattan distance. This
+means that LMNN is optimizing k-NN performance under the Manhattan distance, not
+under the Euclidean distance.
+
+```c++
+// See https://datasets.mlpack.org/vehicle.csv.
+arma::mat dataset;
+mlpack::data::Load("vehicle.csv", dataset, true);
+
+// The labels are contained as the last row of the dataset.
+arma::Row labels =
+ arma::conv_to>::from(dataset.row(dataset.n_rows - 1));
+dataset.shed_row(dataset.n_rows - 1);
+
+// Create the LMNN object and optimize. Use k=3 and Nesterov momentum SGD,
+// printing a progress bar during optimization. Because Nesterov momentum SGD
+// is an ensmallen optimizer for differentiable separable functions, we increase
+// updateInterval to reduce the number of neighbor recomputations. We also set
+// the regularization parameter to 1.0 to increase the penalty for nearby
+// neighbors of a different class.
+mlpack::LMNN lmnn(3, 1.0, 100);
+arma::mat distance;
+ens::NesterovMomentumSGD opt(0.000001 /* step size */,
+ 32 /* batch size */,
+ 20 * dataset.n_cols /* 20 epochs */);
+lmnn.LearnDistance(dataset, labels, distance, opt, ens::ProgressBar());
+
+// Now inspect distances between points with the Euclidean distance and with the
+// inner product distance.
+arma::mat transformedDataset = distance * dataset;
+
+// Points 0 and 1 have the same label (0). See their original distance---with
+// both the Euclidean and Manhattan distances---and their transformed distances.
+// We expect these points to get closer together, in the Manhattan distance.
+const double d1 = mlpack::ManhattanDistance::Evaluate(
+ dataset.col(0), dataset.col(1));
+const double d2 = mlpack::ManhattanDistance::Evaluate(
+ transformedDataset.col(0), transformedDataset.col(1));
+
+std::cout << "Distance between points 0 and 1 (same class):" << std::endl;
+std::cout << " - Manhattan distance:" << std::endl;
+std::cout << " * Before LMNN: " << d1 << std::endl;
+std::cout << " * After LMNN: " << d2 << std::endl;
+std::cout << std::endl;
+
+// Point 3 has a different label. We therefore expect this point to get further
+// from point 0 with the Manhattan distance, but not necessarily with the
+// Euclidean distance.
+const double d3 = mlpack::ManhattanDistance::Evaluate(
+ dataset.col(0), dataset.col(3));
+const double d4 = mlpack::ManhattanDistance::Evaluate(
+ transformedDataset.col(0), transformedDataset.col(3));
+
+std::cout << "Distance between points 0 and 3 (different class):" << std::endl;
+std::cout << " - Manhattan distance:" << std::endl;
+std::cout << " * Before LMNN: " << d3 << std::endl;
+std::cout << " * After LMNN: " << d4 << std::endl;
+
+// Note that point 3 has been moved further away from point 0 than point 1.
+```
+
+---
+
+Learn a distance metric while also performing dimensionality reduction, reducing
+the dimensionality of the satellite dataset by 3 dimensions.
+
+```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.labels.csv.
+arma::Row labels;
+mlpack::data::Load("satellite.train.labels.csv", labels, true);
+
+// Use a random initialization for the distance transformation, with the
+// specified output dimensionality.
+arma::mat distance(dataset.n_rows - 3, dataset.n_rows, arma::fill::randu);
+mlpack::LMNN lmnn(3);
+ens::L_BFGS opt;
+opt.MaxIterations() = 10; // You may want more in a real application.
+lmnn.LearnDistance(dataset, labels, distance, opt, ens::Report());
+
+// Now transform the dataset.
+arma::mat transformedData = distance * dataset;
+
+std::cout << "Original data has size " << dataset.n_rows << " x "
+ << dataset.n_cols << "." << std::endl;
+std::cout << "Transformed data has size " << transformedData.n_rows << " x "
+ << transformedData.n_cols << "." << std::endl;
+```
diff --git a/doc/user/methods/nca.md b/doc/user/methods/nca.md
new file mode 100644
index 0000000000..4870907af8
--- /dev/null
+++ b/doc/user/methods/nca.md
@@ -0,0 +1,440 @@
+## NCA
+
+The `NCA` class implements neighborhood components analysis, which can be used
+as both a linear dimensionality reduction technique and a distance learning
+technique (also called metric learning). Neighborhood components analysis finds
+a linear transformation of the dataset that improves `k`-nearest-neighbor
+classification performance.
+
+Note that `NCA` is a computationally intensive technique (each optimization
+iteration takes time quadratic in the data size!), and may be slow to run even
+for datasets of only moderate size. See [`LMNN`](lmnn.md) for another distance
+learning technique that scales better to larger datasets.
+
+#### Simple usage example:
+
+```c++
+// Learn a distance metric that improves kNN classification performance.
+
+// All data and labels are uniform random; 10 dimensional data, 5 classes.
+// Replace with a data::Load() call or similar for a real application.
+arma::mat dataset(10, 1000, arma::fill::randu); // 1000 points.
+arma::Row labels =
+ arma::randi>(1000, arma::distr_param(0, 4));
+
+mlpack::NCA nca; // Step 1: create object.
+arma::mat distance;
+nca.LearnDistance(dataset, labels, distance); // Step 2: learn distance.
+
+// `distance` can now be used as a transformation matrix for the data.
+arma::mat transformedData = distance * dataset;
+// Or, you can create a MahalanobisDistance to evaluate points in the
+// transformed dataset space.
+arma::mat q = distance.t() * distance;
+mlpack::MahalanobisDistance d(std::move(q));
+
+std::cout << "Distance between points 0 and 1:" << std::endl;
+std::cout << " - Before NCA: "
+ << mlpack::EuclideanDistance::Evaluate(dataset.col(0), dataset.col(1))
+ << "." << std::endl;
+std::cout << " - After NCA: "
+ << d.Evaluate(dataset.col(0), dataset.col(1)) << "." << std::endl;
+```
+More examples...
+
+#### Quick links:
+
+ * [Constructors](#constructors): create `NCA` objects.
+ * [`LearnDistance()`](#learning-distances): learn distance metrics.
+ * [Other functionality](#other-functionality) for loading and saving.
+ * [Examples](#simple-examples) of simple usage and integration with other
+ techniques.
+
+#### See also:
+
+
+
+ * [mlpack distance metrics](../core.md#distances)
+ * [`LMNN`](lmnn.md)
+ * [Metric learning on Wikipedia](https://en.wikipedia.org/wiki/Similarity_learning#Metric_learning)
+ * [Neighborhood Components Analysis on Wikipedia](https://en.wikipedia.org/wiki/Neighbourhood_components_analysis)
+ * [Neighbourhood Components Analysis (pdf)](https://proceedings.neurips.cc/paper_files/paper/2004/file/42fe880812925e520249e808937738d2-Paper.pdf)
+
+### Constructors
+
+ * `nca = NCA()`
+ - Create an `NCA` object with default parameters.
+
+---
+
+ * `nca = NCA()`
+ * `nca = NCA(distance)`
+ - Create an `NCA` object using a custom
+ [`DistanceType`](../core.md#distances).
+ - An instantiated `DistanceType` can optionally be passed with the `distance`
+ parameter.
+ - Using a custom `DistanceType` means that `LearnDistance()` will learn a
+ linear transformation for the data *in the metric space of the custom
+ `DistanceType`*.
+ * This means any learned distance may not necessarily improve
+ classification performance with the
+ [Euclidean distance](../core.md#lmetric).
+ * Instead, classification performance will be improved when the learned
+ distance is used with the given `DistanceType` only.
+ - Any mlpack `DistanceType` can be used as a drop-in replacement, or a
+ [custom `DistanceType`](../../developer/distances.md).
+ * A list of mlpack's provided distance metrics can be found
+ [here](../core.md#distances).
+ - ***Note: be sure that you understand the implications of a custom
+ `DistanceType` before using this version.***
+
+---
+
+### Learning Distances
+
+Once an `NCA` object has been created, the `LearnDistance()` method can be used
+to learn a distance.
+
+ * `nca.LearnDistance(data, labels, distance, [callbacks...])`
+ * `nca.LearnDistance(data, labels, distance, optimizer, [callbacks...])`
+ - Learn a distance metric on the given `data` and `labels`, filling
+ `distance` with a transformation matrix that can be used to map the data
+ into the space of the learned distance.
+ - Optionally, pass an instantiated
+ [ensmallen optimizer](https://www.ensmallen.org) and/or
+ [ensmallen callbacks](https://www.ensmallen.org/docs.html#callback-documentation)
+ to be used for the learning process.
+ - If `distance` already has size `r` x `data.n_rows` for some `r` less than
+ or equal to `data.n_rows`, it will be used as the starting point for
+ optimization. Otherwise, the identity matrix with size `data.n_rows` x
+ `data.n_rows` will be used.
+ - When optimization is complete, `distance` will have size `r` x
+ `data.n_rows`, where `r` is less than or equal to `data.n_rows`.
+ * *Note*: If `r < data.n_rows`, then NCA has learned a distance metric that
+ also reduces the dimensionality of the data. See the
+ [last example](#simple-examples).
+
+To use `distance`, either:
+
+ * Compute a new transformed dataset as `distance * data`, or
+ * Use an instantiated [`MahalanobisDistance`](../core.md#mahalanobisdistance)
+ with `distance.t() * distance` as the `Q` matrix.
+
+See the [examples section](#simple-examples) for more details.
+
+***Caveat:*** NCA operates by repeatedly computing expressions of the form
+`exp(-distance.Evaluate(data.col(i), data.col(j)))` (that is, the exponential of
+the negative distance between two points). When distances are very large, this
+***quantity underflows to 0*** and results will not be reasonable.
+
+ - This situation can be detected, usually by a result where `distance` is equal
+ to the identity matrix.
+ - Alternately, if the [`ens::ProgressBar()`
+ callback](https://www.ensmallen.org/docs.html#progressbar) is used, a loss of
+ 0 often means this situation has occurred.
+ - To mitigate the problem, consider scaling data such that the maximum pairwise
+ distance is less than 10. See the [simple examples](#simple-examples) that
+ use the `vehicle` dataset.
+
+#### `LearnDistance()` Parameters:
+
+| **name** | **type** | **description** |
+|----------|----------|-----------------|
+| `data` | [`arma::mat`](../matrices.md) | [Column-major](../matrices.md#representing-data-in-mlpack) training matrix. |
+| `labels` | [`arma::Row`](../matrices.md) | Training labels, [between `0` and `numClasses - 1`](../load_save.md#normalizing-labels) (inclusive). Should have length `data.n_cols`. |
+| `distance` | [`arma::mat`](../matrices.md) | Output matrix to store transformation matrix representing learned distance. |
+| `optimizer` | [any ensmallen optimizer](https://www.ensmallen.org) | Instantiated ensmallen optimizer for [differentiable functions](https://www.ensmallen.org/docs.html#differentiable-functions) or [differentiable separable functions](https://www.ensmallen.org/docs.html#differentiable-separable-functions). | `ens::StandardSGD()` |
+| `callbacks...` | [any set of ensmallen callbacks](https://www.ensmallen.org/docs.html#callback-documentation) | Optional callbacks for the ensmallen optimizer, such as e.g. `ens::ProgressBar()`, `ens::Report()`, or others. | _(N/A)_ |
+
+***Note***: any matrix type can be used for `data` and `distance`, so long as
+that type implements the Armadillo API. So, e.g., `arma::fmat` can be used.
+
+### Other Functionality
+
+ * An `NCA` object can be serialized with
+ [`data::Save()` and `data::Load()`](../load_save.md#mlpack-objects).
+ Note that this is only meaningful if a custom `DistanceType` is being used,
+ and that custom `DistanceType` has state to be saved.
+
+ * `nca.Distance()` will return the `DistanceType` being used for learning.
+ Unless a custom `DistanceType` was specified in the constructor,
+ this simply returns a [`SquaredEuclideanDistance`](../core.md#lmetric)
+ object.
+
+### Simple Examples
+
+Learn a distance metric to improve classification performance on the iris
+dataset, and show improved performance when using
+[`NaiveBayesClassifier`](naive_bayes_classifier.md).
+
+```c++
+// See https://datasets.mlpack.org/iris.csv.
+arma::mat dataset;
+mlpack::data::Load("iris.csv", dataset, true);
+// See https://datasets.mlpack.org/iris.labels.csv.
+arma::Row labels;
+mlpack::data::Load("iris.labels.csv", labels, true);
+
+// Create an NCA object and learn a distance.
+arma::mat distance;
+mlpack::NCA nca;
+nca.LearnDistance(dataset, labels, distance);
+
+// The distance matrix has size equal to the dimensionality of the data.
+std::cout << "Learned distance size: " << distance.n_rows << " x "
+ << distance.n_cols << "." << std::endl;
+
+// Learn a NaiveBayesClassifier model on the data and print the performance.
+mlpack::NaiveBayesClassifier nbc1(dataset, labels, 3);
+arma::Row predictions;
+nbc1.Classify(dataset, predictions);
+std::cout << "Naive Bayes Classifier without NCA: "
+ << arma::accu(labels == predictions) << " of " << labels.n_elem
+ << " correct." << std::endl;
+
+// Now transform the data and learn another NaiveBayesClassifier.
+arma::mat transformedDataset = distance * dataset;
+mlpack::NaiveBayesClassifier nbc2(transformedDataset, labels, 3);
+nbc2.Classify(transformedDataset, predictions);
+std::cout << "Naive Bayes Classifier with NCA: "
+ << arma::accu(labels == predictions) << " of " << labels.n_elem
+ << " correct." << std::endl;
+```
+
+---
+
+Learn a distance metric on the ionosphere dataset, using 32-bit floating point
+to represent the data and metric.
+
+```c++
+// See https://datasets.mlpack.org/ionosphere.csv.
+arma::fmat dataset;
+mlpack::data::Load("ionosphere.csv", dataset, true);
+
+// The labels are the last row of the dataset.
+arma::Row labels =
+ arma::conv_to>::from(dataset.row(dataset.n_rows - 1));
+dataset.shed_row(dataset.n_rows - 1);
+
+// Create an NCA object and learn distance on float32 data.
+// To keep computation time down, we use an instantiated optimizer that will
+// only perform 10 epochs of training. (In a real application you may want to
+// train for longer!)
+arma::fmat distance;
+mlpack::NCA nca;
+
+ens::StandardSGD opt;
+opt.MaxIterations() = 10 * dataset.n_cols;
+nca.LearnDistance(dataset, labels, distance, opt, ens::ProgressBar());
+
+// We want to compute six quantities:
+//
+// - Average distance to points of the same class before NCA.
+// - Average distance to points of the same class after NCA, using
+// MahalanobisDistance.
+// - Average distance to points of the same class after NCA, using the
+// transformed dataset.
+//
+// - The same three quantities above, but for points of the other class.
+//
+// NCA should reduce the average distance to points in the same class, while
+// increasing the average distance to points in other classes.
+float distSums[6] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
+size_t sameCount = 0;
+arma::fmat q = distance.t() * distance;
+mlpack::MahalanobisDistance md(std::move(q));
+arma::fmat transformedDataset = distance * dataset;
+for (size_t i = 1; i < dataset.n_cols; ++i)
+{
+ const double d1 = mlpack::EuclideanDistance::Evaluate(
+ dataset.col(0), dataset.col(i));
+ const double d2 = md.Evaluate(dataset.col(0), dataset.col(i));
+ const double d3 = mlpack::EuclideanDistance::Evaluate(
+ transformedDataset.col(0), transformedDataset.col(i));
+
+ // Determine whether the point has the same label as point 0.
+ if (labels[i] == labels[0])
+ {
+ distSums[0] += d1;
+ distSums[1] += d2;
+ distSums[2] += d3;
+ ++sameCount;
+ }
+ else
+ {
+ distSums[3] += d1;
+ distSums[4] += d2;
+ distSums[5] += d3;
+ }
+}
+
+// Turn the results into average distances across the class.
+distSums[0] /= sameCount;
+distSums[1] /= sameCount;
+distSums[2] /= sameCount;
+distSums[3] /= (dataset.n_cols - sameCount);
+distSums[4] /= (dataset.n_cols - sameCount);
+distSums[5] /= (dataset.n_cols - sameCount);
+
+// Print the results.
+std::cout << "Average distance between point 0 and other points of the same "
+ << "class:" << std::endl;
+std::cout << " - Before NCA: " << distSums[0] << "."
+ << std::endl;
+std::cout << " - After NCA (with MahalanobisDistance): " << distSums[1] << "."
+ << std::endl;
+std::cout << " - After NCA (with transformed dataset): " << distSums[2] << "."
+ << std::endl;
+std::cout << std::endl;
+
+std::cout << "Average distance between point 0 and points of other classes: "
+ << std::endl;
+std::cout << " - Before NCA: " << distSums[3] << "."
+ << std::endl;
+std::cout << " - After NCA (with MahalanobisDistance): " << distSums[4] << "."
+ << std::endl;
+std::cout << " - After NCA (with transformed dataset): " << distSums[5] << "."
+ << std::endl;
+std::cout << std::endl;
+
+std::cout << "Ratio of other-class to same-class distances:" << std::endl;
+std::cout << "(We expect this to go up.)" << std::endl;
+std::cout << " - Before NCA: " << (distSums[3] / distSums[0]) << "."
+ << std::endl;
+std::cout << " - After NCA: " << (distSums[5] / distSums[2]) << "."
+ << std::endl;
+```
+
+---
+
+Learn a distance metric on the iris dataset, using the L-BFGS optimizer with
+callbacks.
+
+```c++
+// See https://datasets.mlpack.org/iris.csv.
+arma::mat dataset;
+mlpack::data::Load("iris.csv", dataset, true);
+// See https://datasets.mlpack.org/iris.labels.csv.
+arma::Row labels;
+mlpack::data::Load("iris.labels.csv", labels, true);
+
+// Learn a distance with ensmallen's L-BFGS optimizer.
+ens::L_BFGS lbfgs;
+lbfgs.NumBasis() = 5;
+lbfgs.MaxIterations() = 1000;
+
+arma::mat distance;
+mlpack::NCA nca;
+
+// Use a callback that prints a final optimization report.
+nca.LearnDistance(dataset, labels, distance, lbfgs, ens::Report());
+```
+
+---
+
+
+
+Learn a distance metric on the vehicle dataset, but instead of using the
+Euclidean distance as the underlying metric, use the Manhattan distance. This
+means that NCA is optimizing k-NN performance under the Manhattan distance, not
+under the Euclidean distance.
+
+```c++
+// See https://datasets.mlpack.org/vehicle.csv.
+arma::mat dataset;
+mlpack::data::Load("vehicle.csv", dataset, true);
+
+// The labels are contained as the last row of the dataset.
+arma::Row labels =
+ arma::conv_to>::from(dataset.row(dataset.n_rows - 1));
+dataset.shed_row(dataset.n_rows - 1);
+
+// Because typical distances between points in the vehicle dataset are large,
+// we will center the dataset and scale it to have points in the unit ball.
+// (That is, all points will have values in each dimension between -1 and 1.)
+// This means that the maximum pairwise distance is 2.
+dataset.each_col() -= arma::mean(dataset, 1);
+dataset /= arma::max(arma::max(arma::abs(dataset)));
+
+// Create the NCA object and optimize. Use Nesterov momentum SGD, printing a
+// progress bar during optimization.
+mlpack::NCA nca;
+arma::mat distance;
+ens::NesterovMomentumSGD opt(0.01 /* step size */,
+ 32 /* batch size */,
+ 20 * dataset.n_cols /* 20 epochs */);
+nca.LearnDistance(dataset, labels, distance, opt, ens::ProgressBar());
+
+// Now inspect distances between points with the Euclidean distance and with the
+// inner product distance.
+arma::mat transformedDataset = distance * dataset;
+
+// Points 0 and 1 have the same label (0). See their original distance---with
+// both the Euclidean and Manhattan distances---and their transformed distances.
+// We expect these points to get closer together, in the Manhattan distance.
+const double d1 = mlpack::ManhattanDistance::Evaluate(
+ dataset.col(0), dataset.col(1));
+const double d2 = mlpack::ManhattanDistance::Evaluate(
+ transformedDataset.col(0), transformedDataset.col(1));
+
+std::cout << "Distance between points 0 and 1 (same class):" << std::endl;
+std::cout << " - Manhattan distance:" << std::endl;
+std::cout << " * Before NCA: " << d1 << std::endl;
+std::cout << " * After NCA: " << d2 << std::endl;
+std::cout << std::endl;
+
+// Point 3 has a different label. We therefore expect this point to get further
+// from point 0 with the Manhattan distance, but not necessarily with the
+// Euclidean distance.
+const double d3 = mlpack::ManhattanDistance::Evaluate(
+ dataset.col(0), dataset.col(3));
+const double d4 = mlpack::ManhattanDistance::Evaluate(
+ transformedDataset.col(0), transformedDataset.col(3));
+
+std::cout << "Distance between points 0 and 3 (different class):" << std::endl;
+std::cout << " - Manhattan distance:" << std::endl;
+std::cout << " * Before NCA: " << d3 << std::endl;
+std::cout << " * After NCA: " << d4 << std::endl;
+
+// Note that point 3 has been moved further away from point 0 than point 1.
+```
+
+---
+
+Learn a distance metric while also performing dimensionality reduction, reducing
+the dimensionality of the vehicle dataset by 2 dimensions.
+
+```c++
+// See https://datasets.mlpack.org/vehicle.csv.
+arma::mat dataset;
+mlpack::data::Load("vehicle.csv", dataset, true);
+
+// The labels are contained as the last row of the dataset.
+arma::Row labels =
+ arma::conv_to>::from(dataset.row(dataset.n_rows - 1));
+dataset.shed_row(dataset.n_rows - 1);
+
+// Because typical distances between points in the vehicle dataset are large,
+// we will center the dataset and scale it to have points in the unit ball.
+// (That is, all points will have values in each dimension between -1 and 1.)
+// This means that the maximum pairwise distance is 2.
+dataset.each_col() -= arma::mean(dataset, 1);
+dataset /= arma::max(arma::max(arma::abs(dataset)));
+
+// Use a random initialization for the distance transformation, with the
+// specified output dimensionality.
+arma::mat distance(dataset.n_rows - 2, dataset.n_rows, arma::fill::randu);
+mlpack::NCA nca;
+ens::L_BFGS opt;
+opt.MaxIterations() = 10; // You may want more in a real application.
+nca.LearnDistance(dataset, labels, distance, opt);
+
+// Now transform the dataset.
+arma::mat transformedData = distance * dataset;
+
+std::cout << std::endl << std::endl;
+std::cout << "Original data has size " << dataset.n_rows << " x "
+ << dataset.n_cols << "." << std::endl;
+std::cout << "Transformed data has size " << transformedData.n_rows << " x "
+ << transformedData.n_cols << "." << std::endl;
+```
diff --git a/src/mlpack/core.hpp b/src/mlpack/core.hpp
index 73eeaaece4..7205587ab5 100644
--- a/src/mlpack/core.hpp
+++ b/src/mlpack/core.hpp
@@ -37,6 +37,7 @@
// Now the core mlpack classes.
#include
#include
+#include
#include
#include
#include
diff --git a/src/mlpack/core/util/first_element_is_arma.hpp b/src/mlpack/core/util/first_element_is_arma.hpp
new file mode 100644
index 0000000000..60adcc0fe3
--- /dev/null
+++ b/src/mlpack/core/util/first_element_is_arma.hpp
@@ -0,0 +1,44 @@
+/**
+ * @file core/util/first_element_is_arma.hpp
+ * @author Ryan Curtin
+ *
+ * Utility struct to detect whether the first element in a parameter pack is an
+ * Armadillo type.
+ */
+#ifndef MLPACK_CORE_UTIL_FIRST_ELEMENT_IS_ARMA_HPP
+#define MLPACK_CORE_UTIL_FIRST_ELEMENT_IS_ARMA_HPP
+
+#include
+
+namespace mlpack {
+
+// This utility struct returns the first type of a parameter pack.
+template
+struct First
+{
+ typedef void type;
+};
+
+// This matches whenever CallbackTypes has one or more elements.
+template
+struct First
+{
+ typedef T type;
+};
+
+// This utility template struct detects whether the first element in a
+// parameter pack is an Armadillo type. It is entirely for the deprecated
+// constructor below and can be removed when that is removed during the
+// release of mlpack 5.0.0.
+template
+struct FirstElementIsArma
+{
+ static constexpr bool value = arma::is_arma_type<
+ typename std::remove_reference<
+ typename First::type
+ >::type>::value;
+};
+
+}
+
+#endif
diff --git a/src/mlpack/methods/lmnn/constraints.hpp b/src/mlpack/methods/lmnn/constraints.hpp
index 082b8961ed..7704d239fe 100644
--- a/src/mlpack/methods/lmnn/constraints.hpp
+++ b/src/mlpack/methods/lmnn/constraints.hpp
@@ -27,12 +27,25 @@ namespace mlpack {
* data point) and Triplets() (Generates sets of {dataset, target neighbors,
* impostors} tripltets.)
*/
-template
+template,
+ typename DistanceType = SquaredEuclideanDistance>
class Constraints
{
public:
//! Convenience typedef.
- typedef NeighborSearch KNN;
+ typedef NeighborSearch KNN;
+
+ // Convenience typedef for element type of data.
+ typedef typename MatType::elem_type ElemType;
+ // Convenience typedef for column vector of data.
+ typedef typename GetColType::type VecType;
+ // Convenience typedef for cube of data.
+ typedef typename GetCubeType::type CubeType;
+ // Convenience typedef for dense matrix of indices.
+ typedef typename GetUDenseMatType::type UMatType;
+ // Convenience typedef for dense vector of indices.
+ typedef typename GetColType::type UVecType;
/**
* Constructor for creating a Constraints instance.
@@ -41,8 +54,8 @@ class Constraints
* @param labels Input dataset labels.
* @param k Number of target neighbors, impostors & triplets.
*/
- Constraints(const arma::mat& dataset,
- const arma::Row& labels,
+ Constraints(const MatType& dataset,
+ const LabelsType& labels,
const size_t k);
/**
@@ -54,10 +67,10 @@ class Constraints
* @param labels Input dataset labels.
* @param norms Input dataset norms.
*/
- void TargetNeighbors(arma::Mat& outputMatrix,
- const arma::mat& dataset,
- const arma::Row& labels,
- const arma::vec& norms);
+ void TargetNeighbors(UMatType& outputMatrix,
+ const MatType& dataset,
+ const LabelsType& labels,
+ const VecType& norms);
/**
* Calculates k similar labeled nearest neighbors for a batch of dataset and
@@ -70,10 +83,10 @@ class Constraints
* @param begin Index of the initial point of dataset.
* @param batchSize Number of data points to use.
*/
- void TargetNeighbors(arma::Mat& outputMatrix,
- const arma::mat& dataset,
- const arma::Row& labels,
- const arma::vec& norms,
+ void TargetNeighbors(UMatType& outputMatrix,
+ const MatType& dataset,
+ const LabelsType& labels,
+ const VecType& norms,
const size_t begin,
const size_t batchSize);
@@ -86,10 +99,10 @@ class Constraints
* @param labels Input dataset labels.
* @param norms Input dataset norms.
*/
- void Impostors(arma::Mat& outputMatrix,
- const arma::mat& dataset,
- const arma::Row& labels,
- const arma::vec& norms);
+ void Impostors(UMatType& outputMatrix,
+ const MatType& dataset,
+ const LabelsType& labels,
+ const VecType& norms);
/**
* Calculates k differently labeled nearest neighbors & distances to
@@ -101,11 +114,11 @@ class Constraints
* @param labels Input dataset labels.
* @param norms Input dataset norms.
*/
- void Impostors(arma::Mat& outputNeighbors,
- arma::mat& outputDistance,
- const arma::mat& dataset,
- const arma::Row& labels,
- const arma::vec& norms);
+ void Impostors(UMatType& outputNeighbors,
+ MatType& outputDistance,
+ const MatType& dataset,
+ const LabelsType& labels,
+ const VecType& norms);
/**
* Calculates k differently labeled nearest neighbors for a batch of dataset
@@ -118,10 +131,10 @@ class Constraints
* @param begin Index of the initial point of dataset.
* @param batchSize Number of data points to use.
*/
- void Impostors(arma::Mat& outputMatrix,
- const arma::mat& dataset,
- const arma::Row& labels,
- const arma::vec& norms,
+ void Impostors(UMatType& outputMatrix,
+ const MatType& dataset,
+ const LabelsType& labels,
+ const VecType& norms,
const size_t begin,
const size_t batchSize);
@@ -137,11 +150,11 @@ class Constraints
* @param begin Index of the initial point of dataset.
* @param batchSize Number of data points to use.
*/
- void Impostors(arma::Mat& outputNeighbors,
- arma::mat& outputDistance,
- const arma::mat& dataset,
- const arma::Row& labels,
- const arma::vec& norms,
+ void Impostors(UMatType& outputNeighbors,
+ MatType& outputDistance,
+ const MatType& dataset,
+ const LabelsType& labels,
+ const VecType& norms,
const size_t begin,
const size_t batchSize);
@@ -158,12 +171,12 @@ class Constraints
* @param points Indices of data points to calculate impostors on.
* @param numPoints Number of points to actually calculate impostors on.
*/
- void Impostors(arma::Mat& outputNeighbors,
- arma::mat& outputDistance,
- const arma::mat& dataset,
- const arma::Row& labels,
- const arma::vec& norms,
- const arma::uvec& points,
+ void Impostors(UMatType& outputNeighbors,
+ MatType& outputDistance,
+ const MatType& dataset,
+ const LabelsType& labels,
+ const VecType& norms,
+ const UVecType& points,
const size_t numPoints);
/**
@@ -175,10 +188,10 @@ class Constraints
* @param labels Input dataset labels.
* @param norms Input dataset norms.
*/
- void Triplets(arma::Mat& outputMatrix,
- const arma::mat& dataset,
- const arma::Row& labels,
- const arma::vec& norms);
+ void Triplets(UMatType& outputMatrix,
+ const MatType& dataset,
+ const LabelsType& labels,
+ const VecType& norms);
//! Get the number of target neighbors (k).
const size_t& K() const { return k; }
@@ -195,13 +208,13 @@ class Constraints
size_t k;
//! Store unique labels.
- arma::Row uniqueLabels;
+ LabelsType uniqueLabels;
//! Store indices of data points having similar label.
- std::vector indexSame;
+ std::vector indexSame;
//! Store indices of data points having different label.
- std::vector indexDiff;
+ std::vector indexDiff;
//! False if nothing has ever been precalculated.
bool precalculated;
@@ -210,15 +223,15 @@ class Constraints
* Precalculate the unique labels, and indices of similar
* and different datapoints on the basis of labels.
*/
- inline void Precalculate(const arma::Row& labels);
+ inline void Precalculate(const LabelsType& labels);
/**
* Re-order neighbors on the basis of increasing norm in case
* of ties among distances.
*/
- inline void ReorderResults(const arma::mat& distances,
- arma::Mat& neighbors,
- const arma::vec& norms);
+ inline void ReorderResults(const MatType& distances,
+ UMatType& neighbors,
+ const VecType& norms);
};
} // namespace mlpack
diff --git a/src/mlpack/methods/lmnn/constraints_impl.hpp b/src/mlpack/methods/lmnn/constraints_impl.hpp
index 6f227fd970..27ed28417a 100644
--- a/src/mlpack/methods/lmnn/constraints_impl.hpp
+++ b/src/mlpack/methods/lmnn/constraints_impl.hpp
@@ -17,10 +17,10 @@
namespace mlpack {
-template
-Constraints::Constraints(
- const arma::mat& /* dataset */,
- const arma::Row& labels,
+template
+Constraints::Constraints(
+ const MatType& /* dataset */,
+ const LabelsType& labels,
const size_t k) :
k(k),
precalculated(false)
@@ -36,11 +36,11 @@ Constraints::Constraints(
}
}
-template
-inline void Constraints::ReorderResults(
- const arma::mat& distances,
- arma::Mat& neighbors,
- const arma::vec& norms)
+template
+inline void Constraints::ReorderResults(
+ const MatType& distances,
+ UMatType& neighbors,
+ const VecType& norms)
{
// Shortcut...
if (neighbors.n_rows == 1)
@@ -64,24 +64,21 @@ inline void Constraints::ReorderResults(
if (start != end)
{
// We must sort these elements by norm.
- arma::Col newNeighbors =
- neighbors.col(i).subvec(start, end - 1);
- arma::uvec indices = ConvTo::From(newNeighbors);
-
- arma::uvec order = arma::sort_index(norms.elem(indices));
- neighbors.col(i).subvec(start, end - 1) =
- newNeighbors.elem(order);
+ UVecType indices = neighbors.col(i).subvec(start, end - 1);
+ UVecType order = arma::sort_index(norms.elem(indices));
+ neighbors.col(i).subvec(start, end - 1) = indices.elem(order);
}
}
}
}
// Calculates k similar labeled nearest neighbors.
-template
-void Constraints::TargetNeighbors(arma::Mat& outputMatrix,
- const arma::mat& dataset,
- const arma::Row& labels,
- const arma::vec& norms)
+template
+void Constraints::TargetNeighbors(
+ UMatType& outputMatrix,
+ const MatType& dataset,
+ const LabelsType& labels,
+ const VecType& norms)
{
// Perform pre-calculation. If neccesary.
Precalculate(labels);
@@ -89,8 +86,8 @@ void Constraints::TargetNeighbors(arma::Mat& outputMatrix,
// KNN instance.
KNN knn;
- arma::Mat neighbors;
- arma::mat distances;
+ UMatType neighbors;
+ MatType distances;
for (size_t i = 0; i < uniqueLabels.n_cols; ++i)
{
@@ -114,28 +111,29 @@ void Constraints::TargetNeighbors(arma::Mat& outputMatrix,
// Calculates k similar labeled nearest neighbors on a
// batch of data points.
-template
-void Constraints::TargetNeighbors(arma::Mat& outputMatrix,
- const arma::mat& dataset,
- const arma::Row& labels,
- const arma::vec& norms,
- const size_t begin,
- const size_t batchSize)
+template
+void Constraints::TargetNeighbors(
+ UMatType& outputMatrix,
+ const MatType& dataset,
+ const LabelsType& labels,
+ const VecType& norms,
+ const size_t begin,
+ const size_t batchSize)
{
// Perform pre-calculation. If neccesary.
Precalculate(labels);
- arma::mat subDataset = dataset.cols(begin, begin + batchSize - 1);
- arma::Row sublabels = labels.cols(begin, begin + batchSize - 1);
+ MatType subDataset = dataset.cols(begin, begin + batchSize - 1);
+ LabelsType sublabels = labels.cols(begin, begin + batchSize - 1);
// KNN instance.
KNN knn;
- arma::Mat neighbors;
- arma::mat distances;
+ UMatType neighbors;
+ MatType distances;
// Vectors to store indices.
- arma::uvec subIndexSame;
+ UVecType subIndexSame;
for (size_t i = 0; i < uniqueLabels.n_cols; ++i)
{
@@ -161,11 +159,12 @@ void Constraints::TargetNeighbors(arma::Mat& outputMatrix,
}
// Calculates k differently labeled nearest neighbors.
-template
-void Constraints::Impostors(arma::Mat& outputMatrix,
- const arma::mat& dataset,
- const arma::Row& labels,
- const arma::vec& norms)
+template
+void Constraints::Impostors(
+ UMatType& outputMatrix,
+ const MatType& dataset,
+ const LabelsType& labels,
+ const VecType& norms)
{
// Perform pre-calculation. If neccesary.
Precalculate(labels);
@@ -173,8 +172,8 @@ void Constraints::Impostors(arma::Mat& outputMatrix,
// KNN instance.
KNN knn;
- arma::Mat neighbors;
- arma::mat distances;
+ UMatType neighbors;
+ MatType distances;
for (size_t i = 0; i < uniqueLabels.n_cols; ++i)
{
@@ -198,12 +197,13 @@ void Constraints::Impostors(arma::Mat& outputMatrix,
// Calculates k differently labeled nearest neighbors. The function
// writes back calculated neighbors & distances to passed matrices.
-template
-void Constraints::Impostors(arma::Mat& outputNeighbors,
- arma::mat& outputDistance,
- const arma::mat& dataset,
- const arma::Row& labels,
- const arma::vec& norms)
+template
+void Constraints::Impostors(
+ UMatType& outputNeighbors,
+ MatType& outputDistance,
+ const MatType& dataset,
+ const LabelsType& labels,
+ const VecType& norms)
{
// Perform pre-calculation. If neccesary.
Precalculate(labels);
@@ -211,8 +211,8 @@ void Constraints::Impostors(arma::Mat& outputNeighbors,
// KNN instance.
KNN knn;
- arma::Mat neighbors;
- arma::mat distances;
+ UMatType neighbors;
+ MatType distances;
for (size_t i = 0; i < uniqueLabels.n_cols; ++i)
{
@@ -237,28 +237,29 @@ void Constraints::Impostors(arma::Mat& outputNeighbors,
// Calculates k differently labeled nearest neighbors on a
// batch of data points.
-template
-void Constraints::Impostors(arma::Mat& outputMatrix,
- const arma::mat& dataset,
- const arma::Row& labels,
- const arma::vec& norms,
- const size_t begin,
- const size_t batchSize)
+template
+void Constraints::Impostors(
+ UMatType& outputMatrix,
+ const MatType& dataset,
+ const LabelsType& labels,
+ const VecType& norms,
+ const size_t begin,
+ const size_t batchSize)
{
// Perform pre-calculation. If neccesary.
Precalculate(labels);
- arma::mat subDataset = dataset.cols(begin, begin + batchSize - 1);
- arma::Row sublabels = labels.cols(begin, begin + batchSize - 1);
+ MatType subDataset = dataset.cols(begin, begin + batchSize - 1);
+ LabelsType sublabels = labels.cols(begin, begin + batchSize - 1);
// KNN instance.
KNN knn;
- arma::Mat neighbors;
- arma::mat distances;
+ UMatType neighbors;
+ MatType distances;
// Vectors to store indices.
- arma::uvec subIndexSame;
+ UVecType subIndexSame;
for (size_t i = 0; i < uniqueLabels.n_cols; ++i)
{
@@ -285,29 +286,30 @@ void Constraints::Impostors(arma::Mat& outputMatrix,
// Calculates k differently labeled nearest neighbors & distances on a
// batch of data points.
-template
-void Constraints::Impostors(arma::Mat& outputNeighbors,
- arma::mat& outputDistance,
- const arma::mat& dataset,
- const arma::Row& labels,
- const arma::vec& norms,
- const size_t begin,
- const size_t batchSize)
+template
+void Constraints::Impostors(
+ UMatType& outputNeighbors,
+ MatType& outputDistance,
+ const MatType& dataset,
+ const LabelsType& labels,
+ const VecType& norms,
+ const size_t begin,
+ const size_t batchSize)
{
// Perform pre-calculation. If neccesary.
Precalculate(labels);
- arma::mat subDataset = dataset.cols(begin, begin + batchSize - 1);
- arma::Row sublabels = labels.cols(begin, begin + batchSize - 1);
+ MatType subDataset = dataset.cols(begin, begin + batchSize - 1);
+ LabelsType sublabels = labels.cols(begin, begin + batchSize - 1);
// KNN instance.
KNN knn;
- arma::Mat neighbors;
- arma::mat distances;
+ UMatType neighbors;
+ MatType distances;
// Vectors to store indices.
- arma::uvec subIndexSame;
+ UVecType subIndexSame;
for (size_t i = 0; i < uniqueLabels.n_cols; ++i)
{
@@ -335,14 +337,15 @@ void Constraints::Impostors(arma::Mat& outputNeighbors,
// Calculates k differently labeled nearest neighbors & distances over some
// data points.
-template
-void Constraints::Impostors(arma::Mat& outputNeighbors,
- arma::mat& outputDistance,
- const arma::mat& dataset,
- const arma::Row& labels,
- const arma::vec& norms,
- const arma::uvec& points,
- const size_t numPoints)
+template
+void Constraints::Impostors(
+ UMatType& outputNeighbors,
+ MatType& outputDistance,
+ const MatType& dataset,
+ const LabelsType& labels,
+ const VecType& norms,
+ const UVecType& points,
+ const size_t numPoints)
{
// Perform pre-calculation. If neccesary.
Precalculate(labels);
@@ -350,11 +353,11 @@ void Constraints::Impostors(arma::Mat& outputNeighbors,
// KNN instance.
KNN knn;
- arma::Mat neighbors;
- arma::mat distances;
+ UMatType neighbors;
+ MatType distances;
// Vectors to store indices.
- arma::uvec subIndexSame;
+ UVecType subIndexSame;
for (size_t i = 0; i < uniqueLabels.n_cols; ++i)
{
@@ -384,31 +387,35 @@ void Constraints::Impostors(arma::Mat& outputNeighbors,
// Generates {data point, target neighbors, impostors} triplets using
// TargetNeighbors() and Impostors().
-template
-void Constraints::Triplets(arma::Mat& outputMatrix,
- const arma::mat& dataset,
- const arma::Row& labels,
- const arma::vec& norms)
+template
+void Constraints::Triplets(
+ UMatType& outputMatrix,
+ const MatType& dataset,
+ const LabelsType& labels,
+ const VecType& norms)
{
// Perform pre-calculation. If neccesary.
Precalculate(labels);
size_t N = dataset.n_cols;
- arma::Mat impostors(k, dataset.n_cols);
+ UMatType impostors(k, dataset.n_cols);
Impostors(impostors, dataset, labels, norms);
- arma::Mat targetNeighbors(k, dataset.n_cols);;
+ UMatType targetNeighbors(k, dataset.n_cols);;
TargetNeighbors(targetNeighbors, dataset, labels, norms);
- outputMatrix = arma::Mat(3, k * k * N , arma::fill::zeros);
+ outputMatrix = UMatType(3, k * k * N , arma::fill::zeros);
- for (size_t i = 0, r = 0; i < N; ++i)
+ #pragma omp parallel for collapse(3)
+ for (size_t i = 0; i < N; ++i)
{
for (size_t j = 0; j < k; ++j)
{
- for (size_t l = 0; l < k; l++, r++)
+ for (size_t l = 0; l < k; l++)
{
+ const size_t r = i * (k * k) + j * k + l;
+
// Generate triplets.
outputMatrix(0, r) = i;
outputMatrix(1, r) = targetNeighbors(j, i);
@@ -418,9 +425,9 @@ void Constraints::Triplets(arma::Mat& outputMatrix,
}
}
-template
-inline void Constraints::Precalculate(
- const arma::Row& labels)
+template
+inline void Constraints::Precalculate(
+ const LabelsType& labels)
{
// Make sure the calculation is necessary.
if (precalculated)
@@ -431,6 +438,7 @@ inline void Constraints::Precalculate(
indexSame.resize(uniqueLabels.n_elem);
indexDiff.resize(uniqueLabels.n_elem);
+ #pragma omp parallel for
for (size_t i = 0; i < uniqueLabels.n_elem; ++i)
{
// Store same and diff indices.
diff --git a/src/mlpack/methods/lmnn/lmnn.hpp b/src/mlpack/methods/lmnn/lmnn.hpp
index 92646df11a..8dadc5b31e 100644
--- a/src/mlpack/methods/lmnn/lmnn.hpp
+++ b/src/mlpack/methods/lmnn/lmnn.hpp
@@ -49,7 +49,7 @@ namespace mlpack {
* @tparam OptimizerType Optimizer to use for developing distance.
*/
template
+ typename DeprecatedOptimizerType = ens::AMSGrad>
class LMNN
{
public:
@@ -63,11 +63,27 @@ class LMNN
* @param k Number of targets to consider.
* @param distance Type of distance metric used for computation.
*/
+ [[deprecated("Will be removed in mlpack 5.0.0. Pass the dataset directly to "
+ "LearnDistance() instead.")]]
LMNN(const arma::mat& dataset,
const arma::Row& labels,
const size_t k,
const DistanceType distance = DistanceType());
+ /**
+ * Construct the LMNN object, optionally with an instantiated distance metric.
+ *
+ * @param k Number of target neighbors to consider.
+ * @param regularization Penalty to apply to objective function.
+ * @param updateInterval Number of iterations between each recomputation of
+ * true neighbors and impostors.
+ * @param distance Instantiated distance metric for computation.
+ */
+ LMNN(const size_t k,
+ const double regularization = 0.5,
+ const size_t updateInterval = 1,
+ DistanceType distance = DistanceType());
+
/**
* Perform Large Margin Nearest Neighbors metric learning. The output
@@ -80,25 +96,99 @@ class LMNN
* @param callbacks Callback function for ensmallen optimizer `OptimizerType`.
* See https://www.ensmallen.org/docs.html#callback-documentation.
*/
- template
+ template::value>::type,
+ typename = typename std::enable_if<
+ !FirstElementIsArma::value
+ >::type>
+ [[deprecated("Will be removed in mlpack 5.0.0. Use the version that takes a "
+ "dataset as a parameter.")]]
void LearnDistance(arma::mat& outputMatrix, CallbackTypes&&... callbacks);
+ /**
+ * Perform Large Margin Nearest Neighbors metric learning. The output
+ * distance matrix is written into the passed reference. If the
+ * LearnDistance() is called with an outputMatrix with correct dimensions,
+ * then that matrix will be used as the starting point for optimization.
+ *
+ * @param dataset Dataset to learn distance metric on.
+ * @param labels Labels for dataset.
+ * @param outputMatrix Covariance matrix of Mahalanobis distance.
+ * @param callbacks Callback function for ensmallen optimizer `OptimizerType`.
+ * See https://www.ensmallen.org/docs.html#callback-documentation.
+ */
+ template::type,
+ LMNNFunction,
+ MatType
+ >::value>::type,
+ typename = typename std::enable_if::value>::type>
+ void LearnDistance(const MatType& dataset,
+ const LabelsType& labels,
+ MatType& outputMatrix,
+ CallbackTypes&&... callbacks) const;
+
+ /**
+ * Perform Large Margin Nearest Neighbors metric learning. The output
+ * distance matrix is written into the passed reference. If the
+ * LearnDistance() is called with an outputMatrix with correct dimensions,
+ * then that matrix will be used as the starting point for optimization.
+ *
+ * @param dataset Dataset to learn distance metric on.
+ * @param labels Labels for dataset.
+ * @param optimizer Instantiated ensmallen optimizer to use for LMNN.
+ * @param outputMatrix Covariance matrix of Mahalanobis distance.
+ * @param callbacks Callback function for ensmallen optimizer `OptimizerType`.
+ * See https://www.ensmallen.org/docs.html#callback-documentation.
+ */
+ template,
+ MatType
+ >::value>::type>
+ void LearnDistance(const MatType& dataset,
+ const LabelsType& labels,
+ MatType& outputMatrix,
+ OptimizerType& optimizer,
+ CallbackTypes&&... callbacks) const;
//! Get the dataset reference.
- const arma::mat& Dataset() const { return dataset; }
+ [[deprecated("Will be removed in mlpack 5.0.0. Use the LearnDistance() "
+ "version that takes the optimizer as a parameter instead.")]]
+ const arma::mat& Dataset() const { return *dataset; }
//! Get the labels reference.
- const arma::Row& Labels() const { return labels; }
+ [[deprecated("Will be removed in mlpack 5.0.0. Use the LearnDistance() "
+ "version that takes the optimizer as a parameter instead.")]]
+ const arma::Row& Labels() const { return *labels; }
//! Access the regularization value.
const double& Regularization() const { return regularization; }
//! Modify the regularization value.
double& Regularization() { return regularization; }
- //! Access the range value.
- const size_t& Range() const { return range; }
- //! Modify the range value.
- size_t& Range() { return range; }
+ //! Access the iteration update interval value.
+ const size_t& UpdateInterval() const { return updateInterval; }
+ //! Modify the iteration update interval value.
+ size_t& UpdateInterval() { return updateInterval; }
+
+ [[deprecated("Will be removed in mlpack 5.0.0. Use UpdateInterval() "
+ "instead.")]]
+ const size_t& Range() const { return updateInterval; }
+ [[deprecated("Will be removed in mlpack 5.0.0. Use UpdateInterval() "
+ "instead.")]]
+ size_t& Range() { return updateInterval; }
//! Access the value of k.
const size_t& K() const { return k; }
@@ -106,15 +196,23 @@ class LMNN
size_t K() { return k; }
//! Get the optimizer.
- const OptimizerType& Optimizer() const { return optimizer; }
- OptimizerType& Optimizer() { return optimizer; }
+ [[deprecated("Will be removed in mlpack 5.0.0. Use the LearnDistance() "
+ "version that takes the optimizer as a parameter instead.")]]
+ const DeprecatedOptimizerType& Optimizer() const { return optimizer; }
+ //! Modify the optimizer.
+ [[deprecated("Will be removed in mlpack 5.0.0. Use the LearnDistance() "
+ "version that takes the optimizer as a parameter instead.")]]
+ DeprecatedOptimizerType& Optimizer() { return optimizer; }
+
+ // Serialize the LMNN object.
+ template
+ void serialize(Archive& ar, const unsigned int /* version */);
private:
- //! Dataset reference.
- const arma::mat& dataset;
-
- //! Labels reference.
- const arma::Row& labels;
+ //! Dataset pointer (will be removed in mlpack 5.0.0).
+ const arma::mat* dataset;
+ //! Labels pointer (will be removed in mlpack 5.0.0).
+ const arma::Row* labels;
//! Number of target points.
size_t k;
@@ -122,14 +220,14 @@ class LMNN
//! Regularization value.
double regularization;
- //! Range after which impostors need to be recalculated.
- size_t range;
+ //! Number of iterations after which impostors need to be recalculated.
+ size_t updateInterval;
//! Distance to be used.
DistanceType distance;
- //! The optimizer to use.
- OptimizerType optimizer;
+ //! The optimizer to use (will be removed in mlpack 5.0.0).
+ DeprecatedOptimizerType optimizer;
}; // class LMNN
} // namespace mlpack
diff --git a/src/mlpack/methods/lmnn/lmnn_function.hpp b/src/mlpack/methods/lmnn/lmnn_function.hpp
index f35fcf8cd0..e64b8ae0d0 100644
--- a/src/mlpack/methods/lmnn/lmnn_function.hpp
+++ b/src/mlpack/methods/lmnn/lmnn_function.hpp
@@ -41,9 +41,22 @@ namespace mlpack {
* operate on one point in the dataset. This is useful for optimizers like
* stochastic gradient descent (see ens::SGD).
*/
-template
+template,
+ typename DistanceType = SquaredEuclideanDistance>
class LMNNFunction
{
+ // Convenience typedef for element type of data.
+ typedef typename MatType::elem_type ElemType;
+ // Convenience typedef for column vector of data.
+ typedef typename GetColType::type VecType;
+ // Convenience typedef for cube of data.
+ typedef typename GetCubeType::type CubeType;
+ // Convenience typedef for dense matrix of indices.
+ typedef typename GetUDenseMatType::type UMatType;
+ // Convenience typedef for dense vector of indices.
+ typedef typename GetColType::type UVecType;
+
public:
/**
* Constructor for LMNNFunction class.
@@ -52,14 +65,14 @@ class LMNNFunction
* @param labels Input dataset labels.
* @param k Number of target neighbors to be used.
* @param regularization Regularization value.
- * @param range Range after which impostors need to be recalculated.
+ * @param updateInterval Number of iterations before impostors are recomputed.
* @param distance Type of distance metric used for computation.
*/
- LMNNFunction(const arma::mat& dataset,
- const arma::Row& labels,
+ LMNNFunction(const MatType& dataset,
+ const LabelsType& labels,
size_t k,
double regularization,
- size_t range,
+ size_t updateInterval,
DistanceType distance = DistanceType());
@@ -69,13 +82,13 @@ class LMNNFunction
void Shuffle();
/**
- * Evaluate the LMNN function for the given transformation matrix. This is the
- * non-separable implementation, where the objective function is not
+ * Evaluate the LMNN function for the given transformation matrix. This is
+ * the non-separable implementation, where the objective function is not
* decomposed into the sum of several objective functions.
*
* @param transformation Transformation matrix of Mahalanobis distance.
*/
- double Evaluate(const arma::mat& transformation);
+ ElemType Evaluate(const MatType& transformation);
/**
* Evaluate the LMNN objective function for the given transformation matrix on
@@ -89,9 +102,9 @@ class LMNNFunction
* @param begin Index of the initial point to use for objective function.
* @param batchSize Number of points to use for objective function.
*/
- double Evaluate(const arma::mat& transformation,
- const size_t begin,
- const size_t batchSize = 1);
+ ElemType Evaluate(const MatType& transformation,
+ const size_t begin,
+ const size_t batchSize = 1);
/**
* Evaluate the gradient of the LMNN function for the given transformation
@@ -103,7 +116,7 @@ class LMNNFunction
* @param gradient Matrix to store the calculated gradient in.
*/
template
- void Gradient(const arma::mat& transformation, GradType& gradient);
+ void Gradient(const MatType& transformation, GradType& gradient);
/**
* Evaluate the gradient of the LMNN function for the given transformation
@@ -121,7 +134,7 @@ class LMNNFunction
* @param batchSize Number of points to use for objective function.
*/
template
- void Gradient(const arma::mat& transformation,
+ void Gradient(const MatType& transformation,
const size_t begin,
GradType& gradient,
const size_t batchSize = 1);
@@ -137,8 +150,8 @@ class LMNNFunction
* @param gradient Matrix to store the calculated gradient in.
*/
template
- double EvaluateWithGradient(const arma::mat& transformation,
- GradType& gradient);
+ ElemType EvaluateWithGradient(const MatType& transformation,
+ GradType& gradient);
/**
* Evaluate the LMNN objective function together with gradient for the given
@@ -156,13 +169,13 @@ class LMNNFunction
* @param batchSize Number of points to use for objective function.
*/
template
- double EvaluateWithGradient(const arma::mat& transformation,
- const size_t begin,
- GradType& gradient,
- const size_t batchSize = 1);
+ ElemType EvaluateWithGradient(const MatType& transformation,
+ const size_t begin,
+ GradType& gradient,
+ const size_t batchSize = 1);
//! Return the initial point for the optimization.
- const arma::mat& GetInitialPoint() const { return initialPoint; }
+ const MatType& GetInitialPoint() const { return initialPoint; }
/**
* Get the number of functions the objective function can be decomposed into.
@@ -171,7 +184,7 @@ class LMNNFunction
size_t NumFunctions() const { return dataset.n_cols; }
//! Return the dataset passed into the constructor.
- const arma::mat& Dataset() const { return dataset; }
+ const MatType& Dataset() const { return dataset; }
//! Access the regularization value.
const double& Regularization() const { return regularization; }
@@ -183,26 +196,26 @@ class LMNNFunction
//! Modify the value of k.
size_t& K() { return k; }
- //! Access the value of range.
- const size_t& Range() const { return range; }
- //! Modify the value of k.
- size_t& Range() { return range; }
+ //! Access the number of iterations between impostor recomputation.
+ const size_t& UpdateInterval() const { return updateInterval; }
+ //! Modify the number of iterations between impostor recomputation..
+ size_t& UpdateInterval() { return updateInterval; }
private:
//! data. This will be an alias until Shuffle() is called.
- arma::mat dataset;
+ MatType dataset;
//! labels. This will be an alias until Shuffle() is called.
- arma::Row labels;
+ LabelsType labels;
//! Initial parameter point.
- arma::mat initialPoint;
+ MatType initialPoint;
//! Store transformed dataset.
- arma::mat transformedDataset;
+ MatType transformedDataset;
//! Store target neighbors of data points.
- arma::Mat targetNeighbors;
+ UMatType targetNeighbors;
//! Initial impostors.
- arma::Mat impostors;
+ UMatType impostors;
//! Cache distance. Used to avoid repetive calculation.
- arma::mat distanceMat;
+ MatType distanceMat;
//! Number of target neighbors.
size_t k;
//! The instantiated distance metric.
@@ -211,28 +224,28 @@ class LMNNFunction
double regularization;
//! Keep iterations count.
size_t iteration;
- //! Range after which impostors need to be recalculated.
- size_t range;
+ //! Number of iterations before impostors need to be recalculated.
+ size_t updateInterval;
//! Constraints Object.
- Constraints constraint;
+ Constraints constraint;
//! Holds pre-calculated cij.
- arma::mat pCij;
+ MatType pCij;
//! Holds the norm of each data point.
- arma::vec norm;
+ VecType norm;
//! Hold previous eval values for each datapoint.
- arma::cube evalOld;
+ CubeType evalOld;
//! Hold previous maximum norm of impostor.
- arma::mat maxImpNorm;
+ MatType maxImpNorm;
//! Holds previous transformation matrix. Used for L-BFGS like optimizer.
- arma::mat transformationOld;
+ MatType transformationOld;
//! Holds previous transformation matrices.
- std::vector oldTransformationMatrices;
+ std::vector oldTransformationMatrices;
//! Holds number of points which are using each transformation matrix.
std::vector oldTransformationCounts;
//! Holds points to transformation matrix mapping.
- arma::vec lastTransformationIndices;
+ VecType lastTransformationIndices;
//! Used for storing points to re-calculate impostors for.
- arma::uvec points;
+ UVecType points;
//! Flag for controlling use of bounds over impostors.
bool impBounds;
/**
@@ -242,12 +255,12 @@ class LMNNFunction
*/
inline void Precalculate();
//! Update cache transformation matrices.
- inline void UpdateCache(const arma::mat& transformation,
+ inline void UpdateCache(const MatType& transformation,
const size_t begin,
const size_t batchSize);
//! Calculate norm of change in transformation.
- inline void TransDiff(std::map& transformationDiffs,
- const arma::mat& transformation,
+ inline void TransDiff(std::unordered_map& transDiffs,
+ const MatType& transformation,
const size_t begin,
const size_t batchSize);
};
diff --git a/src/mlpack/methods/lmnn/lmnn_function_impl.hpp b/src/mlpack/methods/lmnn/lmnn_function_impl.hpp
index 01aa77afea..58dc54011c 100644
--- a/src/mlpack/methods/lmnn/lmnn_function_impl.hpp
+++ b/src/mlpack/methods/lmnn/lmnn_function_impl.hpp
@@ -18,18 +18,19 @@
namespace mlpack {
-template
-LMNNFunction::LMNNFunction(const arma::mat& datasetIn,
- const arma::Row& labelsIn,
- size_t k,
- double regularization,
- size_t range,
- DistanceType distance) :
+template
+LMNNFunction::LMNNFunction(
+ const MatType& datasetIn,
+ const LabelsType& labelsIn,
+ size_t k,
+ double regularization,
+ size_t updateInterval,
+ DistanceType distance) :
k(k),
distance(distance),
regularization(regularization),
iteration(0),
- range(range),
+ updateInterval(updateInterval),
constraint(datasetIn, labelsIn, k),
points(datasetIn.n_cols),
impBounds(false)
@@ -60,7 +61,7 @@ LMNNFunction::LMNNFunction(const arma::mat& datasetIn,
lastTransformationIndices.zeros();
// Reserve the first element of cache.
- arma::mat emptyMat;
+ MatType emptyMat;
oldTransformationMatrices.push_back(emptyMat);
oldTransformationCounts.push_back(dataset.n_cols);
@@ -92,18 +93,18 @@ LMNNFunction::LMNNFunction(const arma::mat& datasetIn,
}
//! Shuffle the dataset.
-template
-void LMNNFunction::Shuffle()
+template
+void LMNNFunction::Shuffle()
{
- arma::mat newDataset = dataset;
- arma::Mat newLabels = labels;
- arma::cube newEvalOld = evalOld;
- arma::vec newlastTransformationIndices = lastTransformationIndices;
- arma::mat newMaxImpNorm = maxImpNorm;
- arma::vec newNorm = norm;
+ MatType newDataset = dataset;
+ LabelsType newLabels = labels;
+ CubeType newEvalOld = evalOld;
+ VecType newlastTransformationIndices = lastTransformationIndices;
+ MatType newMaxImpNorm = maxImpNorm;
+ VecType newNorm = norm;
// Generate ordering.
- arma::uvec ordering = arma::shuffle(arma::linspace(0,
+ UVecType ordering = arma::shuffle(arma::linspace(0,
dataset.n_cols - 1, dataset.n_cols));
ClearAlias(dataset);
@@ -126,9 +127,9 @@ void LMNNFunction::Shuffle()
}
// Update cache transformation matrices.
-template
-inline void LMNNFunction::UpdateCache(
- const arma::mat& transformation,
+template
+inline void LMNNFunction::UpdateCache(
+ const MatType& transformation,
const size_t begin,
const size_t batchSize)
{
@@ -162,31 +163,13 @@ inline void LMNNFunction::UpdateCache(
}
oldTransformationCounts[index] += batchSize;
-
- #ifdef DEBUG
- size_t total = 0;
- for (size_t i = 1; i < oldTransformationCounts.size(); ++i)
- {
- std::ostringstream oss;
- oss << "transformation counts for matrix " << i
- << " invalid (" << oldTransformationCounts[i] << ")!";
- Log::Assert(oldTransformationCounts[i] <= dataset.n_cols, oss.str());
- total += oldTransformationCounts[i];
- }
-
- std::ostringstream oss;
- oss << "total count for transformation matrices invalid (" << total
- << ", " << "should be " << dataset.n_cols << "!";
- if (begin + batchSize == dataset.n_cols)
- Log::Assert(total == dataset.n_cols, oss.str());
- #endif
}
// Calculate norm of change in transformation.
-template
-inline void LMNNFunction::TransDiff(
- std::map& transformationDiffs,
- const arma::mat& transformation,
+template
+inline void LMNNFunction::TransDiff(
+ std::unordered_map& transformationDiffs,
+ const MatType& transformation,
const size_t begin,
const size_t batchSize)
{
@@ -209,22 +192,24 @@ inline void LMNNFunction::TransDiff(
}
//! Evaluate cost over whole dataset.
-template
-double LMNNFunction::Evaluate(const arma::mat& transformation)
+template
+typename MatType::elem_type
+LMNNFunction::Evaluate(
+ const MatType& transformation)
{
- double cost = 0;
+ ElemType cost = 0;
// Apply distance metric over dataset.
transformedDataset = transformation * dataset;
- double transformationDiff = 0;
+ ElemType transformationDiff = 0;
if (!transformationOld.is_empty())
{
// Calculate norm of change in transformation.
transformationDiff = arma::norm(transformation - transformationOld);
}
- if (!transformationOld.is_empty() && iteration++ % range == 0)
+ if (!transformationOld.is_empty() && iteration++ % updateInterval == 0)
{
if (impBounds)
{
@@ -251,7 +236,7 @@ double LMNNFunction