* Add pipeline to documentation homepage. * Fix for mobile devices. * Add little pipelines to go at the top of each page. * Overhaul index page. * Overhaul README to remove redundant material. * Add installation documentation. * Update pipelines. * Allow nesting of deeper details. * Add a pipeline to the top of the load/save page. * Add prerequisites link to main pipeline. * Add better but not finished sidebar. * Add a couple new documentation pages. * Fix URLs in svg. * Incremental checkin. * Fix Youtube URLs. * Incremental checkin. * Minor fixes. * Add first pass at evaluation/deployment pages. * Minor spacing and link fixes. * Flesh out a number of additional pages and write basic compilation documentation. * Fix some minor issues, and add Docker deployment page (not totally finished yet). * Add developer documentation landing page. * Hopefully getting close to the final set of changes here. * Remove this documentation for now. * Fix a few links, and the size of the sidebar. * Fix some additional links. * Fix a bunch more links. * Fix another link that now has a better place. * Refactor test-docs.sh to handle documentation that is a standalone program. * Fix file exclusions. * Fully qualify typename. * Update name of file. * Fix syntax error. * Remove files that are not meant to be compiled. * Also skip the quickstart. * Move quickstart entry to the top. * Remove gray coloring of binding documentation. * Update name of sidebar link. * Update to working link. * Fix Wikipedia anchor.
462 lines
19 KiB
Markdown
462 lines
19 KiB
Markdown
## `LARS`
|
|
|
|
The `LARS` class implements the least-angle regression (LARS) algorithm for
|
|
L1-penalized and L2-penalized linear regression. `LARS` can also solve the
|
|
LASSO (least absolute shrinkage and selection operator) problem. The LARS
|
|
algorithm is a *path* algorithm, and thus will recover solutions for *all* L1
|
|
penalty parameters greater than or equal to the given L1 penalty parameter.
|
|
|
|
#### Simple usage example:
|
|
|
|
```c++
|
|
// Train a LARS model on random numeric data and make predictions.
|
|
|
|
// All data and responses are uniform random; this uses 10 dimensional data.
|
|
// Replace with a data::Load() call or similar for a real application.
|
|
arma::mat dataset(10, 1000, arma::fill::randu); // 1000 points.
|
|
arma::rowvec responses = arma::randn<arma::rowvec>(1000);
|
|
arma::mat testDataset(10, 500, arma::fill::randu); // 500 test points.
|
|
|
|
mlpack::LARS lars(true, 0.1 /* L1 penalty */); // Step 1: create model.
|
|
lars.Train(dataset, responses); // Step 2: train model.
|
|
arma::rowvec predictions;
|
|
lars.Predict(testDataset, predictions); // Step 3: use model to predict.
|
|
|
|
// Print some information about the test predictions.
|
|
std::cout << arma::accu(predictions > 0.7) << " test points predicted to have"
|
|
<< " responses greater than 0.7." << std::endl;
|
|
std::cout << arma::accu(predictions < 0) << " test points predicted to have "
|
|
<< "negative responses." << std::endl;
|
|
```
|
|
<p style="text-align: center; font-size: 85%"><a href="#simple-examples">More examples...</a></p>
|
|
|
|
#### Quick links:
|
|
|
|
* [Constructors](#constructors): create `LARS` objects.
|
|
* [`Train()`](#training): train model.
|
|
* [`Predict()`](#prediction): predict with a trained model.
|
|
* [Other functionality](#other-functionality) for loading, saving, and
|
|
inspecting.
|
|
* [The LARS path](#the-lars-path): use models from the LARS path with different
|
|
L1 penalty values.
|
|
* [Examples](#simple-examples) of simple usage and links to detailed example
|
|
projects.
|
|
* [Template parameters](#advanced-functionality-different-element-types) for
|
|
using different element types for a model.
|
|
|
|
#### See also:
|
|
|
|
* [`LinearRegression`](linear_regression.md)
|
|
* [mlpack regression techniques](../modeling.md#regression)
|
|
* [Least-angle Regression on Wikipedia](https://en.wikipedia.org/wiki/Least-angle_regression)
|
|
|
|
### Constructors
|
|
|
|
* `lars = LARS(useCholesky=false, lambda1=0.0, lambda2=0.0, tolerance=1e-16, fitIntercept=true, normalizeData=true)`
|
|
- Initialize the model without training.
|
|
- You will need to call [`Train()`](#training) later to train the model
|
|
before calling [`Predict()`](#prediction).
|
|
|
|
---
|
|
|
|
* `lars = LARS(data, responses, colMajor=true, useCholesky=true, lambda1=0.0, lambda2=0.0, tolerance=1e-16, fitIntercept=true, normalizeData=true)`
|
|
- Train model on the given data and responses, using the given settings for
|
|
hyperparameters.
|
|
|
|
---
|
|
|
|
* `lars = LARS(data, responses, colMajor, useCholesky, gramMatrix, lambda1=0.0, lambda2=0.0, tolerance=1e-16, fitIntercept=true, normalizeData=true)`
|
|
- *(Advanced constructor)*.
|
|
- Train model on the given data and responses, using a precomputed Gram
|
|
matrix (`gramMatrix`, equivalent to `data * data.t()`).
|
|
- Using a precomputed Gram matrix can save time, if it has already been
|
|
computed.
|
|
- ***Note:*** any precomputed Gram matrix must also match the settings of
|
|
`fitIntercept` and `normalizeData`; so, if both are `true`, then
|
|
`gramMatrix` must be computed on mean-centered data whose features are
|
|
normalized to have unit variance. In addition, if `lambda2 > 0`, then
|
|
it is expected that `lambda2` is added to each element on the diagonal of
|
|
`gramMatrix`.
|
|
|
|
---
|
|
|
|
#### Constructor Parameters:
|
|
|
|
| **name** | **type** | **description** | **default** |
|
|
|----------|----------|-----------------|-------------|
|
|
| `data` | [`arma::mat`](../matrices.md) | Training matrix. | _(N/A)_ |
|
|
| `responses` | [`arma::rowvec`](../matrices.md) | Training responses (e.g. values to predict). Should have length `data.n_cols`. | _(N/A)_ |
|
|
| `colMajor` | `bool` | Should be set to `true` if `data` is [column-major](../matrices.md#representing-data-in-mlpack). Passing row-major data can avoid a transpose operation. | `false` |
|
|
| `useCholesky` | `bool` | If `true`, use the Cholesky decomposition of the Gram matrix to solve linear systems (as opposed to the full Gram matrix). | `false` |
|
|
| `gramMatrix` | [`arma::mat`](../matrices.md) | Precomputed Gram matrix of `data` (i.e. `data * data.t()` for column-major data). | _(N/A)_ |
|
|
| `lambda1` | `double` | L1 regularization penalty parameter. | `0.0` |
|
|
| `lambda2` | `double` | L2 regularization penalty parameter. | `0.0` |
|
|
| `tolerance` | `double` | Tolerance on feature correlations for convergence. | `1e-16` |
|
|
| `fitIntercept` | `bool` | If `true`, an intercept term will be included in the model. | `true` |
|
|
| `normalizeData` | `bool` | If `true`, data will be normalized before fitting the model. | `true` |
|
|
|
|
As an alternative to passing hyperparameters, each hyperparameter can be set
|
|
with a standalone method. The following functions can be used before calling
|
|
`Train()` to set hyperparameters:
|
|
|
|
* `lars.UseCholesky() = useChol;` will set whether or not the Cholesky
|
|
decomposition will be used during training to `useChol`.
|
|
* `lars.Lambda1() = lambda1;` will set the L1 regularization penalty parameter
|
|
to `lambda1`.
|
|
* `lars.Lambda2() = lambda2;` will set the L2 regularization penalty parameter
|
|
to `lambda2`.
|
|
* `lars.Tolerance() = tol;` will set the convergence tolerance to `tol`.
|
|
* `lars.FitIntercept(fitIntercept);` will set whether an intercept will be fit
|
|
to `fitIntercept`. If an external Gram matrix has been specified, this will
|
|
throw an exception.
|
|
* `lars.NormalizeData(normalizeData);` will set whether data should be
|
|
normalized to `normalizeData`. If an external Gram matrix has been
|
|
specified, this will throw an exception.
|
|
|
|
***Notes:***
|
|
|
|
- The `lambda1` parameter implicitly controls the sparsity of the model; for
|
|
more sparse models (i.e. fewer nonzero weights), specify a larger `lambda1`.
|
|
|
|
- Specifying a too-small `lambda1` or `lambda2` value may cause the model to
|
|
overfit; however, setting it too large may cause the model to underfit.
|
|
Because LARS is a path algorithm, the
|
|
[`SelectBeta()`](#other-functionality) functions can be used to select models
|
|
with different values of `lambda1`. For tuning `lambda2`,
|
|
[Automatic hyperparameter tuning](../hpt.md) can be used.
|
|
|
|
<!-- TODO: fix the link to the hyperparameter tuner -->
|
|
|
|
- `fitIntercept` and `normalizeData` are recommended to be set as `true`, in
|
|
accordance with the original LARS algorithm. `false` can be used for
|
|
`fitIntercept` if the features and responses are already mean-centered, and
|
|
`false` can also be used for `normalizeData` if the features are already
|
|
unit-variance. Using `false` for either option can provide a small amount of
|
|
speedup.
|
|
|
|
- `useCholesky` should generally be set to `true` and in most situations will
|
|
result in faster training.
|
|
|
|
### Training
|
|
|
|
If training is not done as part of the constructor call, it can be done with the
|
|
`Train()` function:
|
|
|
|
* `lars.Train(data, responses, colMajor=true, useCholesky=true, lambda1=0.0, lambda2=0.0, tolerance=1e-16, fitIntercept=true, normalizeData=true)`
|
|
- Train the model on the given data.
|
|
|
|
---
|
|
|
|
* `lars.Train(data, responses, colMajor, useCholesky, gramMatrix, lambda1=0.0, lambda2=0.0, tolerance=1e-16, fitIntercept=true, normalizeData=true)`
|
|
- *(Advanced training.)*
|
|
- Train model on the given data and responses, using a precomputed Gram
|
|
matrix (`gramMatrix`, equivalent to `data * data.t()`).
|
|
- Using a precomputed Gram matrix can save time, if it has already been
|
|
computed.
|
|
- ***Note:*** any precomputed Gram matrix must also match the settings of
|
|
`fitIntercept` and `normalizeData`; so, if both are `true`, then
|
|
`gramMatrix` must be computed on mean-centered data whose features are
|
|
normalized to have unit variance. In addition, if `lambda2 > 0`, then
|
|
it is expected that `lambda2` is added to each element on the diagonal of
|
|
`gramMatrix`.
|
|
|
|
---
|
|
|
|
Types of each argument are the same as in the table for constructors
|
|
[above](#constructor-parameters).
|
|
|
|
***Notes:***
|
|
|
|
* Training is not incremental. A second call to `Train()` will retrain the
|
|
model from scratch.
|
|
|
|
* `Train()` returns the squared error (loss) of the model on the training set
|
|
as a `double`. To obtain the MSE, divide by the number of training points.
|
|
|
|
### Prediction
|
|
|
|
Once a `LARS` model is trained, the `Predict()` member function
|
|
can be used to make predictions for new data.
|
|
|
|
* `double predictedValue = lars.Predict(point)`
|
|
- ***(Single-point)***
|
|
- Make a prediction for a single point, returning the predicted value.
|
|
|
|
---
|
|
|
|
* `lars.Predict(data, predictions, colMajor=true)`
|
|
- ***(Multi-point)***
|
|
- Make predictions for a set of points.
|
|
- The prediction for data point `i` can be accessed with `predictions[i]`.
|
|
|
|
---
|
|
|
|
#### Prediction Parameters:
|
|
|
|
| **usage** | **name** | **type** | **description** |
|
|
|-----------|----------|----------|-----------------|
|
|
| _single-point_ | `point` | [`arma::vec`](../matrices.md) | Single point for prediction. |
|
|
||||
|
|
| _multi-point_ | `data` | [`arma::mat`](../matrices.md) | Set of [column-major](../matrices.md) points for classification. |
|
|
| _multi-point_ | `predictions` | [`arma::rowvec&`](../matrices.md) | Vector of `double`s to store predictions into. Will be set to length `data.n_cols`. |
|
|
| _multi-point_ | `colMajor` | `bool` | Should be set to `true` if `data` is [column-major](../matrices.md). Passing row-major data can avoid a transpose operation. (Default `true`.) |
|
|
|
|
### Other Functionality
|
|
|
|
* A `LARS` model can be serialized with
|
|
[`data::Save()` and `data::Load()`](../load_save.md#mlpack-objects).
|
|
|
|
* `lars.Beta()` will return an `arma::vec` with the model parameters. This
|
|
will have length equal to the dimensionality of the model. Note that
|
|
`lars.Beta()` can be changed to a different model on the LARS path using the
|
|
[`lars.SelectBeta()` method](#the-lars-path).
|
|
|
|
* `lars.Intercept()` will return a `double` representing the fitted intercept
|
|
term, or 0 if `lars.FitIntercept()` is `false`.
|
|
|
|
* `lars.ActiveSet()` will return a `std::vector<size_t>&` containing the
|
|
indices of nonzero dimensions in the model parameters (`lars.Beta()`).
|
|
|
|
* `lars.ComputeError(data, responses, colMajor=true)` will return a `double`
|
|
containing the squared error of the model on `data`, given that
|
|
the true responses are `responses`. To obtain the MSE, divide by the number
|
|
of points in `data`.
|
|
|
|
### The LARS Path
|
|
|
|
LARS is a *path* (or stepwise) algorithm, meaning it adds one feature at a time
|
|
to the model. This in turn means that when we train a LARS model with
|
|
`lambda1` set to `l`, we also recover every possible LARS model on the same data
|
|
with a `lambda1` greater than `l`.
|
|
|
|
The `LARS` class provides a way to access all of the models on the path, and
|
|
switch between them for prediction purposes:
|
|
|
|
* `lars.BetaPath()` returns a `std::vector<arma::vec>&` containing each set of
|
|
model weights on the LARS path.
|
|
|
|
* `lars.InterceptPath()` returns a `std::vector<double>&` containing each
|
|
intercept value on the LARS path. These values are only meaningful if
|
|
`lars.FitIntercept()` is `true`.
|
|
|
|
* `lars.LambdaPath()` returns a `std::vector<double>&` containing each
|
|
`lambda1` value that is associated with each element in `lars.BetaPath()` and
|
|
`lars.InterceptPath()`. That is, `lars.LambdaPath()[i]` is the `lambda1`
|
|
value corresponding to the model defined by `lars.BetaPath()[i]` and
|
|
`lars.InterceptPath()[i]`.
|
|
|
|
* `lars.SelectBeta(lambda1)` will set the model weights (`lars.ActiveSet()`,
|
|
`lars.Beta()` and `lars.Intercept()`) to the path location with L1 penalty
|
|
`lambda1`. This is equivalent to calling `lars.Train(data, responses,
|
|
colMajor, useCholesky, lambda1)`---but much more efficient! `lambda1`
|
|
cannot be less than `lars.Lambda1()`, or an exception will be thrown.
|
|
|
|
* `lars.SelectedLambda1()` returns the currently selected L1 regularization
|
|
penalty parameter.
|
|
|
|
* For any value `lambda1` between `lars.LambdaPath()[i]` and
|
|
`lars.LambdaPath()[i + 1]`, the corresponding model is a linear interpolation
|
|
between `lars.BetaPath()[i]` and `lars.BetaPath()[i + 1]` (and
|
|
`lars.InterceptPath()[i]` and `lars.InterceptPath()[i + 1]`). This exact
|
|
linear interpolation is what is computed by `lars.SelectBeta(lambda1)`.
|
|
|
|
### Simple Examples
|
|
|
|
See also the [simple usage example](#simple-usage-example) for a trivial usage
|
|
of the `LARS` class.
|
|
|
|
---
|
|
|
|
Train a LARS model in the constructor, and print the MSE on training and
|
|
test data for each set of weights in the path.
|
|
|
|
```c++
|
|
// See https://datasets.mlpack.org/wave_energy_farm_100.csv.
|
|
arma::mat data;
|
|
mlpack::data::Load("wave_energy_farm_100.csv", data, true);
|
|
|
|
// Split the last row off: it is the responses. Also, normalize the responses
|
|
// to [0, 1].
|
|
arma::rowvec responses = data.row(data.n_rows - 1);
|
|
responses /= responses.max();
|
|
data.shed_row(data.n_rows - 1);
|
|
|
|
// Split into a training and test dataset. 20% of the data is held out as a
|
|
// test set.
|
|
arma::mat trainingData, testData;
|
|
arma::rowvec trainingResponses, testResponses;
|
|
mlpack::data::Split(data, responses, trainingData, testData, trainingResponses,
|
|
testResponses, 0.2);
|
|
|
|
// Train a LARS model with lambda1 = 1e-5 and lambda2 = 1e-6.
|
|
mlpack::LARS lars(trainingData, trainingResponses, true, true, 1e-5, 1e-6);
|
|
|
|
// Iterate over all the models in the path.
|
|
const size_t pathLength = lars.BetaPath().size();
|
|
for (size_t i = 0; i < pathLength; ++i)
|
|
{
|
|
// Use the i'th model in the path.
|
|
lars.SelectBeta(lars.LambdaPath()[i]);
|
|
|
|
// ComputeError() returns the total loss, which we need to divide by the
|
|
// number of points to get the MSE.
|
|
const double trainMSE = lars.ComputeError(trainingData, trainingResponses) /
|
|
trainingData.n_cols;
|
|
const double testMSE = lars.ComputeError(testData, testResponses) /
|
|
testData.n_cols;
|
|
std::cout << "L1 penalty parameter: " << lars.SelectedLambda1() << std::endl;
|
|
std::cout << " MSE on training set: " << trainMSE << "." << std::endl;
|
|
std::cout << " MSE on test set: " << testMSE << "." << std::endl;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
Train a LARS model, print predictions for a random point, and save to a file.
|
|
|
|
```c++
|
|
// See https://datasets.mlpack.org/admission_predict.csv.
|
|
arma::mat data;
|
|
mlpack::data::Load("admission_predict.csv", data, true);
|
|
|
|
// See https://datasets.mlpack.org/admission_predict.responses.csv.
|
|
arma::rowvec responses;
|
|
mlpack::data::Load("admission_predict.responses.csv", responses, true);
|
|
|
|
// Train a LARS model with only L2 regularization.
|
|
mlpack::LARS lars(data, responses, true, true, 0.0, 0.1 /* lambda2 */);
|
|
|
|
// Predict on a random point.
|
|
arma::vec point = arma::randu<arma::vec>(data.n_rows);
|
|
const double prediction = lars.Predict(point);
|
|
|
|
std::cout << "Prediction on random point: " << prediction << "." << std::endl;
|
|
|
|
// Save the model to "lars_model.bin" with the name "lars".
|
|
mlpack::data::Save("lars_model.bin", "lars", lars, true);
|
|
```
|
|
|
|
---
|
|
|
|
Load a LARS model from disk and print some information about it.
|
|
|
|
```c++
|
|
// This assumes a model named "lars" has previously been saved to
|
|
// "lars_model.bin".
|
|
mlpack::LARS lars;
|
|
mlpack::data::Load("lars_model.bin", "lars", lars, true);
|
|
|
|
if (lars.BetaPath().size() == 0)
|
|
{
|
|
std::cout << "lars_model.bin contains an untrained LARS model." << std::endl;
|
|
}
|
|
else
|
|
{
|
|
std::cout << "Information on the LARS model in lars_model.bin:" << std::endl;
|
|
|
|
std::cout << " - Model dimensionality: " << lars.Beta().n_elem << "."
|
|
<< std::endl;
|
|
std::cout << " - Has intercept: "
|
|
<< (lars.FitIntercept() ? std::string("yes") : std::string("no")) << "."
|
|
<< std::endl;
|
|
std::cout << " - Current L1 regularization penalty parameter value: "
|
|
<< lars.SelectedLambda1() << "." << std::endl;
|
|
std::cout << " - L2 regularization penalty parameter: " << lars.Lambda2()
|
|
<< "." << std::endl;
|
|
std::cout << " - Number of nonzero elements in model: "
|
|
<< lars.ActiveSet().size() << "." << std::endl;
|
|
std::cout << " - Number of models in LARS path: " << lars.BetaPath().size()
|
|
<< "." << std::endl;
|
|
std::cout << " - Model weight for dimension 0: " << lars.Beta()[0] << "."
|
|
<< std::endl;
|
|
|
|
if (lars.FitIntercept())
|
|
{
|
|
std::cout << " - Intercept value: " << lars.Intercept() << "." << std::endl;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
Train several models with different L2 regularization penalty parameters, using
|
|
a precomputed Gram matrix.
|
|
|
|
```c++
|
|
// See https://datasets.mlpack.org/admission_predict.csv.
|
|
arma::mat data;
|
|
mlpack::data::Load("admission_predict.csv", data, true);
|
|
|
|
// See https://datasets.mlpack.org/admission_predict.responses.csv.
|
|
arma::rowvec responses;
|
|
mlpack::data::Load("admission_predict.responses.csv", responses, true);
|
|
|
|
// Precompute Gram matrix.
|
|
arma::mat gramMatrix = data * data.t();
|
|
|
|
std::vector<double> lambda2Values = { 0.01, 0.1, 1.0, 10.0, 100.0 };
|
|
for (double lambda2 : lambda2Values)
|
|
{
|
|
// Build a LARS model using the precomputed Gram matrix. We did not normalize
|
|
// or center the data before computing the Gram matrix, so we have to set
|
|
// fitIntercept and normalizeData accordingly.
|
|
mlpack::LARS lars(data, responses, true, true, gramMatrix, 0.01, lambda2,
|
|
1e-16, false, false);
|
|
|
|
std::cout << "MSE with L2 penalty " << lambda2 << ": "
|
|
<< (lars.ComputeError(data, responses) / data.n_cols) << "." << std::endl;
|
|
}
|
|
```
|
|
|
|
### Advanced Functionality: Different Element Types
|
|
|
|
The `LARS` class has one template parameter that can be used to
|
|
control the element type of the model. The full signature of the class is:
|
|
|
|
```
|
|
LARS<ModelMatType>
|
|
```
|
|
|
|
`ModelMatType` specifies the type of matrix used for the internal representation
|
|
of model parameters. Any matrix type that implements the Armadillo API can be
|
|
used.
|
|
|
|
Note that the `Train()` and `Predict()` functions themselves are templatized and
|
|
can allow any matrix type that has the same element type. So, for instance, a
|
|
`LARS<arma::sp_mat>` can accept an `arma::mat` for training.
|
|
|
|
The example below trains a LARS model on 32-bit precision data, using
|
|
`arma::sp_fmat` to store the model parameters.
|
|
|
|
```c++
|
|
// Create random, sparse 1000-dimensional data.
|
|
arma::fmat dataset(1000, 5000, arma::fill::randu);
|
|
|
|
// Generate noisy responses from random data.
|
|
arma::fvec trueWeights(1000, arma::fill::randu);
|
|
arma::frowvec responses = trueWeights.t() * dataset +
|
|
0.01 * arma::randu<arma::frowvec>(5000) /* noise term */;
|
|
|
|
mlpack::LARS<arma::sp_fmat> lars;
|
|
lars.Lambda1() = 0.1;
|
|
lars.Lambda2() = 0.01;
|
|
|
|
lars.Train(dataset, responses);
|
|
|
|
// Compute the MSE on the training set and a random test set.
|
|
arma::fmat testDataset(1000, 2500, arma::fill::randu);
|
|
arma::frowvec testResponses = trueWeights.t() * testDataset +
|
|
0.01 * arma::randu<arma::frowvec>(2500) /* noise term */;
|
|
|
|
const float trainMSE = lars.ComputeError(dataset, responses) / dataset.n_cols;
|
|
const float testMSE = lars.ComputeError(testDataset, testResponses) /
|
|
testDataset.n_cols;
|
|
|
|
std::cout << "MSE on training set: " << trainMSE << "." << std::endl;
|
|
std::cout << "MSE on test set: " << testMSE << "." << std::endl;
|
|
```
|
|
|
|
***Note:*** it is generally only more efficient to use a sparse type (e.g.
|
|
`arma::sp_mat`) for `ModelMatType` when the L1 regularization parameter is set
|
|
such that a highly sparse model is produced.
|