/*
* Copyright 1999-2002 Carnegie Mellon University.
* Portions Copyright 2002 Sun Microsystems, Inc.
* Portions Copyright 2002 Mitsubishi Electric Research Laboratories.
* All Rights Reserved. Use is subject to license terms.
*
* See the file "license.terms" for information on usage and
* redistribution of this file, and for a DISCLAIMER OF ALL
* WARRANTIES.
*
*/
package edu.cmu.sphinx.linguist.acoustic.tiedstate;
import edu.cmu.sphinx.frontend.FloatData;
import edu.cmu.sphinx.util.LogMath;
import java.io.Serializable;
import java.util.Arrays;
/**
* Defines the set of shared elements for a GaussianMixture. Since these elements are potentially
* shared by a number of {@link GaussianMixture GaussianMixtures}, these elements should not be
* written to. The GaussianMixture defines a single probability density function along with a set of
* adaptation parameters.
* <p>
* Note that all scores and weights are in LogMath log base
*/
// TODO: Since many of the subcomponents of a MixtureComponent are shared, are
// there some potential opportunities to reduce the number of computations in scoring
// senones by sharing intermediate results for these subcomponents?
@SuppressWarnings("serial")
public class MixtureComponent implements Cloneable, Serializable {
private float[] mean;
/** Mean after transformed by the adaptation parameters. */
protected float[] meanTransformed;
private float[][] meanTransformationMatrix;
private float[] meanTransformationVector;
private float[] variance;
/** Precision is the inverse of the variance. This includes adaptation. */
protected float[] precisionTransformed;
private float[][] varianceTransformationMatrix;
private float[] varianceTransformationVector;
protected float distFloor;
private float varianceFloor;
public static final float DEFAULT_VAR_FLOOR = 0.0001f; // this also seems to be the default of SphinxTrain
public static final float DEFAULT_DIST_FLOOR = 0.0f;
protected float logPreComputedGaussianFactor;
/**
* Create a MixtureComponent with the given sub components.
*
* @param mean the mean vector for this PDF
* @param variance the variance for this PDF
*/
public MixtureComponent(float[] mean, float[] variance) {
this(mean, null, null, variance, null, null, DEFAULT_DIST_FLOOR, DEFAULT_VAR_FLOOR);
}
/**
* Create a MixtureComponent with the given sub components.
*
* @param mean the mean vector for this PDF
* @param meanTransformationMatrix transformation matrix for this pdf
* @param meanTransformationVector transform vector for this PDF
* @param variance the variance for this PDF
* @param varianceTransformationMatrix var. transform matrix for this PDF
* @param varianceTransformationVector var. transform vector for this PDF
*/
public MixtureComponent(
float[] mean,
float[][] meanTransformationMatrix,
float[] meanTransformationVector,
float[] variance,
float[][] varianceTransformationMatrix,
float[] varianceTransformationVector) {
this(mean, meanTransformationMatrix, meanTransformationVector, variance,
varianceTransformationMatrix, varianceTransformationVector, DEFAULT_DIST_FLOOR, DEFAULT_VAR_FLOOR);
}
/**
* Create a MixtureComponent with the given sub components.
*
* @param mean the mean vector for this PDF
* @param meanTransformationMatrix transformation matrix for this pdf
* @param meanTransformationVector transform vector for this PDF
* @param variance the variance for this PDF
* @param varianceTransformationMatrix var. transform matrix for this PDF
* @param varianceTransformationVector var. transform vector for this PDF
* @param distFloor the lowest score value (in linear domain)
* @param varianceFloor the lowest value for the variance
*/
public MixtureComponent(
float[] mean,
float[][] meanTransformationMatrix,
float[] meanTransformationVector,
float[] variance,
float[][] varianceTransformationMatrix,
float[] varianceTransformationVector,
float distFloor,
float varianceFloor) {
assert variance.length == mean.length;
this.mean = mean;
this.meanTransformationMatrix = meanTransformationMatrix;
this.meanTransformationVector = meanTransformationVector;
this.variance = variance;
this.varianceTransformationMatrix = varianceTransformationMatrix;
this.varianceTransformationVector = varianceTransformationVector;
assert distFloor >= 0.0 : "distFloot seems to be already in log-domain";
this.distFloor = LogMath.getLogMath().linearToLog(distFloor);
this.varianceFloor = varianceFloor;
transformStats();
logPreComputedGaussianFactor = precomputeDistance();
}
/**
* Returns the mean for this component.
*
* @return the mean
*/
public float[] getMean() {
return mean;
}
/**
* Returns the variance for this component.
*
* @return the variance
*/
public float[] getVariance() {
return variance;
}
/**
* Calculate the score for this mixture against the given feature.
* <p>
* Note: The support of <code>DoubleData</code>-features would require an array conversion to
* float[]. Because getScore might be invoked with very high frequency, features are restricted
* to be <code>FloatData</code>s.
*
* @param feature the feature to score
* @return the score, in log, for the given feature
*/
public float getScore(FloatData feature) {
return getScore(feature.getValues());
}
/**
* Calculate the score for this mixture against the given feature. We model the output
* distributions using a mixture of Gaussians, therefore the current implementation is simply
* the computation of a multi-dimensional Gaussian. <p> <b>Normal(x) = exp{-0.5 * (x-m)' *
* inv(Var) * (x-m)} / {sqrt((2 * PI) ^ N) * det(Var))}</b></p>
* <p>
* where <b>x</b> and <b>m</b> are the incoming cepstra and mean vector respectively,
* <b>Var</b> is the Covariance matrix, <b>det()</b> is the determinant of a matrix,
* <b>inv()</b> is its inverse, <b>exp</b> is the exponential operator, <b>x'</b> is the
* transposed vector of <b>x</b> and <b>N</b> is the dimension of the vectors <b>x</b> and
* <b>m</b>.
*
* @param feature the feature to score
* @return the score, in log, for the given feature
*/
public float getScore(float[] feature) {
float logDval = logPreComputedGaussianFactor;
// First, compute the argument of the exponential function in
// the definition of the Gaussian, then convert it to the
// appropriate base. If the log base is <code>Math.E</code>,
// then no operation is necessary.
for (int i = 0; i < feature.length; i++) {
float logDiff = feature[i] - meanTransformed[i];
logDval += logDiff * logDiff * precisionTransformed[i];
}
// logDval = -logVal / 2;
// At this point, we have the ln() of what we need, that is,
// the argument of the exponential in the javadoc comment.
// Convert to the appropriate base.
logDval = LogMath.getLogMath().lnToLog(logDval);
// System.out.println("MC: getscore " + logDval);
// TODO: Need to use mean and variance transforms here
if (Float.isNaN(logDval)) {
System.out.println("gs is Nan, converting to 0");
logDval = LogMath.LOG_ZERO;
}
if (logDval < distFloor) {
logDval = distFloor;
}
return logDval;
}
/**
* Pre-compute factors for the Mahalanobis distance. Some of the Mahalanobis distance
* computation can be carried out in advance. Specifically, the factor containing only variance
* in the Gaussian can be computed in advance, keeping in mind that the the determinant of the
* covariance matrix, for the degenerate case of a mixture with independent components - only
* the diagonal elements are non-zero - is simply the product of the diagonal elements. <p>
* We're computing the expression:
* <pre>{sqrt((2 * PI) ^ N) * det(Var))}</pre>
*
* @return the precomputed distance
*/
public float precomputeDistance() {
double logPreComputedGaussianFactor = 0.0; // = log(1.0)
// Compute the product of the elements in the Covariance
// matrix's main diagonal. Covariance matrix is assumed
// diagonal - independent dimensions. In log, the product
// becomes a summation.
for (int i = 0; i < variance.length; i++) {
logPreComputedGaussianFactor +=
Math.log(precisionTransformed[i] * -2);
// variance[i] = 1.0f / (variance[i] * 2.0f);
}
// We need the minus sign since we computed
// logPreComputedGaussianFactor based on precision, which is
// the inverse of the variance. Therefore, in the log domain,
// the two quantities have opposite signs.
// The covariance matrix's dimension becomes a multiplicative
// factor in log scale.
logPreComputedGaussianFactor =
Math.log(2.0 * Math.PI) * variance.length
- logPreComputedGaussianFactor;
// The sqrt above is a 0.5 multiplicative factor in log scale.
return -(float)logPreComputedGaussianFactor * 0.5f;
}
/** Applies transformations to means and variances. */
public void transformStats() {
int featDim = mean.length;
/*
* The transformed mean vector is given by:
*
* <p><b>M = A * m + B</b></p>
*
* where <b>M</b> and <b>m</b> are the mean vector after and
* before transformation, respectively, and <b>A</b> and
* <b>B</b> are the transformation matrix and vector,
* respectively.
*
* if A or B are <code>null</code> the according substeps are skipped
*/
if (meanTransformationMatrix != null) {
meanTransformed = new float[featDim];
for (int i = 0; i < featDim; i++)
for (int j = 0; j < featDim; j++)
meanTransformed[i] += mean[j] * meanTransformationMatrix[i][j];
} else {
meanTransformed = mean;
}
if (meanTransformationVector != null)
for (int k = 0; k < featDim; k++)
meanTransformed[k] += meanTransformationVector[k];
/**
* We do analogously with the variance. In this case, we also
* invert the variance, and work with precision instead of
* variance.
*/
if (varianceTransformationMatrix != null) {
precisionTransformed = new float[variance.length];
for (int i = 0; i < featDim; i++)
for (int j = 0; j < featDim; j++)
precisionTransformed[i] += variance[j] * varianceTransformationMatrix[i][j];
} else
precisionTransformed = variance.clone();
if (varianceTransformationVector != null)
for (int k = 0; k < featDim; k++)
precisionTransformed[k] += varianceTransformationVector[k];
for (int k = 0; k < featDim; k++) {
float flooredPrecision = (precisionTransformed[k] < varianceFloor ? varianceFloor : precisionTransformed[k]);
precisionTransformed[k] = 1.0f / (-2.0f * flooredPrecision);
}
}
@Override
public MixtureComponent clone() throws CloneNotSupportedException {
MixtureComponent mixComp = (MixtureComponent)super.clone();
mixComp.distFloor = distFloor;
mixComp.varianceFloor = varianceFloor;
mixComp.logPreComputedGaussianFactor = logPreComputedGaussianFactor;
mixComp.mean = this.mean != null ? this.mean.clone() : null;
if (meanTransformationMatrix != null) {
mixComp.meanTransformationMatrix = this.meanTransformationMatrix.clone();
for (int i = 0; i < meanTransformationMatrix.length; i++)
mixComp.meanTransformationMatrix[i] = meanTransformationMatrix[i].clone();
}
mixComp.meanTransformationVector = this.meanTransformationVector != null ?
this.meanTransformationVector.clone() : null;
mixComp.meanTransformed = this.meanTransformed != null ? this.meanTransformed.clone() : null;
mixComp.variance = this.variance != null ? this.variance.clone() : null;
if (varianceTransformationMatrix != null) {
mixComp.varianceTransformationMatrix = this.varianceTransformationMatrix.clone();
for (int i = 0; i < varianceTransformationMatrix.length; i++)
mixComp.varianceTransformationMatrix[i] = varianceTransformationMatrix[i].clone();
}
mixComp.varianceTransformationVector = this.varianceTransformationVector != null ?
this.varianceTransformationVector.clone() : null;
mixComp.precisionTransformed = this.precisionTransformed != null ?
this.precisionTransformed.clone() : null;
return mixComp;
}
@Override
public String toString() {
return "mu=" + Arrays.toString(mean) + " cov=" + Arrays.toString(variance);
}
}