AI-320 · Lesson 7 of 12 · 30 min

Monte Carlo sampling

Almost every quantity of interest in Bayesian inference is an expectation under the posterior: the posterior mean is \(\mathbb E[\theta \mid D]\), a predictive probability is \(\mathbb E[p(\tilde x \mid \theta)]\), a credible interval comes from posterior quantiles, and the probability that B beats A is \(\mathbb E[\mathbf 1(p_B > p_A)]\). Lessons 1 and 3 computed these exactly for conjugate models and small discrete networks. Realistic models have posteriors with no closed form, over thousands or millions of dimensions, where exact integration is impossible. Monte Carlo methods replace integrals by averages over random samples. This lesson covers basic Monte Carlo estimation, direct sampling methods, importance sampling, Markov chain Monte Carlo (MCMC) with the Metropolis-Hastings and Gibbs algorithms, convergence diagnostics, and Hamiltonian Monte Carlo, the engine of modern probabilistic programming.

Monte Carlo estimation

If \(x^{(1)}, \ldots, x^{(S)}\) are independent samples from \(p(x)\), then for any function \(f\)

\[\mathbb{E}_p[f(x)] = \int f(x)\,p(x)\,dx \approx \hat f_S = \frac{1}{S}\sum_{s=1}^{S} f(x^{(s)})\]

The estimate is unbiased, and by the central limit theorem its standard error is \(\sigma_f/\sqrt S\), where \(\sigma_f\) is the standard deviation of \(f(x)\). Two facts make Monte Carlo powerful: the error does not depend on the dimension of \(x\), unlike grid-based numerical integration, whose cost grows exponentially with dimension; and the error can be estimated from the samples themselves. The price is slow convergence: each extra decimal digit of accuracy costs 100 times as many samples.

Monte Carlo estimation of pi, and why naive sampling fails in high dimensions
import numpy as np

rng = np.random.default_rng(0)
for S in [100, 10_000, 1_000_000]:
    pts = rng.uniform(-1, 1, size=(S, 2))
    inside = (pts ** 2).sum(axis=1) <= 1
    est = 4 * inside.mean()
    se = 4 * inside.std(ddof=1) / np.sqrt(S)
    print(f"S = {S:>9,}:  pi estimate {est:.5f}  standard error {se:.5f}  actual error {abs(est - np.pi):.5f}")

# the same estimator in 20 dimensions: fraction of the cube inside the unit ball
d, S = 20, 1_000_000
pts = rng.uniform(-1, 1, size=(S, d))
print(f"fraction of 20-D cube inside the ball: {np.mean((pts ** 2).sum(axis=1) <= 1):.2e}  (exact 2.46e-08)")
Output
S =       100:  pi estimate 2.92000  standard error 0.17848  actual error 0.22159
S =    10,000:  pi estimate 3.16360  standard error 0.01627  actual error 0.02201
S = 1,000,000:  pi estimate 3.14152  standard error 0.00164  actual error 0.00007
fraction of 20-D cube inside the ball: 0.00e+00  (exact 2.46e-08)

The standard error shrinks by a factor of 10 for every 100-fold increase in samples, and it correctly predicts the typical size of the actual error. The second experiment shows the catch: the error is independent of dimension only relative to the standard deviation of \(f\). In 20 dimensions, the unit ball fills only \(2.5 \times 10^{-8}\) of the cube, so not one of a million uniform samples lands inside it and the estimate is exactly zero. Sampling from the wrong distribution wastes nearly all samples, which is why the rest of this lesson is about drawing samples from the distribution that matters.

Sampling from simple distributions

Inverse transform sampling

If \(u\) is uniform on (0, 1) and \(F\) is a cumulative distribution function, then \(x = F^{-1}(u)\) has distribution \(F\). For the exponential distribution with rate \(\lambda\), \(F(x) = 1 - e^{-\lambda x}\), so \(x = -\log(1 - u)/\lambda\). Inverse transforms are exact and efficient when \(F^{-1}\) is available; standard libraries use them, along with specialized transformations such as the Box-Muller method for normal variables, to sample all the common distributions.

Rejection sampling

To sample from a density \(p(x) = \tilde p(x)/Z\) known only up to its normalizing constant, choose a proposal \(q(x)\) that is easy to sample and a constant \(M\) with \(\tilde p(x) \le Mq(x)\) everywhere. Draw \(x \sim q\) and \(u \sim U(0, 1)\), and accept \(x\) if \(u < \tilde p(x)/(Mq(x))\). Accepted samples are exact draws from \(p\). The acceptance rate is \(Z/M\), so rejection sampling works only when \(q\) matches \(p\) closely. In high dimensions even small mismatches compound across dimensions, \(M\) becomes astronomically large, and almost every proposal is rejected.

Plot of a bimodal target density, unnormalized, under a scaled proposal envelope M times q of x, a broad Gaussian that lies everywhere above the target. Random points uniformly scattered under the envelope are shown: points below the target curve are accepted and drawn filled; points between the target and the envelope are rejected and drawn as open circles.
Rejection sampling: points under the envelope are accepted if they also lie under the target.

Importance sampling

Instead of rejecting samples, importance sampling keeps them all and weights them. For samples \(x^{(s)} \sim q\),

\[\mathbb{E}_p[f(x)] = \mathbb{E}_q\!\left[f(x)\frac{p(x)}{q(x)}\right] \approx \frac{1}{S}\sum_s w_s\,f(x^{(s)}), \qquad w_s = \frac{p(x^{(s)})}{q(x^{(s)})}\]

If \(p\) is known only up to a constant, the self-normalized estimator divides by \(\sum_s w_s\) instead of \(S\). Importance sampling is valuable for rare events, where a good proposal places samples in the important region, and for reweighting samples from one distribution to answer questions about another, as in off-policy evaluation in reinforcement learning. Its weakness is weight degeneracy: if \(q\) has lighter tails than \(p\) or is poorly matched, a few samples carry nearly all the weight and the estimate is unreliable. The effective sample size, \(\mathrm{ESS} = (\sum w_s)^2/\sum w_s^2\), measures how many equally weighted samples the weighted set is worth.

Importance sampling for a rare tail probability
import numpy as np
from scipy.stats import norm

rng = np.random.default_rng(1)
S = 10_000
exact = norm.sf(4.5)                                     # P(X > 4.5) for X ~ N(0, 1)

x = rng.normal(size=S)                                   # naive Monte Carlo
naive = np.mean(x > 4.5)

y = rng.normal(loc=4.5, size=S)                          # proposal centred on the rare region
w = norm.pdf(y) / norm.pdf(y, loc=4.5)
vals = w * (y > 4.5)
imp = vals.mean()
se = vals.std(ddof=1) / np.sqrt(S)
ess = w.sum() ** 2 / (w ** 2).sum()

print(f"exact               {exact:.3e}")
print(f"naive MC            {naive:.3e}  (number of hits: {int(np.sum(x > 4.5))})")
print(f"importance sampling {imp:.3e}  +/- {se:.1e}")
print(f"ESS of the weights for estimating the whole distribution: {ess:.1f} of {S}")
Output
exact               3.398e-06
naive MC            0.000e+00  (number of hits: 0)
importance sampling 3.464e-06  +/- 7.8e-08
ESS of the weights for estimating the whole distribution: 8.5 of 10000

Naive Monte Carlo with 10,000 samples almost never sees an event with probability \(3.4 \times 10^{-6}\), while importance sampling with a shifted proposal estimates it to within about 2%, consistent with its reported standard error. The tiny ESS shows the other side: the same weighted samples would be useless for estimating anything about the bulk of \(p\). A proposal must be matched to the question being asked.

Markov chain Monte Carlo

Rejection and importance sampling need a proposal that already resembles the target, which is impractical for complex posteriors in many dimensions. MCMC takes a different approach: it constructs a Markov chain that wanders through the parameter space and whose long-run distribution of states is the target \(p(x)\). Each state depends on the previous one, so the samples are correlated, but averages over a long run still converge to expectations under \(p\).

A Markov chain with transition kernel \(T(x' \mid x)\) leaves \(p\) invariant if \(\int T(x' \mid x)\,p(x)\,dx = p(x')\). A simple sufficient condition is detailed balance:

\[p(x)\,T(x' \mid x) = p(x')\,T(x \mid x')\]

meaning that, at equilibrium, the flow of probability from \(x\) to \(x'\) equals the flow back. If the chain also can reach every region (irreducibility) and does not cycle periodically, it converges to \(p\) from any starting point.

Metropolis-Hastings

The Metropolis-Hastings algorithm builds such a chain from any proposal distribution \(q(x' \mid x)\). From the current state \(x\), propose \(x' \sim q(x' \mid x)\) and accept it with probability

\[A(x \to x') = \min\!\left(1,\; \frac{\tilde p(x')\,q(x \mid x')}{\tilde p(x)\,q(x' \mid x)}\right)\]

otherwise stay at \(x\) (and count \(x\) again as the next sample). Only the ratio \(\tilde p(x')/\tilde p(x)\) is needed, so the normalizing constant, which is the hard part of Bayesian inference, cancels. With a symmetric proposal such as a Gaussian random walk, \(q\) cancels too, giving the original Metropolis algorithm: always move uphill, and move downhill with probability equal to the density ratio.

The proposal scale controls efficiency. Tiny steps are almost always accepted but explore slowly, like a random walk with short strides; huge steps propose distant points that are almost always rejected, so the chain sits still. For random-walk Metropolis in high dimensions, theory suggests tuning the scale for an acceptance rate around 23%, and around 44% in one dimension.

Random-walk Metropolis on a correlated Gaussian with four proposal scales
import numpy as np

def log_target(x):                       # correlated 2-D Gaussian, correlation 0.9
    P = np.linalg.inv(np.array([[1.0, 0.9], [0.9, 1.0]]))
    return -0.5 * x @ P @ x

def ess(chain):                          # effective sample size from autocorrelations
    x = chain - chain.mean(); n = len(x)
    acf = np.correlate(x, x, mode="full")[n - 1:] / (x @ x)
    s = 0.0
    for k in range(1, n // 2, 2):        # sum pairs while positive (Geyer)
        pair = acf[k] + acf[k + 1]
        if pair < 0: break
        s += pair
    return n / (1 + 2 * s)

rng = np.random.default_rng(2)
for step in [0.05, 0.5, 1.2, 5.0]:
    x = np.zeros(2); lp = log_target(x); chain = []; acc = 0
    for _ in range(20_000):
        prop = x + step * rng.normal(size=2)
        lp_prop = log_target(prop)
        if np.log(rng.uniform()) < lp_prop - lp:
            x, lp = prop, lp_prop; acc += 1
        chain.append(x[0])
    chain = np.array(chain[2000:])
    print(f"step {step:4.2f}: acceptance {acc / 20_000:5.1%}  mean {chain.mean():+.3f}  "
          f"var {chain.var():.3f}  ESS {ess(chain):7.0f} of {len(chain)}")
Output
step 0.05: acceptance 94.7%  mean -0.109  var 0.709  ESS       5 of 18000
step 0.50: acceptance 53.9%  mean -0.042  var 0.998  ESS     273 of 18000
step 1.20: acceptance 25.8%  mean -0.019  var 0.964  ESS     823 of 18000
step 5.00: acceptance  3.4%  mean -0.020  var 1.025  ESS     229 of 18000

The true mean is 0 and the variance is 1. The smallest step accepts almost every proposal but moves so little that 18,000 samples are worth only a handful of independent ones, and its estimates are poor. The largest step rejects nearly everything. The intermediate scales, with acceptance rates of about 25 to 55%, give far more effective samples, and the best of the four, at 26% acceptance, is close to the theoretical optimum. Even the best scale yields an ESS far below the chain length, because the strong correlation forces small steps along the narrow direction; this is the motivation for the gradient-based methods below.

Three trace plots of a sampled parameter against iteration for 2000 iterations. With a tiny step the trace wanders slowly like a random walk and does not cover the distribution. With a well-tuned step the trace looks like dense noise around zero, rapidly crossing the whole range. With a huge step the trace is flat for long stretches with occasional jumps, showing frequent rejections.
Trace plots for step sizes that are too small, well tuned and too large.

Gibbs sampling

Gibbs sampling updates one variable (or block of variables) at a time by sampling it from its full conditional distribution given all the others, \(x_i \sim p(x_i \mid \mathbf x_{-i})\). It is a special case of Metropolis-Hastings whose acceptance probability is always 1. In a graphical model, the full conditional depends only on the variable's Markov blanket (Lesson 3), and in conjugate models it often has a standard form, so Gibbs sampling needs no tuning. It was the workhorse of Bayesian computation for decades, through software such as BUGS and JAGS, and is still used for topic models and many hierarchical models.

Its weakness is strongly correlated variables: updating one coordinate at a time with the other held fixed allows only tiny moves along a narrow ridge, so the chain mixes slowly.

Gibbs sampling of a bivariate normal: mixing slows as correlation grows
import numpy as np

rng = np.random.default_rng(3)
for rho in [0.0, 0.9, 0.99]:
    x = y = 0.0; xs = []
    sd = np.sqrt(1 - rho ** 2)
    for _ in range(20_000):
        x = rng.normal(rho * y, sd)          # x | y ~ N(rho y, 1 - rho^2)
        y = rng.normal(rho * x, sd)          # y | x ~ N(rho x, 1 - rho^2)
        xs.append(x)
    xs = np.array(xs[1000:])
    lag1 = np.corrcoef(xs[:-1], xs[1:])[0, 1]
    print(f"rho = {rho:4.2f}: lag-1 autocorrelation {lag1:.3f}  (theory {rho ** 2:.3f})  "
          f"sample variance {xs.var():.3f}")
Output
rho = 0.00: lag-1 autocorrelation -0.001  (theory 0.000)  sample variance 0.988
rho = 0.90: lag-1 autocorrelation 0.807  (theory 0.810)  sample variance 0.993
rho = 0.99: lag-1 autocorrelation 0.980  (theory 0.980)  sample variance 0.969

For the bivariate normal, the lag-1 autocorrelation of the Gibbs chain is \(\rho^2\). At \(\rho = 0.99\) it is 0.98, so roughly 100 iterations are needed per effectively independent sample. Reparameterizing to reduce posterior correlations, or updating correlated variables together in blocks, are the standard remedies.

Diagnosing convergence

An MCMC run produces numbers whatever happens, so checking that they represent the target is essential. Standard practice:

  • Burn-in (warm-up): discard the early part of each chain, before it reaches the high-probability region from its arbitrary starting point. Modern samplers also use warm-up to tune step sizes.
  • Trace plots: plot each parameter against iteration. A healthy chain looks like stationary noise ("a fuzzy caterpillar"); trends, long flat stretches or slow wandering indicate problems.
  • Multiple chains from dispersed starting points: if they agree, there is some confidence they have found the same distribution. The potential scale reduction factor \(\hat R\) compares the variance between chains with the variance within them; values above about 1.01 signal that the chains have not mixed.
  • Effective sample size: because samples are autocorrelated, \(S\) correlated draws carry the information of \(\mathrm{ESS} = S/(1 + 2\sum_k \rho_k)\) independent ones, where \(\rho_k\) is the lag-\(k\) autocorrelation. The Monte Carlo standard error of a posterior mean is \(\sigma/\sqrt{\mathrm{ESS}}\). A few hundred effective samples per quantity is usually enough for means and central intervals; tail quantiles need more.

Diagnostics can show that something is wrong but never prove that a chain has converged: a multimodal posterior can trap every chain in one mode, and all diagnostics then look perfect. Thinning (keeping every \(k\)th sample) saves storage but does not improve estimates.

Autocorrelation function plots against lag from 0 to 50 for two chains. A well-mixing chain has autocorrelation falling to near zero within about 5 lags. A poorly mixing chain decays slowly, still above 0.5 at lag 50. Each panel is annotated with its effective sample size as a fraction of the chain length.
Autocorrelation functions: fast decay means many effective samples; slow decay means few.

Worked problem

Effective sample size and standard error

A chain of 10,000 samples of a parameter has lag-\(k\) autocorrelations approximately \(0.8^k\). The posterior standard deviation is 2.0. Estimate the ESS and the Monte Carlo standard error of the posterior mean.

Show solution
  1. \(\sum_{k \ge 1} 0.8^k = 0.8/(1 - 0.8) = 4\).
  2. \(\mathrm{ESS} = 10{,}000/(1 + 2 \times 4) = 10{,}000/9 = 1111\).
  3. Standard error: \(2.0/\sqrt{1111} = 0.060\).

Answer: About 1100 effective samples and a Monte Carlo standard error of 0.06, small compared with the posterior standard deviation of 2.0, so the posterior mean is estimated adequately.

Hamiltonian Monte Carlo

Random-walk proposals explore by diffusion, so the number of steps needed to cross a distribution grows with the square of its width relative to the step size, and in \(d\) dimensions the step size must shrink as dimension grows. Hamiltonian Monte Carlo (HMC) uses the gradient of the log density to make long, directed moves. It introduces an auxiliary momentum variable \(\mathbf r\) and treats \(-\log\tilde p(\mathbf x)\) as a potential energy surface: a frictionless particle with random initial momentum slides across the surface, following Hamilton's equations, and its end point is the proposal.

\[H(\mathbf x, \mathbf r) = -\log\tilde p(\mathbf x) + \tfrac12\mathbf r^\top M^{-1}\mathbf r\]

The dynamics are simulated with the leapfrog integrator for \(L\) steps of size \(\epsilon\), and a Metropolis test corrects for integration error. Because the simulated trajectory nearly conserves \(H\), proposals far from the start are accepted with high probability. HMC scales much better with dimension than random-walk methods and handles correlated posteriors well, especially when the mass matrix \(M\) is adapted to the posterior covariance during warm-up.

The No-U-Turn Sampler (NUTS) chooses the trajectory length automatically by extending it until it starts to double back, and with step-size adaptation it makes HMC essentially tuning-free. NUTS is the default sampler in Stan, PyMC and NumPyro, which let users write a model in a few lines and obtain posterior samples together with diagnostics such as \(\hat R\), ESS and divergent-transition warnings, which flag regions of high curvature where the sampler cannot be trusted. HMC requires continuous parameters and differentiable log densities; discrete latent variables must be summed out or handled with other samplers.

Contour plot of a strongly correlated two-dimensional Gaussian posterior shaped like a narrow diagonal ellipse. A random-walk Metropolis path of 36 steps makes a tangled cluster of short moves covering only a small part of the ellipse. An HMC trajectory of the same number of gradient evaluations sweeps smoothly along the length of the ellipse in a curved path, with its end points far apart.
Random-walk Metropolis diffuses slowly; HMC follows the geometry of the posterior with long, directed moves.

Sequential Monte Carlo

Some problems require updating a posterior repeatedly as data arrive, such as tracking a moving object or estimating a machine's hidden state from streaming sensor data. Particle filters represent the posterior over the current state by a weighted set of samples (particles): each new observation reweights them by its likelihood (importance sampling), and resampling replaces low-weight particles with copies of high-weight ones to avoid degeneracy. Particle filters generalize the HMM forward algorithm of Lesson 3 to continuous and non-linear state spaces, and sequential Monte Carlo methods built on the same ideas also estimate marginal likelihoods.

Lesson 8 applies these samplers to Bayesian regression and hierarchical models, Lesson 10 places them inside probabilistic programming systems and the Bayesian workflow, and Lesson 11 develops particle filters in detail.

Common mistakes

  • MistakeReporting MCMC results without checking convergence.

    InsteadRun several chains, inspect traces, and check R-hat and effective sample size for every quantity of interest.

  • MistakeTreating MCMC samples as independent when computing standard errors.

    InsteadUse the effective sample size; correlated samples carry less information.

  • MistakeTuning random-walk proposals for a very high acceptance rate.

    InsteadHigh acceptance usually means tiny steps and slow mixing; aim for roughly 25 to 45%, or use HMC/NUTS.

  • MistakeUsing importance sampling with a light-tailed proposal.

    InsteadThe proposal must cover the target's tails; monitor the weight ESS.

  • MistakeIgnoring divergent transitions reported by HMC.

    InsteadDivergences indicate regions the sampler cannot explore; reparameterize the model.

Summary and key terms

  • Monte Carlo estimates expectations by sample averages with standard error \(\sigma_f/\sqrt S\), independent of dimension but slow.
  • Inverse transform and rejection sampling draw exact samples but fail in high dimensions.
  • Importance sampling reweights samples from a proposal; weight degeneracy is measured by the ESS.
  • MCMC builds a Markov chain whose stationary distribution is the target; detailed balance ensures invariance.
  • Metropolis-Hastings needs only density ratios, so the normalizing constant cancels; the step size controls mixing.
  • Gibbs sampling updates each variable from its full conditional; it mixes slowly when variables are strongly correlated.
  • Diagnose with burn-in, trace plots, multiple chains, R-hat and effective sample size.
  • HMC uses gradients for long, directed proposals; NUTS automates tuning in Stan, PyMC and NumPyro.
  • Particle filters apply importance sampling and resampling sequentially to streaming data.
Monte Carlo estimate
An average of a function over random samples approximating an expectation.
Inverse transform sampling
Sampling by applying the inverse CDF to uniform random numbers.
Rejection sampling
Exact sampling by accepting proposals under a scaled envelope.
Importance sampling
Estimating expectations with weighted samples from a proposal distribution.
Effective sample size
The number of independent samples equivalent to a weighted or correlated set.
Markov chain Monte Carlo
Sampling by simulating a Markov chain with the target as its stationary distribution.
Detailed balance
A condition under which a Markov chain leaves the target distribution invariant.
Metropolis-Hastings
An MCMC algorithm that accepts proposals with a probability based on density ratios.
Gibbs sampling
MCMC that samples each variable from its full conditional distribution.
Burn-in
Initial samples discarded before the chain reaches equilibrium.
R-hat
A diagnostic comparing between-chain and within-chain variance.
Hamiltonian Monte Carlo
MCMC that uses simulated Hamiltonian dynamics driven by the gradient of the log density.
NUTS
The No-U-Turn Sampler, an adaptive form of HMC.
Particle filter
Sequential importance sampling with resampling for state-space models.

Practice problems

Work each problem on paper first, then reveal the answer to check.

  1. A Monte Carlo estimate with 10,000 samples has standard error 0.02. How many samples give 0.002?

    Show answer

    100 times as many: 1,000,000.

  2. Give the inverse-transform formula for sampling an exponential distribution with rate 3.

    Show answer

    x = −ln(1 − u)/3, with u uniform on (0, 1).

  3. A rejection sampler uses M = 50 with a normalized target. What is the acceptance rate?

    Show answer

    1/50 = 2%.

  4. Five importance weights are 1, 1, 1, 1 and 20. What is the ESS?

    Show answer

    (24)²/(4 + 400) = 576/404 = 1.43.

  5. Why does the normalizing constant not matter in Metropolis-Hastings?

    Show answer

    The acceptance probability uses the ratio p̃(x′)/p̃(x), in which Z cancels.

  6. A random-walk Metropolis chain accepts 98% of proposals. What does this suggest?

    Show answer

    The step size is too small; the chain moves slowly and has a low ESS. Increase the step.

  7. For a Gibbs sampler on a bivariate normal with ρ = 0.95, what is the lag-1 autocorrelation and the approximate ESS of 10,000 samples (assume ρ_k = ρ^{2k})?

    Show answer

    Lag-1 = 0.9025; Σρ_k = 0.9025/0.0975 = 9.26; ESS = 10,000/19.5 ≈ 510.

  8. Four chains give R-hat = 1.15 for a parameter. What should you do?

    Show answer

    The chains disagree; run longer, check traces for multimodality or poor mixing, and consider reparameterizing.

  9. What does HMC need that random-walk Metropolis does not?

    Show answer

    Gradients of the log density (continuous, differentiable parameters).

  10. What problem does resampling solve in a particle filter?

    Show answer

    Weight degeneracy: after several updates a few particles carry all the weight; resampling concentrates particles where the posterior mass is.

Lesson quiz · 5 questions

Score 60% or more to complete this lesson.

Take the quiz