gov.sandia.cognition.statistics.distribution.MultivariateGaussian Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cognitive-foundry Show documentation
Show all versions of cognitive-foundry Show documentation
A single jar with all the Cognitive Foundry components.
/*
* File: MultivariateGaussian.java
* Authors: Justin Basilico and Kevin R. Dixon
* Company: Sandia National Laboratories
* Project: Cognitive Foundry
*
* Copyright February 28, 2006, 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.statistics.distribution;
import gov.sandia.cognition.annotation.CodeReview;
import gov.sandia.cognition.annotation.CodeReviewResponse;
import gov.sandia.cognition.annotation.PublicationReference;
import gov.sandia.cognition.annotation.PublicationType;
import gov.sandia.cognition.math.ComplexNumber;
import gov.sandia.cognition.math.MultivariateStatisticsUtil;
import gov.sandia.cognition.math.matrix.VectorFactory;
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.VectorInputEvaluator;
import gov.sandia.cognition.math.matrix.mtj.DenseMatrixFactoryMTJ;
import gov.sandia.cognition.math.matrix.mtj.decomposition.CholeskyDecompositionMTJ;
import gov.sandia.cognition.statistics.AbstractDistribution;
import gov.sandia.cognition.statistics.AbstractIncrementalEstimator;
import gov.sandia.cognition.statistics.AbstractSufficientStatistic;
import gov.sandia.cognition.statistics.ClosedFormComputableDistribution;
import gov.sandia.cognition.statistics.DistributionEstimator;
import gov.sandia.cognition.statistics.DistributionWeightedEstimator;
import gov.sandia.cognition.statistics.EstimableDistribution;
import gov.sandia.cognition.statistics.ProbabilityDensityFunction;
import gov.sandia.cognition.util.AbstractCloneableSerializable;
import gov.sandia.cognition.util.ObjectUtil;
import gov.sandia.cognition.util.Pair;
import gov.sandia.cognition.util.WeightedValue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Random;
/**
* The MultivariateGaussian class implements a multidimensional Gaussian
* distribution that contains a mean vector and a covariance matrix.
* If your underlying distribution is univariate (scalar),
* then use the UnivariateGaussian class, as its operations
* are MUCH less computationally intensive.
*
* @author Justin Basilico
* @author Kevin R. Dixon
* @since 1.0
*/
@CodeReview(
reviewer="Jonathan McClain",
date="2006-05-15",
changesNeeded=true,
comments={
"A few minor changes needed.",
"Comments indicated with a / / / comment in the first column."
},
response=@CodeReviewResponse(
respondent="Kevin R. Dixon",
date="2006-05-15",
moreChangesNeeded=false,
comments="Fixed."
)
)
@PublicationReference(
author="Wikipedia",
title="Multivariate normal distribution",
type=PublicationType.WebPage,
year=2009,
url="http://en.wikipedia.org/wiki/Multivariate_normal_distribution"
)
public class MultivariateGaussian
extends AbstractDistribution
implements ClosedFormComputableDistribution,
EstimableDistribution
{
/**
* Tolerance check for symmetry covariance tolerance, {@value}.
*/
public static final double DEFAULT_COVARIANCE_SYMMETRY_TOLERANCE = 1e-5;
/**
* Default dimensionality of the Gaussian, {@value}
*/
public static final int DEFAULT_DIMENSIONALITY = 2;
/**
* Natural logarithm of 2pi.
*/
public static final double LOG_TWO_PI = Math.log(2*Math.PI);
/**
* Mean of the MultivariateGaussian.
*/
private Vector mean;
/**
* Covariance matrix of the MultivariateGaussian.
*/
private Matrix covariance;
/**
* Natural logarithm of the covariance matrix, automatically computed.
*/
private Double logCovarianceDeterminant;
/**
* Inverse of the covariance matrix, automatically computed.
*/
private Matrix covarianceInverse;
/**
* Natural logarithm of the leading likelihood coefficient,
* automatically computed.
*/
private Double logLeadingCoefficient;
/**
* Default constructor.
*/
public MultivariateGaussian()
{
this( DEFAULT_DIMENSIONALITY );
}
/**
* Creates a new instance of MultivariateGaussian.
* @param dimensionality
* Dimensionality of the Gaussian to create.
*/
public MultivariateGaussian(
int dimensionality )
{
this( VectorFactory.getDefault().createVector(dimensionality),
MatrixFactory.getDefault().createIdentity(dimensionality,dimensionality) );
}
/**
* Creates a new instance of MultivariateGaussian.
*
* @param mean The mean of the Gaussian distribution.
* @param covariance The covariance matrix, which should be a symmetric
* matrix.
*/
public MultivariateGaussian(
Vector mean,
Matrix covariance)
{
this.setMean(mean);
this.setCovariance(covariance);
}
/**
* Creates a new instance of MultivariateGaussian.
*
* @param other The other MultivariateGaussian to copy.
*/
public MultivariateGaussian(
MultivariateGaussian other)
{
this( ObjectUtil.cloneSafe( other.getMean() ),
ObjectUtil.cloneSafe( other.getCovariance() ) );
}
@Override
public MultivariateGaussian clone()
{
MultivariateGaussian clone = (MultivariateGaussian) super.clone();
clone.setMean( ObjectUtil.cloneSafe( this.getMean() ) );
clone.setCovariance( ObjectUtil.cloneSafe( this.getCovariance() ) );
return clone;
}
@Override
public MultivariateGaussian.PDF getProbabilityFunction()
{
return new MultivariateGaussian.PDF( this );
}
/**
* Computes the z value squared, such that p(x) = coefficient * exp{-0.5*z^2}
* @param input
* input about which to compute the z-value squared
* @return
* z-value squared
*/
public double computeZSquared(
Vector input )
{
// Subtract the mean
Vector delta = input.minus(this.mean);
// Compute the weighted inner product.
double zsquared = delta.times(this.getCovarianceInverse()).dotProduct(delta);
return zsquared;
}
/**
* Multiplies this Gaussian with the other Gaussian. This is also
* equivalent to computing the posterior belief using one of the Gaussians
* as the prior and one as the conditional likelihood.
* @param other
* Other Gaussian to multiply with this.
* @return
* Multiplied Gaussians.
*/
public MultivariateGaussian times(
MultivariateGaussian other )
{
Vector m1 = this.mean;
Matrix c1inv = this.getCovarianceInverse();
Vector m2 = other.getMean();
Matrix c2inv = other.getCovarianceInverse();
Matrix Cinv = c1inv.plus( c2inv );
Matrix C = Cinv.inverse();
Vector m = C.times(
c1inv.times( m1 ).plus( c2inv.times( m2 ) ) );
return new MultivariateGaussian( m, C );
}
/**
* Convolves this Gaussian with the other Gaussian.
* @param other
* Other Gaussian to convolve with this.
* @return
* Convolved Gaussians.
*/
public MultivariateGaussian convolve(
MultivariateGaussian other )
{
Vector meanHat = this.mean.plus( other.getMean() );
Matrix covarianceHat = this.getCovariance().plus( other.getCovariance() );
return new MultivariateGaussian( meanHat, covarianceHat );
}
/**
* Gets the dimensionality of the Gaussian. This is the expected
* dimensionality of the vectors that can be used with the Gaussian.
*
* @return The dimensionality of the multivariate Gaussian.
*/
public int getInputDimensionality()
{
return (this.mean != null) ? this.mean.getDimensionality() : 0;
}
@Override
public Vector getMean()
{
return this.mean;
}
/**
* Sets the mean vector.
*
* @param mean The new mean, throws NullPointerException if null
*/
public void setMean(
Vector mean)
{
if ( mean == null )
{
throw new NullPointerException("Mean cannot be null.");
}
this.mean = mean;
}
/**
* Gets the covariance matrix of the Gaussian.
*
* @return The covariance matrix of the Gaussian.
*/
public Matrix getCovariance()
{
if( this.covariance == null )
{
this.covariance = this.covarianceInverse.inverse();
}
return this.covariance;
}
/**
* Sets the covariance matrix.
* @param covariance The new covariance matrix,
* the method forces the matrix to be symmetric by averaging the
* off-diagonal components into a clone of this parameter.
*/
public void setCovariance(
Matrix covariance)
{
this.setCovariance(covariance,DEFAULT_COVARIANCE_SYMMETRY_TOLERANCE);
}
/**
* Sets the covariance matrix.
*
* @param covariance The new covariance matrix,
* the method forces the matrix to be symmetric by averaging the
* off-diagonal components into a clone of this parameter.
* @param symmetryTolerance Tolerance for symmetry check
*/
public void setCovariance(
Matrix covariance,
double symmetryTolerance )
{
if( !covariance.isSymmetric(symmetryTolerance) )
{
covariance = covariance.clone();
int N = covariance.getNumRows();
for( int i = 1; i < N; i++ )
{
for( int j = 0; j < i; j++ )
{
double vij = covariance.getElement(i, j);
double vji = covariance.getElement(j, i);
if( vij != vji )
{
double v = (vij + vji) / 2.0;
covariance.setElement(i, j, v);
covariance.setElement(j, i, v);
}
}
}
}
this.covariance = covariance;
// Flag the other values for recomputation...
this.covarianceInverse = null;
this.logCovarianceDeterminant = null;
this.logLeadingCoefficient = null;
}
/**
* Gets the inverse of the covariance matrix.
*
* @return The inverse of the covariance matrix.
*/
public Matrix getCovarianceInverse()
{
// Need to recompute the covariance inverse.
if( this.covarianceInverse == null )
{
this.covarianceInverse = this.covariance.inverse();
}
return this.covarianceInverse;
}
/**
* Sets the covariance inverse
* @param covarianceInverse
* Inverse of the covariance matrix
*/
public void setCovarianceInverse(
Matrix covarianceInverse )
{
this.setCovarianceInverse(covarianceInverse,
DEFAULT_COVARIANCE_SYMMETRY_TOLERANCE);
}
/**
* Sets the covariance inverse
* @param covarianceInverse
* Inverse of the covariance matrix
* @param symmetryTolerance
* Tolerance to enforce symmetry
*/
public void setCovarianceInverse(
Matrix covarianceInverse,
double symmetryTolerance )
{
if( !covarianceInverse.isSymmetric(symmetryTolerance) )
{
covarianceInverse = covarianceInverse.clone();
int N = covarianceInverse.getNumRows();
for( int i = 1; i < N; i++ )
{
for( int j = 0; j < i; j++ )
{
final double vij = covarianceInverse.getElement(i, j);
final double vji = covarianceInverse.getElement(j, i);
if( vij != vji )
{
final double v = (vij + vji) / 2.0;
covarianceInverse.setElement(i, j, v);
covarianceInverse.setElement(j, i, v);
}
}
}
}
this.covarianceInverse = covarianceInverse;
// Flag the other values for recomputation...
this.covariance = null;
this.logCovarianceDeterminant = null;
this.logLeadingCoefficient = null;
}
/**
* Getter for logCovarianceDeterminant
* @return
* Natural logarithm of the covariance matrix, automatically computed.
*/
public double getLogCovarianceDeterminant()
{
if( this.logCovarianceDeterminant == null )
{
// Compute the determinant of the matrix.
ComplexNumber logDeterminant = this.covariance.logDeterminant();
// There should be no imaginary part, as the the determinant is
// a positive value
this.logCovarianceDeterminant = logDeterminant.getRealPart();
}
return this.logCovarianceDeterminant;
}
/**
* Getter for logLeadingCoefficient
* @return
* Natural logarithm of the leading likelihood coefficient,
* automatically computed.
*/
public double getLogLeadingCoefficient()
{
if( this.logLeadingCoefficient == null )
{
final int k = this.getInputDimensionality();
this.logLeadingCoefficient =
(-0.5*k*LOG_TWO_PI) + (-0.5*this.getLogCovarianceDeterminant());
}
return this.logLeadingCoefficient;
}
@Override
public boolean equals(
Object randomVariable)
{
boolean retval = false;
if( randomVariable instanceof MultivariateGaussian )
{
MultivariateGaussian other = (MultivariateGaussian) randomVariable;
retval = (this.getMean().equals( other.getMean() )) &&
(this.getCovariance().equals( other.getCovariance() ));
}
return retval;
}
@Override
public int hashCode()
{
int hash = 7;
hash = 53 * hash + ObjectUtil.hashCodeSafe(this.mean);
hash = 53 * hash + ObjectUtil.hashCodeSafe(this.getCovariance());
return hash;
}
@Override
public void sampleInto(
final Random random,
final int sampleCount,
final Collection super Vector> output)
{
final Matrix covSqrt = CholeskyDecompositionMTJ.create(
DenseMatrixFactoryMTJ.INSTANCE.copyMatrix(this.getCovariance())).getR();
sampleInto(this.mean, covSqrt, random, sampleCount, output);
}
/**
* Returns a collection of draws this Gaussian with the given mean
* and covariance. If you need random draws from a Gaussian many times, I
* would recommend that you cache the square-root decomposition of the
* covariance and pass that to the method randomCovarianceSquareRoot().
*
* @param mean mean of the Gaussian
* @param covarianceSquareRoot square-root decomposition of the
* covariance of the Gaussian
* @param sampleCount number of times to draw from this random variable
* @param random Random-number generator
* @return ArrayList of Vectors drawn from this Gaussian distribution
*/
public static ArrayList sample(
Vector mean,
Matrix covarianceSquareRoot,
Random random,
int sampleCount)
{
final ArrayList result = new ArrayList(sampleCount);
sampleInto(mean, covarianceSquareRoot, random, sampleCount, result);
return result;
}
/**
* Performs a collection of draws this Gaussian with the given mean
* and covariance. If you need random draws from a Gaussian many times, I
* would recommend that you cache the square-root decomposition of the
* covariance and pass that to the method randomCovarianceSquareRoot().
*
* @param mean mean of the Gaussian
* @param covarianceSquareRoot square-root decomposition of the
* covariance of the Gaussian
* @param sampleCount number of times to draw from this random variable
* @param random Random-number generator
* @param output The collection to put the samples into.
*/
public static void sampleInto(
final Vector mean,
final Matrix covarianceSquareRoot,
final Random random,
final int sampleCount,
final Collection super Vector> output)
{
for( int n = 0; n < sampleCount; n++ )
{
output.add(sample(mean, covarianceSquareRoot, random));
}
}
/**
* Returns a single draw from the Gaussian with the given mean
* and covariance.
* @param mean mean of the Gaussian
* @param covarianceSquareRoot square-root decomposition of the
* covariance of the Gaussian
* @param random Random-number generator
* @return Vector drawn from this Gaussian distribution
*/
public static Vector sample(
Vector mean,
Matrix covarianceSquareRoot,
Random random )
{
int M = covarianceSquareRoot.getNumRows();
Vector x = VectorFactory.getDefault().createVector( M );
for( int i = 0; i < M; i++ )
{
x.setElement( i, random.nextGaussian() );
}
Vector sample = covarianceSquareRoot.times( x );
sample.plusEquals(mean);
return sample;
}
/**
* Scales the MultivariateGaussian by premultiplying by the given Matrix
* @param premultiplyMatrix
* Matrix against which to premultiply this
* @return
* Scaled MultivariateGaussian
*/
public MultivariateGaussian scale(
Matrix premultiplyMatrix)
{
Vector m = premultiplyMatrix.times( this.mean );
Matrix C = premultiplyMatrix.times( this.getCovariance() ).times(
premultiplyMatrix.transpose() );
return new MultivariateGaussian( m, C );
}
/**
* Adds two MultivariateGaussian random variables together
* and returns the resulting MultivariateGaussian
* @param other
* MultivariateGaussian to add to this MultivariateGaussian
* @return
* Effective addition of the two MultivariateGaussian random variables
*/
public MultivariateGaussian plus(
MultivariateGaussian other )
{
Vector m = this.mean.plus( other.getMean() );
Matrix C = this.getCovariance().plus( other.getCovariance() );
return new MultivariateGaussian( m, C );
}
@Override
public String toString()
{
String retval = "Mean: " + this.getMean() + "\n"
+ "Covariance:\n" + this.getCovariance();
return retval;
}
@Override
public Vector convertToVector()
{
return this.mean.stack( this.getCovariance().convertToVector() );
}
@Override
public void convertFromVector(
Vector parameters )
{
int N = this.getInputDimensionality();
this.setMean( parameters.subVector( 0, N-1 ) );
Matrix m = this.getCovariance();
m.convertFromVector(
parameters.subVector( N, parameters.getDimensionality()-1 ) );
this.setCovariance( m );
}
@Override
public MultivariateGaussian.MaximumLikelihoodEstimator getEstimator()
{
return new MultivariateGaussian.MaximumLikelihoodEstimator();
}
/**
* PDF of a multivariate Gaussian
*/
public static class PDF
extends MultivariateGaussian
implements ProbabilityDensityFunction,
VectorInputEvaluator
{
/**
* Default constructor.
*/
public PDF()
{
super();
}
/**
* Creates a new instance of MultivariateGaussian.
* @param dimensionality
* Dimensionality of the Gaussian to create.
*/
public PDF(
int dimensionality )
{
super( dimensionality );
}
/**
* Creates a new instance of MultivariateGaussian.
*
* @param mean The mean of the Gaussian distribution.
* @param covariance The covariance matrix, which should be a symmetric
* matrix.
*/
public PDF(
Vector mean,
Matrix covariance)
{
super( mean, covariance );
}
/**
* Creates a new instance of MultivariateGaussian.
*
* @param other The other MultivariateGaussian to copy.
*/
public PDF(
MultivariateGaussian other)
{
super( other );
}
@Override
public Double evaluate(
Vector input )
{
return Math.exp( this.logEvaluate(input) );
}
@Override
public double logEvaluate(
Vector input)
{
double zsquared = this.computeZSquared(input);
return this.getLogLeadingCoefficient() - 0.5*zsquared;
}
@Override
public MultivariateGaussian.PDF getProbabilityFunction()
{
return this;
}
}
/**
* Computes the Maximum Likelihood Estimate of the MultivariateGaussian
* given a set of Vectors
*/
public static class MaximumLikelihoodEstimator
extends AbstractCloneableSerializable
implements DistributionEstimator
{
/**
* Default covariance used in estimation, {@value}.
*/
public static final double DEFAULT_COVARIANCE =
UnivariateGaussian.MaximumLikelihoodEstimator.DEFAULT_VARIANCE;
/**
* Amount to add to the diagonal of the covariance matrix
*/
private double defaultCovariance;
/**
* Default constructor;
*/
public MaximumLikelihoodEstimator()
{
this( DEFAULT_COVARIANCE );
}
/**
* Creates a new instance of MaximumLikelihoodEstimator
* @param defaultCovariance
* Amount to add to the diagonal of the covariance matrix
*/
public MaximumLikelihoodEstimator(
double defaultCovariance )
{
this.defaultCovariance = defaultCovariance;
}
/**
* Computes the Gaussian that estimates the maximum likelihood of
* generating the given set of samples.
*
* @return The Gaussian that estimates the maximum likelihood of generating
* the given data.
* @param defaultCovariance amount to add to the diagonals of the covariance
* matrix, typically on the order of 1e-4, can be 0.0.
* @param data The samples to calculate the Gaussian from
* throws IllegalArgumentException if samples has 1 or fewer samples.
*/
public static MultivariateGaussian.PDF learn(
Collection extends Vector> data,
final double defaultCovariance )
{
final int N = data.size();
if( N <= 1 )
{
throw new IllegalArgumentException(
"Need at least 2 data points to compute covariance" );
}
Pair mle =
MultivariateStatisticsUtil.computeMeanAndCovariance(data);
Vector mean = mle.getFirst();
Matrix covariance = mle.getSecond();
// Add "defaultCovariance" to the diagonal entries
if( defaultCovariance != 0.0 )
{
final int M = mean.getDimensionality();
for( int i = 0; i < M; i++ )
{
double v = covariance.getElement(i, i);
covariance.setElement(i, i, v + defaultCovariance );
}
}
return new MultivariateGaussian.PDF( mean, covariance );
}
/**
* Computes the Gaussian that estimates the maximum likelihood of
* generating the given set of samples.
*
* @return The Gaussian that estimates the maximum likelihood of generating
* the given data.
* @param data The samples to calculate the Gaussian from
* throws IllegalArgumentException if samples has 1 or fewer samples.
*/
@Override
public MultivariateGaussian.PDF learn(
Collection extends Vector> data)
{
return MaximumLikelihoodEstimator.learn(
data, this.defaultCovariance );
}
}
/**
* Computes the Weighted Maximum Likelihood Estimate of the
* MultivariateGaussian given a weighted set of Vectors
*/
public static class WeightedMaximumLikelihoodEstimator
extends AbstractCloneableSerializable
implements DistributionWeightedEstimator
{
/**
* Default covariance used in estimation, {@value}.
*/
public static final double DEFAULT_COVARIANCE =
MaximumLikelihoodEstimator.DEFAULT_COVARIANCE;
/**
* Amount to add to the diagonal of the covariance matrix
*/
private double defaultCovariance;
/**
* Default constructor.
*/
public WeightedMaximumLikelihoodEstimator()
{
this( DEFAULT_COVARIANCE );
}
/**
* Creates a new instance of WeightedMaximumLikelihoodEstimator
* @param defaultCovariance
* Amount to add to the diagonal of the covariance matrix
*/
public WeightedMaximumLikelihoodEstimator(
double defaultCovariance )
{
this.defaultCovariance = defaultCovariance;
}
/**
* Computes the Gaussian that estimates the maximum likelihood of
* generating the given set of weighted samples.
*
* @return The Gaussian that estimates the maximum likelihood of
* generating the given weighted data.
* @param data The weighted samples to calculate the Gaussian from
* throws IllegalArgumentException if samples has 1 or fewer samples.
*/
@Override
public MultivariateGaussian.PDF learn(
Collection extends WeightedValue extends Vector>> data )
{
return WeightedMaximumLikelihoodEstimator.learn(
data, this.defaultCovariance );
}
/**
* Computes the Gaussian that estimates the maximum likelihood of
* generating the given set of weighted samples.
*
*
* @return The Gaussian that estimates the maximum likelihood of
* generating the given weighted data.
* @param defaultCovariance
* Amount to add to the diagonal of the covariance matrix
* @param data The weighted samples to calculate the Gaussian from
* throws IllegalArgumentException if samples has 1 or fewer samples.
*/
public static MultivariateGaussian.PDF learn(
Collection extends WeightedValue extends Vector>> data,
final double defaultCovariance )
{
// Make sure there are enough samples.
final int N = data.size();
if( N <= 1 )
{
// Error: Not enough samples.
throw new IllegalArgumentException(
"The number of samples must be greater than 1.");
}
Pair mle =
MultivariateStatisticsUtil.computeWeightedMeanAndCovariance(data);
Vector mean = mle.getFirst();
Matrix covariance = mle.getSecond();
if( defaultCovariance != 0.0 )
{
final int M = covariance.getNumRows();
for( int i = 0; i < M; i++ )
{
final double v = covariance.getElement(i, i);
covariance.setElement(i, i, v+defaultCovariance );
}
}
return new MultivariateGaussian.PDF( mean, covariance );
}
}
/**
* Implements the sufficient statistics of the MultivariateGaussian.
*/
public static class SufficientStatistic
extends AbstractSufficientStatistic
{
/**
* Default covariance of the statistics, {@value}.
*/
public static final double DEFAULT_COVARIANCE =
MaximumLikelihoodEstimator.DEFAULT_COVARIANCE;
/**
* The mean of the Gaussian
*/
private Vector mean;
/**
* This is the sum-squared differences
*/
private Matrix sumSquaredDifferences;
/**
* Default covariance of the distribution
*/
protected double defaultCovariance;
/**
* Default constructor
*/
public SufficientStatistic()
{
this( DEFAULT_COVARIANCE );
}
/**
* Creates a new instance of SufficientStatistic
* @param defaultCovariance
* Default covariance of the distribution
*/
public SufficientStatistic(
double defaultCovariance)
{
super();
this.clear();
this.defaultCovariance = defaultCovariance;
}
@Override
public MultivariateGaussian.SufficientStatistic clone()
{
return (MultivariateGaussian.SufficientStatistic) super.clone();
}
/**
* Resets this set of sufficient statistics to its empty state.
*/
public void clear()
{
this.count = 0;
this.mean = null;
this.sumSquaredDifferences = null;
}
@Override
public void update(
Vector value)
{
// We've added another value.
this.count++;
// Compute the difference between the value and the current mean.
final int dim = value.getDimensionality();
if( this.mean == null )
{
this.mean = VectorFactory.getDefault().createVector(dim);
}
Vector delta = value.minus( this.mean );
// Update the mean based on the difference between the value
// and the mean along with the new count.
this.mean.plusEquals( delta.scale(1.0/this.count) );
// Update the squared differences from the mean, using the new
// mean in the process.
if( this.sumSquaredDifferences == null )
{
this.sumSquaredDifferences =
MatrixFactory.getDefault().createIdentity(dim,dim);
this.sumSquaredDifferences.scaleEquals(this.getDefaultCovariance());
}
Vector delta2 = value.minus( this.mean );
this.sumSquaredDifferences.plusEquals( delta.outerProduct(delta2) );
}
@Override
public MultivariateGaussian.PDF create()
{
return new MultivariateGaussian.PDF(
this.getMean(), this.getCovariance() );
}
@Override
public void create(
MultivariateGaussian distribution)
{
distribution.setMean( this.getMean() );
distribution.setCovariance( this.getCovariance() );
}
/**
* Getter for defaultCovariance
* @return
* Default covariance of the distribution
*/
public double getDefaultCovariance()
{
return this.defaultCovariance;
}
/**
* Setter for defaultCovariance
* @param defaultCovariance
* Default covariance of the distribution
*/
public void setDefaultCovariance(
double defaultCovariance)
{
this.defaultCovariance = defaultCovariance;
}
/**
* Getter for mean
* @return
* The mean of the Gaussian
*/
public Vector getMean()
{
return this.mean;
}
/**
* Getter for sumSquaredDifferences
* @return
* This is the sum-squared differences
*/
public Matrix getSumSquaredDifferences()
{
return this.sumSquaredDifferences;
}
/**
* Gets the covariance of the Gaussian.
*
* @return
* The covariance.
*/
public Matrix getCovariance()
{
if( this.count <= 0 )
{
return null;
}
else if( this.count == 1 )
{
// This allows the default variance to be used.
return this.sumSquaredDifferences.clone();
}
else
{
return this.sumSquaredDifferences.scale( 1.0/(this.count - 1.0) );
}
}
}
/**
* The estimator that creates a MultivariateGaussian from a stream of
* values.
*/
public static class IncrementalEstimator
extends AbstractIncrementalEstimator
{
/**
* Default covariance, {@value}.
*/
public static final double DEFAULT_COVARIANCE = MaximumLikelihoodEstimator.DEFAULT_COVARIANCE;
/**
* Default covariance of the distribution
*/
private double defaultCovariance;
/**
* Default constructor
*/
public IncrementalEstimator()
{
this( DEFAULT_COVARIANCE );
}
/**
* Creates a new instance of IncrementalEstimator
* @param defaultCovariance
* Default covariance of the distribution
*/
public IncrementalEstimator(
double defaultCovariance)
{
super();
this.setDefaultCovariance(defaultCovariance);
}
/**
* Getter for defaultCovariance
* @return
* Default covariance of the distribution
*/
public double getDefaultCovariance()
{
return this.defaultCovariance;
}
/**
* Setter for defaultCovariance
* @param defaultCovariance
* Default covariance of the distribution
*/
public void setDefaultCovariance(
double defaultCovariance)
{
this.defaultCovariance = defaultCovariance;
}
@Override
public MultivariateGaussian.SufficientStatistic createInitialLearnedObject()
{
return new MultivariateGaussian.SufficientStatistic(
this.getDefaultCovariance() );
}
}
/**
* Implements the sufficient statistics of the MultivariateGaussian
* while estimating the inverse of the covariance matrix. This is only
* slightly more computationally intensive than estimating the covariance
* directly, but does not require a single matrix inversion. This is
* useful when it's the covariance inverse ("precision") that you're
* interested in.
*/
@PublicationReference(
author="Wikipedia",
title="Sherman–Morrison formula",
type=PublicationType.WebPage,
year=2011,
url="http://en.wikipedia.org/wiki/Sherman%E2%80%93Morrison_formula"
)
public static class SufficientStatisticCovarianceInverse
extends AbstractSufficientStatistic
{
/**
* Default covariance of the statistics, {@value}.
*/
public static final double DEFAULT_COVARIANCE_INVERSE =
1.0/MultivariateGaussian.MaximumLikelihoodEstimator.DEFAULT_COVARIANCE;
/**
* The mean of the Gaussian
*/
private Vector mean;
/**
* This is the sum-squared differences
*/
private Matrix sumSquaredDifferencesInverse;
/**
* Default covariance inverse of the distribution
*/
protected double defaultCovarianceInverse;
/**
* Default constructor
*/
public SufficientStatisticCovarianceInverse()
{
this( DEFAULT_COVARIANCE_INVERSE );
}
/**
* Creates a new instance of SufficientStatisticCovarianceInverse
* @param defaultCovarianceInverse
* Default covariance inverse of the distribution
*/
public SufficientStatisticCovarianceInverse(
double defaultCovarianceInverse)
{
super();
this.clear();
this.defaultCovarianceInverse = defaultCovarianceInverse;
}
@Override
public SufficientStatisticCovarianceInverse clone()
{
return (SufficientStatisticCovarianceInverse) super.clone();
}
/**
* Resets this set of sufficient statistics to its empty state.
*/
public void clear()
{
this.count = 0;
this.mean = null;
this.sumSquaredDifferencesInverse = null;
}
@Override
public void update(
Vector value)
{
// We've added another value.
this.count++;
// Compute the difference between the value and the current mean.
final int dim = value.getDimensionality();
if( this.mean == null )
{
this.mean = VectorFactory.getDefault().createVector(dim);
}
Vector delta = value.minus( this.mean );
// Update the mean based on the difference between the value
// and the mean along with the new count.
this.mean.plusEquals( delta.scale(1.0/this.count) );
// Update the squared differences from the mean, using the new
// mean in the process.
if( this.sumSquaredDifferencesInverse == null )
{
this.sumSquaredDifferencesInverse =
MatrixFactory.getDefault().createIdentity(dim,dim);
this.sumSquaredDifferencesInverse.scaleEquals(this.getDefaultCovarianceInverse());
}
Vector delta2 = value.minus( this.mean );
// This is the Sherman–Morrison formula:
// inv(A+uv') = inv(A)-(inv(A)uv'inv(A))/(1+v'inv(A)*u)
Vector Aiu = this.sumSquaredDifferencesInverse.times( delta );
Vector vtAi = delta2.times(this.sumSquaredDifferencesInverse);
double denom = 1.0 + delta2.dotProduct(Aiu);
vtAi.scaleEquals(1.0/denom);
Matrix update = Aiu.outerProduct(vtAi);
this.sumSquaredDifferencesInverse.minusEquals(update);
}
@Override
public MultivariateGaussian.PDF create()
{
final Vector m = this.getMean();
MultivariateGaussian.PDF retval =
new MultivariateGaussian.PDF( m.getDimensionality() );
retval.setMean(m);
retval.setCovarianceInverse(this.getCovarianceInverse());
return retval;
}
@Override
public void create(
MultivariateGaussian distribution)
{
distribution.setMean( this.getMean() );
distribution.setCovarianceInverse( this.getCovarianceInverse() );
}
/**
* Getter for defaultCovarianceInverse
* @return
* Default covariance Inverse of the distribution
*/
public double getDefaultCovarianceInverse()
{
return this.defaultCovarianceInverse;
}
/**
* Setter for defaultCovarianceInverse
* @param defaultCovarianceInverse
* Default covariance Inverse of the distribution
*/
public void setDefaultCovariance(
double defaultCovarianceInverse)
{
this.defaultCovarianceInverse = defaultCovarianceInverse;
}
/**
* Getter for mean
* @return
* The mean of the Gaussian
*/
public Vector getMean()
{
return this.mean;
}
/**
* Getter for sumSquaredDifferences
* @return
* This is the sum-squared differences
*/
public Matrix getSumSquaredDifferencesInverse()
{
return this.sumSquaredDifferencesInverse;
}
/**
* Gets the covariance Inverse of the Gaussian.
*
* @return
* The covariance.
*/
public Matrix getCovarianceInverse()
{
if( this.count <= 0 )
{
return null;
}
else if( this.count == 1 )
{
// This allows the default variance to be used.
return this.sumSquaredDifferencesInverse.clone();
}
else
{
return this.sumSquaredDifferencesInverse.scale( this.count - 1.0 );
}
}
}
/**
* The estimator that creates a MultivariateGaussian from a stream of
* values by estimating the mean and covariance inverse (as opposed to
* the covariance directly), without ever performing a matrix inversion.
* This is useful when you're interested in the covariance inverse
* (precision) instead of the covariance itself.
*/
public static class IncrementalEstimatorCovarianceInverse
extends AbstractIncrementalEstimator
{
/**
* Default covariance Inverse, {@value}.
*/
public static final double DEFAULT_COVARIANCE_INVERSE = 1.0/MaximumLikelihoodEstimator.DEFAULT_COVARIANCE;
/**
* Default covariance Inverse of the distribution
*/
private double defaultCovarianceInverse;
/**
* Default constructor
*/
public IncrementalEstimatorCovarianceInverse()
{
this( DEFAULT_COVARIANCE_INVERSE );
}
/**
* Creates a new instance of IncrementalEstimatorCovarianceInverse
* @param defaultCovarianceInverse
* Default covariance Inverse of the distribution
*/
public IncrementalEstimatorCovarianceInverse(
double defaultCovarianceInverse )
{
super();
this.setDefaultCovarianceInverse(defaultCovarianceInverse);
}
/**
* Getter for defaultCovarianceInverse
* @return
* Default covariance Inverse of the distribution
*/
public double getDefaultCovarianceInverse()
{
return this.defaultCovarianceInverse;
}
/**
* Setter for defaultCovarianceInverse
* @param defaultCovarianceInverse
* Default covariance Inverse of the distribution
*/
public void setDefaultCovarianceInverse(
double defaultCovarianceInverse)
{
this.defaultCovarianceInverse = defaultCovarianceInverse;
}
@Override
public MultivariateGaussian.SufficientStatisticCovarianceInverse createInitialLearnedObject()
{
return new MultivariateGaussian.SufficientStatisticCovarianceInverse(
this.getDefaultCovarianceInverse() );
}
}
}