8.6 Practice
The practice problems for Chapter 8 ask you to run MCMC on simple models, diagnose convergence, and compare HMC with grid approximation.
Easy problems
8E1. Draw a DAG for the Metropolis algorithm. What are the nodes and what are the arrows?
8E2. Why does the Metropolis algorithm only need the ratio of probabilities, not the absolute values?
8E3. What is the difference between Metropolis, Metropolis-Hastings, and Gibbs sampling?
8E4. What does R-hat measure? What value indicates convergence?
Medium problems
8M1. Implement the Metropolis algorithm for a simple posterior and compare the samples to the exact posterior.
# 8M1: Metropolis algorithm for a simple posterior
# Model: y ~ Normal(mu, 1), prior: mu ~ Normal(0, 10)
# Exact posterior: mu ~ Normal(ybar, 1/n)
set.seed(42)
n <- 50
y <- rnorm(n, mean = 5, sd = 1)
ybar <- mean(y)
# Metropolis
n_iter <- 10000
mu <- 0
samples <- numeric(n_iter)
for (t in 1:n_iter) {
proposal <- mu + rnorm(1, sd = 0.3)
log_post <- function(m) -n/2 * (ybar - m)^2 - m^2 / 200
if (log(runif(1)) < log_post(proposal) - log_post(mu)) mu <- proposal
samples[t] <- mu
}
cat("Metropolis:\n")
cat(" Mean:", round(mean(samples), 3), "\n")
cat(" SD:", round(sd(samples), 3), "\n")
cat("\nExact posterior:\n")
cat(" Mean:", round(ybar, 3), "\n")
cat(" SD:", round(1/sqrt(n), 3), "\n")
# Compare histograms
hist(samples, breaks = 50, col = adjustcolor("steelblue", 0.5),
freq = FALSE, main = "8M1: Metropolis vs exact posterior",
xlab = "mu")
curve(dnorm(x, mean = ybar, sd = 1/sqrt(n)), add = TRUE, lwd = 3, col = "firebrick")
legend("topright", legend = c("Metropolis", "Exact"), col = c("steelblue", "firebrick"), lwd = 2)8M2. Run the Metropolis algorithm with different proposal standard deviations (0.1, 0.5, 1.0). How does the proposal affect the chain?
# 8M2: Effect of proposal SD on Metropolis
set.seed(42)
n <- 50
y <- rnorm(n, mean = 5, sd = 1)
ybar <- mean(y)
log_post <- function(m) -n/2 * (ybar - m)^2 - m^2 / 200
run_metropolis <- function(prop_sd, n_iter = 3000) {
mu <- 0
samples <- numeric(n_iter)
accepts <- 0
for (t in 1:n_iter) {
proposal <- mu + rnorm(1, sd = prop_sd)
if (log(runif(1)) < log_post(proposal) - log_post(mu)) {
mu <- proposal
accepts <- accepts + 1
}
samples[t] <- mu
}
return(list(samples = samples, accept_rate = accepts / n_iter))
}
par(mfrow = c(3, 1))
for (sd in c(0.1, 0.5, 1.0)) {
result <- run_metropolis(sd)
plot(result$samples, type = "l", col = "steelblue",
main = sprintf("Proposal SD = %.1f (accept rate = %.2f)", sd, result$accept_rate),
xlab = "Iteration", ylab = "mu")
}
par(mfrow = c(1, 1))
cat("Small proposal: high accept rate, slow exploration\n")
cat("Large proposal: low accept rate, fast but jumpy\n")
cat("Medium proposal: balanced accept rate and exploration\n")Hard problems
8H1. Implement a simple HMC sampler for a 2D Gaussian posterior. Check convergence with R-hat.
# 8H1: Simple HMC for a 2D Gaussian
set.seed(42)
# Target: 2D standard normal
log_post <- function(theta) -0.5 * sum(theta^2)
grad_log_post <- function(theta) -theta
# HMC sampler
hmc_2d <- function(n_iter, step_size = 0.2, n_leapfrog = 15) {
theta <- c(0, 0) # starting value
samples <- matrix(0, nrow = n_iter, ncol = 2)
for (t in 1:n_iter) { # Random momentum
p <- rnorm(2)
# Leapfrog
theta_new <- theta
p_new <- p
for (i in 1:n_leapfrog) {
p_new <- p_new + 0.5 * step_size * grad_log_post(theta_new)
theta_new <- theta_new + step_size * p_new
p_new <- p_new + 0.5 * step_size * grad_log_post(theta_new)
}
# Metropolis accept
current_H <- -log_post(theta) + 0.5 * sum(p^2)
proposed_H <- -log_post(theta_new) + 0.5 * sum(p_new^2)
if (log(runif(1)) < current_H - proposed_H) theta <- theta_new
samples[t, ] <- theta
}
return(samples)
}
# Run 4 chains
chains <- list()
for (c in 1:4) {
chains[[c]] <- hmc_2d(2000)
}
# Compute R-hat for each parameter
compute*rhat <- function(chain_list) {
m <- length(chain_list)
n <- nrow(chain_list[[1]])
mat <- sapply(chain_list, function(x) x[, 1])
W <- mean(apply(mat, 2, var))
B <- n * var(colMeans(mat))
var*hat <- (n - 1) / n * W + B / n
return(sqrt(var_hat / W))
}
cat("HMC 2D Gaussian:\n")
cat(" Parameter 1: mean =", round(mean(sapply(chains, function(x) mean(x[,1]))), 3),
" R-hat =", round(compute_rhat(chains), 4), "\n")
# Plot
plot(chains[[1]][,1], chains[[1]][,2], type = "l", col = "steelblue",
main = "8H1: HMC 2D Gaussian", xlab = "theta1", ylab = "theta2")
for (c in 2:4) {
lines(chains[[c]][,1], chains[[c]][,2], col = "steelblue")
}
points(0, 0, pch = 16, col = "firebrick", cex = 2)
legend("topright", legend = c("HMC samples", "True mean"),
col = c("steelblue", "firebrick"), pch = c(NA, 16), lwd = c(1, NA))8H2. Compare the effective sample size of random-walk Metropolis and HMC for the same target distribution.
8H3. Simulate data from a model with a non-normal posterior (e.g., a mixture). Fit it with both quap and HMC. How do the results differ?