```
In Bayesian statistics, one common question of interest is to be able to sample from a distribution with no directly available sampler. Here we are interested in sampling from
\[f(x) \propto \exp\bigg\{\frac{(x - \mu)^2}{\sigma^2}\bigg\} \times x^2.\]
library(magrittr)
library(ggplot2)
## Target unnormalized density constructor
construct_target_density <- function(mu, sigma, C = 1) {
function(x) {
exp(-(x - mu)^2 / (sigma^2)) * x^2 / C
}
}
For \(\mu = 0, \sigma^2 = 1\), one can find the normalizing constant for \(f(x) \propto \exp(-x^2) \times x^2\) is \(C = \sqrt{\pi}/2\).
## Simple case with mu = 0, sigma = 1
target_density0 <- construct_target_density(mu = 0, sigma = 1, C = sqrt(pi)/2)
## Examine visually to examine peak height
g1 <- ggplot(data = data.frame(x = 0), mapping = aes(x = x)) +
geom_path(stat = "function", fun = target_density0,
n = 10001, mapping = aes(color = "1")) +
geom_path(stat = "function", fun = dnorm,
n = 10001, mapping = aes(color = "2")) +
geom_path(stat = "function", fun = function(x) {5*dnorm(x, sd = 3)},
n = 10001, mapping = aes(color = "3")) +
scale_x_continuous(limits = c(-10,10)) +
scale_color_manual(name = "Functions",
values = c("blue", "red", "green"), # Color specification
labels = c("Target", "N(0,1^2)", "5*N(0,3^2)")) +
theme_bw() + theme(legend.key = element_blank())
print(g1)
RejectionSampling <- function(target_density, proposal_density, proposal_generator, R) {
## Initialize a vector (all 0's)
v <- numeric(length = R)
## Acceptance indicator (all FALSE)
a <- logical(length = R)
for (r in seq_len(R)) {
## Obtain a proposed value
proposal <- proposal_generator()
v[r] <- proposal
## Evaluate both densities at the proposed value
target_density_value <- target_density(proposal)
proposal_density_value <- proposal_density(proposal)
## This is assured to be [0,1]
acceptance_ratio <- target_density_value / proposal_density_value
## If U(0,1) is less than or equal to acceptance ratio, accept
if(runif(n = 1) <= acceptance_ratio) {
a[r] <- TRUE
}
}
data.frame(v = v,
a = a)
}
out0 <- RejectionSampling(target_density = target_density0,
proposal_density = function(x) {5*dnorm(x, sd = 3)},
proposal_generator = function() {rnorm(n = 1, sd = 3)},
R = 10^5)
## Acceptance ratio (should be close to 1/5 = 0.2)
mean(out0$a)
## [1] 0.19822
g1 + geom_density(data = subset(out0, a == TRUE), mapping = aes(x = v))
Here we adopt the proposal distribution as
\[q(x^*|x_{t-1}) = N(x^*; x_{t-1}, 3^2),\]
which is symmetric, i.e. \(q(x^*|x_{t-1}) = q(x_{t-1}|x^*) = \frac{1}{\sqrt{2\pi\times 3^2}}\exp\big\{-\frac{(x^* - x_{t-1})^2}{2\times 3^2}\big\}\). Thus we are in fact using the Metropolis algorithm.
MHSampling <- function(target_density, proposal_generator, R) {
## Initialize a vector (all 0's)
v <- numeric(length = R)
## Acceptance indicator (all FALSE)
a <- logical(length = R)
for (r in seq_len(R)[-1]) {
## Obtain a proposed value (here the proposal distribution is symmetric)
proposal <- proposal_generator(v[r-1])
## Evaluate both densities at the proposed value
target_density_at_proposal <- target_density(proposal)
target_density_at_previous <- target_density(v[r-1])
## This is assured to be [0,1]
acceptance_ratio <- min(target_density_at_proposal / target_density_at_previous, 1)
if (runif(n = 1) <= acceptance_ratio) {
## If U(0,1) is less than or equal to acceptance ratio
## Make a move
v[r] <- proposal
a[r] <- TRUE
} else {
## Otherwise, don't move
v[r] <- v[r-1]
}
}
data.frame(v = v,
a = a)
}
out1 <- MHSampling(target_density = target_density0,
proposal_generator = function(par) {rnorm(n = 1, mean = par, sd = 3)},
R = 10^5)
out1$index <- seq_along(out1$v)
## Acceptance ratio
mean(out1$a)
## [1] 0.33803
## Plot of chain
ggplot(data = out1, mapping = aes(x = index, y = v)) +
geom_point(alpha = 1/10) +
theme_bw() + theme(legend.key = element_blank())
## Check auto-correlation (slicing by 10 looks ok)
acf(out1$v)
## Plot of chain (every 10-th point)
ggplot(data = subset(out1, index %% 10 == 0), mapping = aes(x = index, y = v)) +
geom_point(alpha = 1/10) +
theme_bw() + theme(legend.key = element_blank())
## Density plot after slicing
g1 + geom_density(data = subset(out1, index %% 10 == 0),
mapping = aes(x = v))
This time we adopt the new proposal distribution as
\[q(x^*|x_{t-1}) = U(x^*; -5,5).\]
Again, it is symmetric.
## Another attempt using a different proposal distribution
out2 <- MHSampling(target_density = target_density0,
proposal_generator = function(par) {runif(n = 1, min = -5, max = 5)},
R = 10^5)
out2$index <- seq_along(out2$v)
## Acceptance ratio
mean(out2$a)
## [1] 0.29826
## Plot of chain
ggplot(data = out2, mapping = aes(x = index, y = v)) +
geom_point(alpha = 1/10) +
theme_bw() + theme(legend.key = element_blank())
## Check auto-correlation (slicing by 10 looks ok)
acf(out2$v)
## Plot of chain (every 10-th point)
ggplot(data = subset(out2, index %% 10 == 0), mapping = aes(x = index, y = v)) +
geom_point(alpha = 1/10) +
theme_bw() + theme(legend.key = element_blank())
## Density plot after slicing
g1 + geom_density(data = subset(out2, index %% 10 == 0),
mapping = aes(x = v))
Gibbs sampler is designed to sample from joint distribution of a set of random variables. Here we consider introducing a new random variable \(Z\) and consider the following joing density \((X, Z)\) as
\[f_{X,Z}(x,z) \propto \exp\{-x^2\} I(z < |x|, z \geq 0)z.\]
If we are able to sample from this joint density, the marginally we have
\[\begin{align*} f_X(x) &\propto \int \exp\{-x^2\} I(z < |x|, z \geq 0)zdz\\ &\propto \exp\{-x^2\}x^2, \end{align*}\]which is what we want.
In this case, Gibbs sampler provides a way to sample from the joint density by specifying the full conditional distributions as
\[\begin{align*} f_{X|Z}(x|z) &\propto \exp\{-x^2\}I(z < |x|, z \geq 0) \quad \text{(which is a truncated normal)}\\ f_{Z|X}(z|x) &\propto zI(z < |x|, z \geq 0) \quad \text{(which is a scaled Beta(2,1) distribution)}\\ \end{align*}\]Gibbs_Sampling <- function(R, burnin = 0.1){
rtrunc <- function(Z){ # Define the truncated normal sampler based on A-R sampling
X = 0
while (abs(X) <= Z){
X = rnorm(1, sd = sqrt(1/2))
}
return(X)
}
X_list = numeric(length = R)
Z_list = numeric(length = R)
for (i in seq_len(R)[-1]){
X_list[i] = rtrunc(Z_list[i-1]) # truncated normal
Z_list[i] = rbeta(n = 1, 2, 1) * abs(X_list[i]) # proportional to beta(2,1)
}
afterburnin = X_list[seq(R * burnin + 1, R)]
data.frame(v = afterburnin)
}
out4 <- Gibbs_Sampling(R = 10^5)
out4$index <- seq_along(out4$v)
## Plot of chain
ggplot(data = out4, mapping = aes(x = index, y = v)) +
geom_point(alpha = 1/10) +
theme_bw() + theme(legend.key = element_blank())
## Density plot after slicing
g1 + geom_density(data = out4,
mapping = aes(x = v))