Animating the Metropolis algorithm
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
The Metropolis algorithm, and its generalization (Metropolis-Hastings algorithm) provide elegant methods for obtaining sequences of random samples from complex probability distributions. When I first read about modern MCMC methods, I had trouble visualizing the convergence of Markov chains in higher dimensional cases. So, I thought I might put together a visualization in a two-dimensional case.
I’ll use a simple example: estimating a population mean and standard deviation. We’ll define some population level parameters, collect some data, then use the Metropolis algorithm to simulate the joint posterior of the mean and standard deviation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | # population level parameters
mu <- 7
sigma <- 3
# collect some data (e.g. a sample of heights)
n <- 50
x <- rnorm(n, mu, sigma)
# log-likelihood function
ll <- function(x, muhat, sigmahat){
sum(dnorm(x, muhat, sigmahat, log=T))
}
# prior densities
pmu <- function(mu){
dnorm(mu, 0, 100, log=T)
}
psigma <- function(sigma){
dunif(sigma, 0, 10, log=T)
}
# posterior density function (log scale)
post <- function(x, mu, sigma){
ll(x, mu, sigma) + pmu(mu) + psigma(sigma)
}
geninits <- function(){
list(mu = runif(1, 4, 10),
sigma = runif(1, 2, 6))
}
jump <- function(x, dist = .1){ # must be symmetric
x + rnorm(1, 0, dist)
}
iter = 10000
chains <- 3
posterior <- array(dim = c(chains, 2, iter))
accepted <- array(dim=c(chains, iter - 1))
for (c in 1:chains){
theta.post <- array(dim=c(2, iter))
inits <- geninits()
theta.post[1, 1] <- inits$mu
theta.post[2, 1] <- inits$sigma
for (t in 2:iter){
# theta_star = proposed next values for parameters
theta_star <- c(jump(theta.post[1, t-1]), jump(theta.post[2, t-1]))
pstar <- post(x, mu = theta_star[1], sigma = theta_star[2])
pprev <- post(x, mu = theta.post[1, t-1], sigma = theta.post[2, t-1])
lr <- pstar - pprev
r <- exp(lr)
# theta_star is accepted if posterior density is higher w/ theta_star
# if posterior density is not higher, it is accepted with probability r
# else theta does not change from time t-1 to t
accept <- rbinom(1, 1, prob = min(r, 1))
accepted[c, t - 1] <- accept
if (accept == 1){
theta.post[, t] <- theta_star
} else {
theta.post[, t] <- theta.post[, t-1]
}
}
posterior[c, , ] <- theta.post
}
|
Then, to visualize the evolution of the Markov chains, we can make plots of the chains in 2-parameter space, along with the posterior density at different iterations, joining these plots together using ImageMagick (in the terminal) to create an animated .gif:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | require(sm)
seq1 <- seq(1, 300, by=5)
seq2 <- seq(300, 500, by=10)
seq3 <- seq(500, iter, by=300)
sequence <- c(seq1, seq2, seq3)
length(sequence)
xlims <- c(4, 10)
ylims <- c(1, 6)
dir.create("metropolis_ex")
setwd("metropolis_ex")
png(file = "metrop%03d.png", width=700, height=350)
for (i in sequence){
par(mfrow=c(1, 2))
plot(posterior[1, 1, 1:i], posterior[1, 2, 1:i],
type="l", xlim=xlims, ylim=ylims, col="blue",
xlab="mu", ylab="sigma", main="Markov chains")
lines(posterior[2, 1, 1:i], posterior[2, 2, 1:i],
col="purple")
lines(posterior[3, 1, 1:i], posterior[3, 2, 1:i],
col="red")
text(x=7, y=1.2, paste("Iteration ", i), cex=1.5)
sm.density(x=cbind(c(posterior[, 1, 1:i]), c(posterior[, 2, 1:i])),
xlab="mu", ylab="sigma",
zlab="", zlim=c(0, .7),
xlim=xlims, ylim=ylims, col="white")
title("Posterior density")
}
dev.off()
system("convert -delay 15 *.png metrop.gif")
file.remove(list.files(pattern=".png"))
|

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.