/*
* Copyright 1999-2004 Carnegie Mellon University.
* Portions Copyright 2004 Sun Microsystems, Inc.
* Portions Copyright 2004 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.
*
* adapted from Sphinx3Loader by Christophe Cerisara
*
*/
package edu.cmu.sphinx.linguist.acoustic.tiedstate;
import edu.cmu.sphinx.decoder.adaptation.ClusteredDensityFileData;
import edu.cmu.sphinx.decoder.adaptation.Transform;
import edu.cmu.sphinx.linguist.acoustic.*;
import edu.cmu.sphinx.linguist.acoustic.tiedstate.HTK.GMMDiag;
import edu.cmu.sphinx.linguist.acoustic.tiedstate.HTK.HMMSet;
import edu.cmu.sphinx.linguist.acoustic.tiedstate.HTK.HMMState;
import edu.cmu.sphinx.linguist.acoustic.tiedstate.HTK.SingleHMM;
import static edu.cmu.sphinx.linguist.acoustic.tiedstate.Pool.Feature.*;
import edu.cmu.sphinx.util.*;
import edu.cmu.sphinx.util.props.*;
import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Triphone selection algorithm:
*
* 1) Look for a triphone that matches exactly in terms of unit, unit
* context and word position. Word position can be one of 'beginning',
* 'end', 'internal', 'single' and 'unknown'.
* 2) If not found, try to find a match at any word position.
* 3) If still not found, if any of the context units are non-silence
* filler units, replace them with silence and try again.
* 4) Finally, if still not found, the context-independent version is used
*
* The code is in TiedStateAcousticModel.lookupNearestHMM
*/
/**
*
* Remark1: S4 does not use HMM tying: the HTK "tiedlist" is not loaded nor user
* for now
*
* Remark2: HTK does nearly never backoff to monophones, whereas S4 might do it.
* Hence, all 1ph must be present, which is not always the case in HTK models.
* In this file, we have simply tied all 1phs to the first 3ph found with the
* same base phone. This is clearly not the best option, this may impact the
* performances. A better alternative should be proposed.
*
* Remark3: HTK is case-sensitive, while S4 requires upper-case names. A good
* option would be to modify S4 to make it case-sensitive, but in this version,
* I have rather built a class "NamesConversion" to transform the models,
* lexicons and grammar.
*
* Remark4: HTK does not have separate filler file. It should be quite easy to
* define the fillers in the configuration of HTKLoader, but I have not yet done
* it. For now, a separate filler file must be built.
*
* Remark5: The HTK/Julius lexicon has different fields for the word name and
* the printed output. We simply delete and don't use this field ("[...]") in
* this version. This modify the way scoring must be done. WARNING !
*
* Remark6: Diphones are not allowed in S4 (?), and when they occur in the HTK
* MMF, the 2ph is transformed into a 3ph with "SIL" context. When both the
* transformed 2ph and the 3ph coexists in the MMF file, only the first one is
* kept (!)
*
* Remark7: HTKloader does not support yet all the HTK format. However it should
* work for standard 3ph models (it supports state/transition tying).
*
*/
public class HTKLoader implements Loader {
/** The unit manager */
@S4Component(type = UnitManager.class)
public final static String PROP_UNIT_MANAGER = "unitManager";
/** Specifies whether the model to be loaded is in ASCII or binary format */
@S4Boolean(defaultValue = true)
public final static String PROP_IS_BINARY = "isBinary";
/** The name of the model definition file (contains the HMM data) */
@S4String(mandatory = true, defaultValue = "hmmdefs")
public final static String PROP_MODEL = "modelDefinition";
/** Shall we use the real 1ph or tie them from the 3ph ? */
@S4Boolean(defaultValue = true)
public final static String PROP_TIE_1PH = "tie1ph";
/** The property for the name of the acoustic properties file. */
@S4String(defaultValue = "model.props")
public final static String PROP_PROPERTIES_FILE = "propertiesFile";
/** The property for the length of feature vectors. */
@S4Integer(defaultValue = 39)
public final static String PROP_VECTOR_LENGTH = "vectorLength";
/** Mixture component score floor. */
@S4Double(defaultValue = 0.0f)
public final static String PROP_MC_FLOOR = "MixtureComponentScoreFloor";
/** Variance floor. */
@S4Double(defaultValue = 0.0001f)
public final static String PROP_VARIANCE_FLOOR = "varianceFloor";
/** Mixture weight floor. */
@S4Double(defaultValue = 1e-7f)
public final static String PROP_MW_FLOOR = "mixtureWeightFloor";
protected final static String FILLER = "filler";
protected final static String SILENCE_CIPHONE = "SIL";
protected final static int BYTE_ORDER_MAGIC = 0x11223344;
/** Supports this version of the acoustic model */
public final static String MODEL_VERSION = "0.3";
protected final static int CONTEXT_SIZE = 1;
private Pool<float[]> meansPool;
private Pool<float[]> variancePool;
private Pool<float[][]> matrixPool;
private Pool<float[][]> meanTransformationMatrixPool;
private Pool<float[]> meanTransformationVectorPool;
private Pool<float[][]> varianceTransformationMatrixPool;
private Pool<float[]> varianceTransformationVectorPool;
private GaussianWeights mixtureWeights;
private Pool<Senone> senonePool;
private Map<String, Unit> contextIndependentUnits;
private HMMManager hmmManager;
private LogMath logMath;
private UnitManager unitManager;
private Properties properties;
private boolean swap;
protected final static String DENSITY_FILE_VERSION = "1.0";
protected final static String MIXW_FILE_VERSION = "1.0";
protected final static String TMAT_FILE_VERSION = "1.0";
// --------------------------------------
// Configuration variables
// --------------------------------------
private String name;
private Logger logger;
private String location;
private String model;
private String dataDir;
private String propsFile;
private float distFloor;
private float mixtureWeightFloor;
private float varianceFloor;
private boolean useCDUnits;
private boolean loaded;
private boolean tie1ph;
public HTKLoader(String propsFile,
UnitManager unitManager, boolean isBinary, int vectorLength,
String model, boolean tie1ph, float distFloor,
float mixtureWeightFloor, float varianceFloor) {
this.logger = Logger.getLogger(getClass().getName());
this.propsFile = propsFile;
loadProperties();
logMath = LogMath.getLogMath();
this.unitManager = unitManager;
this.model = model;
this.tie1ph = tie1ph;
this.distFloor = distFloor;
this.mixtureWeightFloor = mixtureWeightFloor;
this.varianceFloor = varianceFloor;
}
public HTKLoader() {
}
public void newProperties(PropertySheet ps) throws PropertyException {
logger = ps.getLogger();
propsFile = ps.getString(PROP_PROPERTIES_FILE);
loadProperties();
unitManager = (UnitManager) ps.getComponent(PROP_UNIT_MANAGER);
String mdef = (String) properties.get(PROP_MODEL);
model = mdef != null ? mdef : ps.getString(PROP_MODEL);
tie1ph = ps.getBoolean(PROP_TIE_1PH);
distFloor = ps.getFloat(PROP_MC_FLOOR);
mixtureWeightFloor = ps.getFloat(PROP_MW_FLOOR);
varianceFloor = ps.getFloat(PROP_VARIANCE_FLOOR);
}
private void loadProperties() {
if (properties == null) {
properties = new Properties();
try {
URL url = getClass().getResource(propsFile);
if (url != null)
properties.load(url.openStream());
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
public void load() throws IOException {
if (!loaded) {
hmmManager = new HMMManager();
contextIndependentUnits = new LinkedHashMap<String, Unit>();
// dummy pools for these elements
meanTransformationMatrixPool = null;
meanTransformationVectorPool = null;
varianceTransformationMatrixPool = null;
varianceTransformationVectorPool = null;
// do the actual acoustic model loading
loadModelFiles(model);
System.err.println("HTK -> S4 conversion finished");
loaded = true;
}
}
public String getName() {
return name;
}
/**
* Return the acoustic model properties.
*
* @return the acoustic model properties
*/
public Properties getProperties() {
if (properties == null) {
loadProperties();
}
return properties;
}
/**
* Return the location.
*
* @return the location
*/
protected String getLocation() {
return location;
}
/**
* Loads the AcousticModel from an HTK MMF
*
*
* @param MMFname the name of the acoustic model; if null we just load from the default
* location
*
* @throws java.io.IOException
*/
private void loadModelFiles(String MMFname) throws IOException {
logger.config("Loading HTK acoustic model: " + MMFname);
logger.config(" Path : " + location);
logger.config(" modellName: " + model);
logger.config(" dataDir : " + dataDir);
HTKStruct htkmods = new HTKStruct();
htkmods.load(MMFname);
meansPool = htkmods.htkMeans(MMFname);
variancePool = htkmods.htkVars(MMFname, varianceFloor);
mixtureWeights = htkmods.htkWeights(MMFname, mixtureWeightFloor);
matrixPool = htkmods.htkTrans(MMFname);
// senonePool represents the set of the emitting states
senonePool = createSenonePool(distFloor, varianceFloor);
loadHMMPool(useCDUnits, htkmods, location + File.separator + model);
}
public Map<String, Unit> getContextIndependentUnits() {
return contextIndependentUnits;
}
/**
* Creates the senone pool from the rest of the pools. assumes the means and
* variances are in the same order
*
* @param distFloor the lowest allowed score
* @param varianceFloor the lowest allowed variance
* @return the senone pool
*/
private Pool<Senone> createSenonePool(float distFloor, float varianceFloor) {
Pool<Senone> pool = new Pool<Senone>("senones");
int numMeans = meansPool.size();
int numVariances = variancePool.size();
// TODO: remove this variable to allow different number of states per
// HMM
int numGaussiansPerSenone = mixtureWeights.getGauPerState();
int numSenones = mixtureWeights.getStatesNum();
int whichGaussian = 0;
logger.fine("NG " + numGaussiansPerSenone);
logger.fine("NS " + numSenones);
logger.fine("NMNS " + numMeans);
logger.fine("NMNS " + numVariances);
assert numGaussiansPerSenone > 0;
assert numVariances == numSenones * numGaussiansPerSenone;
assert numMeans == numSenones * numGaussiansPerSenone;
float [][] meansTransformationMatrix = meanTransformationMatrixPool == null ?
null : meanTransformationMatrixPool.get(0);
float [] meansTransformationVector = meanTransformationVectorPool == null ?
null : meanTransformationVectorPool.get(0);
float [][] varianceTransformationMatrix = varianceTransformationMatrixPool == null ?
null : varianceTransformationMatrixPool.get(0);
float [] varianceTransformationVector = varianceTransformationVectorPool == null ?
null : varianceTransformationVectorPool.get(0);
for (int i = 0; i < numSenones; i++) {
MixtureComponent[] mixtureComponents = new MixtureComponent[numGaussiansPerSenone];
for (int j = 0; j < numGaussiansPerSenone; j++) {
mixtureComponents[j] = new MixtureComponent(
meansPool.get(whichGaussian),
meansTransformationMatrix,
meansTransformationVector,
variancePool.get(whichGaussian),
varianceTransformationMatrix,
varianceTransformationVector,
distFloor,
varianceFloor);
whichGaussian++;
}
Senone senone = new GaussianMixture(mixtureWeights, mixtureComponents, i);
pool.put(i, senone);
}
return pool;
}
/**
* Reads the next word (text separated by whitespace) from the given stream.
*
* the input stream
*
* @return the next word
* @throws IOException
* on error
*/
String readWord(DataInputStream dis) throws IOException {
StringBuilder sb = new StringBuilder();
char c;
// skip leading whitespace
do {
c = readChar(dis);
} while (Character.isWhitespace(c));
// read the word
do {
sb.append(c);
c = readChar(dis);
} while (!Character.isWhitespace(c));
return sb.toString();
}
/**
* Reads a single char from the stream.
*
* the stream to read
*
* @return the next character on the stream
* @throws IOException
* if an error occurs
*/
private char readChar(DataInputStream dis) throws IOException {
return (char) dis.readByte();
}
/**
* Read an integer from the input stream, byte-swapping as necessary.
*
*
* @param dis the input stream
* @return an integer value
* @throws IOException
* on error
*/
protected int readInt(DataInputStream dis) throws IOException {
if (swap) {
return Utilities.readLittleEndianInt(dis);
} else {
return dis.readInt();
}
}
/**
* Read a float from the input stream, byte-swapping as necessary. the input
* stream
*
* @param dis the input stream
* @return a floating value
* @throws IOException
* on error
*/
protected float readFloat(DataInputStream dis) throws IOException {
float val;
if (swap) {
val = Utilities.readLittleEndianFloat(dis);
} else {
val = dis.readFloat();
}
return val;
}
/**
* Reads the given number of floats from the stream and returns them in an
* array of floats.
*
* @param dis the stream to read data from the number of floats to read
* @param size size of the array
* @return an array of size float elements
* @throws IOException
* if an exception occurs
*/
protected float[] readFloatArray(DataInputStream dis, int size)
throws IOException {
float[] data = new float[size];
for (int i = 0; i < size; i++) {
data[i] = readFloat(dis);
}
return data;
}
/**
* Loads the model file. A set of density arrays are created and placed in
* the given pool.
*
*
* @param useCDUnits if true, loads also the context dependent units
* @param htkModels the set of HTK models
* @param path the path to a density file
*
* @throws IOException
* if an error occurs while loading the data
*/
protected void loadHMMPool(boolean useCDUnits, HTKStruct htkModels,
String path) throws IOException {
int numStatePerHMM;
// assert numTiedState == mixtureWeightsPool.getFeature(NUM_SENONES, 0);
// assert numTiedTransitionMatrices == matrixPool.size();
// Load the base phones
/**
* This version loads the phones from the .MMF But contrary to S4, HTK
* do not produce reliable monophones, and some monophones may be
* missing ! An option might be to load 1ph trained independently from
* the 3ph but this file may not be always available. An easy fallback
* solution for now is to tie all the monophone to the first 3ph with
* the same base name found...
*/
if (!tie1ph) {
for (Iterator<SingleHMM> monoPhones = htkModels.hmmsHTK.get1phIt(); monoPhones
.hasNext();) {
SingleHMM hmm = monoPhones.next();
if (hmm == null)
break;
String name = hmm.getName();
String attribute;
if (name.equals("sil") || name.equals("sp")
|| name.equals("bb") || name.equals("xx")
|| name.equals("hh"))
attribute = FILLER;
else
attribute = "nofiller";
// get the transition index
int tmat = hmm.trIdx;
// This includes non-emitting states !
numStatePerHMM = hmm.getNstates();
int[] stid = new int[hmm.getNbEmittingStates()];
// stid contains the number of senone as it in senone pool
int j = 0;
for (int ii = 0; ii < numStatePerHMM; ii++) {
if (hmm.isEmitting(ii)) {
HMMState s = hmm.getState(ii);
// we use the index of HMM in MMF file
stid[j] = htkModels.hmmsHTK.getStateIdx(s);
j++;
// assert stid[j] >= 0 && stid[j] <
// numContextIndependentTiedState;
}
}
// assert tmat < numTiedTransitionMatrices;
Unit unit = unitManager.getUnit(name, attribute.equals(FILLER));
contextIndependentUnits.put(unit.getName(), unit);
if (logger.isLoggable(Level.FINE)) {
logger.fine("Loaded " + unit);
}
// The first filler
if (unit.isFiller() && unit.getName().equals(SILENCE_CIPHONE)) {
unit = UnitManager.SILENCE;
}
float[][] transitionMatrix = matrixPool.get(tmat);
SenoneSequence ss = getSenoneSequence(stid);
HMM hmm2 = new SenoneHMM(unit, ss, transitionMatrix,
HMMPosition.lookup("-"));
hmmManager.put(hmm2);
}
} else {
// build the 1ph by tying to the first 3ph
for (int i = 0; i < htkModels.hmmsHTK.getNhmms(); i++) {
SingleHMM hmm = htkModels.hmmsHTK.getHMM(i);
if (hmm == null)
break;
String name = hmm.getBaseName();
if (!contextIndependentUnits.containsKey(name)) {
String attribute;
if (name.equals("SIL") || name.equals("SP")
|| name.equals("BB") || name.equals("XX")
|| name.equals("HH"))
attribute = FILLER;
else
attribute = "nofiller";
// get the transition index
int tmat = hmm.trIdx;
// warning ! this includes non-emitting states !
numStatePerHMM = hmm.getNstates();
int[] stid = new int[hmm.getNbEmittingStates()];
// stid contains the identifier of senone used during
// creation of senone pool
int j = 0;
for (int ii = 0; ii < numStatePerHMM; ii++) {
if (hmm.isEmitting(ii)) {
HMMState s = hmm.getState(ii);
// We get an index of HMM in MMF file
stid[j] = htkModels.hmmsHTK.getStateIdx(s);
j++;
// assert stid[j] >= 0 && stid[j] <
// numContextIndependentTiedState;
}
}
// assert tmat < numTiedTransitionMatrices;
Unit unit = unitManager.getUnit(name, attribute
.equals(FILLER));
contextIndependentUnits.put(unit.getName(), unit);
if (logger.isLoggable(Level.FINE)) {
logger.fine("Loaded " + unit);
}
// The first filler
if (unit.isFiller()
&& unit.getName().equals(SILENCE_CIPHONE)) {
unit = UnitManager.SILENCE;
}
float[][] transitionMatrix = matrixPool.get(tmat);
SenoneSequence ss = getSenoneSequence(stid);
HMM hmm2 = new SenoneHMM(unit, ss, transitionMatrix,
HMMPosition.lookup("-"));
hmmManager.put(hmm2);
}
}
}
// Load the context dependent phones. If the useCDUnits
// property is false, the CD phones will not be created, but
// the values still need to be read in from the file.
String lastUnitName = "";
Unit lastUnit = null;
int[] lastStid = null;
SenoneSequence lastSenoneSequence = null;
List<String> HMMdejavu = new ArrayList<String>();
for (Iterator<SingleHMM> triPhones = htkModels.hmmsHTK.get3phIt(); triPhones
.hasNext();) {
SingleHMM hmm = triPhones.next();
if (hmm == null)
break;
String name = hmm.getBaseName();
String left = hmm.getLeft();
String right = hmm.getRight();
{
// diphones are transformed into triphones with SIL context, as
// it looks
// like S4 do not support diphones
if (left.equals("-"))
left = "SIL";
if (right.equals("-"))
right = "SIL";
String s = left + ' ' + name + ' ' + right;
if (HMMdejavu.contains(s)) {
// this may happen when a diphone is transformed into a
// triphone with SIL
continue;
}
HMMdejavu.add(s);
}
// TODO: For this moment we don't know if the HMMS are in the end or
// not
String position = "i";
int tmat = hmm.trIdx;
numStatePerHMM = hmm.getNstates();
int[] stid = new int[hmm.getNbEmittingStates()];
int j = 0;
for (int ii = 0; ii < numStatePerHMM; ii++) {
if (hmm.isEmitting(ii)) {
HMMState s = hmm.getState(ii);
stid[j] = htkModels.hmmsHTK.getStateIdx(s);
j++;
// assert stid[j] >= 0 && stid[j] <
// numContextIndependentTiedState;
}
}
if (useCDUnits) {
Unit unit;
String unitName = (name + ' ' + left + ' ' + right);
if (unitName.equals(lastUnitName)) {
unit = lastUnit;
} else {
Unit[] leftContext = new Unit[1];
leftContext[0] = contextIndependentUnits.get(left);
Unit[] rightContext = new Unit[1];
rightContext[0] = contextIndependentUnits.get(right);
Context context = LeftRightContext.get(leftContext,
rightContext);
unit = unitManager.getUnit(name, false, context);
}
lastUnitName = unitName;
lastUnit = unit;
if (logger.isLoggable(Level.FINE)) {
logger.fine("Loaded " + unit);
}
float[][] transitionMatrix = matrixPool.get(tmat);
SenoneSequence ss = lastSenoneSequence;
if (ss == null || !sameSenoneSequence(stid, lastStid)) {
ss = getSenoneSequence(stid);
}
lastSenoneSequence = ss;
lastStid = stid;
HMM hmm2 = new SenoneHMM(unit, ss, transitionMatrix,
HMMPosition.lookup(position));
hmmManager.put(hmm2);
}
}
}
/**
* Returns true if the given senone sequence IDs are the same.
*
* @param ssid1 first sequence of senones
* @param ssid2 second sequence of senones
* @return true if the given senone sequence IDs are the same, false
* otherwise
*/
protected boolean sameSenoneSequence(int[] ssid1, int[] ssid2) {
if (ssid1.length == ssid2.length) {
for (int i = 0; i < ssid1.length; i++) {
if (ssid1[i] != ssid2[i]) {
return false;
}
}
return true;
} else {
return false;
}
}
/**
* Gets the senone sequence representing the given senones.
*
* @param stateId is the array of senone state ids
* @return the senone sequence associated with the states
*/
protected SenoneSequence getSenoneSequence(int[] stateId) {
Senone[] senones = new Senone[stateId.length];
for (int i = 0; i < stateId.length; i++) {
senones[i] = senonePool.get(stateId[i]);
}
return new SenoneSequence(senones);
}
public Pool<float[]> getMeansPool() {
return meansPool;
}
public Pool<float[][]> getMeansTransformationMatrixPool() {
return meanTransformationMatrixPool;
}
public Pool<float[]> getMeansTransformationVectorPool() {
return meanTransformationVectorPool;
}
public Pool<float[]> getVariancePool() {
return variancePool;
}
public Pool<float[][]> getVarianceTransformationMatrixPool() {
return varianceTransformationMatrixPool;
}
public Pool<float[]> getVarianceTransformationVectorPool() {
return varianceTransformationVectorPool;
}
public GaussianWeights getMixtureWeights() {
return mixtureWeights;
}
public Pool<float[][]> getTransitionMatrixPool() {
return matrixPool;
}
public float[][] getTransformMatrix() {
return null;
}
public Pool<Senone> getSenonePool() {
return senonePool;
}
public int getLeftContextSize() {
return CONTEXT_SIZE;
}
public int getRightContextSize() {
return CONTEXT_SIZE;
}
public HMMManager getHMMManager() {
return hmmManager;
}
public void logInfo() {
logger.info("HTKLoader");
meansPool.logInfo(logger);
variancePool.logInfo(logger);
matrixPool.logInfo(logger);
senonePool.logInfo(logger);
if (meanTransformationMatrixPool != null)
meanTransformationMatrixPool.logInfo(logger);
if (meanTransformationVectorPool != null)
meanTransformationVectorPool.logInfo(logger);
if (varianceTransformationMatrixPool != null)
varianceTransformationMatrixPool.logInfo(logger);
if (varianceTransformationVectorPool != null)
varianceTransformationVectorPool.logInfo(logger);
senonePool.logInfo(logger);
logger.info("Context Independent Unit Entries: "
+ contextIndependentUnits.size());
hmmManager.logInfo(logger);
}
class HTKStruct {
HMMSet hmmsHTK;
public void load(String name) {
System.err.println("HTK loading...");
hmmsHTK = new HMMSet();
hmmsHTK.loadHTK(name);
// TODO: Is tied list useful for sphinx4
// hmmsHTK.loadTiedList(name + ".tiedlist");
System.err.println("HTK loading finished");
}
int getNumStates() {
return hmmsHTK.getNstates();
}
int getGMMSize() {
GMMDiag gmm = hmmsHTK.gmms.get(0);
return gmm.getNgauss();
}
int getNcoefs() {
GMMDiag gmm = hmmsHTK.gmms.get(0);
return gmm.getNcoefs();
}
int getNumHMMs() {
return hmmsHTK.getNhmms();
}
public Pool<float[]> htkMeans(String path) {
Pool<float[]> pool = new Pool<float[]>(path);
// Suppose this is the number of shared states
int numStates = getNumStates();
int numStreams = 1;
int numGaussiansPerState = getGMMSize();
pool.setFeature(NUM_SENONES, numStates);
pool.setFeature(NUM_STREAMS, numStreams);
pool.setFeature(NUM_GAUSSIANS_PER_STATE, numGaussiansPerState);
int ncoefs = getNcoefs();
for (int i = 0; i < numStates; i++) {
GMMDiag gmm = hmmsHTK.gmms.get(i);
for (int j = 0; j < numGaussiansPerState; j++) {
float[] density = new float[ncoefs];
for (int k = 0; k < ncoefs; k++) {
density[k] = gmm.getMean(j, k);
}
int id = i * numGaussiansPerState + j;
// the order of the means is the order in the HMMSet.gmms
// vector which is the order of appearance in the MMF file
pool.put(id, density);
}
}
return pool;
}
public Pool<float[]> htkVars(String path, float floor) {
Pool<float[]> pool = new Pool<float[]>(path);
int numStates = getNumStates();
int numStreams = 1;
int numGaussiansPerState = getGMMSize();
pool.setFeature(NUM_SENONES, numStates);
pool.setFeature(NUM_STREAMS, numStreams);
pool.setFeature(NUM_GAUSSIANS_PER_STATE, numGaussiansPerState);
int ncoefs = getNcoefs();
for (int i = 0; i < numStates; i++) {
GMMDiag gmm = hmmsHTK.gmms.get(i);
for (int j = 0; j < numGaussiansPerState; j++) {
float[] vars = new float[ncoefs];
for (int k = 0; k < ncoefs; k++) {
// TODO: check: shall we put inverse vars here ?
vars[k] = gmm.getVar(j, k);
}
Utilities.floorData(vars, varianceFloor);
int id = i * numGaussiansPerState + j;
// the order of the vars is the order in the HMMSet.gmms
// vector which is the order of appearance in the MMF file
pool.put(id, vars);
}
}
return pool;
}
public GaussianWeights htkWeights(String path, float floor) {
int numStates = getNumStates();
int numStreams = 1;
int numGaussiansPerState = getGMMSize();
GaussianWeights mixtureWeights = new GaussianWeights(path, numStates, numGaussiansPerState, numStreams);
for (int i = 0; i < numStates; i++) {
GMMDiag gmm = hmmsHTK.gmms.get(i);
float[] logWeights = new float[numGaussiansPerState];
for (int j = 0; j < numGaussiansPerState; j++) {
logWeights[j] = gmm.getWeight(j);
}
Utilities.floorData(logWeights, mixtureWeightFloor);
logMath.linearToLog(logWeights);
// the order of the weights is the order in the HMMSet.gmms
// vector which is the order of appearance in the MMF file
mixtureWeights.put(i, 0, logWeights);
}
return mixtureWeights;
}
public Pool<float[][]> htkTrans(String path) {
Pool<float[][]> pool = new Pool<float[][]>(path);
int numMatrices = getNumHMMs();
int i = 0;
/*
* the order of the transitions is first, the order of the trans
* macro, and then the order of the trans in the HMMs... in other
* words, this is simply the order of the trans in the MMF !
*/
// tied-trans
if (hmmsHTK.transitions != null)
for (; i < hmmsHTK.transitions.size(); i++) {
float[][] tr = hmmsHTK.transitions.get(i);
float[][] tmat = new float[tr.length][tr[0].length];
for (int j = 0; j < tmat.length; j++)
for (int k = 0; k < tmat[j].length; k++) {
tmat[j][k] = logMath.linearToLog(tr[j][k]);
}
pool.put(i, tmat);
}
// untied-trans
for (int l = 0; l < numMatrices; l++) {
SingleHMM hmm = hmmsHTK.getHMM(l);
if (hmm.trans != null) {
float[][] tr = hmm.trans;
float[][] tmat = new float[tr.length][tr[0].length];
for (int j = 0; j < tmat.length; j++)
for (int k = 0; k < tmat[j].length; k++) {
tmat[j][k] = logMath.linearToLog(tr[j][k]);
}
hmm.trIdx = i;
pool.put(i++, tmat);
} else {
// The index in the pool is the same as the index in the
// macros
hmm.trIdx = hmm.getTransIdx();
}
}
return pool;
}
}
public void update(Transform transform, ClusteredDensityFileData clusters) {
// TODO not implemented yet
}
}