/*
* Encog(tm) Core v3.4 - Java Version
* http://www.heatonresearch.com/encog/
* https://github.com/encog/encog-java-core
* Copyright 2008-2016 Heaton Research, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* For more information on Heaton Research copyrights, licenses
* and trademarks visit:
* http://www.heatonresearch.com/copyright
*/
package org.encog.ml.hmm.alog;
import java.util.Iterator;
import org.encog.ml.data.MLDataPair;
import org.encog.ml.data.MLDataSet;
import org.encog.ml.hmm.HiddenMarkovModel;
/**
* The Viterbi algorithm is used to find the most likely sequence of hidden
* states (called the Viterbi path) that results in a sequence of observed
* events. Used for the Markov information sources, and more generally, hidden
* Markov models (HMM).
*
* Viterbi AJ (April 1967).
* "Error bounds for convolutional codes and an asymptotically optimum decoding algorithm"
* . IEEE Transactions on Information Theory 13 (2): 260-269.
* doi:10.1109/TIT.1967.1054010.
*/
public class ViterbiCalculator {
private final double[][] delta;
private final int[][] psy;
private final int[] stateSequence;
private double lnProbability;
public ViterbiCalculator(final MLDataSet oseq, final HiddenMarkovModel hmm) {
if (oseq.size() < 1) {
throw new IllegalArgumentException("Must not have empty sequence");
}
this.delta = new double[oseq.size()][hmm.getStateCount()];
this.psy = new int[oseq.size()][hmm.getStateCount()];
this.stateSequence = new int[oseq.size()];
for (int i = 0; i < hmm.getStateCount(); i++) {
this.delta[0][i] = -Math.log(hmm.getPi(i))
- Math.log(hmm.getStateDistribution(i).probability(
oseq.get(0)));
this.psy[0][i] = 0;
}
final Iterator<MLDataPair> oseqIterator = oseq.iterator();
if (oseqIterator.hasNext()) {
oseqIterator.next();
}
int t = 1;
while (oseqIterator.hasNext()) {
final MLDataPair observation = oseqIterator.next();
for (int i = 0; i < hmm.getStateCount(); i++) {
computeStep(hmm, observation, t, i);
}
t++;
}
this.lnProbability = Double.MAX_VALUE;
for (int i = 0; i < hmm.getStateCount(); i++) {
final double thisProbability = this.delta[oseq.size() - 1][i];
if (this.lnProbability > thisProbability) {
this.lnProbability = thisProbability;
this.stateSequence[oseq.size() - 1] = i;
}
}
this.lnProbability = -this.lnProbability;
for (int t2 = oseq.size() - 2; t2 >= 0; t2--) {
this.stateSequence[t2] = this.psy[t2 + 1][this.stateSequence[t2 + 1]];
}
}
private void computeStep(final HiddenMarkovModel hmm, final MLDataPair o,
final int t, final int j) {
double minDelta = Double.MAX_VALUE;
int min_psy = 0;
for (int i = 0; i < hmm.getStateCount(); i++) {
final double thisDelta = this.delta[t - 1][i]
- Math.log(hmm.getTransitionProbability(i, j));
if (minDelta > thisDelta) {
minDelta = thisDelta;
min_psy = i;
}
}
this.delta[t][j] = minDelta
- Math.log(hmm.getStateDistribution(j).probability(o));
this.psy[t][j] = min_psy;
}
public double lnProbability() {
return this.lnProbability;
}
public int[] stateSequence() {
return this.stateSequence.clone();
}
}