- This notebook teaches how to estimate posteriors by sampling, when exact inference is too expensive or impossible
- Concepts are built on the Garden Wold examples with variables to run the query
- The flow is:
- Turn uniform randomness into samples (inverse transform)
- Sample a whole network (prior sampling)
- Watch estimates converge ()
- Condition on evidence by rejection
- Rescue rare evidence with importance weights
- Walk the state space with MCMC (mixing, Gibbs, Metropolis-Hastings)
- The exact posteriors from
pgmpyare reused throughout as the ground-truth reference that every estimate is compared against
Approximate Inference¶
Imports¶
%load_ext autoreload
%autoreload 2
# System libraries.
import logging
# Third-party libraries.
import matplotlib.pyplot as plt
import seaborn as sns# Use this for most notebooks.
import helpers.htutorial as htutori
import L06_02_approximate_inference_utils as utils
htutori.config_notebook()
# Initialize logger.
_LOG = logging.getLogger(__name__)
utils.init_loggers(_LOG)
# Convert `display` into `print()` when running outside IPython.
try:
from IPython.display import display
except ImportError:
display = print # type: ignoreINFO Setting notebook style
INFO Notebook signature
Python 3.12.13
Linux 7f4d4ef8cf8c 6.12.67-linuxkit #1 SMP Sun Jan 25 02:26:28 UTC 2026 aarch64 GNU/Linux
INFO numpy version=2.4.6
WARNING pymc is not installed
INFO matplotlib version=3.10.9
WARNING arviz is not installed
WARNING preliz is not installed
WARNING sns is not installed
WARNING: Logger already initialized: skipping
Part 1: From Randomness to Samples¶
Cell 1.1: Turning Uniform Randomness into Any Distribution¶
Goal:
- Show that a single stream of uniform numbers becomes samples from any distribution via the inverse CDF
Plots:
- Target distribution: a discrete biased die or a continuous exponential
- CDF and inverse map: the CDF with a sampled mapped to its
- Sample histogram: generated samples (solid) vs the target (dotted)
- Comments: the construction and the achieved accuracy
Parameters:
Target: biased die (discrete) vs exponential (continuous)lambda(): rate of the exponentialN(): number of samples drawnseed: random seed
Key observations:
- Every sampler ultimately consumes uniform numbers; the CDF is the adapter
- For discrete targets find the smallest with ; for the exponential invert in closed form,
- Larger fills the histogram in toward the target
# TODO(ai_gp): Split this into two cells one for discrete die and the other for the exponentials. Also update the markdown above.
# Reuse the code as much as possible.
# Map a uniform r through the CDF into a sample from the chosen target.
utils.cell1_1_inverse_transform_widget()Loading...
- One trick underlies everything: stretch a flat number through the CDF and it comes out distributed like the target
- Sampling a network is just doing this many times in the right order
- When has no closed form the same idea works with numerical inversion
Cell 1.2: Prior Sampling from the Sprinkler Network¶
Goal:
- Scale the single-variable trick up to a whole Bayesian network
- Sample variables in topological order to generate full events with no evidence
- Compare estimated frequencies with the exact joint
Plots:
- DAG: the sprinkler network, colored by topological depth
- Marginal: estimated of a tracked variable vs its exact value
- Joint frequencies: estimated joint over all 16 configurations vs exact
- Comments: the sampling order and the achieved errors
Parameters:
N(): number of full events to generateTrack marginal: variable whose marginal estimate is trackedseed: random seed
Key observations:
- Topological order guarantees every parent has a value before its child
- Prior sampling realizes the factorization
- The relative frequency of an event approximates its joint probability
# Generate N complete worlds in topological order and compare with the exact joint.
utils.cell1_2_prior_sampling_widget()- Prior sampling is inverse-transform sampling, once per node, parents first
- The fraction of samples equal to an event estimates that event’s joint probability
- With more samples both the marginal and the joint estimates sharpen
Cell 1.3: Consistency and the 1/sqrt(N) Convergence Rate¶
Goal:
- Make convergence tangible and show estimates are consistent
- Show that error shrinks like , setting expectations for every later sampler
Plots:
- One estimate: a single running estimate converging to the exact value
- Independent chains: a fan of chains (different seeds) narrowing with
- Error vs N: absolute error on log-log axes with a reference slope
- Comments: the exact value and the final error
Parameters:
max N(): largest sample count shownreps: number of independent chains in the fanEstimate: which marginal event is being estimatedseed: random seed
Key observations:
- Estimates are consistent:
- A slope of on log-log axes is the signature of Monte Carlo
- Accuracy is expensive, which motivates smarter samplers
# Show one estimate, many estimates, and the 1/sqrt(N) error rate.
utils.cell1_3_convergence_widget()- Sampling is consistent but slow to sharpen
- The law is unavoidable for plain Monte Carlo
- This is why the rest of the notebook focuses on using each sample better
Part 2: Conditioning on Evidence¶
Cell 2.1: Rejection Sampling¶
Goal:
- Introduce the simplest way to condition on evidence
- Generate prior samples and throw away those that disagree with the evidence
- Expose the central weakness when evidence is rare
Plots:
- Sample stream: dots colored by kept (matches evidence) vs rejected
- Retained fraction: counts of generated, rejected, and kept samples
- Posterior estimate: vs the exact reference
- Comments: the retained fraction and the estimate
Parameters:
N(): total prior samples generatedQuery X: the query variableobserve <node>: evidence selection and valueseed: random seed
Key observations:
- Rejection sampling is consistent: kept samples are distributed as
- The retained fraction equals
- The effective sample size, not , controls accuracy
# Keep only the prior samples that agree with the evidence.
utils.cell2_1_rejection_sampling_widget()- Correct but wasteful: only samples that already agree with the evidence are kept
- The rarer the evidence, the more samples are burned to learn anything
- This motivates keeping every sample and correcting with weights
Cell 2.2: Importance Sampling and Likelihood Weighting¶
Goal:
- Fix rejection’s waste by keeping every sample and correcting with weights
- Show likelihood weighting as the Bayesian-network instance of the idea
- Watch for weight collapse via the effective sample size
Plots:
- Weighted samples: dots sized by importance weight
- Weight distribution: histogram of weights, flagging collapse
- Posterior estimate: weighted estimate vs exact and vs rejection
- Comments: the effective sample size and the estimates
Parameters:
N(): number of weighted samplesQuery X: the query variableobserve <node>: evidence selection and valueseed: random seed
Key observations:
- Drawing from an easier and weighting by stays unbiased
- Every sample is kept, so no work is discarded
- Very uneven weights shrink the effective sample size despite a large
# Keep every sample and correct the bias with importance weights.
utils.cell2_2_likelihood_weighting_widget()- Reweight instead of reject: importance sampling spends every sample
- It focuses effort where the evidence lives
- It only helps if the weights stay reasonably balanced
Part 3: Markov Chain Monte Carlo¶
Cell 3.1: Markov Chains and the Stationary Distribution¶
Goal:
- Introduce the core MCMC idea, a designed random walk over states
- Show its long-run distribution settles to a fixed stationary shape
- Show the limit is independent of where the walk starts
Plots:
- Transition diagram: states as nodes with the current state highlighted
- State distribution: (solid) settling onto the stationary (dotted)
- Convergence: total-variation distance to stationary decaying with
- Comments: the stationary distribution and current distance
Parameters:
t: number of steps takenInitial state: where the walk startsseed: random seed
Key observations:
- A Markov chain is memoryless: the next state depends only on the current one
- Under ergodicity and aperiodicity converges to a unique stationary distribution
- MCMC builds a chain whose stationary distribution is the posterior
# Step the chain and watch the state distribution converge to a fixed shape.
utils.cell3_1_markov_chain_widget()- The magic link: design the walk so the posterior is its equilibrium
- Then just walk and count where the chain lands
- Convergence happens regardless of the starting state
Cell 3.2: Mixing and Burn-in¶
Goal:
- Show that a correct stationary distribution is not enough
- Show that mixing speed determines whether finite-sample estimates are trustworthy
- Introduce burn-in as discarding the chain’s wandering start
Plots:
- Trace: the sampled value over iterations with the burn-in region shaded
- Collected samples: histogram vs the true bimodal posterior
- Autocorrelation: autocorrelation vs lag, high for poor mixing
- Comments: the mixing diagnostics
Parameters:
step: proposal step size controlling mixing qualityburnin: number of initial samples discardedN(): total iterationsseed: random seed
Key observations:
- Good mixing moves between modes often with low correlation
- Early samples reflect the arbitrary start and are discarded as burn-in
- Poor mixing gives biased, high-variance estimates even with a correct target
# Tune the step size between poor and good mixing and set the burn-in.
utils.cell3_2_mixing_burnin_widget()- Right target, wrong speed: a chain can be correct in the limit yet useless
- Watch the trace and the autocorrelation, not just the final histogram
- Small steps stay stuck in one mode; large steps explore both
Cell 3.3: Gibbs Sampling and the Markov Blanket¶
Goal:
- Specialize MCMC to Bayesian networks with Gibbs sampling
- Resample one variable at a time from its Markov blanket
- Hold evidence clamped so every sample is consistent with it
Plots:
- DAG: evidence frozen, the resampled variable and its blanket highlighted
- Full conditional: being sampled from
- Running estimate: vs the exact reference
- Comments: the hidden variables and the estimate
Parameters:
sweeps: number of Gibbs sweepsburnin: burn-in sweeps discardedQuery Xandobserve <node>: query and evidenceseed: random seed
Key observations:
- Gibbs only needs the local conditional
- Evidence variables stay clamped, so no rejection is needed
- It is simple and local but mixes slowly under strong correlations
# Resample each hidden variable from its Markov blanket with evidence clamped.
utils.cell3_3_gibbs_sampling_widget()- Gibbs sampling is MCMC made local: resample one variable from its blanket
- Easy to code and scales to large graphs via local updates
- Watch out for slow mixing under strong correlations
Cell 3.4: Metropolis-Hastings and Accept/Reject Moves¶
Goal:
- Generalize beyond Gibbs to Metropolis-Hastings
- Correct an arbitrary proposal with an acceptance probability
- Unify proposing, accepting, and exploring
Plots:
- Last proposed move: current vs proposed state with acceptance probability
- Trace: accepted states over iterations with the acceptance rate
- Running estimate: vs the exact reference
- Comments: the proposal mix, acceptance rate, and estimate
Parameters:
p_local: probability of a local single-variable move vs a broad jumpiters: number of iterationsburnin: burn-in iterations discardedseed: random seed
Key observations:
- Metropolis-Hastings sometimes accepts downhill moves, escaping local modes
- The acceptance ratio guarantees the posterior is the stationary distribution
- Gibbs sampling is the special case where every proposal is accepted
# Propose a move, then accept or reject it by the Hastings ratio.
utils.cell3_4_metropolis_hastings_widget()- Propose anything, then correct with the Hastings ratio
- Flexibility is the prize and the cost: any proposal is valid, but a bad one mixes slowly
- Gibbs is just the case where you always accept