Add sparse autoencoder contribution by Siddharth. This is the version given in

ticket #345 as Changes3.tar.gz.
This commit is contained in:
Ryan Curtin
2014-04-16 18:30:27 +00:00
parent bc3d650105
commit bdd4f83edc
4 changed files with 577 additions and 0 deletions
@@ -0,0 +1,182 @@
#include <mlpack/core.hpp>
#include <mlpack/core/optimizers/lbfgs/lbfgs.hpp>
#include "sparse_autoencoder_function.hpp"
/**
* Sparse Autoencoder is a neural network whose aim to learn compressed
* representations of the data, typically for dimensionality reduction, with
* a constraint on the activity of the neurons in the network. Sparse
* Autoencoders can be stacked together to learn a hierarchy of features, which
* provide a better representation of the data for classification. This is a
* method used in the recently developed field of Deep Learning. More technical
* details about the model can be found on the following webpage:
* http://deeplearning.stanford.edu/wiki/index.php/UFLDL_Tutorial
*
* An example of how to use the interface is shown below:
* @code
* arma::mat data; // Data matrix.
* const size_t vSize = 64; // Size of visible layer, depends on the data.
* const size_t hSize = 25; // Size of hidden layer, depends on requirements.
*
* // Train the model using default options.
* SparseAutoencoder encoder1(data, vSize, hSize);
*
* const size_t numBasis = 5; // Parameter required for L-BFGS algorithm.
* const size_t numIterations = 100; // Maximum number of iterations.
*
* // Use an instantiated optimizer for the training.
* SparseAutoencoderFunction saf(data, vSize, hSize);
* L_BFGS<SparseAutoencoderFunction> optimizer(saf, numBasis, numIterations);
* SparseAutoencoder<L_BFGS> encoder2(optimizer);
*
* arma::mat features1, features2; // Matrices for storing new representations.
*
* // Get new representations from the trained models.
* encoder1.GetNewFeatures(data, features1);
* encoder2.GetNewFeatures(data, features2);
*/
namespace mlpack {
namespace nn {
template<
template<typename> class OptimizerType = mlpack::optimization::L_BFGS
>
class SparseAutoencoder
{
public:
/**
* Construct the Sparse Autoencoder model with the given training data. This
* will train the model. The parameters 'lambda', 'beta' and 'rho' can be set
* optionally. Changing these parameters will have an effect on regularization
* and sparsity of the model.
*
* @param data Input data with each column as one example.
* @param visibleSize Size of input vector expected at the visible layer.
* @param hiddenSize Size of input vector expected at the hidden layer.
* @param lambda L2-regularization parameter.
* @param beta KL divergence parameter.
* @param rho Sparsity parameter.
*/
SparseAutoencoder(const arma::mat& data,
const size_t visibleSize,
const size_t hiddenSize,
const double lambda = 0.0001,
const double beta = 3,
const double rho = 0.01);
/**
* Construct the Sparse Autoencoder model with the given training data. This
* will train the model. This overload takes an already instantiated optimizer
* and uses it to train the model. The optimizer should hold an instantiated
* SparseAutoencoderFunction object for the function to operate upon. This
* option should be preferred when the optimizer options are to be changed.
*
* @param optimizer Instantiated optimizer with instantiated error function.
*/
SparseAutoencoder(OptimizerType<SparseAutoencoderFunction>& optimizer);
/**
* Transforms the provided data into a more meaningful and compact
* representation. The function basically performs a feedforward computation
* using the learned weights, and returns the hidden layer activations.
*
* @param data Matrix of the provided data.
* @param features The hidden layer representation of the provided data.
*/
void GetNewFeatures(arma::mat& data, arma::mat& features);
/**
* Returns the elementwise sigmoid of the passed matrix, where the sigmoid
* function of a real number 'x' is [1 / (1 + exp(-x))].
*
* @param x Matrix of real values for which we require the sigmoid activation.
*/
arma::mat Sigmoid(const arma::mat& x) const
{
return (1.0 / (1 + arma::exp(-x)));
}
//! Sets size of the visible layer.
void VisibleSize(const size_t visible)
{
this->visibleSize = visible;
}
//! Gets size of the visible layer.
size_t VisibleSize() const
{
return visibleSize;
}
//! Sets size of the hidden layer.
void HiddenSize(const size_t hidden)
{
this->hiddenSize = hidden;
}
//! Gets the size of the hidden layer.
size_t HiddenSize() const
{
return hiddenSize;
}
//! Sets the L2-regularization parameter.
void Lambda(const double l)
{
this->lambda = l;
}
//! Gets the L2-regularization parameter.
double Lambda() const
{
return lambda;
}
//! Sets the KL divergence parameter.
void Beta(const double b)
{
this->beta = b;
}
//! Gets the KL divergence parameter.
double Beta() const
{
return beta;
}
//! Sets the sparsity parameter.
void Rho(const double r)
{
this->rho = r;
}
//! Gets the sparsity parameter.
double Rho() const
{
return rho;
}
private:
//! Parameters after optimization.
arma::mat parameters;
//! Size of the visible layer.
size_t visibleSize;
//! Size of the hidden layer.
size_t hiddenSize;
//! L2-regularization parameter.
double lambda;
//! KL divergence parameter.
double beta;
//! Sparsity parameter.
double rho;
};
}; // namespace nn
}; // namespace mlpack
// Include implementation.
#include "sparse_autoencoder_impl.hpp"
@@ -0,0 +1,192 @@
#include "sparse_autoencoder_function.hpp"
using namespace mlpack;
using namespace mlpack::nn;
using namespace std;
SparseAutoencoderFunction::SparseAutoencoderFunction(const arma::mat& data,
const size_t visibleSize,
const size_t hiddenSize,
const double lambda,
const double beta,
const double rho) :
data(data),
visibleSize(visibleSize),
hiddenSize(hiddenSize),
lambda(lambda),
beta(beta),
rho(rho)
{
// Initialize the parameters to suitable values.
initialPoint = InitializeWeights();
}
/** Initializes the parameter weights if the initial point is not passed to the
* constructor. The weights w1, w2 are initialized to randomly in the range
* [-r, r] where 'r' is decided using the sizes of the visible and hidden
* layers. The biases b1, b2 are initialized to 0.
*/
const arma::mat SparseAutoencoderFunction::InitializeWeights()
{
// The module uses a matrix to store the parameters, its structure looks like:
// vSize 1
// | | |
// hSize| w1 |b1|
// |________|__|
// | | |
// hSize| w2' | |
// |________|__|
// 1| b2' | |
//
// There are (hiddenSize + 1) empty cells in the matrix, but it is small
// compared to the matrix size. The above structure allows for smooth matrix
// operations without making the code too ugly.
arma::mat parameters;
parameters.zeros(2*hiddenSize + 1, visibleSize + 1);
// Initialize w1 and w2 to random values in the range [0, 1].
parameters.submat(0, 0, 2*hiddenSize - 1, visibleSize - 1).randu();
// Decide the parameter 'r' depending on the size of the visible and hidden
// layers. The formula used is r = sqrt(6) / sqrt(vSize + hSize + 1).
const double range = sqrt(6) / sqrt(visibleSize + hiddenSize + 1);
//Shift range of w1 and w2 values from [0, 1] to [-r, r].
parameters.submat(0, 0, 2*hiddenSize - 1, visibleSize - 1) = 2 * range *
(parameters.submat(0, 0, 2*hiddenSize - 1, visibleSize - 1) - 0.5);
return parameters;
}
/** Evaluates the objective function given the parameters.
*/
double SparseAutoencoderFunction::Evaluate(const arma::mat& parameters) const
{
// The objective function is the average squared reconstruction error of the
// network. w1 and b1 are the weights and biases associated with the hidden
// layer, whereas w2 and b2 are associated with the output layer.
// f(w1,w2,b1,b2) = sum((data - sigmoid(w2*sigmoid(w1data + b1) + b2))^2) / 2m
// 'm' is the number of training examples.
// The cost also takes into account the regularization and KL divergence terms
// to control the parameter weights and sparsity of the model respectively.
// Compute the limits for the parameters w1, w2, b1 and b2.
const size_t l1 = hiddenSize;
const size_t l2 = visibleSize;
const size_t l3 = 2*hiddenSize;
// w1, w2, b1 and b2 are not extracted separately, 'parameters' is directly
// used in their place to avoid copying data. The following representations
// are used:
// w1 <- parameters.submat(0, 0, l1-1, l2-1)
// w2 <- parameters.submat(l1, 0, l3-1, l2-1).t()
// b1 <- parameters.submat(0, l2, l1-1, l2)
// b2 <- parameters.submat(l3, 0, l3, l2-1).t()
arma::mat hiddenLayer, outputLayer;
// Compute activations of the hidden and output layers.
hiddenLayer = Sigmoid(parameters.submat(0, 0, l1-1, l2-1) * data +
arma::repmat(parameters.submat(0, l2, l1-1, l2), 1, data.n_cols));
outputLayer = Sigmoid(parameters.submat(l1, 0, l3-1, l2-1).t() * hiddenLayer +
arma::repmat(parameters.submat(l3, 0, l3, l2-1).t(), 1, data.n_cols));
arma::mat rhoCap, diff;
// Average activations of the hidden layer.
rhoCap = arma::sum(hiddenLayer, 1) / data.n_cols;
// Difference between the reconstructed data and the original data.
diff = outputLayer - data;
double wL2SquaredNorm;
// Calculate squared L2-norms of w1 and w2.
wL2SquaredNorm = arma::accu(parameters.submat(0, 0, l3-1, l2-1) %
parameters.submat(0, 0, l3-1, l2-1));
double sumOfSquaresError, weightDecay, klDivergence, cost;
// Calculate the reconstruction error, the regularization cost and the KL
// divergence cost terms. 'sumOfSquaresError' is the average squared l2-norm
// of the reconstructed data difference. 'weightDecay' is the squared l2-norm
// of the weights w1 and w2. 'klDivergence' is the cost of the hidden layer
// activations not being low. It is given by the following formula:
// KL = sum_over_hSize(rho*log(rho/rhoCaq) + (1-rho)*log((1-rho)/(1-rhoCap)))
sumOfSquaresError = 0.5 * arma::accu(diff % diff) / data.n_cols;
weightDecay = 0.5 * lambda * wL2SquaredNorm;
klDivergence = beta * arma::accu(rho * arma::log(rho / rhoCap) + (1 - rho) *
arma::log((1 - rho) / (1 - rhoCap)));
// The cost is the sum of the terms calculated above.
cost = sumOfSquaresError + weightDecay + klDivergence;
return cost;
}
/** Calculates and stores the gradient values given a set of parameters.
*/
void SparseAutoencoderFunction::Gradient(const arma::mat& parameters,
arma::mat& gradient) const
{
// Performs a feedforward pass of the neural network, and computes the
// activations of the output layer as in the Evaluate() method. It uses the
// Backpropagation algorithm to calculate the delta values at each layer,
// except for the input layer. The delta values are then used with input layer
// and hidden layer activations to get the parameter gradients.
// Compute the limits for the parameters w1, w2, b1 and b2.
const size_t l1 = hiddenSize;
const size_t l2 = visibleSize;
const size_t l3 = 2*hiddenSize;
// w1, w2, b1 and b2 are not extracted separately, 'parameters' is directly
// used in their place to avoid copying data. The following representations
// are used:
// w1 <- parameters.submat(0, 0, l1-1, l2-1)
// w2 <- parameters.submat(l1, 0, l3-1, l2-1).t()
// b1 <- parameters.submat(0, l2, l1-1, l2)
// b2 <- parameters.submat(l3, 0, l3, l2-1).t()
arma::mat hiddenLayer, outputLayer;
// Compute activations of the hidden and output layers.
hiddenLayer = Sigmoid(parameters.submat(0, 0, l1-1, l2-1) * data +
arma::repmat(parameters.submat(0, l2, l1-1, l2), 1, data.n_cols));
outputLayer = Sigmoid(parameters.submat(l1, 0, l3-1, l2-1).t() * hiddenLayer +
arma::repmat(parameters.submat(l3, 0, l3, l2-1).t(), 1, data.n_cols));
arma::mat rhoCap, diff;
// Average activations of the hidden layer.
rhoCap = arma::sum(hiddenLayer, 1) / data.n_cols;
// Difference between the reconstructed data and the original data.
diff = outputLayer - data;
arma::mat klDivGrad, delOut, delHid;
// The delta vector for the output layer is given by diff * f'(z), where z is
// the preactivation and f is the activation function. The derivative of the
// sigmoid function turns out to be f(z) * (1 - f(z)). For every other layer
// in the neural network which comes before the output layer, the delta values
// are given del_n = w_n' * del_(n+1) * f'(z_n). Since our cost function also
// includes the KL divergence term, we adjust for that in the formula below.
klDivGrad = beta * (-(rho / rhoCap) + (1 - rho) / (1 - rhoCap));
delOut = diff % outputLayer % (1 - outputLayer);
delHid = (parameters.submat(l1, 0, l3-1, l2-1) * delOut +
arma::repmat(klDivGrad, 1, data.n_cols)) % hiddenLayer % (1-hiddenLayer);
gradient.zeros(2*hiddenSize + 1, visibleSize + 1);
// Compute the gradient values using the activations and the delta values. The
// formula also accounts for the regularization terms in the objective.
// function.
gradient.submat(0, 0, l1-1, l2-1) = delHid * data.t() / data.n_cols + lambda *
parameters.submat(0, 0, l1-1, l2-1);
gradient.submat(l1, 0, l3-1, l2-1) = (delOut * hiddenLayer.t() / data.n_cols +
lambda * parameters.submat(l1, 0, l3-1, l2-1).t()).t();
gradient.submat(0, l2, l1-1, l2) = arma::sum(delHid, 1) / data.n_cols;
gradient.submat(l3, 0, l3, l2-1) = (arma::sum(delOut, 1) / data.n_cols).t();
}
@@ -0,0 +1,141 @@
#include <mlpack/core.hpp>
namespace mlpack {
namespace nn {
/**
* This is a class for the Sparse Autoencoder objective function. It can be used
* to create learning models like Self-Taught Learning, Stacked Autoencoders,
* Conditional Random Fields etc.
*/
class SparseAutoencoderFunction
{
public:
SparseAutoencoderFunction(const arma::mat& data,
const size_t visibleSize,
const size_t hiddenSize,
const double lambda = 0.0001,
const double beta = 3,
const double rho = 0.01);
//Initializes the parameters of the model to suitable values.
const arma::mat InitializeWeights();
/**
* Evaluates the objective function of the Sparse Autoencoder model using the
* given parameters. The cost function has terms for the reconstruction
* error, regularization cost and the sparsity cost. The objective function
* takes a low value when the model is able to reconstruct the data well
* using weights which are low in value and when the average activations of
* neurons in the hidden layers agrees well with the sparsity parameter 'rho'.
*
* @param parameters Current values of the model parameters.
*/
double Evaluate(const arma::mat& parameters) const;
/**
* Evaluates the gradient values of the parameters given the current set of
* parameters. The function performs a feedforward pass and computes the error
* in reconstructing the data points. It then uses the backpropagation
* algorithm to compute the gradient values.
*
* @param parameters Current values of the model parameters.
* @param gradient Pointer to matrix where gradient values are stored.
*/
void Gradient(const arma::mat& parameters, arma::mat& gradient) const;
/**
* Returns the elementwise sigmoid of the passed matrix, where the sigmoid
* function of a real number 'x' is [1 / (1 + exp(-x))].
*
* @param x Matrix of real values for which we require the sigmoid activation.
*/
arma::mat Sigmoid(const arma::mat& x) const
{
return (1.0 / (1 + arma::exp(-x)));
}
//! Return the initial point for the optimization.
const arma::mat& GetInitialPoint() const { return initialPoint; }
//! Sets size of the visible layer.
void VisibleSize(const size_t visible)
{
this->visibleSize = visible;
}
//! Gets size of the visible layer.
size_t VisibleSize() const
{
return visibleSize;
}
//! Sets size of the hidden layer.
void HiddenSize(const size_t hidden)
{
this->hiddenSize = hidden;
}
//! Gets the size of the hidden layer.
size_t HiddenSize() const
{
return hiddenSize;
}
//! Sets the L2-regularization parameter.
void Lambda(const double l)
{
this->lambda = l;
}
//! Gets the L2-regularization parameter.
double Lambda() const
{
return lambda;
}
//! Sets the KL divergence parameter.
void Beta(const double b)
{
this->beta = b;
}
//! Gets the KL divergence parameter.
double Beta() const
{
return beta;
}
//! Sets the sparsity parameter.
void Rho(const double r)
{
this->rho = r;
}
//! Gets the sparsity parameter.
double Rho() const
{
return rho;
}
private:
//! The matrix of data points.
const arma::mat& data;
//! Intial parameter vector.
arma::mat initialPoint;
//! Size of the visible layer.
size_t visibleSize;
//! Size of the hidden layer.
size_t hiddenSize;
//! L2-regularization parameter.
double lambda;
//! KL divergence parameter.
double beta;
//! Sparsity parameter.
double rho;
};
}; // namespace nn
}; // namespace mlpack
@@ -0,0 +1,62 @@
namespace mlpack {
namespace nn {
template<template<typename> class OptimizerType>
SparseAutoencoder<OptimizerType>::SparseAutoencoder(const arma::mat& data,
const size_t visibleSize,
const size_t hiddenSize,
double lambda,
double beta,
double rho) :
visibleSize(visibleSize),
hiddenSize(hiddenSize),
lambda(lambda),
beta(beta),
rho(rho)
{
SparseAutoencoderFunction encoderFunction(data, visibleSize, hiddenSize,
lambda, beta, rho);
OptimizerType<SparseAutoencoderFunction> optimizer(encoderFunction);
parameters = encoderFunction.GetInitialPoint();
// Train the model.
Timer::Start("sparse_autoencoder_optimization");
const double out = optimizer.Optimize(parameters);
Timer::Stop("sparse_autoencoder_optimization");
Log::Info << "SparseAutoencoder::SparseAutoencoder(): final objective of "
<< "trained model is " << out << "." << std::endl;
}
template<template<typename> class OptimizerType>
SparseAutoencoder<OptimizerType>::SparseAutoencoder(
OptimizerType<SparseAutoencoderFunction> &optimizer) :
parameters(optimizer.Function().GetInitialPoint()),
visibleSize(optimizer.Function().VisibleSize()),
hiddenSize(optimizer.Function().HiddenSize()),
lambda(optimizer.Function().Lambda()),
beta(optimizer.Function().Beta()),
rho(optimizer.Function().Rho())
{
Timer::Start("sparse_autoencoder_optimization");
const double out = optimizer.Optimize(parameters);
Timer::Stop("sparse_autoencoder_optimization");
Log::Info << "SparseAutoencoder::SparseAutoencoder(): final objective of "
<< "trained model is " << out << "." << std::endl;
}
template<template<typename> class OptimizerType>
void SparseAutoencoder<OptimizerType>::GetNewFeatures(arma::mat& data,
arma::mat& features)
{
const size_t l1 = hiddenSize;
const size_t l2 = visibleSize;
features = Sigmoid(parameters.submat(0, 0, l1-1, l2-1) * data +
arma::repmat(parameters.submat(0, l2, l1-1, l2), 1, data.n_cols));
}
}; // namespace nn
}; // namespace mlpack