rm(list = ls())
# Set global R options
options(scipen = 999)
# Set the graphical theme
::theme_set(ggplot2::theme_light())
ggplot2
# Set global knitr chunk options
::opts_chunk$set(
knitrwarning = FALSE,
message = FALSE,
cache = TRUE
)
set.seed(12345)
library(truncnorm)
library(pracma)
library(ggplot2)
Quantile Importance Sampling
Normal-Normal
Example
We take Gaussian likelihood and Gaussian prior,
\[ \begin{align*} L(x \mid \mu_1, \sigma_1) & = \frac{1}{\sqrt{2 \pi}\sigma_1} e^{-\frac{(x-\mu_1)^2}{2\sigma_1^2}},\\ p(x \mid \mu_2, \sigma_2) & = \frac{1}{\sqrt{2 \pi}\sigma_2} e^{-\frac{(x-\mu_2)^2}{2\sigma_2^2}} \end{align*} \]
The integral \(Z = \int L(x) p(x) dx\) is available in closed form due to the self-conjugacy property of Gaussian prior with the mean parameter of a Gaussian distribution. To see this, note that we can write the product of two Gaussian density functions in the following way:
\[ \begin{gather} \phi(x \mid \mu_1, \sigma_1) \times \phi(x \mid \mu_2, \sigma_2) = \phi(\mu_1 \mid \mu_2, \sqrt{\sigma_1^2 + \sigma_2^2}) \times \phi(x \mid \mu_{\text{post}}, \sigma_{\text{post}}^2) \\ \text{where} \quad \mu_{\text{post}} = \frac{\sigma_1^{-2} \mu_1 + \sigma_2^{-2} \mu_2}{\sigma_1^{-2} + \sigma_2^{-2}}, \sigma_{\text{post}}^2 = \frac{\sigma_1^2 \sigma_2^2}{\sigma_1^2 + \sigma_2^2}. \end{gather} \]
Integrating the right-hand-side will leave the normalizing constant, here expressed as a Gaussian density itself. Let \(\mu_1 = 2, \mu_2 =0\) and \(\sigma_1^2 = \sigma_2^2 = 1\). The normalizing constant will be:
\[ Z = \phi(2 \mid 0, 2) = \frac{1}{\sqrt{2 \pi} \sqrt{2}} e^{-1} = \frac{1}{2 e \sqrt{\pi}}. \]
Estimation by QIS, NS and Naive MC
We define three essential functions below:
vertical.grid
will generate an \(x\)-grid for integration using either the exponential weights (for original nested sampling), or uniform for Yakowitz (Quantile Importance Sampling. )sQ
is simply a sample quantile calculator.nested_sampling
performs the nested sampling algorithm: removes the lowest likelihood, simulates a new draw from the constrained prior space and calculates the Lorenz curve.
# Generate grid
= function(l,N,type = NULL){
vertical.grid # "u" - uniform
# "e" - exponential
if(type == "u"){
= runif(N)
ugrid = c(sort(ugrid),1)
res # res = sort(runif(N))
else if(type == "e"){
}= exp(-(0:l)/N)
res
}
return(res)
}# Quantile
= function(q,Y){
sQ # q-quantile of Y
= length(Y)
N = Y[ceiling(N*q)]
res return(res)
}# Nested sampling (normal prior, normal likelihood)
= function(mu1,sigma1,mu2,sigma2,N,tol=0.001){
nested_sampling # "mu1", "sigma1" - likelihood
# "mu2", "sigma2" - prior
= 1/(sqrt(2*pi)*sigma1)
Lmax
= rnorm(N,mu2,sigma2)
theta = dnorm(theta,mu1,sigma1)
L = NULL
phi = 1
error
while(error >= tol)
{= which.min(L)
index = min(L)
Lmin = c(phi,Lmin)
phi
= abs(Lmin-Lmax)/Lmax
error
= -log(Lmin*sqrt(2*pi)*sigma1)
term = mu1 - sqrt(term*2*sigma1^2)
a = mu1 + sqrt(term*2*sigma1^2)
b
= rtruncnorm(1,a,b,mean = mu2,sd = sigma2)
newTheta = dnorm(newTheta,mu1,sigma1)
newL = newTheta
theta[index] = newL
L[index]
}return(list(phi=phi, L = L))
}
Naive Monte Carlo
# Test example
# Prior and Likelihood
= 2
mu1 = 1
sigma1 = 0
mu2 = 1
sigma2
trueZ = 1/(2*exp(1)*sqrt(pi))) (
[1] 0.1037769
= 100
N = 1000
r = NULL
mc.naive = FALSE
verbose for(i in 1:r){
if(isTRUE(verbose) && i %% 100 == 0)
cat("Iteration ",i, "\n")
= rnorm(N,mu2,sigma2)
X = dnorm(X,mu1,sigma1)
Y = c(mc.naive,sum(Y)/N)
mc.naive
}
mean(mc.naive)
[1] 0.1038586
hist(mc.naive, breaks = 30, main = "Naive Monte Carlo Estimates")
abline(v=trueZ,col="red",lwd=2)
Quantile Importance Sampling
= 100
N = 1000
r
= NULL
mc.qis = FALSE
verbose
for(i in 1:r){
if(isTRUE(verbose) && i %% 10 == 0)
cat("Iteration ",i, "\n")
= 1000
M = rnorm(M,mu2,sigma2)
X = dnorm(X,mu1,sigma1)
Y = sort(Y)
Y
= vertical.grid(l=NULL,N,type = "u")
simu.grid.unif
= sQ(simu.grid.unif,Y)
Lambda = c(mc.qis,trapz(simu.grid.unif,Lambda)) ## QIS
mc.qis
}
mean(mc.qis)
[1] 0.103908
hist(mc.qis, breaks = 30, main = "QIS (Yakowitz) Estimates")
abline(v=trueZ,col="red",lwd=2)
Nested Sampling with exponential weights
= NULL
mc.nested = NULL
mc.nested.rect = FALSE
verbose
for(i in 1:r){
if(isTRUE(verbose) && i %% 10 == 0)
cat("Iteration ",i, "\n")
= 1000
M = rnorm(M,mu2,sigma2)
X = dnorm(X,mu1,sigma1)
Y = sort(Y)
Y
= nested_sampling(mu1,sigma1,mu2,sigma2,N,tol = 1e-7)
value = vertical.grid(length(value$phi),N,"e")
simu.grid
# Lambda = sQ(simu.grid,Y)
= c(mc.nested,-trapz(simu.grid[-1],value$phi)) ## Trapezoidal rule (Yakowitz)
mc.nested
= c(mc.nested.rect, -sum((value$phi)*diff(simu.grid)))
mc.nested.rect
}
mean(mc.nested.rect)
[1] 0.1042129
mean(mc.nested)
[1] 0.1036941
hist(mc.nested, breaks = 30, color = rgb(1,0,0,0.5), main = "Nested Riemann")
abline(v=trueZ,col="red",lwd=2)
hist(mc.nested.rect, breaks = 30, main = "Nested Rectangular")
abline(v=trueZ,col="red",lwd=2)
Comparison
Performance table
### Comparison table
## Numerical
<- mean(mc.qis);
mean.qis <- mean(mc.naive)
mean.naive <- median(mc.qis); median.naive <- median(mc.naive)
median.qis <- median(abs((mc.qis)-(trueZ))/(trueZ))
mape.qis <- median(abs(((mc.naive)-(trueZ))/(trueZ)))
mape.naive <- sqrt((mean((mc.qis-trueZ)^2)))
rmse.qis <- sqrt((mean((mc.naive-trueZ)^2)))
rmse.naive
<- mean(mc.nested);
mean.nested <- mean(mc.nested.rect)
mean.nested.rect <- median(mc.nested); median.nested.rect <- median(mc.nested.rect)
median.nested <- median(abs((mc.nested)-(trueZ))/(trueZ))
mape.nested <- median(abs(((mc.nested.rect)-(trueZ))/(trueZ)))
mape.nested.rect <- sqrt((mean((mc.nested-trueZ)^2)))
rmse.nested <- sqrt((mean((mc.nested.rect-trueZ)^2)))
rmse.nested.rect
<- rbind((cbind(mean.qis,mean.naive,mean.nested,mean.nested.rect)),
perf # (cbind(median.qis,median.naive)),
cbind(mape.qis, mape.naive,mape.nested, mape.nested.rect)),
(cbind(rmse.qis,rmse.naive, rmse.nested, rmse.nested.rect)))
(
colnames(perf) <- c("QIS", "Naive", "Nested-Rect", "Nested-Riemann");
row.names(perf) <- c("Mean", "MAPE", "RMSE")
library(kableExtra)
kable(perf, format = "html") %>%
kable_styling()
QIS | Naive | Nested-Rect | Nested-Riemann | |
---|---|---|---|---|
Mean | 0.1039080 | 0.1038586 | 0.1036941 | 0.1042129 |
MAPE | 0.0214313 | 0.0730794 | 0.0583801 | 0.0583841 |
RMSE | 0.0035507 | 0.0115004 | 0.0090859 | 0.0091414 |
Visual comparison
library(ggplot2)
= rbind(data.frame(MC = mc.qis, method = "QIS"),
mc.data data.frame(MC = mc.nested.rect, method = "Nested - Rectangular"),
data.frame(MC = mc.nested, method = "Nested - Riemann"),
data.frame(MC = mc.naive, method = "Classic MC"))
<- ggplot(mc.data, aes(MC, fill = method)) +
(norm.plt geom_histogram(alpha=0.75,binwidth = 0.005, position="identity",aes(y = ..density..))+
geom_density(alpha=0.75,adjust = 1, stat="density",position="identity",aes(y = ..density..))+
geom_vline(xintercept=trueZ)+ #coord_flip()+
facet_wrap(vars(method), ncol = 2, scales = "fixed") +
ggtitle(paste0("Monte Carlo: Yakowitz"))+
theme_bw()+
theme(legend.position = "bottom")
)