/******************************************************************************* * 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; import java.util.LinkedList; import java.util.List; import java.util.Vector; import be.ac.ulg.montefiore.run.jahmm.Hmm; import be.ac.ulg.montefiore.run.jahmm.Observation; import be.ac.ulg.montefiore.run.jahmm.Opdf; /** * This class can be used to compute the most probable state sequence matching * a given observation sequence (given an HMM). * @author Dominik Jain */ public class ViterbiCalculator<O extends Observation> { /** * delta[t][i] is the maximum probability of being in state i at time t, * i.e. the probability of the most likely path leading to state i at time t */ protected Vector<double[]> delta; private Vector<int[]> psy; protected Hmm<O> hmm; protected int step = 0; protected double viterbiPathLogProb; public ViterbiCalculator(Hmm<O> hmm) { delta = new Vector<double[]>(); psy = new Vector<int[]>(); this.hmm = hmm; } /** * goes one step forward * @param o current observation * @return log probability of current Viterbi path (most likely sequence) */ public double step(O o) { double best = Double.NEGATIVE_INFINITY; if(step == 0) { double[] d = new double[hmm.nbStates()]; for(int i = 0; i < hmm.nbStates(); i++) { d[i] = Math.log(hmm.getPi(i)); Opdf<O> opdf = hmm.getOpdf(i); double pObs = opdf.probability(o); d[i] += Math.log(pObs); best = Math.max(d[i], best); } delta.add(d); viterbiPathLogProb = best; } else { double[] curDelta = new double[hmm.nbStates()]; int[] curPsy = new int[hmm.nbStates()]; for(int j = 0; j < hmm.nbStates(); j++) { curDelta[j] = computeStep(o, j, curPsy); best = Math.max(curDelta[j], best); } delta.add(curDelta); psy.add(curPsy); viterbiPathLogProb = best; } ++step; return viterbiPathLogProb; } /** * find log probability of most likely path to state j given current observation */ protected double computeStep(O o, int j, int[] curPsy) { double maxDelta = Double.NEGATIVE_INFINITY; int max_psy = 0; double[] prev = delta.lastElement(); for (int i = 0; i < hmm.nbStates(); i++) { double transitionProb = hmm.getAij(i, j); double transitionLP = Math.log(transitionProb); double thisDelta = prev[i] + transitionLP; if(thisDelta > maxDelta) { maxDelta = thisDelta; max_psy = i; // most likely path is from state i } } double observationLP = Math.log(hmm.getOpdf(j).probability(o)); curPsy[j] = max_psy; return maxDelta + observationLP; } public void run(Iterable<? extends O> observations) { int i = 0; for(O o : observations) { System.out.printf("Viterbi step %d\r", i++); step(o); } System.out.println(); } /** * Returns the logarithm of the probability of the * most likely state path and the observations that have been passed * * @return <code>ln(P[O,S|H])</code> where <code>O</code> is the given * observation sequence, <code>H</code> the given HMM and * <code>S</code> the most likely state sequence of this observation * sequence given this HMM. */ public double getViterbiPathLogProbability() { return viterbiPathLogProb; } /** * Returns a (clone of) the array containing the computed most likely * state sequence. * * @return The state sequence; the i-th value of the array is the index * of the i-th state of the state sequence. */ public List<Integer> getViterbiPath() { List<Integer> ret = new LinkedList<Integer>(); double lnProbability = Double.NEGATIVE_INFINITY; Integer best = null; double[] finalProbs = delta.lastElement(); for (int i = 0; i < hmm.nbStates(); i++) { double thisProbability = finalProbs[i]; if (thisProbability > lnProbability) { best = i; lnProbability = thisProbability; } } ret.add(best); for(int i = psy.size()-1; i >= 0; i--) { best = psy.get(i)[best]; ret.add(0, best); } return ret; } }