diff --git a/HISTORY.md b/HISTORY.md index e3ba45d0a7..a85d71443a 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -44,6 +44,11 @@ * Fix divide-by-zero edge case for LARS (#3701). + * Templatize `SparseCoding` and `LocalCoordinateCoding` to allow different + matrix types (#3709, #3711). + + * Fix handling of unused atoms in `LocalCoordinateCoding` (#3711). + * Move minimum required C++ version from C++14 to C++17 (#3704). ### mlpack 4.3.0 diff --git a/doc/index.md b/doc/index.md index ad139d423b..d733726ec9 100644 --- a/doc/index.md +++ b/doc/index.md @@ -120,6 +120,8 @@ Prepare data for machine learning algorithms. Transform data from one space to another. + * [`LocalCoordinateCoding`](user/methods/local_coordinate_coding.md): local + coordinate coding with dictionary 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 1f5d15fe67..3f4aaeabd8 100644 --- a/doc/sidebar.html +++ b/doc/sidebar.html @@ -179,6 +179,11 @@ when the sidebar is built for each page.
LocalCoordinateCoding
+
+ NMF
diff --git a/doc/user/core.md b/doc/user/core.md
index ee3c0dd1f6..3ed5cd073e 100644
--- a/doc/user/core.md
+++ b/doc/user/core.md
@@ -81,7 +81,7 @@ avoid copies.
- If `offset` is `0`, then the alias is identical: the first element of
`a` is the first element of `mat`. Otherwise, the first element of `a`
is the `offset`'th element of `mat`; elements in `mat` are ordered in
- a [column-major way](../matrices.md#representing-data-in-mlpack).
+ a [column-major way](matrices.md#representing-data-in-mlpack).
- If `strict` is `true`, the size of `a` cannot be changed.
- `mat` and `a` should have the same matrix type (e.g. `arma::mat`,
`arma::fmat`, `arma::sp_mat`).
@@ -93,7 +93,7 @@ avoid copies.
- If `offset` is `0`, then the alias is identical: the first element of
`a` is the first element of `cube`. Otherwise, the first element of `a`
is the `offset`'th element of `cube`; elements in `cube` are ordered in
- a [column-major way](../matrices.md#representing-data-in-mlpack).
+ a [column-major way](matrices.md#representing-data-in-mlpack).
- If `strict` is `true`, the size of `a` cannot be changed.
- `cube` and `a` should have the same cube type (e.g. `arma::cube`,
`arma::fcube`).
diff --git a/doc/user/methods/local_coordinate_coding.md b/doc/user/methods/local_coordinate_coding.md
new file mode 100644
index 0000000000..5d68945416
--- /dev/null
+++ b/doc/user/methods/local_coordinate_coding.md
@@ -0,0 +1,375 @@
+## `LocalCoordinateCoding`
+
+The `LocalCoordinateCoding` class implements local coordinate coding, a
+variation of [sparse coding](sparse_coding.md) with dictionary learning. Local
+coordinate coding is a form of representation learning, and can be used to
+represent each point in a dataset as a linear combination of a few nearby
+*atoms* in the learned dictionary.
+
+#### Simple usage example:
+
+```c++
+// Create a random dataset with 100 points in 40 dimensions, and then a random
+// test dataset with 50 points.
+arma::mat data(40, 100, arma::fill::randn);
+arma::mat testData(40, 50, arma::fill::randn);
+
+// Perform local coordinate coding with 20 atoms and an L1 penalty of 0.1.
+mlpack::LocalCoordinateCoding lcc(20, 0.1); // Step 1: create object.
+double objective = lcc.Train(data); // Step 2: learn dictionary.
+arma::mat codes;
+lcc.Encode(testData, codes); // Step 3: encode new data.
+
+// Print some information about the test encoding.
+std::cout << "Average density of encoded test data: "
+ << 100.0 * arma::mean(arma::sum(codes != 0)) / codes.n_rows << "\%."
+ << std::endl;
+```
+
+
+#### Quick links:
+
+ * [Constructors](#constructors): create `LocalCoordinateCoding` objects.
+ * [`Train()`](#training): train model (learn dictionary).
+ * [`Encode()`](#encoding): encode points with a trained model.
+ * [Other functionality](#other-functionality) for loading, saving, and
+ inspecting.
+ * [Examples](#simple-examples) of simple usage and links to detailed example
+ projects.
+ * [Template parameters](#advanced-functionality-template-parameters) for
+ advanced functionality: different element types and dictionary initialization
+ strategies.
+
+#### See also:
+
+ * [`SparseCoding`](sparse_coding.md)
+ * [`LARS`](lars.md) (used internally by `LocalCoordinateCoding`)
+ * [mlpack transformations](../../index.md#transformations)
+ * [Sparse dictionary learning on Wikipedia](https://en.wikipedia.org/wiki/Sparse_dictionary_learning)
+ * [Nonlinear learning using local coordinate coding (pdf)](https://proceedings.neurips.cc/paper_files/paper/2009/file/2afe4567e1bf64d32a5527244d104cea-Paper.pdf)
+
+### Constructors
+
+ * `lcc = LocalCoordinateCoding()`
+ * `lcc = LocalCoordinateCoding(atoms=0, lambda=0.0, maxIter=0, tol=0.01)`
+ - Create a `LocalCoordinateCoding` object without learning a dictionary on
+ data.
+ - If `atoms` is set to `0` (the default), it will need to be set to a value
+ greater than `0` before `Train()` is called (`lcc.Atoms() = atoms` can be
+ used for this).
+
+ * `lcc = LocalCoordinateCoding(data, atoms, lambda=0.0, maxIter=0, tol=0.01)`
+ - Create a `LocalCoordinateCoding` object and train the dictionary on the
+ given `data`.
+ - The dictionary will contain `atoms` elements.
+
+ * `lcc = LocalCoordinateCoding(data, atoms, lambda, maxIter, tol, initializer)`
+ - *Advanced constructor*: create a `LocalCoordinateCoding` object that will
+ use a custom dictionary initializer and train on the given `data`.
+ - The dictionary will contain `atoms` elements.
+ - `initializer` will be used to initialize the dictionary; see [Advanced
+ Functionality: Different Dictionary Initialization
+ Strategies](#dictionaryinitializer-different-dictionary-initialization-strategies)
+ for details.
+
+#### Constructor Parameters:
+
+| **name** | **type** | **description** | **default** |
+|----------|----------|-----------------|-------------|
+| `data` | [`arma::mat`](../matrices.md) | [Column-major](../matrices.md#representing-data-in-mlpack) training matrix. | _(N/A)_ |
+| `atoms` | `size_t` | Number of atoms in dictionary. | _(N/A)_ |
+| `lambda` | `double` | L1 regularization penalty. Used in both `Train()` and `Encode()` steps. | `0.0` |
+| `maxIter` | `size_t` | Maximum number of iterations for dictionary learning. `0` means no limit. | `0` |
+| `tol` | `double` | Objective function tolerance for terminating dictionary learning. | `0.01` |
+
+As an alternative to passing `atoms`, `lambda`, `maxIter`, or `tol`, these can
+be set with a standalone method. The following functions can be used before
+calling `Train()`:
+
+ * `lcc.Atoms() = a;` will set the number of atoms to use in the dictionary to
+ `a`. Changing this after calling `Train()` will not make a difference to the
+ dictionary size.
+
+ * `lcc.Lambda() = l;` will set the L1 regularization penalty to `l1`. This can
+ be set after `Train()` to force sparser encodings when `Encode()` is called.
+
+ * `lcc.MaxIterations() = m;` will set the maximum number of iterations for
+ dictionary learning to `m`. `0` means that the algorithm will run until
+ convergence.
+
+ * `lcc.Tolerance() = t;` will set the objective tolerance for convergence of
+ the dictionary learning algorithm to `t`.
+
+***Caveats***:
+
+ * Larger settings of `atoms` (i.e. larger dictionary sizes) will be able to
+ more accurately represent the data, but may take longer to learn.
+
+ * Larger values of `lambda` will cause the model to use sparser encodings for
+ data (e.g. fewer nearby anchor points) when `Train()` and `Encode()` are
+ called, but when `lambda` is too large, the codings may be inaccurate
+ representations of the original points.
+
+
+
+ * If `lambda` is set too large, encodings may be empty (e.g. all zeros).
+
+ * Training is not incremental; a second call to `Train()` will reinitialize the
+ dictionary and restart the learning process.
+
+### Training
+
+If training the dictionary is not done as part of the constructor call, it can
+be done with one of the following versions of the `Train()` member function:
+
+ * `lcc.Train(data)`
+ * `lcc.Train(data, initializer)`
+ - Train the local coordinate coding dictionary on the given `data`.
+ - Optionally, use the given `initializer` to initialize the dictionary (see
+ [`DictionaryInitializer`](#dictionaryinitializer-different-dictionary-initialization-strategies)
+ for more details).
+
+### Encoding
+
+Once a `LocalCoordinateCoding` model has a trained dictionary, the `Encode()`
+member function can be used to encode new data points.
+
+ * `lcc.Encode(data, codes)`
+ - Encode `data` (a [column-major data
+ matrix](../matrices.md#representing-data-in-mlpack)) as a sparse set of
+ local atoms of the dictionary, storing the result in `codes`.
+ - Both `data` and `codes` should be the same matrix type (e.g. `arma::mat`);
+ see [Different Element Types](#mattype-different-element-types) for more
+ details.
+ - `codes` will be set to have `atoms` rows and `data.n_cols` columns.
+ - Column `i` of `codes` corresponds to the coding of the `i`'th column of
+ `data`. Each row represents the weight associated with each atom in the
+ dictionary.
+
+After encoding, the original data can be recovered (approximately) as
+`lcc.Dictionary() * data`.
+
+### Other Functionality
+
+ * A `LocalCoordinateCoding` model can be serialized with
+ [`data::Save()` and `data::Load()`](../load_save.md#mlpack-objects).
+
+ * `lcc.Dictionary()` will return an `arma::mat&` containing the dictionary
+ matrix. The matrix has `data.n_rows` rows and `atoms` columns; each column
+ corresponds to an atom in the dictionary. Dictionary atoms are regularized
+ to be close to the manifold that data lie on.
+
+ * `double obj = lcc.Objective(data, codes)` computes the local coordinate
+ coding objective function on the given `data` and encodings `codes`. This
+ can be used after `Encode()` to test the quality of the encodings (a smaller
+ objective is better).
+
+### Simple Examples
+
+See also the [simple usage example](#simple-usage-example) for a trivial usage
+of the `LocalCoordinateCoding` class.
+
+---
+
+Train a local coordinate coding model on the cloud dataset and print the
+reconstruction error.
+
+```c++
+// See https://datasets.mlpack.org/cloud.csv.
+arma::mat dataset;
+mlpack::data::Load("cloud.csv", dataset, true);
+
+mlpack::LocalCoordinateCoding lcc;
+lcc.Atoms() = 50;
+lcc.Lambda() = 1e-5;
+lcc.MaxIterations() = 25;
+lcc.Train(dataset);
+
+// Encode the training dataset.
+arma::mat codes;
+lcc.Encode(dataset, codes);
+
+std::cout << "Input matrix size: " << dataset.n_rows << " x " << dataset.n_cols
+ << "." << std::endl;
+std::cout << "Codes matrix size: " << codes.n_rows << " x " << codes.n_cols
+ << "." << std::endl;
+
+// Reconstruct the original matrix.
+arma::mat recon = lcc.Dictionary() * codes;
+double error = std::sqrt(arma::norm(dataset - recon, "fro") / dataset.n_elem);
+std::cout << "RMSE of reconstructed matrix: " << error << "." << std::endl;
+```
+
+---
+
+Train a local coordinate coding model on the iris dataset and save the model to
+disk.
+
+```c++
+// See https://datasets.mlpack.org/iris.train.csv.
+arma::mat dataset;
+mlpack::data::Load("iris.train.csv", dataset, true);
+
+// Train the model in the constructor.
+mlpack::LocalCoordinateCoding lcc(dataset,
+ 10 /* atoms */,
+ 0.1 /* L1 penalty */);
+
+// Save the model to disk.
+mlpack::data::Save("lcc.bin", "lcc", lcc);
+```
+
+---
+
+Train a local coordinate coding model on the satellite dataset, trying several
+different regularization parameters and checking the objective value on a
+held-out test dataset.
+
+```c++
+// See https://datasets.mlpack.org/satellite.train.csv.
+arma::mat trainData;
+mlpack::data::Load("satellite.train.csv", trainData, true);
+// See https://datasets.mlpack.org/satellite.test.csv.
+arma::mat testData;
+mlpack::data::Load("satellite.test.csv", testData, true);
+
+for (double lambdaPow = -6; lambdaPow <= -2; lambdaPow += 1)
+{
+ const double lambda = std::pow(10.0, lambdaPow);
+ mlpack::LocalCoordinateCoding lcc(50 /* atoms */);
+ lcc.Lambda() = lambda;
+ lcc.MaxIterations() = 25; // Keep iterations low so this runs relatively fast.
+
+ const double trainObj = lcc.Train(trainData);
+
+ // Compute the objective on the test set.
+ arma::mat codes;
+ lcc.Encode(testData, codes);
+ const double testObj = lcc.Objective(testData, codes);
+
+ std::cout << "Lambda: " << std::setfill(' ') << std::setw(3) << lambda
+ << "; ";
+ std::cout << "training set objective: " << std::setw(6) << trainObj << "; ";
+ std::cout << "test set objective: " << std::setw(6) << testObj << "."
+ << std::endl;
+}
+```
+
+### Advanced Functionality: Template Parameters
+
+The `LocalCoordinateCoding` class has one class template parameter that can be
+used for custom behavior. The full signature of the class is:
+
+```
+LocalCoordinateCoding