The Viterbi Algorithm
Suppose a speech recognizer hears a noisy audio signal. It must decide whether the speaker said "recognize speech" or "wreck a nice beach." Both phrases produce eerily similar sounds. The system cannot simply read off the answer. It must reason over sequences of hidden linguistic states. It must weigh competing hypotheses. Then it must commit to the single explanation that best accounts for the evidence as a whole. This is the generic problem faced by any system built on a Hidden Markov Model. Given observations, recover the most probable sequence of hidden states. This differs from finding the most probable state at each isolated position. The Viterbi algorithm solves this exactly and efficiently. It is a dynamic-programming procedure. It finds the single hidden-state sequence maximizing the joint probability of that sequence together with the observed data, under an HMM. Without it, finding the best path would require enumerating every possible state sequence. This number grows exponentially with sequence length. This makes exact inference intractable for anything beyond toy examples. The algorithm matters because HMMs, and the Viterbi decoding that accompanies them, quietly powered decades of real-world language and speech technology. Applications include part-of-speech tagging, speech recognition, and gene finding in bioinformatics. As later articles on Named-Entity Recognition will show, this extends to sequence-labeling tasks. In these tasks, each word must be assigned a tag conditioned on its neighbors. Even in the era of neural sequence models, the Viterbi algorithm survives. It lives on inside CRF layers stacked on top of neural encoders. Its core idea is dynamic programming over a trellis of partial hypotheses. This idea reappears throughout NLP and beyond. It appears in algorithms like the Forward Algorithm and Beam Search. This article gives a precise definition of the decoding problem. It provides a fully worked pseudocode implementation with backpointers. It includes a diagram of the trellis structure the algorithm searches. It offers a complexity and correctness analysis grounded in the optimal-substructure property of Markov chains. Finally, it presents a hand-traced numerical example. This ensures every recurrence step is visible and checkable.
- Viterbi finds the single most probable hidden-state sequence given observations and an HMM, not just per-position best guesses.
- It uses dynamic programming over a trellis of (state, time) cells, exploiting the Markov property for optimal substructure.
- Time complexity is O(N²T) for N states and T observations; space is O(NT) for the DP table plus backpointers.
- Backpointers, stored during the forward max-recurrence, let you reconstruct the best path by tracing backward from the end.
- It differs from the Forward Algorithm, which sums over all paths to get total probability instead of maximizing over one.
- It underlies part-of-speech tagging, speech recognition decoding, and forms the inference engine inside CRF-based sequence labelers used in Named-Entity Recognition.
The Viterbi algorithm replaces an exponential search over hidden-state sequences with a polynomial dynamic program, by recursively caching the best path to each state at each time step.
The idea
An HMM defines a joint distribution over a hidden state sequence and an observation sequence , parameterized by an initial distribution , a transition matrix (with ), and an emission distribution (with ). Given a fixed observation sequence, decoding means recovering the hidden states that produced it. There are two distinct decoding questions one might ask, and it is essential not to conflate them. The first question — answered by the Forward Algorithm — is "what is the total probability of this observation sequence, summed over all possible hidden paths?" The second question — answered by Viterbi — is "what is the single hidden path with the highest joint probability?" These differ because summing over many moderately-probable paths can outweigh one highly-probable path; the forward algorithm answers a marginal-probability question, while Viterbi answers a MAP (maximum a posteriori) question over entire sequences. Formally, Viterbi computes
where is the HMM. A naive solution would enumerate all possible state sequences (for hidden states and time steps) and score each one — computationally infeasible even for modest . Viterbi avoids this by exploiting the Markov property: the probability of being in state at time , having taken the best possible path so far, depends only on which state was occupied at time , not on the full history before that. This licenses a dynamic-programming recurrence in which the best partial path ending in each state is built incrementally from the best partial paths one step earlier.
How it works
The central data structure is a trellis: a grid with one column per time step and one row per hidden state . Each cell will hold two numbers: , the probability of the most likely path that ends in state at time and accounts for observations ; and , a backpointer recording which state at time was used to achieve that maximum. The algorithm fills this trellis left to right, then reconstructs the optimal path by walking backpointers from the best final cell back to time 1.
Initialization. At there is no predecessor, so the best path ending in state is simply the prior probability of starting in times the probability of emitting from :
Recursion. For and every state , consider every possible predecessor state . The best path landing in at time must have come from whichever predecessor maximizes (best path to ) × (transition ), and then it emits from :
Termination. The probability of the single best sequence overall is the maximum over across all final states, and the last state of that optimal sequence is the argmax:
Backtrace. Starting from , repeatedly follow backward: , for , producing the full optimal state sequence. In practice, probabilities are replaced by log-probabilities and products become sums, both to avoid numerical underflow over long sequences and to turn multiplications into additions for speed. ``` function VITERBI(observations o[1..T], states S, initial π, transitions A, emissions B): N ← number of states allocate delta[1..N][1..T] # delta[s][t] = best path score ending in state s at time t allocate psi[1..N][1..T] # psi[s][t] = backpointer: best predecessor state
# --- Initialization (t = 1) --- for s in 1..N: delta[s][1] ← log(π[s]) + log(B[s][o[1]]) psi[s][1] ← NONE
# --- Recursion (t = 2 .. T) --- for t in 2..T: for s in 1..N: best_score ← -infinity best_prev ← NONE for s_prev in 1..N: score ← delta[s_prev][t-1] + log(A[s_prev][s]) if score > best_score: best_score ← score best_prev ← s_prev delta[s][t] ← best_score + log(B[s][o[t]]) psi[s][t] ← best_prev
# --- Termination --- best_final_score ← -infinity best_final_state ← NONE for s in 1..N: if delta[s][T] > best_final_score: best_final_score ← delta[s][T] best_final_state ← s
# --- Backtrace --- path[T] ← best_final_state for t in T .. 2: path[t-1] ← psi[path[t]][t] return path[1..T], best_final_score ``` Two edge cases deserve care. If some emission or transition probability is exactly zero, its log is ; the max-search must handle this without crashing (representable as a very large negative sentinel, not literal division). If , the recursion loop never executes and the answer is simply the argmax of the initialization row — the pseudocode above handles this correctly since termination reads directly from column 1 in that case.
Complexity & correctness
Time complexity. The recursion fills cells at each of time steps, and computing each cell's requires scanning all possible predecessor states . This gives time overall for the forward fill. The termination step is and the backtrace is , both dominated by the fill. So total time is in the worst case (dense transition matrix), with no better general algorithm possible without additional structure, since every transition weight may in principle matter to the optimum.
Space complexity. The full trellis of and values requires space. This can be reduced: if only the final best probability is needed (not the path), only the current and previous columns of need to be kept, giving space — but the backpointer table must still be stored in full () if the actual path is to be recovered, since backtracing needs a complete history of choices.
Correctness. The proof is an induction on establishing the invariant: > Invariant: For every state and every time , equals the probability of the single most likely state sequence that is consistent with observations . Base case (): There is only one "path" of length 1 ending in state : start there. Its probability is exactly , matching the initialization, so the invariant holds trivially. Inductive step: Assume the invariant holds at , i.e., is truly the best score for every predecessor . Any path ending in state at time passes through some state at time . By the Markov property, the probability of the full path factors as
i.e., the score up through times the transition into times the emission at . Because this factorization holds for every choice of predecessor independently of the internal structure of the path before time , the overall best path ending in at must use, as its prefix, the best possible path ending in some at — otherwise substituting a better prefix (which exists, by the definition of "best") would strictly increase the total probability, contradicting optimality. This is precisely the optimal substructure property required for dynamic programming to be valid: the global optimum decomposes into optimal solutions to subproblems. Hence
is correct, matching the recurrence, completing the induction. At termination, is therefore the true maximum over all complete paths, and backtracing reconstructs an actual sequence achieving it (each was chosen precisely to be the predecessor realizing that max, so following pointers backward reconstructs a fully consistent, globally-optimal path).
Tradeoffs. Viterbi is exact and efficient given correct HMM parameters, but it inherits any mis-specification in , , — a poorly estimated emission distribution yields a confidently wrong path. It also commits to a single best sequence; if the application actually wants marginal state probabilities (e.g., "what's regardless of the rest of the path?") the Forward-Backward Algorithm is the right tool, not Viterbi. Finally, the cost becomes a real bottleneck when is large (e.g., large tagsets), motivating beam-pruned approximate variants that trade exactness for speed — this is the same tension that shows up in neural sequence decoding via Beam Search.
Worked example
Consider a minimal part-of-speech HMM with two hidden states, (noun, verb), and the observation sequence "flies like flowers" reduced for simplicity to two tokens: flies, like. Suppose the parameters are:
- Initial:
- Transitions:
- Emissions:
Step 1 — Initialization (, word = "flies"):
| state | computation | value |
|---|---|---|
| N | ||
| V |
Step 2 — Recursion (, word = "like"): for each state, check both predecessors. For state N at :
Tie — take either (say, the first, N, by convention); , . For state V at :
Max is from N; , .
Step 3 — Termination: compare vs . The maximum is , at state V.
Step 4 — Backtrace: ; , so .
Result: the optimal tag sequence is N, V — "flies" tagged as noun, "like" tagged as verb — with joint probability . This matches intuition: "flies" as a noun (the insects) followed by "like" as a verb (the action) is indeed the natural reading, and the algorithm found it by comparing all four candidate two-step paths implicitly via just multiply-operations, rather than enumerating full paths from scratch and rescoring each (a saving that becomes exponential in longer sequences).
Common pitfalls & traps
| Pitfall | Why it happens | Fix |
|---|---|---|
| Confusing Viterbi with Forward | Both fill a similar trellis | Forward sums over predecessors; Viterbi maximizes. Different recurrence, different question answered. |
| Numerical underflow | Multiplying many probabilities shrinks to 0 in floating point over long | Work in log-space; sums replace products. |
| Forgetting to store backpointers | Only tracking best scores gives you the value, not the sequence | Store at every cell during the fill, not just at the end. |
| Off-by-one in initialization | Treating like a normal recursive step, referencing a nonexistent | Handle as a distinct base case using only and . |
| Zero probabilities crashing log | , and some implementations mishandle this in comparisons | Use a large negative sentinel or explicit smoothing of the HMM parameters. |
| Assuming the best local tag sequence exists per-position | Greedily picking the highest-probability state independently at each is not equivalent to Viterbi | Only the full joint DP recurrence guarantees the global best path; greedy per-position choices can be globally suboptimal. |
Pioneers & history
The algorithm is named for Andrew Viterbi, an electrical engineer who devised it in the context of decoding convolutionally-encoded signals over noisy communication channels — a problem structurally identical to HMM decoding, since a convolutional code's trellis and an HMM's state trellis share the same mathematical skeleton. The technique was independently discovered and rediscovered across several fields — coding theory, speech recognition, and computational biology — because dynamic programming over a Markov trellis is a naturally recurring structural pattern wherever hidden sequential state must be inferred from noisy observations.
Origins · 1 minAndrew J. Viterbi and how this was discovered
Viterbi developed the algorithm in 1967 while analyzing error-correcting convolutional codes for the emerging field of digital communications, showing that maximum-likelihood decoding of such codes could be done efficiently via a trellis-based dynamic program rather than exhaustive search. The technique's generality — any process with Markovian hidden state and noisy observations — meant it was later adopted wholesale by speech recognition researchers and, subsequently, computational linguists building statistical taggers and parsers.
Where this matters
Viterbi decoding is the inference backbone of classical statistical speech recognition systems, where hidden phoneme or word states must be recovered from acoustic observations frame by frame. It is equally central to statistical part-of-speech tagging, where the hidden states are grammatical tags and observations are words — a direct precursor to the tagging and span-labeling techniques used in Named-Entity Recognition, where a similar trellis recovers the most likely sequence of entity tags (e.g., B-PER, I-PER, O) given a sentence. In bioinformatics, Viterbi decoding recovers the most probable sequence of gene/intron states along a DNA strand from an HMM trained on sequence data. In digital communications — its original home — it remains the standard decoder for convolutional and trellis codes in cellular standards. Even modern neural architectures that stack a Conditional Random Field on top of a BiLSTM or Transformer encoder still invoke Viterbi decoding at inference time, because the CRF layer imposes exactly the Markovian tag-transition structure that makes the algorithm both applicable and exact; understanding Viterbi is therefore a prerequisite for understanding how such hybrid neural/structured models actually produce their output sequences at prediction time.
Ask the tutor: How does the Viterbi algorithm's dynamic-programming recurrence change when moving from a generative HMM to a discriminative linear-chain CRF used in modern NER systems?
Is Viterbi decoding guaranteed to find the globally optimal state sequence?
Yes, given correct HMM parameters. The proof rests on the Markov property: the probability of any full path factors into a prefix ending at time times a transition times an emission. This means the best path ending in state at time must extend the best path ending in some predecessor state at — extending anything else would be provably suboptimal. This optimal-substructure property is exactly what licenses dynamic programming, so the recurrence Viterbi computes is mathematically guaranteed to match the true global maximum, not merely an approximation to it.
How is Viterbi different from Beam Search?
Viterbi is exact: it explores every possible predecessor at every time step and is guaranteed to return the true global optimum, at cost. Beam Search is an approximation that only keeps the top- partial hypotheses at each step, discarding the rest, trading exactness for speed when is very large (as in neural language generation with huge vocabularies). When is small enough (as in typical HMM tagsets), exact Viterbi is usually preferred since it is both feasible and provably optimal; beam search becomes necessary once exhaustive search over all predecessors is too costly.
Can Viterbi decoding fail if the HMM parameters are poorly estimated?
Yes. Viterbi is only as good as the model it decodes. It always returns the mathematically optimal path under the given parameters, but if those transition or emission probabilities were poorly estimated from limited or biased training data, the "optimal" path can still be linguistically or factually wrong. This is a modeling problem, not an algorithmic one — the dynamic program itself is exact; the garbage-in-garbage-out risk lies entirely in parameter estimation, typically done via Expectation-Maximization or supervised counting.
Why can't we just enumerate all state sequences directly for short sentences?
For very short sequences with few states, brute-force enumeration is technically feasible, but it scales as , exponential in sequence length. A ten-word sentence with even a modest 20-tag tagset already implies candidate sequences — computationally impossible to score one by one. Viterbi's dynamic program reuses partial computations across overlapping prefixes, collapsing this exponential blowup into a polynomial-time algorithm, which is precisely why it is indispensable rather than a mere convenience.
What is the relationship between Viterbi and the Forward-Backward algorithm?
Both operate on the same HMM trellis but answer different questions. Viterbi returns the single best joint state sequence (a MAP estimate over the whole path). Forward-Backward instead computes, for each individual time step, the marginal posterior probability of being in each state given all observations — useful for parameter re-estimation (as in the Baum-Welch variant of Expectation-Maximization) or for soft-decoding applications where you want per-position confidence rather than one hard sequence.
Does Viterbi decoding require the transition and emission probabilities to be true probabilities?
Mathematically yes, for the derivation to correspond to actual joint-probability maximization, but the algorithm's mechanics work identically with any non-negative scores, including unnormalized log-potentials as used in Conditional Random Fields. This is precisely why the same dynamic-programming recurrence, with sums replacing products in log-space, is reused verbatim as the exact inference routine inside neural CRF-based sequence labelers, even though those models are trained discriminatively rather than as generative probability models.
What happens if two candidate predecessor paths tie exactly in score?
The algorithm as specified breaks ties arbitrarily (commonly, by keeping whichever predecessor was encountered first in the iteration order), and the resulting path is still a valid, globally-optimal sequence — just not necessarily the unique one, since multiple state sequences may share the same maximal joint probability. If a deterministic, reproducible tie-break rule matters for an application (e.g., regression testing), it should be fixed explicitly, such as by preferring the lexicographically smallest state index.