Viterbi algorithm: Difference between revisions

Content deleted Content added
Dcoetzee (talk | contribs)
m No links in title
MarkSweep (talk | contribs)
example with Python code for the Viterbi algorithm
Line 6:
 
Recently, the terms "Viterbi path" and "Viterbi algorithm" have been applied to related dynamic programming algorithms, such as [[parser|parsing]], as well.
 
 
==A concrete example==
 
Assume you have a friend who lives far away and who you call daily to talk about what each of you did that day. Your friend has only three things he's interested in: 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. You have no definite information about the weather where your friend lives, but you know general trends. Based on what he tells you he did each day, you try to guess what the weather must have been like.
 
You believe that the weather operates as a discrete [[Markov chain]]. There are two states, "Rainy" and "Sunny", but you cannot observe them directly, that is, they are ''hidden'' from you. On each day, there is a certain chance that your friend will perform one of the following activities, depending on the weather: "walk", "shop", or "clean". Since your friend tells you about his activities, those are the ''observations''. The entire system is that of a [[hidden Markov model]] (HMM).
 
You know the general weather trends in the area and you know what your friend likes to do on average. In other words, the parameters of the HMM are known. In fact, you can write them 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 fragment, <code>start_probability</code> refers to your uncertainty about which state the HMM is in when your friend first calls you (all you know is that it tends to be rainy on average). The <code>transition_probability</code> refers to 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 <code>emission_probability</code> tells you how likely your friend is to perform a certain activity on each day. If it's rainy, there is a 50% chance that he is cleaning his apartment; if it's sunny, there is a 60% chance that he will go outside for a walk.
 
You talk to your friend three days in a row and discover 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. You have two questions: What is the overall probability of this sequence of observations? And what is the most likely sequence of rainy/sunny days that would explain these observations? The first question is answered by the '''forward algorithm'''; the second by the Viterbi algorithm. These two algorithm are structurally so similar (in fact, they are both instances of the same abstract algorithm) that they can be implemented in a single function:
 
def forward_viterbi(y, X, sp, tp, ep):
T = {}
for state in X:
## prob. V. path V. prob.
T[state] = (sp[state], [state], sp[state])
for output in y:
U = {}
for next_state in X:
total = 0
argmax = None
valmax = 0
for state in X:
(prob, v_path, v_prob) = T[state]
p = ep[state][output] * tp[state][next_state]
prob *= p
v_prob *= p
total += prob
if v_prob > valmax:
argmax = v_path + [next_state]
valmax = v_prob
U[next_state] = (total, argmax, valmax)
T = U
## apply sum/max to the final states:
total = 0
argmax = None
valmax = 0
for state in X:
(prob, v_path, v_prob) = T[state]
total += prob
if v_prob > valmax:
argmax = v_path
valmax = v_prob
return (total, argmax, valmax)
 
The function <code>forward_viterbi</code> takes the following arguments: <code>y</code> is the sequence of observations, e.g. <code>['walk', 'shop', 'clean']</code>; <code>X</code> is the set of hidden states; <code>sp</code> is the start probability; <code>tp</code> are the transition probabilities; and <code>ep</code> are the emission probabilities.
 
The algorithm works on the mappings <code>T</code> and <code>U</code>. Each is a mapping from a state to a triple <code>(prob, v_path, v_prob)</code>, where <code>prob</code> is the total probability of all paths from the start to the current state, <code>v_path</code> is the Viterbi path up to the current state, and <code>v_prob</code> is the probability of the Viterbi path up to the current state. The mapping <code>T</code> holds this information for a given point ''t'' in time, and the main loop constructs <code>U</code>, which holds similar information for time ''t''+1. Because of the [[Markov property]], information about any point in time prior to ''t'' is not needed.
 
The algorithm begins by initializing ''T'' to the start probabilities: the total probability for a state is just the start probability of that state; and the Viterbi path to a start state is the singleton path consisting only of that state; the probability of the Viterbi path is the same as the start probability.
 
The main loop considers the observations from <code>y</code> in sequence. Its [[loop invariant]] is that <code>T</code> contains the correct information up to but excluding the point in time of the current observation. The algorithm then computes the triple <code>(prob, v_path, v_prob)</code> for each possible next state. The total probability of a given next state, <code>total</code> is obtained by adding up the probabilities of all paths reaching that state. More precisely, the algorithm iterates over all possible source states. For each source state, <code>T</code> holds the total probability of all paths to that state. This probability is then multiplied by the emission probability of the current observation and the transition probability from the source state to the next state. The resulting probability <code>prob</code> is then added to <code>total</code>. The probability of the Viterbi path is computed in a similar fashion, but instead of adding across all paths one performs a discrete maximization. Initially the maximum value <code>valmax</code> is zero. For each source state, the probability of the Viterbi path to that state is known. This too is multiplied with the emission and transition probabilities and replaces <code>valmax</code> if it is greater than its current value. The Viterbi path itself is computed as the corresponding [[argmax]] of that maximization, by extending the Viterbi path that leads to the current state with the next state. The triple <code>(prob, v_path, v_prob)</code> computed in this fashion is stored in <code>U</code> and once <code>U</code> has been computed for all possible next states, it replaces <code>T</code>, thus ensuring that the loop invariant holds at the end of the iteration.
 
In the end another summation/maximization is performed (this could also be done inside the main loop by adding a pseudo-observation after the last real observation).
 
In the running example, the forward/Viterbi algorithm is used as follows:
 
def example():
return forward_viterbi(['walk', 'shop', 'clean'],
states,
start_probability,
transition_probability,
emission_probability)
 
This reveals that the total probability of <code>['walk', 'shop', 'clean']</code> is 0.033612 and that the Viterbi path is <code>['Sunny', 'Rainy', 'Rainy, 'Rainy']</code>. The Viterbi path contains four states because the third observation was generated by the third state and a transition to the fourth state. In other words, given the observed activities, it was most likely sunny when your friend went for a walk and then it started to rain the next day and kept on raining.
 
[[Category:Algorithms]]