Rabiner 1989 - Problem 2 - State Posteriors and the Viterbi Algorithm

Summary

Two decoders answer Problem 2. Posterior (marginal) decoding picks with , maximising the expected number of correct states but possibly producing impossible sequences. Viterbi dynamic programming finds the single best path maximising with plus backpointers.

Overview

“There are several possible ways of solving Problem 2”: the difficulty is the optimality criterion. Different criteria give different state sequences.

Main Content

State posterior (eqs 26-28)

with . accounts for and state at ; for given state .

Individually most likely states (eq. 29)

maximises the expected number of correctly decoded individual states. Caveat (paper): it ignores the probability of sequences of states; if some the resulting sequence may not even be a valid path. Alternatives: maximise expected correct pairs or triples.

Viterbi algorithm (eqs 30-35)

Define (eq. 30), the best score along a single path ending in state . Induction: (eq. 31).

  1. Initialisation: , .
  2. Recursion (, ): , .
  3. Termination: , .
  4. Backtracking: , . It equals the forward calculation with replacing plus backtracking; same lattice. In logs (eqs 104-105): , no scaling needed, and , can be precomputed.

Why the two decoders differ (illustration)

Posterior decoding can return a sequence containing a zero-probability transition (the paper’s caveat), whereas Viterbi never does, because Viterbi maximises the joint path probability. Constructing such a case needs at least three states: with two states and , state 1 can only be followed by state 1, so and the decoded pair cannot occur. In the simulated 2-state Poisson HMM (200 steps, rates 1 and 6, ) Viterbi was 99.5% accurate and posterior decoding 99.0%: close here because states are well separated.

import numpy as np
def viterbi(logB, logA, logpi):
    T, N = logB.shape; d = np.zeros((T, N)); psi = np.zeros((T, N), int); d[0] = logpi + logB[0]
    for t in range(1, T):
        s = d[t-1][:, None] + logA; psi[t] = s.argmax(0); d[t] = s.max(0) + logB[t]   # eqs 33a-b in logs
    q = np.zeros(T, int); q[-1] = d[-1].argmax()
    for t in range(T-2, -1, -1): q[t] = psi[t+1][q[t+1]]                              # eq. 35
    return q, d[-1].max()
# posterior decoding: gamma = exp(la + lb - logsumexp(la[-1])); q = gamma.argmax(1)

Examples (PyMC-style)

E24 runs forward-backward and Viterbi per posterior draw to get regime probabilities and a decoded path with uncertainty (the Viterbi path varies across draws of ; posterior state probabilities average over them). Untested sketch: for each draw in idata.posterior, compute la, lb from the sampled as above, then average across draws.

Connections

Used in Problem 3 as the segmentation step in segmental -means (Rabiner 1989 - Implementation Issues - Scaling, Multiple Sequences and Initialisation). Movement-state decoding: Model Building with Latent Variables - Animal Movement.

See Also

Rabiner 1989 - Problem 1 - Forward and Backward Algorithms, Rabiner 1989 - Problem 3 - Baum-Welch Reestimation