/* * 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.frontend.feature; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.LinkedList; import java.util.List; import java.util.Locale; import edu.cmu.sphinx.frontend.*; import edu.cmu.sphinx.frontend.endpoint.SpeechEndSignal; import edu.cmu.sphinx.util.props.PropertyException; import edu.cmu.sphinx.util.props.PropertySheet; import edu.cmu.sphinx.util.props.S4Integer; /** * Subtracts the mean of all the input so far from the Data objects. * * Unlike the {@link BatchCMN}, it does not read in the entire stream of Data * objects before it calculates the mean. It estimates the mean from already * seen data and subtracts the mean from the Data objects on the fly. Therefore, * there is no delay introduced by LiveCMN in general. The only real issue is an * initial CMN estimation, for that some amount of frames are read initially * and cmn estimation is calculated from them. * <p> * The properties that affect this processor are defined by the fields * {@link #PROP_INITIAL_CMN_WINDOW}, {@link #PROP_CMN_WINDOW}, and * {@link #PROP_CMN_SHIFT_WINDOW}. Please follow the link * "Constant Field Values" below to see the actual name of the Sphinx * properties. * <p> * The mean of all the input cepstrum so far is not reestimated for each * cepstrum. This mean is recalculated after every * {@link #PROP_CMN_SHIFT_WINDOW} cepstra. This mean is estimated by dividing * the sum of all input cepstrum so far. After obtaining the mean, the sum is * exponentially decayed by multiplying it by the ratio: * * <pre> * cmnWindow/(cmnWindow + number of frames since the last recalculation) * </pre> * * @see BatchCMN */ public class LiveCMN extends BaseDataProcessor { private DecimalFormat formatter = new DecimalFormat("0.00;-0.00", new DecimalFormatSymbols(Locale.US));; /** The property for the live CMN initial window size. */ @S4Integer(defaultValue = 200) public static final String PROP_INITIAL_CMN_WINDOW = "initialCmnWindow"; private int initialCmnWindow; /** The property for the live CMN window size. */ @S4Integer(defaultValue = 300) public static final String PROP_CMN_WINDOW = "cmnWindow"; private int cmnWindow; /** * The property for the CMN shifting window. The shifting window specifies * how many cepstrum after which we re-calculate the cepstral mean. */ @S4Integer(defaultValue = 400) public static final String PROP_CMN_SHIFT_WINDOW = "shiftWindow"; private int cmnShiftWindow; // # of Cepstrum to recalculate mean private double[] currentMean; // array of current means private double[] sum; // array of current sums private int numberFrame; // total number of input Cepstrum List<Data> initialList = new LinkedList<Data>(); public LiveCMN(double initialMean, int cmnWindow, int cmnShiftWindow, int initialCmnWindow) { initLogger(); this.cmnWindow = cmnWindow; this.cmnShiftWindow = cmnShiftWindow; this.initialCmnWindow = initialCmnWindow; } public LiveCMN() { } @Override public void newProperties(PropertySheet ps) throws PropertyException { super.newProperties(ps); cmnWindow = ps.getInt(PROP_CMN_WINDOW); cmnShiftWindow = ps.getInt(PROP_CMN_SHIFT_WINDOW); initialCmnWindow = ps.getInt(PROP_INITIAL_CMN_WINDOW); } /** Initializes this LiveCMN. */ @Override public void initialize() { super.initialize(); } /** * Initializes the currentMean and sum arrays with the given cepstrum * length. */ private void initMeansSums() { int size = -1; for (Data data : initialList) { if (!(data instanceof DoubleData)) continue; double[] cepstrum = ((DoubleData) data).getValues(); // Initialize arrays if needed if (size < 0) { size = cepstrum.length; sum = new double[size]; numberFrame = 0; } // Accumulate cepstrum, avoid counting zero energy in CMN if (cepstrum[0] >= 0) { for (int j = 0; j < size; j++) { sum[j] += cepstrum[j]; } numberFrame++; } } // If we didn't meet any data, do nothing if (size < 0) return; currentMean = new double[size]; for (int j = 0; j < size; j++) { currentMean[j] = sum[j] / numberFrame; } } /** * Returns the next Data object, which is a normalized Data produced by this * class. Signals are returned unmodified. * * @return the next available Data object, returns null if no Data object is * available * @throws DataProcessingException * if there is a data processing error */ @Override public Data getData() throws DataProcessingException { Data input, output; // Collect initial data for estimation if (sum == null) { while (initialList.size() < initialCmnWindow) { input = getPredecessor().getData(); initialList.add(input); if (input instanceof SpeechEndSignal || input instanceof DataEndSignal) break; } initMeansSums(); output = initialList.remove(0); } else if (!initialList.isEmpty()) { // Return the previously collected data output = initialList.remove(0); } else { // Process normal frame output = getPredecessor().getData(); } normalize(output); return output; } /** * Normalizes the given Data with using the currentMean array. Updates the * sum array with the given Data. * * @param data * the Data object to normalize */ private void normalize(Data data) { if (!(data instanceof DoubleData)) return; double[] cepstrum = ((DoubleData) data).getValues(); if (cepstrum.length != sum.length) { throw new Error("Data length (" + cepstrum.length + ") not equal sum array length (" + sum.length + ')'); } // Accumulate cepstrum, avoid counting zero energy in CMN if (cepstrum[0] >= 0) { for (int j = 0; j < cepstrum.length; j++) { sum[j] += cepstrum[j]; } numberFrame++; } // Subtract current mean for (int j = 0; j < cepstrum.length; j++) { cepstrum[j] -= currentMean[j]; } if (numberFrame > cmnShiftWindow) { StringBuilder cmn = new StringBuilder(); // calculate the mean first for (int i = 0; i < currentMean.length; i++) { cmn.append (formatter.format(currentMean[i])); cmn.append(' '); } logger.info(cmn.toString()); updateMeanSumBuffers(); } } /** * Updates the currentMean buffer with the values in the sum buffer. Then * decay the sum buffer exponentially, i.e., divide the sum with * numberFrames. */ private void updateMeanSumBuffers() { // update the currentMean buffer with the sum buffer double sf = 1.0 / numberFrame; System.arraycopy(sum, 0, currentMean, 0, sum.length); multiplyArray(currentMean, sf); // decay the sum buffer exponentially if (numberFrame >= cmnShiftWindow) { multiplyArray(sum, (sf * cmnWindow)); numberFrame = cmnWindow; } } /** * Multiplies each element of the given array by the multiplier. * * @param array * the array to multiply * @param multiplier * the amount to multiply by */ private static void multiplyArray(double[] array, double multiplier) { for (int i = 0; i < array.length; i++) { array[i] *= multiplier; } } }