Viterbi algorithm - Fuhz Articles
Visually Impaired Edition.


Viterbi algorithm article, this Fuhz page will hopefully provide the answers to the who, what where and why on the Viterbi algorithm topic.
At the bottom of the page we often provide links to external documents relating to Viterbi algorithm which may also help your research. Every effort is made to ensure the content on this page is as accurate and error free as possible, however whenever researching information that requires the utmost accuracy such as a term paper it is always best to cross reference facts with numerous sources.


Viterbi algorithm - Wikipedia, the free encyclopedia

Viterbi algorithm

From Wikipedia, the free encyclopedia
Jump to: navigation, search

The Viterbi algorithm is a dynamic programming algorithm for finding the most likely sequence of hidden states – called the Viterbi path – that results in a sequence of observed events, especially in the context of Markov information sources, and more generally, hidden Markov models. The forward algorithm is a closely related algorithm for computing the probability of a sequence of observed events. These algorithms belong to the realm of information theory.

The algorithm makes a number of assumptions.

These assumptions are all satisfied in a first-order hidden Markov model.

The terms "Viterbi path" and "Viterbi algorithm" are also applied to related dynamic programming algorithms that discover the single most likely explanation for an observation. For example, in statistical parsing a dynamic programming algorithm can be used to discover the single most likely context-free derivation (parse) of a string, which is sometimes called the "Viterbi parse".

The Viterbi algorithm was conceived by Andrew Viterbi in 1967 as a decoding algorithm for convolutional codes over noisy digital communication links. For more details on the history of the development of the algorithm see David Forney's article [1]. The algorithm has found universal application in decoding the convolutional codes used in both CDMA and GSM digital cellular, dial-up modems, satellite, deep-space communications, and 802.11 wireless LANs. It is now also commonly used in speech recognition, keyword spotting, computational linguistics, and bioinformatics. For example, in speech-to-text (speech recognition), the acoustic signal is treated as the observed sequence of events, and a string of text is considered to be the "hidden cause" of the acoustic signal. The Viterbi algorithm finds the most likely string of text given the acoustic signal.

Contents

Overview

The assumptions listed above can be elaborated as follows. The Viterbi algorithm operates on a state machine assumption. That is, at any time the system being modeled is in some state. There are a finite number of states. While multiple sequences of states (paths) can lead to a given state, at least one of them is a most likely path to that state, called the "survivor path". This is a fundamental assumption of the algorithm because the algorithm will examine all possible paths leading to a state and only keep the one most likely. This way the algorithm does not have to keep track of all possible paths, only one per state.

A second key assumption is that a transition from a previous state to a new state is marked by an incremental metric, usually a number. This transition is computed from the event. The third key assumption is that the events are cumulative over a path in some sense, usually additive. So the crux of the algorithm is to keep a number for each state. When an event occurs, the algorithm examines moving forward to a new set of states by combining the metric of a possible previous state with the incremental metric of the transition due to the event and chooses the best. The incremental metric associated with an event depends on the transition possibility from the old state to the new state. For example in data communications, it may be possible to only transmit half the symbols from an odd numbered state and the other half from an even numbered state. Additionally, in many cases the state transition graph is not fully connected. A simple example is a car that has 3 states — forward, stop and reverse — and a transition from forward to reverse is not allowed. It must first enter the stop state. After computing the combinations of incremental metric and state metric, only the best survives and all other paths are discarded. There are modifications to the basic algorithm which allow for a forward search in addition to the backwards one described here.

Path history must be stored. In some cases, the search history is complete because the state machine at the encoder starts in a known state and there is sufficient memory to keep all the paths. In other cases, a programmatic solution must be found for limited resources: one example is convolutional encoding, where the decoder must truncate the history at a depth large enough to keep performance to an acceptable level. Although the Viterbi algorithm is very efficient and there are modifications that reduce the computational load, the memory requirements tend to remain constant.

Algorithm

Supposing we are given a Hidden Markov Model (HMM) with states Y, initial probabilities πi of being in state i and transition probabilities ai,j of transitioning from state i to state j. Say we observe outputs x_0,\dots, x_T. The state sequence y_0,\dots,y_T most likely to have produced the observations is given by the recurrence relations:1


\begin{array}{rcl}
V_{0,k} &=& \mathrm{P}\big( x_0 \ | \ k \big) \cdot \pi_k \\
V_{t,k} &=& \mathrm{P}\big( x_t \ | \ k \big) \cdot \max_{y \in Y} \left( a_{y,k}V_{t-1,y}\right)
\end{array}

Here Vt,k is the probability of the most probable state sequence responsible for the first t + 1 observations (we add one because indexing started at 0) that has k as its final state. The Viterbi path can be retrieved by saving back pointers which remember which state y was used in the second equation. Let Ptr(k,t) be the function that returns the value of y used to compute Vt,k if t > 0, or k if t = 0. Then:


\begin{array}{rcl}
y_T &=& \arg\max_{y \in Y} (V_{T,y}) \\
y_{t-1} &=& \mathrm{Ptr}(y_t,t)
\end{array}

The complexity of this algorithm is O(T\times\left|{Y}\right|^2).

Example

Consider two friends, Alice and Bob, who live far apart from each other and who talk together daily over the telephone about what they did that day. Bob is only interested in three activities: walking in the park, shopping, and cleaning his apartment. The choice of what to do is determined exclusively by the weather on a given day. Alice has no definite information about the weather where Bob lives, but she knows general trends. Based on what Bob tells her he did each day, Alice tries to guess what the weather must have been like.

Alice believes that the weather operates as a discrete Markov chain. There are two states, "Rainy" and "Sunny", but she cannot observe them directly, that is, they are hidden from her. On each day, there is a certain chance that Bob will perform one of the following activities, depending on the weather: "walk", "shop", or "clean". Since Bob tells Alice about his activities, those are the observations. The entire system is that of a hidden Markov model (HMM).

Alice knows the general weather trends in the area, and what Bob likes to do on average. In other words, the parameters of the HMM are known. They can be written down in the Python programming language:

states = ('Rainy', 'Sunny')
 
observations = ('walk', 'shop', 'clean')
 
start_probability = {'Rainy': 0.6, 'Sunny': 0.4}
 
transition_probability = {
   'Rainy' : {'Rainy': 0.7, 'Sunny': 0.3},
   'Sunny' : {'Rainy': 0.4, 'Sunny': 0.6},
   }
 
emission_probability = {
   'Rainy' : {'walk': 0.1, 'shop': 0.4, 'clean': 0.5},
   'Sunny' : {'walk': 0.6, 'shop': 0.3, 'clean': 0.1},
   }

In this piece of code, start_probability represents Alice's belief about which state the HMM is in when Bob first calls her (all she knows is that it tends to be rainy on average). The particular probability distribution used here is not the equilibrium one, which is (given the transition probabilities) approximately {'Rainy': 0.57, 'Sunny': 0.43}. The transition_probability represents the change of the weather in the underlying Markov chain. In this example, there is only a 30% chance that tomorrow will be sunny if today is rainy. The emission_probability represents how likely Bob is to perform a certain activity on each day. If it is rainy, there is a 50% chance that he is cleaning his apartment; if it is sunny, there is a 60% chance that he is outside for a walk.

Graphical representation of the given HMM

Alice talks to Bob three days in a row and discovers that on the first day he went for a walk, on the second day he went shopping, and on the third day he cleaned his apartment. Alice has a question: what is the most likely sequence of rainy/sunny days that would explain these observations? This is answered by the Viterbi algorithm.

# Helps visualize the steps of Viterbi.
def print_dptable(V):
    print "    ",
    for i in range(len(V)): print "%7s" % ("%d" % i),
    print
 
    for y in V0.keys():
        print "%.5s: " % y,
        for t in range(len(V)):
            print "%.7s" % ("%f" % Vty),
        print
 
def viterbi(obs, states, start_p, trans_p, emit_p):
    V = {}
    path = {}
 
    # Initialize base cases (t == 0)
    for y in states:
        V0y = start_py * emit_pyobs0
        pathy = y
 
    # Run Viterbi for t > 0
    for t in range(1,len(obs)):
        V.append({})
        newpath = {}
 
        for y in states:
            (prob, state) = max((Vt-1y0 * trans_py0y * emit_pyobst, y0) for y0 in states)
            Vty = prob
            newpathy = pathstate + y
 
        # Don't need to remember the old paths
        path = newpath
 
    print_dptable(V)
    (prob, state) = max((Vlen(obs) - 1y, y) for y in states)
    return (prob, pathstate)

The function viterbi takes the following arguments: obs is the sequence of observations, e.g. ['walk', 'shop', 'clean']; states is the set of hidden states; start_p is the start probability; trans_p are the transition probabilities; and emit_p are the emission probabilities. For simplicity of code, we assume that the observation sequence obs is non-empty and that trans_p[i][j] and emit_p[i][j] is defined for all states i,j.

In the running example, the forward/Viterbi algorithm is used as follows:

def example():
    return viterbi(observations,
                   states,
                   start_probability,
                   transition_probability,
                   emission_probability)
print example()

This reveals that the observations ['walk', 'shop', 'clean'] were most likely generated by states ['Sunny', 'Rainy', 'Rainy'], with probability 0.01344. In other words, given the observed activities, it was most likely sunny when Bob went for a walk and then it started to rain the next day and kept on raining.

The operation of Viterbi's algorithm can be visualized by means of a trellis diagram. The Viterbi path is essentially the shortest path through this trellis. The trellis for the weather example is shown below; the corresponding Viterbi path is in bold:

Trellis diagram for the Viterbi algorithm

When implementing Viterbi's algorithm, it should be noted that many languages use Floating Point arithmetic - as p is small, this may lead to underflow in the results. A common technique to avoid this is to take the logarithm of the probabilities and use it throughout the computation, the same technique used in the Logarithmic Number System. Once the algorithm has terminated, an accurate value can be obtained by performing the appropriate exponentiation.

If you would like to implement this algorithm such that it can accept any number of states, observations, and probabilities from the keyboard, substitute the parameters declaration part with the following code:

# Takes terminal input to create HMM.
states = 
number_of_states=input('How many states? Please enter an integer different from 0 or 1')
index=0
while index<number_of_states:
   states.append(str(raw_input('Give a name to the state number '+str(index))))
   index=index+1
observations = 
number_of_observations=input('How many observations? Please enter an integer different from 0 or 1')
index=0
while index<number_of_observations:
   observations.append(str(raw_input('Give a name to the observation number '+str(index))))
   index=index+1
start_probability = {}
for state in states:
   start_probabilitystate = input('Give a value for the starting probability of the state '+state)
transition_probability = {}
for initial_state in states:
   transition_probabilityinitial_state = {}
   for final_state in states:
      transition_probabilityinitial_statefinal_state=input('Give a value for the transition probability from the state '+initial_state+' to the state '+final_state)
emission_probability = {}       
for state in states:
   emission_probabilitystate= {}
   for observation in observations:
      emission_probabilitystateobservation=input('Give a value for the emission probability from the state '+state+' to the observation '+observation)

Java implementation

import java.util.Hashtable;
 
public class Viterbi 
{
	static final String RAINY = "Rainy";
	static final String SUNNY = "Sunny";
 
	static final String WALK = "walk";
	static final String SHOP = "shop";
	static final String CLEAN = "clean";
 
	public static void main(String args) 
	{
		String states = new String {RAINY, SUNNY};
 
		String observations = new String {WALK, SHOP, CLEAN};
 
		Hashtable<String, Float> start_probability = new Hashtable<String, Float>();
		start_probability.put(RAINY, 0.6f);
		start_probability.put(SUNNY, 0.4f);
 
		// transition_probability
		Hashtable<String, Hashtable<String, Float>> transition_probability = 
			new Hashtable<String, Hashtable<String, Float>>();
			Hashtable<String, Float> t1 = new Hashtable<String, Float>();
			t1.put(RAINY, 0.7f);
			t1.put(SUNNY, 0.3f);
			Hashtable<String, Float> t2 = new Hashtable<String, Float>();
			t2.put(RAINY, 0.4f);
			t2.put(SUNNY, 0.6f);
		transition_probability.put(RAINY, t1);
		transition_probability.put(SUNNY, t2);
 
		// emission_probability
		Hashtable<String, Hashtable<String, Float>> emission_probability = 
			new Hashtable<String, Hashtable<String, Float>>();
			Hashtable<String, Float> e1 = new Hashtable<String, Float>();
			e1.put(WALK, 0.1f);		
			e1.put(SHOP, 0.4f); 
			e1.put(CLEAN, 0.5f);
			Hashtable<String, Float> e2 = new Hashtable<String, Float>();
			e2.put(WALK, 0.6f);		
			e2.put(SHOP, 0.3f); 
			e2.put(CLEAN, 0.1f);
		emission_probability.put(RAINY, e1);
		emission_probability.put(SUNNY, e2);
 
		Object ret = forward_viterbi(observations,
                           states,
                           start_probability,
                           transition_probability,
                           emission_probability);
		System.out.println(((Float) ret0).floatValue());		
		System.out.println((String) ret1);
		System.out.println(((Float) ret2).floatValue());
	}
 
	public static Object forward_viterbi(String obs, String states,
			Hashtable<String, Float> start_p,
			Hashtable<String, Hashtable<String, Float>> trans_p,
			Hashtable<String, Hashtable<String, Float>> emit_p)
	{
		Hashtable<String, Object> T = new Hashtable<String, Object>();
		for (String state : states)
			T.put(state, new Object {start_p.get(state), state, start_p.get(state)});
 
		for (String output : obs)
		{
			Hashtable<String, Object> U = new Hashtable<String, Object>();
			for (String next_state : states)
			{
				float total = 0;
				String argmax = "";
				float valmax = 0;
 
				float prob = 1;
				String v_path = "";
				float v_prob = 1;	
 
				for (String source_state : states)
				{
					Object objs = T.get(source_state);
					prob = ((Float) objs0).floatValue();
					v_path = (String) objs1;
					v_prob = ((Float) objs2).floatValue();
 
					float p = emit_p.get(source_state).get(output) *
							  trans_p.get(source_state).get(next_state);
					prob *= p;
					v_prob *= p;
					total += prob;
					if (v_prob > valmax)
					{
						argmax = v_path + "," + next_state;
						valmax = v_prob;
					}
				}
				U.put(next_state, new Object {total, argmax, valmax});
			}
			T = U;			
		}
 
		float total = 0;
		String argmax = "";
		float valmax = 0;
 
		float prob;
		String v_path;
		float v_prob;
 
		for (String state : states)
		{
			Object objs = T.get(state);
			prob = ((Float) objs0).floatValue();
			v_path = (String) objs1;
			v_prob = ((Float) objs2).floatValue();
			total += prob;
			if (v_prob > valmax)
			{
				argmax = v_path;
				valmax = v_prob;
			}
		}	
		return new Object{total, argmax, valmax};	
	}
}

C# Implementation

using System;
using System.Collections.Generic;
 
namespace ViterbiAlgorithmCmd
{
    class Program
    {
 
        const String RAINY = "Rainy";
        const String SUNNY = "Sunny";
 
        const String WALK = "walk";
        const String SHOP = "shop";
        const String CLEAN = "clean";
 
        static void Main(string args)
        {
            var states = new  {RAINY, SUNNY};
 
            var observations = new  {WALK, SHOP, CLEAN};
 
            var start_probability = new Dictionary<String, double> {{RAINY, 0.6f}, {SUNNY, 0.4f}};
 
            var transition_probability = 
                new Dictionary<String, Dictionary<String, double>>
                {
                    {RAINY, new Dictionary<String, double> {{RAINY, 0.7f}, {SUNNY, 0.3f}}},
                    {SUNNY, new Dictionary<String, double> {{RAINY, 0.4f}, {SUNNY, 0.6f}}}
                };
 
            var emission_probability = 
                new Dictionary<String, Dictionary<String, double>>
                {
                    {RAINY, new Dictionary<String, double> {{WALK, 0.1f}, {SHOP, 0.4f}, {CLEAN, 0.5f}}},
                    {SUNNY, new Dictionary<String, double> {{WALK, 0.6f}, {SHOP, 0.3f}, {CLEAN, 0.1f}}}
                };
 
            Object ret = forward_viterbi(observations, states, start_probability, transition_probability, emission_probability);
            Console.WriteLine(((double) ret0));		
            Console.WriteLine((String) ret1);
            Console.WriteLine(((double) ret2));
        }
 
 
 
 
        public static Object forward_viterbi(String obs, string states,
            Dictionary<string, double> start_p,
            Dictionary<string, Dictionary<string, double>> trans_p,
            Dictionary<string, Dictionary<string, double>> emit_p)
        {
            var T = new Dictionary<String, Object>();
            foreach (var state in states)
                T.Add(state, new Object {start_pstate, state, start_pstate});
 
            double total = 0;
            String argmax = "";
            double valmax = 0;
 
            double prob;
            String v_path;
            double v_prob;
 
 
            foreach (var output in obs)
            {
                var U = new Dictionary<String, Object>();
                foreach (var next_state in states)
                {
                    total = 0;
                    argmax = "";
                    valmax = 0;
 
                    foreach (var source_state in states)
                    {
                        Object objs = Tsource_state;
                        prob = ((double) objs0);
                        v_path = (String) objs1;
                        v_prob = ((double) objs2);
 
                        double p = emit_psource_stateoutput *
                        trans_psource_statenext_state;
                        prob *= p;
                        v_prob *= p;
                        total += prob;
                        if (v_prob > valmax)
                        {
                            argmax = v_path + "," + next_state;
                            valmax = v_prob;
                        }
                    }
                    U.Add(next_state, new Object {total, argmax, valmax});
                }
                T = U;			
            }
 
            total = 0;
            argmax = "";
            valmax = 0;
 
            foreach (var state in states)
            {
                Object objs = Tstate;
                prob = ((double) objs0);
                v_path = (string) objs1;
                v_prob = ((double) objs2);
                total += prob;
                if (v_prob > valmax)
                {
                    argmax = v_path;
                    valmax = v_prob;
                }
            }	
            return new Object{total, argmax, valmax};	
        }
    }
}

Extensions

With the algorithm called iterative Viterbi decoding one can find the subsequence of an observation that matches best (on average) to a given HMM. Iterative Viterbi decoding works by iteratively invoking a modified Viterbi algorithm, reestimating the score for a filler until convergence.

An alternate algorithm, the Lazy Viterbi algorithm, has been proposed recently.2 This works by not expanding any nodes until it really needs to, and usually manages to get away with doing a lot less work (in software) than the ordinary Viterbi algorithm for the same result - however, it is not so easy to parallelize in hardware.

See also

Notes

  1. ^ Xing E, slide 11
  2. ^ "A fast maximum-likelihood decoder for convolutional codes" (PDF). Vehicular Technology Conference. December 2002. pp. 371–375. doi:10.1109/VETECF.2002.1040367. http://people.csail.mit.edu/jonfeld/pubs/lazyviterbi.pdf. 

References

External links


Return to Fuhz Home - This article covering Viterbi algorithm is enhanced for the visually impaired.

Valid XHTML 1.0 Transitional Valid CSS!

Article from Wikipedia. All text is available under the terms of the GNU Free Documentation License - Our Privacy Policy - Thanks Auto Blog Commenter