Viterbi Algorithm › Log-Space Computation
Language AI HandbookAll chapters Viterbi Algorithm › Log-Space ComputationOne section · Sections in this chapter5 / 12 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Multiplying many small probabilities causes numerical underflow. For a sequence of 100 tokens with probabilities around 0.01, the path probability would be approximately , far below the smallest representable floating-point number (approximately for 64-bit floats).
The solution is to work in log space. The logarithm function has a useful property: it converts products into sums. Since , we can rewrite the Viterbi recurrence entirely in terms of log probabilities:
where:
- : the log probability of the best path ending at state at position
- : the log probability of the best path to predecessor state (already computed)
- : the log transition probability from to
- : the log emission probability of word from state
Log probabilities are negative since probabilities are between 0 and 1, and when . The best path has the highest (least negative) log probability. The operation is unchanged because log is monotonic: if , then . This means finding the maximum log probability gives the same answer as finding the maximum probability.
import math
def viterbi_log_space(observations, states, start_prob, trans_prob, emit_prob):
"""
Viterbi algorithm in log space for numerical stability.
"""
n = len(observations)
# Convert to log probabilities
def safe_log(p):
return math.log(p) if p > 0 else float("-inf")
log_start = {s: safe_log(p) for s, p in start_prob.items()}
log_trans = {k: safe_log(p) for k, p in trans_prob.items()}
log_emit = {k: safe_log(p) for k, p in emit_prob.items()}
# Viterbi in log space
V = [{} for _ in range(n)]
backpointer = [{} for _ in range(n)]
# Base case
for s in states:
emit = log_emit.get((s, observations[0]), float("-inf"))
V[0][s] = log_start.get(s, float("-inf")) + emit
backpointer[0][s] = None
# Recursive case
for i in range(1, n):
for s in states:
best_prev = None
best_score = float("-inf")
emit = log_emit.get((s, observations[i]), float("-inf"))
for s_prev in states:
trans = log_trans.get((s_prev, s), float("-inf"))
score = V[i - 1][s_prev] + trans + emit
if score > best_score:
best_score = score
best_prev = s_prev
V[i][s] = best_score
backpointer[i][s] = best_prev
# Find best final state
best_final = max(states, key=lambda s: V[n - 1][s])
best_log_prob = V[n - 1][best_final]
# Backtrack
path = [best_final]
current = best_final
for i in range(n - 1, 0, -1):
current = backpointer[i][current]
path.append(current)
path.reverse()
return path, best_log_prob
# Compare probability space vs log space
path_log, log_prob = viterbi_log_space(
observations, states, start_prob, trans_prob, emit_prob
)Log-Space vs Probability-Space Computation ======================================================= Optimal path (both methods): Sunny → Rainy → Rainy Probability space: P = 0.012960 Log space: log(P) = -4.345888 Converted back: exp(log(P)) = 0.012960 For long sequences, probability space would underflow: Sequence length: 400 Per-step probability: 0.1 Probability space: 0.0 (underflow to 0) Log space: -921.03 (computable)
Both methods find the identical optimal path, confirming that log-space computation preserves correctness. The probability-space result (best_prob) and the exponentiated log-space result match to numerical precision. For the 400-token demonstration, probability space underflows to exactly zero while log space produces a meaningful value near -921. This is why all production Viterbi implementations work in log space.

The visualization makes the underflow problem concrete. With a typical per-step probability of 0.1, probability-space values reach the smallest normal float64 magnitude around sequence length 308, enter the narrow subnormal range, and then underflow to zero. Log-space values, in contrast, decrease linearly and remain perfectly representable regardless of sequence length. Even a 1000-word document produces a log probability around -2300, large in magnitude but entirely computable.
Log-space computation is essential for practical implementations. The algorithm produces identical paths but with stable numerics.