/*
* 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.result;
import java.util.List;
import java.util.ArrayList;
/**
* Class used to collapse all equivalent paths in a Lattice. Results in a Lattices that is deterministic (no Node has
* Edges to two or more equivalent Nodes), and minimal (no Node has Edge from two or more equivalent Nodes).
*/
public class LatticeOptimizer {
protected final Lattice lattice;
/**
* Create a new Lattice optimizer
*
* @param lattice lattice to optimize
*/
public LatticeOptimizer(Lattice lattice) {
this.lattice = lattice;
}
/**
* Code for optimizing Lattices. An optimal lattice has all the same paths as the original, but with fewer nodes
* and edges
* <p>
* Note that these methods are all in Lattice so that it is easy to change the definition of "equivalent" nodes and
* edges. For example, an equivalent node might have the same word, but start or end at a different time.
* <p>
* To experiment with other definitions of equivalent, just create a superclass of Lattice.
*/
public void optimize() {
optimizeForward();
optimizeBackward();
}
/**
* Make the Lattice deterministic, so that no node has multiple outgoing edges to equivalent nodes.
* <p>
* Given two edges from the same node to two equivalent nodes, replace with one edge to one node with outgoing edges
* that are a union of the outgoing edges of the old two nodes.
* <p>
* A --> B --> C \--> B' --> Y
* <p>
* where B and B' are equivalent.
* <p>
* is replaced with
* <p>
* A --> B" --> C \--> Y
* <p>
* where B" is the merge of B and B'
* <p>
* Note that equivalent nodes must have the same incomming edges. For example
* <p>
* A --> B \ \ X --> B'
* <p>
* B and B' would not be equivalent because the incomming edges are different
*/
protected void optimizeForward() {
//System.err.println("*** Optimizing forward ***");
boolean moreChanges = true;
while (moreChanges) {
moreChanges = false;
// search for a node that can be optimized
// note that we use getCopyOfNodes to avoid concurrent changes to nodes
for (Node n : lattice.getCopyOfNodes()) {
// we are iterating down a list of node before optimization
// previous iterations may have removed nodes from the list
// therefore we have to check that the node stiff exists
if (lattice.hasNode(n)) {
moreChanges |= optimizeNodeForward(n);
}
}
}
}
/**
* Look for 2 "to" edges to equivalent nodes. Replace the edges with one edge to one node that is a merge of the
* equivalent nodes
* <p>
* nodes are equivalent if they have equivalent from edges, and the same label
* <p>
* merged nodes have a union of "from" and "to" edges
*
* @param n node
* @return true if Node n required an optimize forward
*/
protected boolean optimizeNodeForward(Node n) {
assert lattice.hasNode(n);
List<Edge> leavingEdges = new ArrayList<Edge>(n.getLeavingEdges());
for (int j = 0; j < leavingEdges.size(); j++) {
Edge e = leavingEdges.get(j);
for (int k = j + 1; k < leavingEdges.size(); k++) {
Edge e2 = leavingEdges.get(k);
/*
* If these are not the same edge, and they point to
* equivalent nodes, we have a hit, return true
*/
assert e != e2;
if (equivalentNodesForward(e.getToNode(), e2.getToNode())) {
mergeNodesAndEdgesForward(e, e2);
return true;
}
}
}
/*
* return false if we did not get a hit
*/
return false;
}
/**
* nodes are equivalent forward if they have "from" edges from the same nodes, and have equivalent labels (Token,
* start/end times)
*
* @param n1 first node
* @param n2 second node
* @return true if n1 and n2 are "equivalent forwards"
*/
protected boolean equivalentNodesForward(Node n1, Node n2) {
assert lattice.hasNode(n1);
assert lattice.hasNode(n2);
// do the labels match?
if (!equivalentNodeLabels(n1, n2)) return false;
// if they have different number of "from" edges they are not equivalent
// or if there is a "from" edge with no match then the nodes are not
// equivalent
return n1.hasEquivalentEnteringEdges(n2);
}
/**
* given edges e1 and e2 from node n to nodes n1 and n2
* <p>
* merge e1 and e2, that is, merge the scores of e1 and e2 create n' that is a merge of n1 and n2 add n' add edge e'
* from n to n'
* <p>
* remove n1 and n2 and all associated edges
*
* @param e1 first edge
* @param e2 second edge
*/
protected void mergeNodesAndEdgesForward(Edge e1, Edge e2) {
assert lattice.hasNode(e1.getFromNode());
assert lattice.hasEdge(e1);
assert lattice.hasEdge(e2);
assert e1.getFromNode() == e2.getFromNode();
Node n1 = e1.getToNode();
Node n2 = e2.getToNode();
assert n1.hasEquivalentEnteringEdges(n2);
assert n1.getWord().equals(n2.getWord());
for (Edge edge : n2.getEnteringEdges()) {
Edge anotherEdge = n1.getEdgeFromNode(edge.getFromNode());
assert anotherEdge != null;
anotherEdge.setAcousticScore
(mergeAcousticScores(edge.getAcousticScore(),
anotherEdge.getAcousticScore()));
anotherEdge.setLMScore(mergeLanguageScores(edge.getLMScore(),
anotherEdge.getLMScore()));
}
// add n2's edges to n1
for (Edge edge : n2.getLeavingEdges()) {
Edge anotherEdge = n1.getEdgeToNode(edge.getToNode());
if (anotherEdge == null) {
lattice.addEdge(n1, edge.getToNode(),
edge.getAcousticScore(), edge.getLMScore());
} else {
// if we got here then n1 and n2 had edges to the same node
// choose the edge with best score
anotherEdge.setAcousticScore
(mergeAcousticScores(edge.getAcousticScore(),
anotherEdge.getAcousticScore()));
anotherEdge.setLMScore(mergeLanguageScores(edge.getLMScore(),
anotherEdge.getLMScore()));
}
}
// remove n2 and all associated edges
lattice.removeNodeAndEdges(n2);
}
/**
* Minimize the Lattice deterministic, so that no node has multiple incoming edges from equivalent nodes.
* <p>
* Given two edges from equivalent nodes to a single nodes, replace with one edge from one node with incoming edges
* that are a union of the incoming edges of the old two nodes.
* <p>
* A --> B --> C X --> B' --/
* <p>
* where B and B' are equivalent.
* <p>
* is replaced with
* <p>
* A --> B" --> C X --/
* <p>
* where B" is the merge of B and B'
* <p>
* Note that equivalent nodes must have the same outgoing edges. For example
* <p>
* A --> X \ \ \ A' --> B
* <p>
* A and A' would not be equivalent because the outgoing edges are different
*/
protected void optimizeBackward() {
//System.err.println("*** Optimizing backward ***");
boolean moreChanges = true;
while (moreChanges) {
moreChanges = false;
// search for a node that can be optimized
// note that we use getCopyOfNodes to avoid concurrent changes to nodes
for (Node n : lattice.getCopyOfNodes()) {
// we are iterating down a list of node before optimization
// previous iterations may have removed nodes from the list
// therefore we have to check that the node stiff exists
if (lattice.hasNode(n)) {
moreChanges |= optimizeNodeBackward(n);
}
}
}
}
/**
* Look for 2 entering edges from equivalent nodes. Replace the edges with one edge to one new node that is a merge
* of the equivalent nodes Nodes are equivalent if they have equivalent to edges, and the same label. Merged nodes
* have a union of entering and leaving edges
*
* @param n node
* @return true if Node n required optimizing backwards
*/
protected boolean optimizeNodeBackward(Node n) {
List<Edge> enteringEdges = new ArrayList<Edge>(n.getEnteringEdges());
for (int j = 0; j < enteringEdges.size(); j++) {
Edge e = enteringEdges.get(j);
for (int k = j + 1; k < n.getEnteringEdges().size(); k++) {
Edge e2 = enteringEdges.get(k);
/*
* If these are not the same edge, and they point to
* equivalent nodes, we have a hit, return true
*/
assert e != e2;
if (equivalentNodesBackward(e.getFromNode(),
e2.getFromNode())) {
mergeNodesAndEdgesBackward(e, e2);
return true;
}
}
}
/*
* return false if we did not get a hit
*/
return false;
}
/**
* nodes are equivalent backward if they have "to" edges to the same nodes, and have equivalent labels (Token,
* start/end times)
*
* @param n1 first node
* @param n2 second node
* @return true if n1 and n2 are "equivalent backwards"
*/
protected boolean equivalentNodesBackward(Node n1, Node n2) {
assert lattice.hasNode(n1);
assert lattice.hasNode(n2);
// do the labels match?
if (!equivalentNodeLabels(n1, n2)) return false;
// if they have different number of "to" edges they are not equivalent
// or if there is a "to" edge with no match then the nodes are not equiv
return n1.hasEquivalentLeavingEdges(n2);
}
/**
* Is the contents of these Node equivalent?
*
* @param n1 first node
* @param n2 second node
* @return true if n1 and n2 have "equivalent labels"
*/
protected boolean equivalentNodeLabels(Node n1, Node n2) {
return (n1.getWord().equals(n2.getWord()) &&
(n1.getBeginTime() == n2.getBeginTime() &&
n1.getEndTime() == n2.getEndTime()));
}
/**
* given edges e1 and e2 to node n from nodes n1 and n2
* <p>
* merge e1 and e2, that is, merge the scores of e1 and e2 create n' that is a merge of n1 and n2 add n' add edge e'
* from n' to n
* <p>
* remove n1 and n2 and all associated edges
*
* @param e1 first edge
* @param e2 second edge
*/
protected void mergeNodesAndEdgesBackward(Edge e1, Edge e2) {
assert lattice.hasNode(e1.getToNode());
assert lattice.hasEdge(e1);
assert lattice.hasEdge(e2);
assert e1.getToNode() == e2.getToNode();
Node n1 = e1.getFromNode();
Node n2 = e2.getFromNode();
assert n1.hasEquivalentLeavingEdges(n2);
assert n1.getWord().equals(n2.getWord());
for (Edge edge : n2.getLeavingEdges()) {
Edge anotherEdge = n1.getEdgeToNode(edge.getToNode());
assert anotherEdge != null;
anotherEdge.setAcousticScore
(mergeAcousticScores(edge.getAcousticScore(),
anotherEdge.getAcousticScore()));
anotherEdge.setLMScore(mergeLanguageScores(edge.getLMScore(),
anotherEdge.getLMScore()));
}
// add n2's "from" edges to n1
for (Edge edge : n2.getEnteringEdges()) {
Edge anotherEdge = n1.getEdgeFromNode(edge.getFromNode());
if (anotherEdge == null) {
lattice.addEdge(edge.getFromNode(), n1,
edge.getAcousticScore(), edge.getLMScore());
} else {
// if we got here then n1 and n2 had edges from the same node
// choose the edge with best score
anotherEdge.setAcousticScore
(mergeAcousticScores(edge.getAcousticScore(),
anotherEdge.getAcousticScore()));
anotherEdge.setLMScore(mergeLanguageScores(edge.getLMScore(),
anotherEdge.getLMScore()));
}
}
// remove n2 and all associated edges
lattice.removeNodeAndEdges(n2);
}
/** Remove all Nodes that have no Edges to them (but not <s>) */
protected void removeHangingNodes() {
for (Node n : lattice.getCopyOfNodes()) {
if (lattice.hasNode(n)) {
if (n == lattice.getInitialNode()) {
} else if (n == lattice.getTerminalNode()) {
} else {
if (n.getLeavingEdges().isEmpty()
|| n.getEnteringEdges().isEmpty()) {
lattice.removeNodeAndEdges(n);
removeHangingNodes();
return;
}
}
}
}
}
/**
* Provides a single method to merge acoustic scores, so that changes to how acoustic score are merged can be made
* at one point only.
*
* @param score1 the first acoustic score
* @param score2 the second acoustic score
* @return the merged acoustic score
*/
private double mergeAcousticScores(double score1, double score2) {
// return lattice.getLogMath().addAsLinear(score1, score2);
return Math.max(score1, score2);
}
/**
* Provides a single method to merge language scores, so that changes to how language score are merged can be made
* at one point only.
*
* @param score1 the first language score
* @param score2 the second language score
* @return the merged language score
*/
private double mergeLanguageScores(double score1, double score2) {
// return lattice.getLogMath().addAsLinear(score1, score2);
return Math.max(score1, score2);
}
}