library("nimble")
library("ggplot2")
library("tidyr")
library("dplyr")
library("MASS")
library("mcmc")
GP Model
A successful specification looks like this:
code <- nimbleCode({
l ~ dgamma(10, 1)
sigma.n ~ dunif(0, 1e5)
Sigma[1:N, 1:N] <- exp(-0.5 * diff[1:N, 1:N] / l ^ 2) + sigma.n ^ 2 * I[1:N, 1:N]
y[1:N] ~ dmnorm(Mu[1:N], cov = Sigma[1:N, 1:N])
})
obs <- data.frame(x = c(-4, -3, -1, 0, 2),
y = c(-2, 0, 1, 2, -1))
N <- length(obs$x)
diff <- outer(obs$x, obs$x, function(xi, xj) (xi-xj)^2)
constants = list(N = N, x = obs$x, Mu = rep(0,N), diff = diff, I = diag(1,N))
inits <- list(l = 1, sigma.n = 10)
data <- data.frame(y = obs$y)
Rmodel <- nimbleModel(code = code, constants = constants, data = data, inits = inits)
We can simulate parameters from the prior:
Rmodel$l
[1] 1
simulate(Rmodel, "l")
Rmodel$l
[1] 10.37676
Other nodes and likelihoods are updated to reflect the new value only after we run calculate()
, so this is the logProb of l
originally:
Rmodel$logProb_l
[1] NA
After we run calculate()
, the logProb
is updated to reflect our simulated value of l
, as are the dependent terms (like the Sigma matrix)
Sigma <- Rmodel$Sigma
calculate(Rmodel)
[1] -29.81744
identical(Sigma, Rmodel$Sigma)
[1] FALSE
Rmodel$logProb_l
[1] -2.122469
Interestingly, we don’t seem to be able to simulate actual data from the model in this way:
Rmodel$y
[1] -2 0 1 2 -1
simulate(Rmodel, "y")
Rmodel$y
[1] -2 0 1 2 -1
identical(Rmodel$y, Rmodel$origData[["y"]])
[1] TRUE
Note that y
remaines fixed to the original data values. We have to do this manually:
mvrnorm(mu = constants$Mu, Sigma = Rmodel$Sigma)
[1] -7.498019 -20.999389 4.939137 11.751509 -8.123005
Define our mcmc procedure in Nimble
Cmodel <- compileNimble(Rmodel)
mcmcspec <- configureMCMC(Rmodel, print=FALSE)
Rmcmc <- buildMCMC(mcmcspec)
Cmcmc <- compileNimble(Rmcmc, project = Rmodel)
system.time(
Cmcmc$run(1e5)
)
user system elapsed
2.411 0.021 2.434
samples <- as.data.frame(as.matrix(Cmcmc$mvSamples))
df <- gather(samples)
Posteriors
ggplot(df) +
geom_density(aes(value)) +
facet_wrap(~key, scale='free')
Note that simulate
continues to draw from the prior, not the posterior:
sigmas <- replicate(1e4, {
simulate(Rmodel, "sigma.n")
Rmodel$sigma.n
})
hist(sigmas)
(Also note this isn’t a particularly efficient way to simulate). Conveniently drawing data, \(y\), from the posterior is even less obvious.
Comparison
lpriors <- function(pars){
dgamma(pars[[1]], 10, 1, log = TRUE) +
dunif(pars[[2]], 0, 1e5, log = TRUE)
}
posterior <- function(pars, x, y){
l <- pars[1]
sigma.n <- pars[2]
SE <- function(Xi,Xj, l) exp(-0.5 * (Xi - Xj) ^ 2 / l ^ 2)
cov <- function(X, Y) outer(X, Y, SE, l)
I <- diag(1, length(x))
K <- cov(x, x)
loglik <- - 0.5 * t(y) %*% solve(K + sigma.n ^ 2 * I) %*% y -
log(det(K + sigma.n ^ 2 * I)) -
length(y) * log(2 * pi) / 2
loglik + lpriors(pars)
}
system.time(
out <- metrop(posterior, initial = c(l = 1,sigma.n = 10), nbatch = 1e5, x = obs$x, y = obs$y)
)
user system elapsed
36.485 0.442 37.202
burnin <- 1e3
thin <- 1e2
df <- cbind(index=1:out$nbatch, as.data.frame(exp(out$batch)))
s <- seq(burnin+1, out$nbatch, by=thin)
df <- df[s,]
df <- df[-1] # drop index
names(df) <- names(out$initial)
df <- gather(df)
ggplot(df) +
geom_density(aes(value)) +
facet_wrap(~key, scale='free') +
scale_x_log10()