Skip to content
Kudos AI
Lire en français
Probabilistic Reasoning

Bayesian Networks and Probabilistic Inference

How a graph and a few small tables stand in for a joint distribution with thousands of entries, how to answer a query against it exactly by enumeration and variable elimination, and what to do when exact inference is out of reach: rejection sampling, likelihood weighting, and Gibbs sampling, each worked through on the same two networks.

9 min readKudos AI

Prerequisites: Bayes' Theorem and Belief Updating

Five nodes and ten numbers assembling into a graph, then one full event traced top to bottom while its five factors multiply down to 0.000628.

Bayes' theorem tells you how to update one belief on one piece of evidence. A real agent holds beliefs about dozens of variables at once, and the joint distribution over them - the object that answers every question - has 2n2^n entries for nn Boolean variables. Nobody can write it down. This article is about the representation that makes the joint usable, and the algorithms that query it.

A. The network is the joint

A Bayesian network is a directed acyclic graph. Each node is a random variable; an arrow from XX to YY makes XX a parent of YY; and each node carries a conditional probability table giving P(Xi∣Parents(Xi))P(X_i \mid \mathrm{Parents}(X_i)), one row per combination of parent values. A Boolean node with kk Boolean parents needs 2k2^k numbers.

Russell and Norvig's example is an alarm. It responds to burglary and, less reliably, to earthquakes; two neighbours, John and Mary, call when they hear it, John sometimes confusing the phone for the alarm and Mary sometimes missing it behind loud music. Arrows B→AB \to A, E→AE \to A, A→JA \to J, A→MA \to M, and ten numbers:

P(b)=0.001P(e)=0.002P(a∣b,e)=0.95P(a∣b,¬e)=0.94P(a∣¬b,e)=0.29P(a∣¬b,¬e)=0.001P(j∣a)=0.90P(j∣¬a)=0.05P(m∣a)=0.70P(m∣¬a)=0.01\begin{array}{ll} P(b) = 0.001 & P(e) = 0.002 \\[4pt] P(a \mid b, e) = 0.95 & P(a \mid b, \lnot e) = 0.94 \\ P(a \mid \lnot b, e) = 0.29 & P(a \mid \lnot b, \lnot e) = 0.001 \\[4pt] P(j \mid a) = 0.90 & P(j \mid \lnot a) = 0.05 \\ P(m \mid a) = 0.70 & P(m \mid \lnot a) = 0.01 \end{array}

The meaning of the picture is one equation. For any complete assignment,

P(x1,…,xn)=∏i=1nP(xi∣parents(Xi)).P(x_1, \dots, x_n) = \prod_{i=1}^{n} P\big(x_i \mid \mathrm{parents}(X_i)\big).

So the probability that the alarm sounds with no burglary and no earthquake, and both neighbours call, is

P(j,m,a,¬b,¬e)=0.90×0.70×0.001×0.999×0.998=0.000628.P(j, m, a, \lnot b, \lnot e) = 0.90 \times 0.70 \times 0.001 \times 0.999 \times 0.998 = 0.000628 .

Every one of the 32 joint entries is available this way, from ten numbers rather than 31. The saving is not a trick: it is the claim, made by the missing arrows, that each variable is conditionally independent of its non-descendants given its parents. Stronger still, every node is independent of all other nodes given its Markov blanket - parents, children, and children's other parents. Given the alarm and the earthquake, the phone calls say nothing further about a burglary.

B. Exact inference

A query asks for P(X∣e)\mathbf{P}(X \mid \mathbf{e}). Since every joint entry is a product of CPT entries, the query is a normalised sum of products over the hidden variables. Both neighbours have called; is it a burglary?

P(b∣j,m)=α P(b)∑eP(e)∑aP(a∣b,e) P(j∣a) P(m∣a).P(b \mid j, m) = \alpha\, P(b) \sum_{e} P(e) \sum_{a} P(a \mid b, e)\,P(j \mid a)\,P(m \mid a).

Four terms for bb, four for ¬b\lnot b, each a product of five numbers:

P(B∣j,m)=α ⟨0.00059224, 0.00149186⟩=⟨0.284, 0.716⟩.\mathbf{P}(B \mid j, m) = \alpha\, \langle 0.00059224,\ 0.00149186 \rangle = \langle 0.284,\ 0.716 \rangle .

Two independent reports lift a one-in-a-thousand prior to 28 percent, and no further, because the no-burglary mass arrives by three comparable routes: a causeless alarm then both calls, 0.0006280.000628; no alarm but both calls anyway, 0.0004980.000498; and an earthquake alarm then both calls, 0.0003650.000365. Together they outweigh the burglary route, 0.0005920.000592, by 2.5 to one.

Enumeration evaluates this as a depth-first tree, and the tree repeats itself: the product P(j∣a) P(m∣a)P(j \mid a)\,P(m \mid a) is computed once under ee and again under ¬e\lnot e. On nn Boolean variables the cost is O(2n)O(2^n) - better than the O(n 2n)O(n\,2^n) of building each joint entry separately, but most of it recomputation. Variable elimination stores each piece as a factor - a table over the variables it still depends on - and combines factors by pointwise product and by summing out. Summing out AA gives a factor over (B,E)(B, E):

f6(B,E)=e¬eb0.5985250.592230¬b0.1830550.001130f_6(B, E) = \begin{array}{c|cc} & e & \lnot e \\ \hline b & 0.598525 & 0.592230 \\ \lnot b & 0.183055 & 0.001130 \end{array}

Summing out EE against P(E)P(E) leaves f7(B)=⟨0.592243, 0.001493⟩f_7(B) = \langle 0.592243,\ 0.001493 \rangle, and multiplying by P(B)P(B) and normalising returns ⟨0.284,0.716⟩\langle 0.284, 0.716 \rangle with every leaf product done once.

Whether this is fast depends on the shape of the graph. On a polytree - at most one undirected path between any two nodes, as here - variable elimination is linear in the size of the network. Add a second path and the intermediate factors can grow exponentially in the worst case. The general problem is #P-hard: as hard as counting the satisfying assignments of a propositional formula. That is the reason the rest of this article exists.

The network below is this one, and every number in it is exact - the posteriors come from summing all 32 assignments, not from sampling. Click a node to say what you know. Start with both neighbours calling: a burglary goes to 28.4%, which is already worth sitting with, since the calls are excellent evidence about the alarm and the alarm is a poor witness to burglary. Then add the earthquake. It makes the calls more likely, and it sends the burglary back towards nothing.

Interactive: say what you know, watch what follows

Click a node to cycle it: unknown, happened, did not happen.

Burglary0.1%Earthquake0.2%Alarm0.3%John calls5.2%Mary calls1.2%
P(burglary)
0.1%
P(earthquake)
0.2%
P(alarm)
0.3%
P(evidence)
1.000000

Nothing is known yet, so every node sits at its prior: a burglary at 0.1%, an earthquake at 0.2%. Click a neighbour and watch the influence travel up the arrows to the alarm and then down to the other neighbour, even though no arrow joins the two neighbours at all.

C. Sampling when exact is impossible

The sprinkler network has four Boolean variables and two paths from Cloudy\mathit{Cloudy} to WetGrass\mathit{WetGrass}, one through the sprinkler and one through rain:

P(c)=0.5P(s∣c)=0.10P(s∣¬c)=0.50P(r∣c)=0.80P(r∣¬c)=0.20P(w∣s,r)=0.99P(w∣s,¬r)=0.90P(w∣¬s,r)=0.90P(w∣¬s,¬r)=0.00\begin{array}{ll} P(c) = 0.5 & \\[4pt] P(s \mid c) = 0.10 & P(s \mid \lnot c) = 0.50 \\ P(r \mid c) = 0.80 & P(r \mid \lnot c) = 0.20 \\[4pt] P(w \mid s, r) = 0.99 & P(w \mid s, \lnot r) = 0.90 \\ P(w \mid \lnot s, r) = 0.90 & P(w \mid \lnot s, \lnot r) = 0.00 \end{array}

Prior sampling draws each variable from its CPT in parent-first order. The probability of producing an event is the product of the entries consulted, which is the joint itself: [c,¬s,r,w][c, \lnot s, r, w] comes out with probability 0.5×0.9×0.8×0.9=0.3240.5 \times 0.9 \times 0.8 \times 0.9 = 0.324. Frequencies therefore converge to probabilities, and the estimate is consistent.

Rejection sampling answers a conditional query by discarding every sample that disagrees with the evidence. For P(Rain∣Sprinkler=true)P(\mathit{Rain} \mid \mathit{Sprinkler} = \text{true}), exact value 0.30.3, a seeded run of 1,000 prior samples rejected 703 and kept 79 with rain against 218 without, for an estimate of 0.2660.266. Seventy percent of the effort went nowhere, and that is the benign case: the survival rate is P(e)P(\mathbf{e}), which falls exponentially with the number of evidence variables. Ask the burglary network for P(B∣j,m)\mathbf{P}(B \mid j, m) and about 21 samples in 10,000 survive.

Python

Runs in your browser. The first run downloads the Python runtime (~10 MB), then it is cached.

Likelihood weighting fixes the evidence variables instead of sampling them, so every event is consistent, and weights each event by the probability of the evidence given the parents the sampler chose. For P(Rain∣c,w)\mathbf{P}(\mathit{Rain} \mid c, w) with ordering C,S,R,WC, S, R, W: the weight starts at 11, CC is evidence so w←0.5w \leftarrow 0.5; SS is sampled from ⟨0.1,0.9⟩\langle 0.1, 0.9 \rangle, say false; RR from ⟨0.8,0.2⟩\langle 0.8, 0.2 \rangle, say true; WW is evidence so w←0.5×P(w∣¬s,r)=0.45w \leftarrow 0.5 \times P(w \mid \lnot s, r) = 0.45. The event is tallied under rain with weight 0.450.45. Nothing is discarded, and because sampling probability times weight equals the joint, the weighted tally is consistent: a thousand samples on the same seed give 0.97580.9758 against an exact 0.97580.9758. The method degrades when the evidence is improbable under the sampled values, because a few heavy weights then dominate everything.

Gibbs sampling is Markov chain Monte Carlo. Fix the evidence, start from any state, and repeatedly resample one non-evidence variable conditioned on its Markov blanket. That conditional is a product of a handful of CPT entries:

P(xi′∣mb(Xi))=α P(xi′∣parents(Xi))∏Yj∈Children(Xi)P(yj∣parents(Yj)).P(x_i' \mid \mathrm{mb}(X_i)) = \alpha\, P\big(x_i' \mid \mathrm{parents}(X_i)\big) \prod_{Y_j \in \mathrm{Children}(X_i)} P\big(y_j \mid \mathrm{parents}(Y_j)\big).

For P(Rain∣s,w)\mathbf{P}(\mathit{Rain} \mid s, w) from the state [c,s,¬r,w][c, s, \lnot r, w], resampling CC uses P(C) P(s∣C) P(¬r∣C)P(C)\,P(s \mid C)\,P(\lnot r \mid C), which gives P(c∣s,¬r)=0.048P(c \mid s, \lnot r) = 0.048; say it comes out false. Resampling RR then uses P(R∣¬c) P(w∣s,R)P(R \mid \lnot c)\,P(w \mid s, R), which gives P(r∣¬c,s,w)=0.216P(r \mid \lnot c, s, w) = 0.216. Every state visited is a sample. A thousand steps on a fixed seed estimate 0.3150.315 against the exact 0.3200.320.

The reason this works is that the chain has a stationary distribution and the Gibbs step satisfies detailed balance with respect to the posterior, which forces the two to coincide: the long-run fraction of time in each state is P(x∣e)P(\mathbf{x} \mid \mathbf{e}). No sample is rejected and no weight collapses. The cost is correlation between consecutive states, so the chain needs time to forget where it started.

Consistency is a claim about a limit, so the figure below puts the limit on the screen: the dashed line is the exact posterior, obtained by enumerating the joint and sharing no code with either sampler. Drag the sample size and watch the two estimates walk toward it. Then switch to the burglary query, where rejection sampling keeps 21 samples in 10,000 and likelihood weighting keeps all of them and still struggles, because fixing the evidence removes the waste rather than the variance.

Interactive: two samplers against the exact answer

The exact value comes from enumerating the joint, not from either sampler.

0.3000
Exact
0.3000
Rejection sampling
0.3045
Likelihood weighting
0.3029
Kept by rejection
289

Rejection sampling reads 0.3045 from the 289 samples it kept of 1,000; likelihood weighting reads 0.3029 from all of them; the exact answer is 0.3000. Both estimators are consistent, which is a claim about a limit rather than about any one run, so the useful thing to do is drag the sample size and watch the two numbers walk toward the third. The discarded share is not noise in the procedure - it is 70.00% of the work, and it grows exponentially with the number of evidence variables.

Where this leaves you

A Bayesian network is a joint distribution you can actually write down. Exact inference is a sum of products that variable elimination performs without repetition, cheap on polytrees and intractable in general. When the graph is too tangled, sampling gives consistent estimates at a cost that depends on how the evidence is handled: rejection wastes, weighting concentrates, and Gibbs wanders - each of which is the right choice somewhere. The training path Probabilistic Reasoning with Bayesian Networks works every one of these computations by hand and by code.

References & further reading

  • Stuart Russell, Peter Norvig, Artificial Intelligence: A Modern Approach, Pearson (3rd edition), 2010· Kudos AI reference library

Copyrighted works are cited for reference only and are not hosted here; please consult the publisher for access.

Related reading

3 min readProbabilistic Reasoning

A Hundred Thousand Samples, Four Hundred of Them Real

On the burglary network with both neighbours calling, rejection sampling keeps 183 of 100,000 draws and likelihood weighting keeps all of them at an effective sample size of 396. Both estimates are about 10% off a posterior of 0.284172, and the reason is exactly computable: 252 samples carry 76% of the weight and 99.975% of the squared weight.

Artificial IntelligenceProbability
5 min readProbabilistic Reasoning

The Week That Cannot Have Happened

Take the most likely state on each day and write them down in order, and you have a report the model assigns probability exactly zero: on a four-day machine-monitoring example the day-by-day answer is healthy, healthy, failed, failed, and healthy to failed is a transition that cannot occur. What the two questions actually are, why smoothing and Viterbi answer different ones, and what the 0.411 posterior on the best path means for anyone who has to act on it.

Artificial IntelligenceProbability
10 min readProbabilistic Reasoning

Learning the Numbers in a Probability Model

Where the numbers in a Bayesian network or a Gaussian actually come from: the three-step maximum-likelihood recipe worked through on discrete and continuous parameters, the Beta prior that repairs what it does to an unseen event, naive Bayes and the single zero count that destroys it, and the EM algorithm for the case where the counts cannot be taken at all - with every figure computed rather than asserted.

ProbabilityStatisticsArtificial Intelligence
← Back to all articles