Skip to content
mlmentorship

Forward-backward and Viterbi: dynamic programming on chains

Sum and max over exponentially many paths in linear time. Forward-backward computes posteriors over hidden states; Viterbi finds the most likely state sequence. The same idea, two semirings.

Published · 7 min read ·Role-specific ·Intermediate

Visual quick review

Visual first · depth when needed

Given one state lattice, distinguish how forward-backward forms a posterior for a state at one timestep from how Viterbi forms one globally consistent state path.

Preparing the visual…

Summary

For a hidden Markov model with timesteps and states, forward-backward computes for every in time. Viterbi computes in the same time. Brute force would be .

These are the canonical examples of dynamic programming on sequences. Speech recognition (HMM-based decoding), part-of-speech tagging, gene-finding, conditional random fields for sequence labeling, and any structured-prediction task with a chain factor graph relies on one or both. The duality between sum (forward-backward) and max (Viterbi) is the textbook example of how the same DP works in two different semirings.

Even in modern deep-learning sequence models, Viterbi shows up: CTC decoding for speech, beam search as an approximation when the state space is too large, CRF layers on top of BERT for NER.

The setup

A hidden Markov model has:

  • States .
  • Observations .
  • Initial .
  • Transitions .
  • Emissions .

Joint probability of a hidden path and observation sequence:

There are possible paths. Both algorithms factor through a DP table.

Learning objective

Does the dynamic program return one state marginal or one complete path?

Forward-backward combines summaries from both sides at one state A three-state chain runs across four timesteps. At state B in timestep three, a right-pointing alpha arrow summarizes every prefix ending at B and a left-pointing beta arrow summarizes every suffix beginning at B. Their product, normalized across states at timestep three, gives the posterior marginal for state B at that timestep. It does not select a complete path. FORWARD-BACKWARD · ONE TIME MARGINAL t=1 t=2 t=3 t=4 ABCABCABCABC alpha: all prefixes beta: all suffixes normalize alpha_3(B) x beta_3(B) across A, B, C
Viterbi preserves one best predecessor per state and backtracks a complete path The same three-state chain runs across four timesteps. Thin solid and dashed lines show candidate transitions. Thick arrowed segments connect A at timestep one, B at timestep two, B at timestep three, and C at timestep four. Backtracking the stored predecessor pointers from the best final state recovers this one globally consistent path. VITERBI · ONE COMPLETE PATH t=1 t=2 t=3 t=4 ABCABCABCABC backtrack stored psi pointers decoded sequence: A -> B -> B -> C
Read it this way: use the same state lattice twice. Forward-backward sums every prefix arriving at a chosen state and every suffix leaving it, then combines those two summaries to answer “which state at this time?” Viterbi replaces each sum with a max, stores the winning predecessor, and backtracks to answer “which single path?” The thick path and arrowheads carry the distinction without relying on color. Original schematic checked against Rabiner (1989) and Viterbi (1967).

Forward algorithm

Define the forward variable

Recurrence:

The total likelihood is . Computing the full table is .

Backward algorithm

The mirror image:

with and

Posterior over states (forward-backward)

This is the per-timestep posterior used in EM training of HMMs and in any system that needs marginal beliefs over hidden states.

Viterbi: the max version

Replace the sum in the forward recurrence with a max:

with . Track the argmax to reconstruct the path:

After the forward pass, the most likely path is recovered by backtracking from through . Same cost.

Sum vs max: the semiring view

Both algorithms have the same shape; only the operations differ:

Algorithm”Add""Multiply”
Forward
Viterbi (or in log space)

Both work because the operations form a semiring (associativity, distributivity). The same DP framework computes max-marginals (Viterbi), sum-marginals (forward-backward), counts (probability of inputs), expectations (segment-level expected counts), and gradients of any of the above.

In log space (always)

Multiplying many small probabilities underflows in float32. Always work in log space:

The logsumexp trick (subtract the max before exponentiating) keeps everything stable.

Modern uses

  • CRF decoding for NER: BERT produces per-token logits; a CRF layer with a learned transition matrix runs Viterbi at inference and forward-backward at training.
  • CTC decoding for speech: a sum-product algorithm over alignments. Different state structure but the same DP machinery.
  • Beam search as approximate Viterbi: when is too large for full DP (e.g. autoregressive language models with vocab 100k), beam search keeps only the top- partial paths at each step.

Common pitfalls

  • Working in probability space instead of log space. Numerical underflow guaranteed beyond .
  • Forgetting to renormalize when doing forward-backward in float32. Some implementations renormalize at each step and accumulate the log-normalizer separately.
  • Confusing the per-timestep argmax of forward-backward with Viterbi. They are different: Viterbi gives the most likely full sequence; per-timestep argmax gives the sequence of most likely states, which can be infeasible (i.e., have zero probability under the model).