Reading and display settings

Appearance

System follows your operating system and keeps following it, even if you change it later. The header's sun, moon and monitor cycle the same three options.

Text size (%) 100%

Default. Scales every text size on the site, equations and tables included.

Reading width 70ch

How much text runs across one line of prose. Narrower is easier to track; wider fits more on screen.

Line spacing 1.6

The leading on body text. Taller leading helps a tired eye stay on the line.

Density

Padding and gaps around controls, cards, and tables — how much breathing room the layout leaves itself.

Motion

System follows your operating system. Reduced removes every transition on this site. Full keeps them on unless your system asks for less.

1

The question

Structure is the only thing that makes inference possible

Probability theory gives you one operation that does everything: sum out the variables you do not care about. Given a joint distribution $p(x_1,\dots,x_n)$ you can answer any question by marginalising, conditioning, or maximising. The difficulty is never the operation, it is the size of the object you apply it to. A full joint over n binary variables needs 2^n-1 independent numbers, and computing a single marginal costs a sum with 2^{n-1} terms. At n=30 that is more terms than there are seconds in the age of the universe.

Yet we routinely reason about models with hundreds of variables. The reason is that the joint distribution has a special form: it factors. Rain depends on clouds and season, not on the price of tea in the shop across the street. If you write each variable as a function of a small set of others — its parents — the full table never has to be stored, because it is a product of many small tables. This part is about the two standard languages for saying exactly which small tables, and the algorithms that run on them.

The first language is the Bayesian network, a directed acyclic graph whose arrows point from cause to effect. It says that the joint is the product of one conditional per node, conditioned on that node's parents. The second is the factor graph, a bipartite picture of variables and the functions they appear in, which drops the directionality and makes the algorithm for computing marginals look like messages passed along wires. The two are translations of each other, and together they turn a problem that is exponential in the number of variables into one that is exponential only in the width of the graph.

Along the way we meet the two facts that surprise everyone. Independence is a property of the graph and the observations together, not of the graph alone: two variables can be independent until you learn the value of a third, and dependent until you learn the value of a fourth. And explaining away — the reason a diagnosis becomes less likely once a second cause is confirmed — is not a quirk of human reasoning but a theorem about colliders.

💡 By the end of this part you'll see why a graph is a compact encoding of conditional independence, how d-separation lets you read those independencies off the paths, why conditioning on a common effect creates dependence, and how sum-product message passing computes marginals by local arithmetic.
2

Bayesian networks and factorisation

One small table per node, and the chain rule does the rest

Start with a joint distribution over $x_1,\dots,x_n$ and apply the chain rule in any order you like. Pick an order that respects the causal story: $p(x_1,\dots,x_n)=\prod_i p(x_i\mid x_1,\dots,x_{i-1})$. That identity is exact but useless, because each conditional still mentions everything before it. The modelling step is to assert that most of those conditionals do not actually depend on most of those predecessors. The variable WetGrass depends on Rain and Sprinkler; whether it is wet has nothing more to say once you know those two, regardless of the season or the barometer. Strike the irrelevant arguments and you are left with a set of parents $\mathrm{pa}(i)$ and the defining equation of a Bayesian network.

$$p(x_1,\dots,x_n)=\prod_{i=1}^{n} p\bigl(x_i \mid x_{\mathrm{pa}(i)}\bigr).$$

Read the equation as a storage budget. Each factor $p(x_i\mid x_{\mathrm{pa}(i)})$ is a table indexed by x_i and its parents. A binary node with no parents costs one number; a binary node with one parent costs two; with two parents, four. In the classic four-node weather model — Cloudy feeding Sprinkler and Rain, both of which feed WetGrass — the network stores 1+2+2+4=9 numbers instead of the 15 a full joint over four binary variables would need. The saving looks modest here and becomes the difference between feasible and impossible at thirty variables, where the network might store a few hundred numbers against a billion.

The arrows are not decoration. If you draw one from A to B, the factorisation includes A in B's conditional; if you do not, it excludes it. A missing arrow is a positive claim: x_i is conditionally independent of that variable given its parents. This is the local Markov property, and it is where all the graph-reading rules in the next section come from. A node is independent of everything that is neither its parent, its child, nor a parent of one of its children, once you condition on those neighbours.

The direction of the arrows is a choice, not a fact about the world. Any DAG that encodes the same conditional independencies describes the same distribution; you can reverse an arrow if you pay for it by enlarging a conditional. But causal directions are usually the ones that keep the tables small, because causes tend to be roughly independent of each other while their effects are correlated. Drawing arrows from cause to effect is therefore a compression strategy as much as a statement of belief.

Parameters needed for a full joint over n binary variables (magenta) against the same variables arranged in a chain, each depending only on the one before (blue). The vertical axis is logarithmic, so each step up is a factor of ten.

Notice that the blue bars are almost flat on this scale. A chain over n binary variables needs 1+2(n-1) numbers, so doubling the number of variables roughly doubles the storage, while the full joint squares it. That is the entire promise of graphical models in one picture: the graph buys you a joint that grows linearly instead of exponentially, at the cost of asserting a structure you must be willing to defend.

3

d-separation and active paths

Independence you can read off the picture

Suppose you want to know whether X and Y are independent given a set of observed variables Z. In a Bayes net you answer this without touching a single probability: walk the graph. Look at every undirected path from X to Y and ask whether information could flow along it. If every path is blocked, the variables are d-separated given Z, and d-separation implies conditional independence in every distribution that factorises according to the graph.

A path is a sequence of distinct nodes connected by edges, traversed in either direction. Each interior node on the path is either a collider, with both path edges pointing into it, or a non-collider, with at least one edge pointing out. The blocking rules are different for the two cases, and that asymmetry is the heart of the subject.

chain$X\to M\to Y$. The middle node M is a non-collider. Observing M blocks the path: once you know the mediator, X has nothing more to say about Y.
fork$X\leftarrow M\to Y$. The common cause M is also a non-collider. Observing M again blocks the path: two symptoms of the same cause become independent once the cause is known.
collider$X\to M\leftarrow Y$. Now both edges point in. The path is blocked by default, and conditioning on M or any of its descendants opens it. This is the opposite of the previous two rules.
$$X \perp\!\!\perp_G Y \mid Z \quad\Longleftrightarrow\quad \text{every path }X\text{--}Y\text{ is blocked by }Z,\qquad \text{collider open}\iff \{M\}\cup\mathrm{desc}(M)\cap Z\ne\varnothing.$$

The collider rule is the one that feels wrong until it clicks. Two independent causes, a sprinkler and rain, each make the grass wet. Before you look outside, learning that the sprinkler ran tells you nothing about the rain. But if you observe that the grass is wet, the two causes are suddenly correlated: rain becomes less likely once you learn the sprinkler was on, because the sprinkler already accounts for the evidence. Information flowed between two variables that had no arrow between them, and it flowed because we conditioned on their shared effect.

The canvas below lets you build this intuition by hand. It shows the four-node weather network: Cloudy is a fork into Sprinkler and Rain, and those two are a collider into WetGrass. Click any node to mark it observed, choose two query nodes, and the page colours each path green when active and red when blocked, then reports whether the queries are d-separated. Try observing Cloudy with Sprinkler and Rain as queries, then clear it and observe WetGrass instead.

Click a node to toggle whether it is observed. Green paths carry information; red dashed paths are blocked. The dark ring marks the two query nodes.

Two warnings about reading too much into the picture. First, d-separation is sufficient for independence but not necessary: a particular distribution may have independencies that its graph does not show, and then the graph is merely not the tightest description. Second, the rules apply to the graph as drawn, so if you reverse an arrow the d-separation verdicts can change even though the distribution has not. The graph is a tool for deriving consequences of a factorisation, and different graphs of the same distribution derive different (all valid) consequences.

4

Explaining away

The collider, measured

Here is the smallest possible collider model. Two independent binary causes A and B, each with its own prior, both feed a binary effect C. The network asserts $p(a,b,c)=p(a)\,p(b)\,p(c\mid a,b)$. Because the joint factorises as a product of a term in a alone and a term in b alone, the marginals satisfy p(a,b)=p(a)p(b) exactly: A and B are independent. No conditioning on C has happened yet, and none is needed.

Now condition on the effect. By Bayes' rule, $p(a,b\mid c)\propto p(a)p(b)p(c\mid a,b)$, and the third factor couples a to b unless c is independent of one of them given the other. In general it is not, so A and B become dependent once C is known. The strength and direction of that dependence are exactly what the sliders below control. Watch three numbers as you move them: the marginal probabilities, which never move; the conditional probabilities given C=1, which both rise when the effect is a noisy OR; and the conditional probability of A given both C=1 and B=1, which falls back below the value given C=1 alone.

$$p(a,b\mid c)=\frac{p(a)\,p(b)\,p(c\mid a,b)}{p(c)},\qquad p(a\mid c,b)=\frac{p(a)p(b)p(c\mid a,b)}{\sum_{a'}p(a')p(b)p(c\mid a',b)}.$$

That last drop is explaining away. Once you already know the sprinkler was on, the wet grass is explained, and the evidence no longer supports rain as strongly. Nothing in the arithmetic knows about causes or explanations; it is a consequence of dividing two sums that share a factor. The same computation is why a medical test result becomes less alarming once a second, unrelated condition that produces the same marker is confirmed, and why a fault diagnosis system has to reason about multiple simultaneous faults rather than assuming the simplest single explanation.

There is a numerical signature of the effect that is worth carrying away. Define the conditional mutual information $I(A;B\mid C)$. In a collider it is strictly positive whenever the effect depends on both causes, while the plain mutual information I(A;B) is exactly zero. The readout below reports both a difference-of-products test and this mutual information, so you can see the independence at the top of the model and the dependence created at the bottom in the same units.

Bars are probabilities of the event being one. Grey is the prior, blue is conditioned on C=1, magenta is A=1 given both C=1 and B=1.

Set the two middle sliders equal and the collider is symmetric; make them different and the two causes explain away at different rates. Push $P(C=1\mid A=0,B=0)$ toward its maximum and the effect fires regardless, so C carries little information and the conditional dependence shrinks. Push it to the minimum and C=1 becomes almost a proof that at least one cause fired, which is precisely the regime where explaining away is strongest.

5

Factor graphs and message passing

The same model, with the arrows removed and the algorithm revealed

The directed graph is convenient for reading independencies, but the algorithm that computes marginals does not care about direction. Write the joint as a product of factors, each a function of a small set of variables. The directed factorisation $\prod_i p(x_i\mid x_{\mathrm{pa}(i)})$ is already such a product, one factor per node. A factor graph is a bipartite graph with a circle for every variable and a square for every factor, connected exactly when the variable appears in the factor. Nothing is lost and a great deal is gained: the directed structure has become an undirected wiring diagram of the arithmetic.

The classic computation on this graph is the sum-product algorithm. Its rule is local. A variable sends to each factor it touches the product of the messages it received from all the other factors. A factor sends to each variable the sum over all its other variables of the factor value times the incoming messages. Compose the rules and every variable receives, from each factor, a summary of everything the rest of the graph knows about that variable.

$$\mu_{v\to f}(x_v)=\!\!\prod_{g\in\mathrm{nb}(v)\setminus\{f\}}\!\!\mu_{g\to v}(x_v),\qquad \mu_{f\to v}(x_v)=\sum_{x_{\mathrm{nb}(f)\setminus v}} f\bigl(x_{\mathrm{nb}(f)}\bigr)\!\!\prod_{u\in\mathrm{nb}(f)\setminus v}\!\!\mu_{u\to f}(x_u).$$

For the collider model, absorb the two priors into a single factor $f(a,b,c)=p(a)p(b)p(c\mid a,b)$. The factor graph then has three variable circles and one factor square, connected to all three. The message from the factor to A is $\mu_{f\to A}(a)=\sum_{b,c} f(a,b,c)\,\mu_{B\to f}(b)\,\mu_{C\to f}(c)$, and the marginal is proportional to that message. It is a two-dimensional sum, not a five-dimensional one, and if the graph were a long chain the same local rule would cost a constant amount per node instead of an exponential amount per query. That is the entire content of the phrase "inference is local".

On trees the algorithm terminates in two sweeps and the messages are exact marginals. On graphs with cycles the same updates can be run until they settle, which is loopy belief propagation; it has no general guarantee but works well enough in practice that it underpins error-correcting codes and much of the inference in vision. The reason trees are special is that a tree has no path that carries information around a loop and back, so no message can depend on itself. Loops reintroduce the circularity that the whole graph was supposed to remove.

The Bayes net on the left, the same model as a factor graph on the right. Blue arrows are variable-to-factor messages; magenta arrows are factor-to-variable. Use the buttons to isolate one sweep.

One translation detail is worth stating because it trips people up. A directed graph and a factor graph of the same distribution are not in one-to-one correspondence: several factor graphs can describe the same distribution, and converting a Bayes net to a factor graph loses the directionality permanently. What is preserved is the set of conditional independencies implied by the factorisation, which is why the two pictures answer the same d-separation questions. The factor graph is the better picture for the algorithm; the Bayes net is the better picture for the story.

6

Where this shows up

Graphs as the default modelling language

Vision

Pose graphs and SLAM

A simultaneous-localisation-and-mapping problem is a factor graph whose variables are robot poses and landmark positions, and whose factors are the measurements between them. The pose graph part of the vision guide solves exactly this structure, and the loop-closure problem there is a loopy graph in the sense above; the SLAM overview places the smoothing and filtering views side by side.

Math

Conditional independence

Everything on this page is a machine for generating conditional independence statements. The base case — what $X\perp Y$ means and why it makes the joint a product — is developed in independence. d-separation is the generalisation of that idea from two variables to a whole graph.

Robotics

Filters as chains

A hidden Markov model is a chain graph: the hidden state at each step depends only on the previous state, and each observation only on its own state. Forward–backward is sum-product on that chain, which is why it runs in linear time. The next part builds it explicitly, and the filtering recurrences there are the messages of this section specialised to a line.

AI / ML

Latent-variable models

Mixture models, variational autoencoders and diffusion models are all graphs in which a small set of latent variables generates the observations. When exact messages are intractable, variational inference replaces them with an optimisation, which is the subject of latent variables.

7

Cheat sheet

Every formula in one place

IdeaFormulaReading
Factorisation$p(x_1,\dots,x_n)=\prod_i p(x_i\mid x_{\mathrm{pa}(i)})$One small conditional table per node.
Chain$X\to M\to Y$Non-collider. Observing M blocks the path.
Fork$X\leftarrow M\to Y$Non-collider. Observing the common cause blocks the path.
Collider$X\to M\leftarrow Y$Blocked by default; observing M or a descendant opens it.
d-separation$X\perp\!\!\perp Y\mid Z$Every path blocked by the observations.
Explaining away$p(a\mid c,b)\ne p(a\mid c)$A second cause lowers the first cause's posterior.
Conditional MI$I(A;B\mid C)>0$ for a colliderMarginal MI is zero; the effect creates the dependence.
Variable message$\mu_{v\to f}=\prod_{g\ne f}\mu_{g\to v}$Pass on everything except what came from f.
Factor message$\mu_{f\to v}=\sum_{x_{\mathrm{nb}(f)\setminus v}} f\prod_{u\ne v}\mu_{u\to f}$Multiply locally, sum out the rest.
Marginal$p(x_v)\propto\prod_{f}\mu_{f\to v}(x_v)$On a tree, exact after two sweeps.
8

Further reading

Where to go deeper

9

Check your understanding

0/6 answered