All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.sandia.cognition.statistics.distribution.MultivariateGaussian Maven / Gradle / Ivy

There is a newer version: 4.0.1
Show newest version
/*
 * 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)
    {
        double zsquared;

        // Subtract the mean
        Vector delta = input.minus(this.mean);

        // Compute the weighted inner product.
        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 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 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 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 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> 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> 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());
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy