Purpose: Find the single most likely sequence of hidden states in a hidden Markov model, given a sequence of observations, in where is the number of observations and the number of states.
Setup
An HMM has:
- states with an initial distribution ;
- transition probabilities ;
- emission probabilities .
Algorithm
It is a shortest-path DP over the trellis — a layered DAG with layers of nodes.
Store to reconstruct the path, then backtrack from .
Code
// work in log space: products become sums, underflow disappears
vector<int> viterbi(const vector<int>& obs, int S,
const vector<double>& logPi,
const vector<vector<double>>& logA,
const vector<vector<double>>& logB) {
int T = obs.size();
vector<vector<double>> dp(T, vector<double>(S, -1e18));
vector<vector<int>> par(T, vector<int>(S, -1));
for (int j = 0; j < S; j++) dp[0][j] = logPi[j] + logB[j][obs[0]];
for (int t = 1; t < T; t++)
for (int j = 0; j < S; j++)
for (int i = 0; i < S; i++) {
double v = dp[t-1][i] + logA[i][j] + logB[j][obs[t]];
if (v > dp[t][j]) { dp[t][j] = v; par[t][j] = i; }
}
int best = max_element(dp[T-1].begin(), dp[T-1].end()) - dp[T-1].begin();
vector<int> path(T);
for (int t = T - 1; t >= 0; t--) { path[t] = best; best = par[t][best]; }
return path;
}Always use logs
Multiplying probabilities underflows a
doubleafter a few hundred steps. Take logarithms once at the start; products become sums and the algorithm becomes an ordinary longest-path DP.
Paradigm
Dynamic programming on a layered DAG. Viterbi is DAG shortest path where the “distance” is negative log probability. Recognising this is the whole insight — everything else is bookkeeping.
Complexity
- Time: ; if the transition matrix is sparse with average out-degree
- Space: for the backpointers; if you only need the probability
Correctness
By induction on : is the probability of the most likely state sequence ending in state having emitted . The Markov property means the future depends on the past only through the current state, so an optimal path to must have an optimal prefix to some — the optimal substructure that makes DP valid. ∎
Viterbi vs Forward algorithm
| Recurrence | Answers | |
|---|---|---|
| Viterbi | over predecessors | the single most likely path |
| Forward | over predecessors | the total probability of the observations |
Same trellis, different semiring — versus .
Variants / Use Cases
- Speech recognition, POS tagging, gene finding — the classical applications
- Convolutional code decoding — Viterbi’s original 1967 problem; still in every modem and satellite link
- Map matching — snapping noisy GPS traces to a road network
- Beam search — a heuristic pruning of the trellis when is huge
- DAG shortest paths — the underlying structure
- Baum-Welch / forward-backward — learning the HMM parameters rather than decoding with them