Hidden Markov models: the Viterbi algorithm and the dishonest casino
Hidden Markov model, Rabiner’s three problems, the forward algorithm, the Viterbi algorithm, posterior decoding and the Baum–Welch algorithm — on a casino that now and then secretly swaps the die for a loaded one.
Series AI basics Part 39 of 52
- Map of artificial intelligence: what is what and how it all fits together
- A brief history of artificial intelligence: from Turing to language models
- Data, model, training, inference: the basic terms on a single example
- How to read mathematical notation: symbols, indices, sums and quantifiers
- Sets and relations: the building blocks of definitions in AI
- Functions and mappings: why every AI model is a function
- Logic and proofs: the rules AI reasons by
- Graphs and trees: the structure that both search and neural networks stand on
- Linear algebra for machine learning: vectors, matrices and why AI mostly multiplies
- Derivatives, the gradient and the chain rule: how a model finds out which way to learn
- Gradient descent: how a model learns
- What probability is: from rolling dice to the Kolmogorov axioms
- Conditional probability and Bayes’ theorem: how to update your estimate when you learn something new
- Random variables and the cumulative distribution function: one function that tells you everything about a distribution
- Probability density and probability mass functions: why a continuous variable has zero probability at a point
- Expected value, variance and covariance: where a distribution lies, how wide it is and what goes with what
- Overview of probability distributions: Bernoulli, binomial, Poisson, uniform, normal and exponential
- Law of large numbers and central limit theorem: why averages behave so predictably
- Statistics and parameter estimation: maximum likelihood, MAP and confidence intervals
- Information theory: entropy, cross-entropy and KL divergence — where loss functions come from
- State-space search: BFS, DFS, UCS and A* with animations
- Games and adversaries: minimax, alpha-beta pruning and Monte Carlo tree search
- Constraint satisfaction: backtracking, constraint propagation and AC-3 on sudoku and a map of Czech regions
- Logic and knowledge systems: forward and backward chaining, resolution and expert systems
- Planning: STRIPS, the blocks world, heuristics from relaxation and planning in AI agents
- Local search and simulated annealing: a travelling salesman through the Czech regional capitals
- Genetic algorithms: population, crossover, mutation and selection on a backpack for a hike
- Evolution strategies and genetic programming: from the one-fifth rule to Kepler’s law
- Ant colony optimization: how a pheromone trail finds a short path
- Particle swarm optimisation: inertia, personal experience and the swarm’s experience
- Neuroevolution: evolving the weights and shape of neural networks, and the NEAT algorithm
- Fuzzy sets and membership functions: how to write down “somewhat warm”
- Fuzzy logic and probability: why “somewhat warm” does not mean “probably warm”
- Fuzzy logic: t-norms, implications and IF–THEN rules
- Fuzzy control: Mamdani and Sugeno from a fan to a pole on a cart
- Fuzzy clustering: the fuzzy c-means algorithm and points between clusters
- Neuro-fuzzy systems (ANFIS): fuzzy rules that learn from data
- Markov chains: the transition matrix, the stationary distribution and PageRank
- Hidden Markov models: the Viterbi algorithm and the dishonest casino
- Bayesian networks: why the grass is wet and what it means for the sprinkler
- The Kalman filter: how to estimate where a car is and how fast it goes from imprecise measurements
- Types of machine learning: supervised, unsupervised, semi-supervised and more
- Prompts, context and RAG: how to give a model the right information
- Variational autoencoders (VAE): new images from a continuous latent space
- Generative adversarial networks (GAN): the forger versus the detective
- Diffusion models: how pure noise becomes an image
- Markov decision processes: states, actions, rewards and the Bellman equations
- Q-learning and DQN: learning the value of actions, from a table to a neural network
- Policy gradient and PPO: learning the policy directly
- How an AI agent works: a language model in a loop with tools
- Programming with AI agents: the task, tests and review
- Evaluating and securing AI systems: how to measure and how to keep watch
Full series syllabus 52 of 83 topics published
- What a hidden Markov model is and how to compute the probability of the observations without going through all the paths.
- How the Viterbi algorithm finds the most probable hidden states, and where it gets them wrong.
- How the model learns its parameters from the observations alone, and how much data it needs.
Useful to know first: Markov chains: the transition matrix, the stationary distribution and PageRank
In the previous part we could see the state of the chain: sunny today, rain tomorrow. Often, though, we only see its effects. A microphone picks up a sound wave, not the word that produced it. A sequencer reads the letters of DNA, not where a gene begins. And in a casino we see what was rolled, not which die the croupier is using. Hidden Markov models are chains whose states we cannot see — together with algorithms that still let us work out what is going on inside. For decades, speech recognition was built on them.
This part builds on Markov chains.
1. The dishonest casino
A classic textbook example (Durbin et al., 1998): the croupier rolls either a fair die or a loaded one, which shows a six with probability 1/2 and each other number with probability 1/10. After each roll he swaps the fair die for the loaded one with probability 0.05, and the loaded one back for the fair one with probability 0.1. We do not see the swap. We only see a sequence of numbers — in our experiment 300 rolls, which begin like this:
163665521623524236522511663566613666222461626262162666626516…
We ask: is the game fair? And if not, when exactly was the loaded die in play?
2. Hidden Markov model
Definition — hidden Markov model
Let N, M ≥ 1 be integers, S = {1, …, N} the set of hidden states and V = {1, …, M} the set of observable symbols. Further let
- π = (π1, …, πN) ∈ ℝ1×N be a probability distribution on S (πi ≥ 0, ∑i πi = 1) — the initial distribution;
- A = (aij) ∈ ℝN×N a stochastic matrix — the transition matrix;
- B = (bi(o))i ∈ S, o ∈ V ∈ ℝN×M a stochastic matrix — the emission probabilities.
A hidden Markov model (HMM) with parameters λ = (π, A, B) is a pair of sequences of random variables X1, X2, … : Ω → S (hidden states) and O1, O2, … : Ω → V (observations) on a probability space (Ω, 𝓕, P) such that:
- (Xt)t ≥ 1 is a homogeneous Markov chain with initial distribution P(X1 = i) = πi and transition matrix A, i.e. aij = P(Xt+1 = j | Xt = i);
- for every T ≥ 1, t ∈ {1, …, T}, o ∈ V and all values x1, …, xT ∈ S and os ∈ V (s ≠ t) for which the condition has positive probability, P(Ot = o | X1 = x1, …, XT = xT, Os = os for s ∈ {1, …, T} ∖ {t}) = bxt(o).
In words: the hidden states change like an ordinary Markov chain, and the observation at time t depends only on the state Xt — not on the other states or on the other observations. We observe only O1, O2, …, not the states Xt.
Behind the curtain runs an ordinary Markov chain — the die changes according to the matrix A. We only see what “falls out” of each state: a roll drawn with the probabilities B. On the loaded die a six is five times more likely than any other number, but it can come up on the fair one too. A single roll therefore gives nothing away — only the pattern across the whole sequence does.
The definition tells us how probable one particular “history” is: a sequence of dice x = (x1, …, xT) together with the rolls O = (O1, …, OT).
P(x, O | λ) = πx1 bx1(O1) · ∏t=2T axt−1 xt bxt(Ot)
- πx1
- the first die has no predecessor to move from — its probability comes from the initial distribution
- axt−1 xt
- each further die depends only on the previous one (the Markov property), so each step needs just one number from the matrix A
- bxt(Ot)
- the roll depends only on the die currently in use — we take the probability of the number that actually came up
- ∏t=2T
- in general we would multiply probabilities conditioned on the entire past (the chain rule); the two independence assumptions of the definition shorten them to these local numbers, which is why the whole is a plain product
Example — two histories of the first three rolls
Our sequence begins with the rolls 1, 6, 3. Let F denote the fair die and L the loaded one. The history “loaded three times”:
P(LLL, 1, 6, 3) = 0.5 · 0.1 · 0.9 · 0.5 · 0.9 · 0.1 = 0.002025,
the history “fair three times”:
P(FFF, 1, 6, 3) = 0.5 · 1/6 · 0.95 · 1/6 · 0.95 · 1/6 = 0.002089
(colours as in the formula above: initial distribution, emissions, transitions). The fair history comes out a whisker more probable: the six on the loaded die does get 0.5 instead of 1/6, but the one and the three get only 0.1 instead of 1/6. (In the simulation, all three rolls were in fact made with the loaded die — three rolls are not enough to tell.)
In his famous 1989 tutorial, Lawrence Rabiner summed up what can be done with such a model as three problems: evaluation (how probable is the observed sequence?), decoding (which hidden states most likely lay behind it?) and learning (what parameters does the model have when we know only the observations?). Each has an elegant algorithm.
3. How probable is the sequence: the forward algorithm
The probability of the observed sequence is a sum over all possible sequences of hidden states. For 300 rolls and two dice there are 2300 ≈ 2 · 1090 of them — more than there are atoms in the universe. Adding them up directly is impossible. The Markov property, however, lets us compute step by step.
Example — the probability of three rolls as a sum over all histories
The probability of the observations is the sum of the joint probability over all possible histories: P(O | λ) = ∑x P(x, O | λ). For the rolls 1, 6, 3 there are 23 = 8 of them:
| History | P(x, O) |
|---|---|
| FFF | 0.002089 |
| FFL | 0.000066 |
| FLF | 0.000035 |
| FLL | 0.000188 |
| LFF | 0.000132 |
| LFL | 0.000004 |
| LLF | 0.000375 |
| LLL | 0.002025 |
| sum | 0.004913 |
Each history costs 2T − 1 = 5 multiplications, 8 · 5 = 40 in total. For T rolls it is (2T − 1) · 2T — for 300 rolls an unimaginable number.
Algorithm — forward algorithm
Input: a hidden Markov model λ = (π, A, B) with states S = {1, …, N} and symbols V; an observed sequence O = (O1, …, OT) ∈ VT, T ≥ 1.
Output: P(O | λ) = P(O1, …, OT) and the forward variables
αt(j) = P(O1, …, Ot, Xt = j | λ), t = 1, …, T, j ∈ S.
- Initialization: for every j ∈ S set α1(j) = πj bj(O1).
- Induction: for t = 1, …, T − 1 and every j ∈ S set αt+1(j) = [∑i=1N αt(i) aij] · bj(Ot+1).
- Termination: after processing time T, return P(O | λ) = ∑j=1N αT(j).
The algorithm stops after T − 1 induction steps; each costs O(N2) operations, so O(N2 T) in total instead of O(T · NT) when summing over all histories. To keep the numbers from underflowing, at every time t we compute ct = ∑j αt(j) (with the already normalized αt−1 in the induction), replace αt(j) by αt(j)/ct and return log P(O | λ) = ∑t=1T log ct.
You do not have to follow every possible history separately. After each roll it is enough to remember two numbers: “how probable is everything I have seen so far, together with the fair die being in play now” — and the same for the loaded one. From them you compute the numbers for the next roll. All histories that end in the same state are merged into a single number.
αt+1(j) = ∑i αt(i) aij · bj(Ot+1)
- αt(i)
- everything we know about the past: the probability of the rolls so far and of die i being in play now; this single number merges all histories ending in i
- aij
- a step of the chain from i to j; thanks to the Markov property it does not matter how we got to i
- ∑i
- we do not know which die was used last time, and j can be reached from any of them — so we sum over all possibilities (the histories are mutually exclusive events)
- bj(Ot+1)
- die j must actually generate the new roll; this does not depend on i, so it can be pulled out in front of the sum
Example — the forward algorithm on the rolls 1, 6, 3
Start: α1(F) = 0.5 · 1/6 = 0.0833, α1(L) = 0.5 · 0.1 = 0.0500. The second roll is a six (colours as in the formula above):
α2(F) = (0.0833 · 0.95 + 0.0500 · 0.1) · 1/6 = 0.0140,
α2(L) = (0.0833 · 0.05 + 0.0500 · 0.9) · 0.5 = 0.0246.
| t | roll | αt(F) | αt(L) | sum = P(O1, …, Ot) | ct |
|---|---|---|---|---|---|
| 1 | 1 | 0.0833 | 0.0500 | 0.133333 | 0.1333 |
| 2 | 6 | 0.0140 | 0.0246 | 0.038611 | 0.2896 |
| 3 | 3 | 0.0026 | 0.0023 | 0.004913 | 0.1273 |
The sum in the last row is exactly the sum of the eight histories from the previous example, only instead of 40 multiplications 2 + 2 · 6 = 14 were enough. The last column ct = P(Ot | O1, …, Ot−1) is the normalizing constant from the scaling: how probable roll t was given the previous ones. Their product is P(O | λ), so log P(O | λ) = ∑t log ct.
Why logarithms? For 3,000 rolls P(O | λ) comes out around 10−2,278, and the smallest positive number a computer can store in ordinary double precision (IEEE 754) is about 4.9 · 10−324. Without scaling, α would underflow to zero.
def dopredu(hody, pi, A, B):
alfa = pi * B[:, hody[0]]
logP = 0.0
for t in range(len(hody)):
if t > 0:
alfa = (alfa @ A) * B[:, hody[t]] # sum over i, then emission
c = alfa.sum() # c_t = P(O_t | O_1, …, O_t−1)
alfa = alfa / c # scaling: α_t now sums to 1
logP += np.log(c)
return logP # log P(O | λ)
For our 300 rolls we get P(rolls | casino model) ≈ 10−230.03. Had the game been fair the whole time, the probability would be (1/6)300 ≈ 10−233.45. The model with an occasional loaded die explains the observed rolls about 2,600 times better — strong evidence of cheating, even though no single roll gives the cheating away.
Definition — likelihood ratio (Bayes factor)
Let (Ω, 𝓕, P) be a probability space, M1, M2 ∈ 𝓕 the events “model 1 holds” and “model 2 holds” with P(M1) > 0, P(M2) > 0, and O ∈ 𝓕 the event “we observed the data” with P(O | M2) > 0. The Bayes factor of model M1 against M2 is the number
K = P(O | M1)/P(O | M2),
i.e. the ratio of the likelihoods of the data under the two models. K > 1 means that the data are more probable under the model M1. If moreover P(O) > 0, then Bayes’ theorem written for both models and divided (P(O) cancels out) gives:
P(M1 | O)/P(M2 | O) = P(O | M1)/P(O | M2) · P(M1)/P(M2)
- P(M1 | O)/P(M2 | O)
- the posterior odds — what we are interested in: how much more we believe in cheating than in a fair game once we have seen the rolls
- P(O | M1)/P(O | M2)
- the Bayes factor — the only place where the data enter the formula; both likelihoods are computed by the forward algorithm. The denominator P(O) from Bayes’ theorem cancels out in the division, which is why we do not need to know it
- P(M1)/P(M2)
- the prior odds — whoever trusts the casino beforehand needs stronger evidence; the data only multiply it
Calculation. log10 K = −230.03 − (−233.45) = 3.42 (3.413 from the unrounded values), so K ≈ 103.413 ≈ 2,590, roughly 2,600. If you considered cheating and a fair game equally likely beforehand (prior odds 1), after 300 rolls the odds are 2,600 · 1 = 2,600 : 1 in favour of cheating. On the widely used scale of Kass and Raftery (1995), a Bayes factor above 150 is “very strong” evidence.
4. When was there cheating: the Viterbi algorithm
Algorithm — Viterbi algorithm (1967)
Input: a hidden Markov model λ = (π, A, B) with states S = {1, …, N}; observations O = (O1, …, OT) ∈ VT, T ≥ 1.
Output: the most probable sequence of hidden states and its probability
x* = argmaxx ∈ ST P(X1 = x1, …, XT = xT, O | λ), P* = P(x*, O | λ).
For t = 1, …, T and j ∈ S let δt(j) be the probability of the best path that ends in state j at time t:
δt(j) = maxx1, …, xt−1 ∈ S P(X1 = x1, …, Xt−1 = xt−1, Xt = j, O1, …, Ot | λ).
- Initialization: for every j ∈ S set δ1(j) = πj bj(O1).
- Induction: for t = 1, …, T − 1 and every j ∈ S set δt+1(j) = maxi ∈ S [δt(i) aij] · bj(Ot+1), ψt+1(j) = argmaxi ∈ S δt(i) aij (ψt+1(j) is the best predecessor; on a tie choose any).
- Termination: P* = maxj ∈ S δT(j) and x*T = argmaxj ∈ S δT(j).
- Backtracking: for t = T − 1, T − 2, …, 1 set x*t = ψt+1(x*t+1); after t = 1 return x* = (x*1, …, x*T) and P*.
The algorithm stops after T − 1 induction steps and T − 1 backtracking steps; the time is again O(N2 T). In practice logarithms are added instead of multiplying products, so that the numbers do not underflow.
The same idea as the forward algorithm, only instead of summing over all paths we take the best one. Into each state at each time leads a single “winning” path — if the best overall path went through there, it would have reached that point along it anyway. This is dynamic programming, a relative of shortest-path search from the part on state-space search.
δt+1(j) = maxi δt(i) aij · bj(Ot+1), ψt+1(j) = argmaxi δt(i) aij
- δt(i)
- the probability of the best history ending with die i at time t; we no longer need the worse histories into i — if the best complete path went through i, it would continue from the best one
- aij
- the cost of moving from i to j; the small aFL = 0.05 is why Viterbi is reluctant to switch because of a single six
- maxi, ψt+1(j)
- unlike the forward algorithm, we do not sum but pick the best predecessor — we want one path, not the probability of all of them; ψ remembers who won (the amber arrows in the trellis), so that the path can be reconstructed backwards at the end
- bj(Ot+1)
- how well die j explains the new roll — just as with α
Twelve rolls are enough to see how Viterbi decides. The first six rolls (5, 2, 2, 5, 1, 1) look fair. Then two sixes come in a row and two more a little later — and the best path switches to the loaded die. In reality the croupier swapped the die only four rolls later; two sixes in a row happen on a fair die too.
Example — Viterbi step by step on the rolls 1, 6, 3
The start is the same as for α: δ1(F) = 0.0833, δ1(L) = 0.0500. For the six on the second roll:
δ2(F) = max(0.0833 · 0.95, 0.0500 · 0.1) · 1/6 = 0.0792 · 1/6 = 0.0132 (F wins),
δ2(L) = max(0.0833 · 0.05, 0.0500 · 0.9) · 0.5 = 0.0450 · 0.5 = 0.0225 (L wins)
(colours as in the formula above).
| t | roll | δt(F) | ψt(F) | δt(L) | ψt(L) |
|---|---|---|---|---|---|
| 1 | 1 | 0.083333 | — | 0.050000 | — |
| 2 | 6 | 0.013194 | F | 0.022500 | L |
| 3 | 3 | 0.002089 | F | 0.002025 | L |
At the end F wins (0.002089 > 0.002025), and following the pointers ψ backwards gives the path FFF — exactly the history from the first example that was the most probable of all eight. The forward algorithm, however, gives for the third roll P(L | O1, O2, O3) = α3(L)/α3(F) + α3(L) = 0.002283/0.004913 = 0.465. The best path and the most probable state agree here (F), but the “victory” is narrow and three rolls are not enough to be sure. In general they need not agree — we will come back to that with posterior decoding.
import numpy as np
def viterbi(hody, pi, A, B):
T, N = len(hody), len(pi)
delta = np.log(pi) + np.log(B[:, hody[0]])
odkud = np.zeros((T, N), dtype=int)
for t in range(1, T):
kandidati = delta[:, None] + np.log(A) # from every i to every j
odkud[t] = kandidati.argmax(axis=0)
delta = kandidati.max(axis=0) + np.log(B[:, hody[t]])
cesta = [int(delta.argmax())]
for t in range(T - 1, 0, -1): # back along the winning pointers
cesta.append(odkud[t][cesta[-1]])
return cesta[::-1]
5. All 300 rolls
The loaded die was in play 88 times, in nine stretches. Viterbi marked 62 rolls as loaded, 56 of them correctly, and overall identified the die correctly for 87.3 % of the rolls. It found the long stretches of cheating, but missed the short ones or merged them together — instead of nine stretches it sees two. A short stretch simply does not produce enough sixes to be worth an “expensive” swap of the die (the probability of a swap is only 0.05).
Definition — posterior probability of a state
Let λ = (π, A, B) be a hidden Markov model with states S = {1, …, N} and O = (O1, …, OT) observations with P(O | λ) > 0. The posterior probability of the state i ∈ S at time t ∈ {1, …, T} is
γt(i) = P(Xt = i | O1, …, OT, λ).
It is computed with the backward variables (for t < T and P(Xt = i) > 0)
βt(i) = P(Ot+1, …, OT | Xt = i, λ), βT(i) = 1.
The forward–backward algorithm computes αt(i) by the forward algorithm, βt(i) from the end (for t = T − 1, …, 1) and from both γt(i) by the formulas below. In words: γt(i) is the probability that the model was in state i at time t, given the whole observed sequence — past and future.
Viterbi gives one answer: “it was this die”. The posterior probability tells you how sure we are — for each roll separately, taking into account everything that came up before and after. The bottom curve in the figure: where it rises above one half, cheating is more likely than not.
γt(i) = αt(i) · βt(i)/P(O | λ), βt(i) = ∑j aij bj(Ot+1) βt+1(j), βT(i) = 1
- γt(i)
- the probability that die i was in play at time t, given all the rolls — for the loaded die this is the red curve in the figure
- αt(i)
- evidence from the past: rolls 1 to t and state i at time t
- βt(i)
- evidence from the future: how well state i explains rolls t + 1 to T. It is computed from the end with the same trick as α — one step of the chain, emission, the rest of the future. We may multiply α and β because, given the state Xt, past and future rolls are independent
- P(O | λ)
- the product α·β is the joint probability P(Xt = i, O); dividing by the probability of all the rolls turns it into a conditional one. It is the same number the forward algorithm gave, and ∑i αt(i) βt(i) = P(O | λ) holds at every t
Example — the second roll: what the future adds
Let us stay with the rolls 1, 6, 3 and ask about the second roll (the six). The backward step from β3 = 1 with the third roll (the three):
β2(F) = 0.95 · 1/6 + 0.05 · 0.1 = 0.1633, β2(L) = 0.1 · 1/6 + 0.9 · 0.1 = 0.1067.
(In each product the first factor is the transition aij, the second the emission of the three bj(3), and β3(j) = 1 is not written.) By the formula above (colours match its terms):
γ2(L) = 0.024583 · 0.106667/0.004913 = 0.534.
If we knew only the first two rolls, we would have P(L | O1, O2) = α2(L)/α2(F) + α2(L) = 0.637. The three that came up afterwards is less probable on the loaded die, so it retroactively lowers the suspicion about the second roll to 0.534.
Definition — posterior decoding and accuracy
Let λ be a hidden Markov model with states S, O = (O1, …, OT) observations and γt(i) the posterior probabilities of the states from the previous definition. Posterior decoding is the sequence x̂ = (x̂1, …, x̂T) ∈ ST, where
x̂t = argmaxi ∈ S γt(i) for every t = 1, …, T
(the state is chosen at each time separately). Further let x ∈ ST be the true sequence of states and y ∈ ST any decoding (posterior or Viterbi). The accuracy of the decoding y is the fraction of times at which it agrees with reality:
accuracy(y, x) = |{t ∈ {1, …, T} : yt = xt}|/T ∈ [0, 1].
Posterior decoding maximizes the expected number of correctly identified rolls, Viterbi the probability that the whole path is correct at once. That is why a posterior sequence may even contain a transition that has probability zero in the model — the casino has no such transition, but in general it can happen.
If for each roll we picked the die with the larger posterior probability, we would get 87.7 % right — a whisker more than Viterbi (263 rolls out of 300 against 262, a difference of a single roll). (Viterbi looks for the best whole path, posterior decoding for the best estimate of each roll separately; these are not the same thing.) And beware of relying on one simulation: across twelve different runs the accuracy of the Viterbi algorithm ranged from 73.3 to 90.0 %, 83.4 % on average. It depends on how long the stretches of cheating that chance produces turn out to be.
6. Learning: the Baum–Welch algorithm
So far we knew the casino’s parameters. What if we only know that there are two dice, and everything else has to be read from the rolls?
Algorithm — Baum–Welch algorithm
It is an EM algorithm (Baum et al., around 1970).
Input: the number of hidden states N ≥ 1, the set of symbols V, observations O = (O1, …, OT) ∈ VT with T ≥ 2, an initial estimate of the parameters λ(0) = (π, A, B) with P(O | λ(0)) > 0 and a number of iterations K ≥ 1 (or a tolerance ε > 0).
Output: parameters λ(K) with likelihood P(O | λ(K)) ≥ P(O | λ(0)).
For k = 0, 1, …, K − 1 repeat with the parameters λ(k):
- E step: use the forward–backward algorithm to compute αt(i), βt(i), P(O | λ(k)), the posterior probabilities γt(i) for t = 1, …, T and the transition probabilities ξt(i, j) = P(Xt = i, Xt+1 = j | O, λ(k)), t = 1, …, T − 1, i, j ∈ S.
- M step: the new parameters λ(k+1) are, for all i, j ∈ S and o ∈ V, πi = γ1(i), aij = ∑t=1T−1 ξt(i, j)/∑t=1T−1 γt(i), bi(o) = ∑t : Ot = o γt(i)/∑t=1T γt(i).
- Stopping: stop after K iterations, or earlier if log P(O | λ(k+1)) − log P(O | λ(k)) < ε.
P(O | λ(k+1)) ≥ P(O | λ(k)) holds — the likelihood never decreases in any iteration. The algorithm converges to a stationary point of the likelihood, in practice usually a local maximum that need not be global; the result therefore depends on the initial estimate.
If we knew when each die was in play, we would get the parameters by simple counting. We do not know that — but with some estimate of the parameters we can compute how probably each die was where. So we compute “soft” counts, from them better parameters, from those better counts… Each round improves the model.
The expected number of transitions is computed from the same α and β as γ, with one step of the chain inserted between them:
ξt(i, j) = αt(i) aij bj(Ot+1) βt+1(j)/P(O | λ)
- αt(i)
- the rolls up to time t and die i at time t — the left end of the transition
- aij
- the transition i → j itself, whose frequency we are estimating
- bj(Ot+1)
- roll t + 1 must be generated by the new die j
- βt+1(j)
- the remaining rolls from t + 2 on, explained from die j — the right end of the transition
- P(O | λ)
- dividing turns the joint probability “transition and all the rolls” into the conditional “transition, given the rolls”
âij = ∑t=1T−1 ξt(i, j)/∑t=1T−1 γt(i)
- âij
- the new estimate of the transition probability — a relative frequency, as if we were counting tally marks
- ∑ ξt(i, j)
- the expected number of transitions i → j; instead of “there was a transition or not” (1 or 0) we add up probabilities, because we cannot see the states
- ∑ γt(i)
- the expected number of departures from state i; the sum runs only to T − 1, because no transition leads out of the last state. Thanks to that, each row of the new matrix sums to 1
repeat (100 times here):
α, β, P(O) ← forward–backward algorithm with the current π, A, B
# E step: soft counts
for each t, i: γ[t][i] ← α[t][i] · β[t][i] / P(O)
for each t < T, i, j: ξ[t][i][j] ← α[t][i] · a[i][j] · b[j](O[t+1]) · β[t+1][j] / P(O)
# M step: relative frequencies
π[i] ← γ[1][i]
a[i][j] ← ∑_{t<T} ξ[t][i][j] / ∑_{t<T} γ[t][i]
b[i](o) ← ∑_{t: O[t]=o} γ[t][i] / ∑_t γ[t][i]
We started from a poor guess: the dice barely differ and are swapped often. From 300 rolls Baum–Welch arrived at a model that hardly ever swaps the dice and gives the loaded die a six with probability only 0.39 — 300 rolls are too few for a reliable estimate. From 3,000 rolls (a different simulation of the same casino) it came out very close to the truth:
| Parameter | Reality | Estimate from 3,000 rolls |
|---|---|---|
| fair → loaded | 0.05 | 0.039 |
| loaded → fair | 0.10 | 0.090 |
| six on the loaded die | 0.50 | 0.491 |
| six on the fair die | 0.167 | 0.163 |
Why 300 rolls are not enough is shown by the likelihood. Over 100 iterations on our 300 rolls it rose from 10−231.26 to 10−227.14 — and that is more than the 10−230.03 of the true casino model. So the learned model explains these particular rolls better than the truth does: it has adapted to the random fluctuations of this specific data too. A maximum likelihood estimate approaches the truth only as the data grow.
7. Hidden Markov models in practice
- Speech recognition. From the 1980s until deep networks took over around 2010, almost all systems were built on HMMs: the hidden states are parts of phonemes, the observations short slices of the sound spectrum, and Viterbi looks for the most probable sequence of words.
- Language. Part-of-speech tagging: the hidden state is the part of speech, the observation the word. “Book” can be a noun or a verb — the context decides.
- Bioinformatics. Gene finding in DNA (coding and non-coding regions are the hidden states) or profile HMMs for protein families in the Pfam database.
- Continuous states. When the hidden state is not “die A or B” but a position and a velocity, the Kalman filter is used instead of an HMM — it has its own part in the outline.
8. Summary
- A hidden Markov model is a Markov chain of hidden states of which we see only the emissions; it is determined by π, A and B.
- The forward algorithm computes the probability of the observations in time O(N2T) instead of summing over 2300 paths; the casino with cheating explains our rolls about 2,600 times better than a fair game.
- The Viterbi algorithm finds the most probable path of hidden states by dynamic programming — in our simulation it identified the die correctly for 87.3 % of the rolls, but missed the short stretches.
- The forward–backward algorithm gives the posterior probability of the state at each time.
- The Baum–Welch algorithm learns the parameters from the observations alone — but it needs enough data: 300 rolls were not enough, 3,000 were.
Want to get your hands on it? Have an AI agent train a two-state HMM on the daily returns of a stock index — and see whether it finds the “calm” and “turbulent” periods of the market on its own.
Do you see it differently? Write to support@coden-app.com.