/*
* File: MultivariateDecorrelator.java
* Authors: Justin Basilico
* Company: Sandia National Laboratories
* Project: Cognitive Foundry
*
* Copyright June 10, 2009, Sandia Corporation.
* Under the terms of Contract DE-AC04-94AL85000, there is a non-exclusive
* license for use of this work by or on behalf of the U.S. Government. Export
* of this program may require a license from the United States Government.
* See CopyrightHistory.txt for complete details.
*
*/
package gov.sandia.cognition.learning.data.feature;
import gov.sandia.cognition.evaluator.Evaluator;
import gov.sandia.cognition.learning.algorithm.BatchLearner;
import gov.sandia.cognition.learning.data.DatasetUtil;
import gov.sandia.cognition.math.RingAccumulator;
import gov.sandia.cognition.math.matrix.Matrix;
import gov.sandia.cognition.math.matrix.MatrixFactory;
import gov.sandia.cognition.math.matrix.Vector;
import gov.sandia.cognition.math.matrix.VectorFactory;
import gov.sandia.cognition.math.matrix.VectorInputEvaluator;
import gov.sandia.cognition.math.matrix.VectorOutputEvaluator;
import gov.sandia.cognition.math.matrix.Vectorizable;
import gov.sandia.cognition.math.matrix.mtj.DenseMatrixFactoryMTJ;
import gov.sandia.cognition.math.matrix.mtj.decomposition.CholeskyDecompositionMTJ;
import gov.sandia.cognition.statistics.distribution.MultivariateGaussian;
import gov.sandia.cognition.util.AbstractCloneableSerializable;
import gov.sandia.cognition.util.ObjectUtil;
import java.util.Collection;
/**
* Decorrelates a data using a mean and full or diagonal covariance matrix.
*
* @author Justin Basilico
* @since 3.0
*/
public class MultivariateDecorrelator
extends AbstractCloneableSerializable
implements Evaluator<Vectorizable, Vector>,
VectorInputEvaluator<Vectorizable, Vector>,
VectorOutputEvaluator<Vectorizable, Vector>
{
/** The underlying Gaussian. */
protected MultivariateGaussian gaussian;
/** The square root of the inverse of the covariance. */
private Matrix covarianceInverseSquareRoot;
/**
* Creates a new instance of MultivariateDecorrelator with no underlying
* Gaussian. It will have to be set later on using setGaussian.
*/
public MultivariateDecorrelator()
{
this((MultivariateGaussian) null);
}
/**
* Creates a new instance of MultivariateDecorrelator with the given
* mean and variance.
*
* @param mean The mean.
* @param covariance The variance.
*/
public MultivariateDecorrelator(
final Vector mean,
final Matrix covariance)
{
this(new MultivariateGaussian(mean, covariance));
}
/**
* Creates a new instance of MultivariateDecorrelator with the given
* multivariate Gaussian.
*
* @param gaussian
* The multivariate Gaussian to use.
*/
public MultivariateDecorrelator(
final MultivariateGaussian gaussian)
{
super();
this.setGaussian(gaussian);
}
/**
* Copy constructor.
* @param other
* MultivariateDecorrelator to copy
*/
public MultivariateDecorrelator(
final MultivariateDecorrelator other)
{
this(ObjectUtil.cloneSafe(other.getGaussian()));
}
/**
* Creates a new copy of this MultivariateDecorrelator.
*
* @return A new copy of this MultivariateDecorrelator.
*/
@Override
public MultivariateDecorrelator clone()
{
final MultivariateDecorrelator clone =
(MultivariateDecorrelator) super.clone();
clone.gaussian = ObjectUtil.cloneSafe(this.gaussian);
clone.covarianceInverseSquareRoot =
ObjectUtil.cloneSafe(this.covarianceInverseSquareRoot);
return clone;
}
/**
* Normalizes the given double value by subtracting the mean and dividing
* by the standard deviation (the square root of the variance).
*
* @param value The value to normalize.
* @return The normalized value.
*/
public Vector evaluate(
final Vectorizable value)
{
final Vector input = value.convertToVector();
return input.minus(this.getMean()).times(
this.getCovarianceInverseSquareRoot());
}
@Override
public int getInputDimensionality()
{
return this.getGaussian().getInputDimensionality();
}
@Override
public int getOutputDimensionality()
{
return this.getGaussian().getInputDimensionality();
}
/**
* Gets the mean of the underlying Gaussian.
*
* @return
* The mean.
*/
public Vector getMean()
{
return this.getGaussian().getMean();
}
/**
* Gets the covariance.
*
* @return
* The covariance
*/
public Matrix getCovariance()
{
return this.getGaussian().getCovariance();
}
/**
* Gets the underlying multivariate Gaussian.
*
* @return The underlying multivariate Gaussian.
*/
public MultivariateGaussian getGaussian()
{
return this.gaussian;
}
/**
* Sets the underlying multivariate Gaussian. A copy of this Gaussian is
* kept in the object.
*
* @param gaussian
* The Gaussian to use.
*/
public void setGaussian(
final MultivariateGaussian gaussian)
{
if (gaussian == null)
{
this.gaussian = null;
this.covarianceInverseSquareRoot = null;
}
else
{
this.gaussian = gaussian.clone();
final CholeskyDecompositionMTJ chokesky =
CholeskyDecompositionMTJ.create(
DenseMatrixFactoryMTJ.INSTANCE.copyMatrix(
(gaussian.getCovarianceInverse())));
this.covarianceInverseSquareRoot = chokesky.getR();
}
}
/**
* Gets the square root of the inverse of the covariance matrix. This value
* is what is used to perform the normalization.
*
* @return
* The square root of the inverse of the covariance matrix.
*/
public Matrix getCovarianceInverseSquareRoot()
{
return this.covarianceInverseSquareRoot;
}
/**
* Learns a normalization based on a mean and full covariance matrix from
* the given data.
*
* @param values
* The values to learn the decorrelator from.
* @param defaultCovariance
* The default value for the covariance. Added to the diagonal of the
* covariance matrix to prevent singular values.
* @return
* The MultivariateDecorrelator created from the multivariate mean and
* variance.
*/
public static MultivariateDecorrelator learnFullCovariance(
final Collection<? extends Vectorizable> values,
final double defaultCovariance)
{
// Convert the values to vector form.
final Collection<Vector> vectorValues =
DatasetUtil.asVectorCollection(values);
// Learn the maximum likelihood estimator of the Gaussian.
final MultivariateGaussian.PDF pdf =
MultivariateGaussian.MaximumLikelihoodEstimator.learn(
vectorValues, defaultCovariance);
return new MultivariateDecorrelator(pdf);
}
/**
* Learns a normalization based on a mean and covariance where the
* covariance matrix is diagonal. That is, each dimension is treated
* separately.
*
* @param values
* The values to use to build the normalizer.
* @param defaultCovariance
* The default value for the covariance. Added to the diagonal of the
* covariance matrix to prevent singular values.
* @return
* The MultivariateDecorrelator created from the multivariate mean and
* variance of the given values.
*/
public static MultivariateDecorrelator learnDiagonalCovariance(
final Collection<? extends Vectorizable> values,
final double defaultCovariance)
{
if (values == null)
{
// Error: Bad values.
throw new IllegalArgumentException("values cannot be null.");
}
final int count = values.size();
if (count <= 0)
{
// Error: Not enough samples.
throw new IllegalArgumentException("values cannot be empty.");
}
// Compute the mean.
final RingAccumulator<Vector> meanAccumulator =
new RingAccumulator<Vector>();
for (Vectorizable value : values)
{
meanAccumulator.accumulate(value.convertToVector());
}
final Vector mean = meanAccumulator.getMean();
// Compute the variance.
final Vector variance =
VectorFactory.getDefault().createVector( mean.getDimensionality() );
for (Vectorizable value : values)
{
final Vector difference = value.convertToVector().minus(mean);
difference.dotTimesEquals(difference);
variance.plusEquals(difference);
}
// Compute the total variance.
variance.scaleEquals(1.0 / (double) count);
// Add the default covariance to the variance.
variance.plusEquals(VectorFactory.getDefault().createVector(
mean.getDimensionality(), defaultCovariance));
// Convert the variance into a diagonal covariance matrix.
final Matrix covariance =
MatrixFactory.getDefault().createDiagonal(variance);
return new MultivariateDecorrelator(mean, covariance);
}
/**
* The {@code FullCovarianceLearner} class implements a {@code BatchLearner}
* object for a {@code MultivariateDecorrelator}.
*/
public static class FullCovarianceLearner
extends AbstractCloneableSerializable
implements BatchLearner<Collection<? extends Vectorizable>, MultivariateDecorrelator>
{
/** The default value for default covariance is {@value}. */
public static final double DEFAULT_DEFAULT_COVARIANCE =
MultivariateGaussian.MaximumLikelihoodEstimator.DEFAULT_COVARIANCE;
/** The default covariance. Added to the diagonal to prevent it from
* becoming singular.
*/
protected double defaultCovariance;
/**
* Creates a new MultivariateDecorrelator.FullCovarianceLearner with
* the default value for default covariance.
*/
public FullCovarianceLearner()
{
this(DEFAULT_DEFAULT_COVARIANCE);
}
/**
* Creates a new MultivariateDecorrelator.FullCovarianceLearner with the
* given value for default covariance.
*
* @param defaultCovariance
* The default covariance value. Added to the diagonal to prevent
* it from becoming singular
*/
public FullCovarianceLearner(
final double defaultCovariance)
{
super();
this.setDefaultCovariance(defaultCovariance);
}
/**
* Learns a MultivariateDecorrelator from the given values by
* computing the mean and covariance of the dimensions.
*
* @param values
* The values to use.
* @return
* The MultivariateDecorrelator computed from the given values.
*/
public MultivariateDecorrelator learn(
final Collection<? extends Vectorizable> values)
{
return MultivariateDecorrelator.learnFullCovariance(
values, this.getDefaultCovariance());
}
/**
* Gets the default covariance value. It is added to the diagonal terms
* of the covariance matrix to attempt to prevent them from becoming
* singular.
*
* @return
* The default covariance value.
*/
public double getDefaultCovariance()
{
return this.defaultCovariance;
}
/**
* Sets the default covariance value. It is added to the diagonal terms
* of the covariance matrix to attempt to prevent them from becoming
* singular.
*
* @param defaultCovariance
* The default covariance value. Must be non-negative.
*/
public void setDefaultCovariance(
final double defaultCovariance)
{
if (defaultCovariance < 0.0)
{
throw new IllegalArgumentException(
"defaultCovariance cannot be negative.");
}
this.defaultCovariance = defaultCovariance;
}
}
/**
* The {@code DiagonalCovarianceLearner} class implements a {@code BatchLearner}
* object for a {@code MultivariateDecorrelator}.
*/
public static class DiagonalCovarianceLearner
extends AbstractCloneableSerializable
implements BatchLearner<Collection<? extends Vectorizable>, MultivariateDecorrelator>
{
/** The default value for default covariance is {@value}. */
public static final double DEFAULT_DEFAULT_COVARIANCE =
MultivariateGaussian.MaximumLikelihoodEstimator.DEFAULT_COVARIANCE;
/** The default covariance. Added to the diagonal to prevent it from
* becoming singular.
*/
protected double defaultCovariance;
/**
* Creates a new MultivariateDecorrelator.DiagonalCovarianceLearner
* with the default value for default covariance.
*/
public DiagonalCovarianceLearner()
{
this(DEFAULT_DEFAULT_COVARIANCE);
}
/**
* Creates a new MultivariateDecorrelator.DiagonalCovarianceLearner with
* the given value for default covariance.
*
* @param defaultCovariance
* The default covariance value. Added to the diagonal to prevent
* it from becoming singular
*/
public DiagonalCovarianceLearner(
final double defaultCovariance)
{
super();
this.setDefaultCovariance(defaultCovariance);
}
/**
* Learns a MultivariateDecorrelator from the given values by
* computing the mean and variance for each dimension separately.
*
* @param values
* The values to use.
* @return
* The MultivariateDecorrelator computed from the given values
* with a diagonal covariance matrix.
*/
public MultivariateDecorrelator learn(
final Collection<? extends Vectorizable> values)
{
return MultivariateDecorrelator.learnDiagonalCovariance(
values, this.getDefaultCovariance());
}
/**
* Gets the default covariance value. It is added to the diagonal terms
* of the covariance matrix to attempt to prevent them from becoming
* singular.
*
* @return
* The default covariance value.
*/
public double getDefaultCovariance()
{
return this.defaultCovariance;
}
/**
* Sets the default covariance value. It is added to the diagonal terms
* of the covariance matrix to attempt to prevent them from becoming
* singular.
*
* @param defaultCovariance
* The default covariance value. Must be non-negative.
*/
public void setDefaultCovariance(
final double defaultCovariance)
{
if (defaultCovariance < 0.0)
{
throw new IllegalArgumentException(
"defaultCovariance cannot be negative.");
}
this.defaultCovariance = defaultCovariance;
}
}
}