Gibbs sampling, from a normal model to a mixture with data augmentation
How Gibbs sampling builds a joint posterior out of easy conditional draws, and how adding a hidden label per observation turns a messy mixture model into a chain of simple updates.
University lab From my coursework in the MSc in Statistics and Machine Learning at Linköping University, rewritten as a note to review later.
Gibbs sampling is the MCMC method that feels most like cheating. The joint posterior of several parameters is hard, but each parameter on its own, given all the others, is easy. So you just take turns drawing from those easy pieces, and the draws end up following the joint posterior. In the third Bayesian Learning lab (pair work) we tried it on daily precipitation data. A plain normal model went smoothly. Then we tried a two-component mixture, and that’s where I learned the most, mostly from a bug.
The basic idea
Say the parameters are . One Gibbs iteration is:
- draw ,
- draw .
Each conditional is called a full conditional. The draws form a Markov chain whose stationary distribution is the joint posterior . Every draw is accepted, so there’s no step size to tune, unlike random-walk Metropolis. For a worked derivation of full conditionals by completing the square, and an indexing bug that breaks the sweep, see Gibbs sampling for a random-walk smoother.
Normal model with a semi-conjugate prior
Model with independent priors and . Because the priors are independent (not the fully conjugate version), the joint posterior isn’t a standard distribution. But the full conditionals are:
Here is the sample size, the sample mean, and the weight the data get relative to the prior (data precision over total precision). Alternate these two draws and you’re done.
To check convergence, look at trace plots (each parameter against iteration: you want a “hairy caterpillar” with no trends) and cumulative means that settle down. With large this chain mixes almost instantly, because and are nearly independent in the posterior.
Mixtures and data augmentation
Precipitation data are skewed: lots of small values, a long tail of big ones. One normal fits that badly. A two-component mixture is more flexible:
where is the weight of component 1. The likelihood is a product of sums, which is unpleasant. The trick is to invent a hidden label for each observation saying which component it came from. Given the labels, everything splits into two ordinary normal models. Given the parameters, each label has a simple probability. So the Gibbs sampler alternates:
- , where is how many points currently have label .
- For each component : draw and from the normal-model conditionals above, using only the points with .
- For each observation: .
Adding latent variables to make conditionals easy is called data augmentation. The same idea is behind EM for mixtures, which uses soft label probabilities instead of sampled labels (see EM for Bernoulli mixtures).
A bug in my draft. In step 2, my code computed each component’s variance update with sum((x - mu1)^2) over all observations instead of just the ones assigned to that component. The mean update used the right subset, the variance didn’t. That makes both variances look like the overall variance and blurs the two components together. It’s an easy slip because the normal-model code is copied and only half-adapted.
Code
set.seed(5)
y <- c(rnorm(300, 10, 5), rnorm(200, 50, 20)); n <- length(y)
mu0 <- mean(y); tau2_0 <- 100^2; nu0 <- 4; s2_0 <- var(y) / 4; alpha <- c(1, 1)
rinvchisq <- function(df, s2) df * s2 / rchisq(1, df)
z <- sample(1:2, n, replace = TRUE); mu <- quantile(y, c(.25, .75)); s2 <- rep(var(y), 2)
keep <- matrix(NA, 2000, 5)
for (it in 1:2000) {
nk <- tabulate(z, 2)
w <- rgamma(2, alpha + nk); pi1 <- w[1] / sum(w) # Beta draw via gammas
for (k in 1:2) {
yk <- y[z == k] # only this component's points
prec <- nk[k] / s2[k] + 1 / tau2_0
mu[k] <- rnorm(1, (sum(yk) / s2[k] + mu0 / tau2_0) / prec, sqrt(1 / prec))
s2[k] <- rinvchisq(nu0 + nk[k], (nu0 * s2_0 + sum((yk - mu[k])^2)) / (nu0 + nk[k]))
}
p1 <- pi1 * dnorm(y, mu[1], sqrt(s2[1]))
p2 <- (1 - pi1) * dnorm(y, mu[2], sqrt(s2[2]))
z <- ifelse(runif(n) < p1 / (p1 + p2), 1, 2) # new labels
keep[it, ] <- c(pi1, mu, s2)
}
round(colMeans(keep[-(1:500), ]), 2) # 0.61 10.14 50.11 27.95 406.21
The true values were , means 10 and 50, variances 25 and 400, so the sampler recovers them after a burn-in.
Two things to watch with mixtures
- Plot the mixture, not the components. To compare with a histogram, the fitted density is on one curve. Plotting the two normals separately (as in my draft) doesn’t show the fit, and neither does a normal with the data’s own mean and variance.
- Label switching. Nothing in the model says which component is “1”. In long runs, or with symmetric priors, the chain can swap the labels, and then posterior means of average over both components. Starting values that separate the components and checking trace plots help. A common fix is an ordering constraint like , though it doesn’t always work well; Stephens (2000) discusses relabelling methods.
Key takeaways
- Gibbs sampling: cycle through full conditionals; every draw is accepted.
- Semi-conjugate priors give easy conditionals even when the joint posterior isn’t standard.
- Mixtures become easy by adding a hidden label per observation (data augmentation).
- Each component’s updates must use only the points currently assigned to it.
- Watch for label switching and compare the full mixture density with the data.
Resources
- Andrew Gelman et al., Bayesian Data Analysis, 3rd ed., Section 11.1 (Gibbs sampler) and Chapter 22 (finite mixture models): http://www.stat.columbia.edu/~gelman/book/
- Stuart Geman, Donald Geman, “Stochastic relaxation, Gibbs distributions, and the Bayesian restoration of images”, IEEE TPAMI 6(6) (1984): https://doi.org/10.1109/TPAMI.1984.4767596
- Matthew Stephens, “Dealing with label switching in mixture models”, JRSS B 62(4) (2000): https://doi.org/10.1111/1467-9868.00265
- 732A91 Bayesian Learning, Linköping University (course lab, spring 2020).