/******************************************************************************* * Copyright (C) 2010-2012 Dominik Jain. * * This file is part of ProbCog. * * ProbCog is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ProbCog is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ProbCog. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package probcog.hmm.latent; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Vector; import probcog.hmm.IObservationModel; import probcog.hmm.ViterbiCalculator; import be.ac.ulg.montefiore.run.jahmm.ObservationVector; /** * This class can be used to compute the most probable state sequence matching * a given observation sequence (given a latent dynamic HMM (LD-HMM)). * @author Dominik Jain */ public class LDViterbiCalculator<O extends ObservationVector> { protected LDHMM hmm; protected Path viterbiPath; protected int step = 0; protected Map<State,Path> prev; // debugging stuff protected boolean verbose = true; protected final boolean debug = false; Vector<Map<State,Path>> paths = null; public class Path implements Comparable<Path> { public State s; public int step; public Path predecessor, segmentStart; public double delta, lpLastSwitch, lpStateTransitionsFromSwitch; public ViterbiCalculator<ObservationVector> vc; protected IObservationModel<ObservationVector> fc; protected static final boolean subComputationViterbi = false; /** * debug values */ public double pTrans, pObs; public Vector<Path> successors = null; /** * constructs a path object that represents the start of a new segment * @param s */ protected Path(State s, Path predecessor) { this.s = s; this.segmentStart = this; if(subComputationViterbi) { if(hmm.subHMMs[s.label] instanceof SubHMMSimple) vc = new ViterbiCalculator<ObservationVector>((SubHMMSimple)hmm.subHMMs[s.label]); else throw new RuntimeException("no longer supported"); } else fc = hmm.getObservationModel(s.label); this.predecessor = predecessor; if(predecessor == null) { this.step = 0; this.lpLastSwitch = 0; } else { this.step = predecessor.step+1; this.lpLastSwitch = predecessor.delta; } this.lpStateTransitionsFromSwitch = 0; } /** * constructs a new path, i.e. the very beginning of a path * @param label initial label * @param observation initial observation */ public Path(int label, O observation) { this(new State(label, 0), null); double lpObs; if(subComputationViterbi) lpObs = vc.step(observation); else { pObs = fc.getObservationProbability(observation); lpObs = Math.log(pObs); } pTrans = hmm.getPi(s.label); lpStateTransitionsFromSwitch += Math.log(pTrans); delta = lpStateTransitionsFromSwitch + lpObs; } /** * constructs a path that results when continuing the given path with the same segment * @param p the path to continue */ protected Path(Path p) { this.s = new State(p.s.label, p.s.dwellTime+1); this.predecessor = p; this.step = predecessor.step+1; this.segmentStart = p.segmentStart; this.vc = p.vc; this.fc = p.fc; this.lpStateTransitionsFromSwitch = p.lpStateTransitionsFromSwitch; this.lpLastSwitch = p.lpLastSwitch; } public Path proceed(int label, O observation) { Path p2; if(label == -1) // remaining in same segment p2 = new Path(this); else // switching to different state p2 = new Path(new State(label, 0), this); // debugging: store successor information if(debug /*&& p2.step > 950*/) { // NOTE: causes severe slowdown if(this.successors == null) this.successors = new Vector<Path>(); this.successors.add(p2); } // update probabilities p2.pTrans = this.s.getTransitionProbability(hmm, label); double lpTrans = Math.log(p2.pTrans); p2.lpStateTransitionsFromSwitch += lpTrans; if(subComputationViterbi) { double lpPath = p2.vc.step(observation); p2.delta = p2.lpLastSwitch + p2.lpStateTransitionsFromSwitch + lpPath; } else { p2.pObs = p2.fc.getObservationProbability(observation); double lpObs = Math.log(p2.pObs); p2.delta = this.delta + lpTrans + lpObs; } return p2; } @Override public String toString() { StringBuffer sb = new StringBuffer(); Path p = this; do { sb.append(p.s.toString()); sb.append(" <- "); p = p.predecessor; } while(p != null); return sb.toString(); } @Override public int compareTo(Path o) { return -Double.compare(this.delta, o.delta); // sort in descending order of delta/probability } } /** * Computes the most likely state sequence matching an observation * sequence given an HMM. * * @param hmm A Hidden Markov Model; * @param oseq An observations sequence. */ public LDViterbiCalculator(LDHMM hmm) { this.hmm = hmm; if(debug) paths = new Vector<Map<State,Path>>(); } public void run(Iterable<? extends O> oseq, int maxStep) { for(O observation : oseq) { step(observation); if(step == maxStep) break; } if(verbose) System.out.println(); // debugging: for each time step compute ordered list of paths Vector<Vector<Path>> sortedPaths = null; if(debug) { sortedPaths = new Vector<Vector<Path>>(); for(Map<State,Path> m : paths) { Vector<Path> v = new Vector<Path>(m.values()); Collections.sort(v); sortedPaths.add(v); } } } public void run(Iterable<? extends O> oseq) { run(oseq, -1); } protected void step(O observation) { viterbiPath = null; if(step == 0) { prev = new HashMap<State, Path>(); for(int i = 0; i < hmm.getNumStates(); i++) { Path p = new Path(i, observation); prev.put(p.s, p); if(viterbiPath == null || p.delta > viterbiPath.delta) viterbiPath = p; } if(debug) paths.add(prev); } else { Map<State, Path> cur = new HashMap<State, Path>(); int infinite = 0, nan = 0; for(Path p1 : prev.values()) { for(int j = -1; j < hmm.getNumStates(); j++) { Path p2 = p1.proceed(j, observation); double delta = p2.delta; if(Double.isInfinite(delta)) { infinite++; continue; } if(Double.isNaN(delta)) { nan++; continue; } Path best = cur.get(p2.s); if(best == null || delta > best.delta) { cur.put(p2.s, p2); if(viterbiPath == null || p2.delta > viterbiPath.delta) viterbiPath = p2; //System.out.println(p2); } } } // debugging: keep path information if(debug) paths.add(cur); // done, proceed to next step prev = cur; if(verbose) System.out.printf("Viterbi step %d: %d paths (%d infinite, %d NaN) \r", step, cur.size(), infinite, nan); if(cur.size() == 0) throw new RuntimeException("No paths with finite probability remain"); } step++; } public double getViterbiPathLogProbability() { return viterbiPath.delta; } /** * @return the Viterbi path (most likely hidden state sequence) */ public LinkedList<Integer> getViterbiPath() { LinkedList<Integer> stateSequence = new LinkedList<Integer>(); Path p = viterbiPath; do { stateSequence.addFirst(p.s.label); p = p.predecessor; } while(p != null); return stateSequence; } }