package dist.hmm;
import shared.DataSet;
/**
* An implementation of the forward backward algorithm,
* which computes forward and backward probabilities
* for use in the Viterbi and Baum-Welch algorithms
* @author Andrew Guillory gtg008g@mail.gatech.edu
* @version 1.0
*/
public class ForwardBackwardProbabilityCalculator {
/**
* The forward probabilities through time,
* that is [t][i] value of in this matrix is the
* probability of being in state i at time t and
* O_1, O_2, ... O_t. These are the alpha values
* in the classic Rabiner paper
*/
private double[][] forwardProbabilities;
/**
* The backward probabilities through time,
* that is the [t][i] value in this matrix is the
* probability of O_t+1, O_t+2, ... O_T given
* that the state is i at time t. These are the
* beta values in the classic Rabiner paper.
*/
private double[][] backwardProbabilities;
/**
* The array of scaling values used to keep the
* probabilities within the dynamic range of
* the computer
*/
private double[] scales;
/**
* The model used to calculate the probabilities
*/
private HiddenMarkovModel model;
/**
* The observation sequence used to calculate probabilities
*/
private DataSet observationSequence;
/**
* Create a new probability calculator
* @param model the hidden markov model
* @param observationSequence the observation sequence
*/
public ForwardBackwardProbabilityCalculator(HiddenMarkovModel model,
DataSet observationSequence) {
this.model = model;
this.observationSequence = observationSequence;
}
/**
* Compute the forward probabilities
* iterates through time calculating probabilties
* @return the probability matrix
*/
public double[][] calculateForwardProbabilities() {
forwardProbabilities = new double[observationSequence.size()][model.getStateCount()];
scales = new double[observationSequence.size()];
// intial step
for (int i = 0; i < model.getStateCount(); i++) {
forwardProbabilities[0][i] =
model.initialStateProbability(i, observationSequence.get(0))
* model.observationProbability(i, observationSequence.get(0));
}
// apply initial scaling factor
double sum = 0;
for (int i = 0; i < model.getStateCount(); i++) {
sum += forwardProbabilities[0][i];
}
scales[0] = 1 / sum;
for (int i = 0; i < model.getStateCount(); i++) {
forwardProbabilities[0][i] *= scales[0];
}
// recursion
for (int t = 1; t < observationSequence.size(); t++) {
for (int i = 0; i < model.getStateCount(); i++) {
// sum over all possible previous states
// times probability of transitioning
double priorSum = 0;
for (int j = 0; j < model.getStateCount(); j++) {
priorSum += forwardProbabilities[t - 1][j]
* model.transitionProbability(j, i, observationSequence.get(t));
}
// factor in current observation
forwardProbabilities[t][i] = priorSum
* model.observationProbability(i, observationSequence.get(t));
}
// apply scaling factor
sum = 0;
for (int i = 0; i < model.getStateCount(); i++) {
sum += forwardProbabilities[t][i];
}
scales[t] = 1 / sum;
for (int i = 0; i < model.getStateCount(); i++) {
forwardProbabilities[t][i] *= scales[t];
}
}
return forwardProbabilities;
}
/**
* Compute the backward probabilities
* iterate backwards through time
* @return the backward probabilitity matrix
*/
public double[][] calculateBackwardProbabilities() {
if (scales == null) {
calculateForwardProbabilities();
}
backwardProbabilities = new double[observationSequence.size()][model.getStateCount()];
// inital step
for (int i = 0; i < model.getStateCount(); i++) {
backwardProbabilities[observationSequence.size() - 1][i] = 1
* scales[observationSequence.size() - 1];
}
// recursion
for (int t = observationSequence.size() - 2; t >= 0; t--) {
for (int i = 0; i < model.getStateCount(); i++) {
// sum over possible future states
double futureSum = 0;
for (int j = 0; j < model.getStateCount(); j++) {
futureSum += model.transitionProbability(i, j, observationSequence.get(t+1))
* model.observationProbability(j, observationSequence.get(t+1))
* backwardProbabilities[t+1][j];
}
backwardProbabilities[t][i] = futureSum * scales[t];
}
}
return backwardProbabilities;
}
/**
* Compute the log probability of the observation sequence
* Calls computerForwardProbabilities if it hasn't been called already
* @return the log probability
*/
public double calculateLogProbability() {
if (scales == null) {
calculateForwardProbabilities();
}
double sum = 0;
for (int t = 0; t < observationSequence.size(); t++) {
sum += Math.log(scales[t]);
}
return -sum;
}
/**
* Compute the probability of the observation sequence
* Calls computerForwardProbabilities if it hasn't been called already
* @return the log probability
*/
public double calculateProbability() {
if (scales == null) {
calculateForwardProbabilities();
}
double product = 1;
for (int t = 0; t < observationSequence.size(); t++) {
product *= scales[t];
}
return 1 / product;
}
}