Monte carlo methods are a class of methods that use randomness to make it easier to solve deterministic problems. Specifally, we wish to compute integrals of the form
where is a probability density, i.e. a non-negative “function” that integrates to 1.
The idea of Monte-Carlo, is to observe that, if is a random variable distributed according to , then
We can then estimate the expectation using the law of large numbers. We suppose that are all random variables distributed according to . Then the law of large numbers gives that
A classical example of this, is to compute the area of the unit circle ()
where is the indicator function of the unit disk.
import numpy as np
import scipy
import matplotlib.pyplot as pltn = 1000;
xs = 2*np.random.rand(2,n)-1
plt.scatter(xs[0,:],xs[1,:])
ids = np.linalg.norm(xs,axis=0)<1
plt.scatter(xs[0,ids],xs[1,ids])
fn = 4*sum(ids)/n
'estimate = {}'.format(fn)
'error = {}'.format(fn-np.pi)'error = 0.04240734641020705'
Let’s look at how this converges with
ns = np.int32(np.logspace(0,6,100))
errs = []
for n in ns:
xs = 2*np.random.rand(2,n)-1
ids = np.linalg.norm(xs,axis=0)<1
errs.append((4*sum(ids)/n)-np.pi)
plt.loglog(ns,np.abs(errs),'o-')
plt.loglog(ns,1/np.sqrt(ns),'-')
plt.xlabel('n')
plt.ylabel('Error')
plt.show()
Let’s look at why the error is decaying like . Let’s let the estimator
The variance of is
If we rearrange this a bit, we get
Since the ’s are independent, well .
If we take square root
we see that the average error will decay like .
This one over square root error is universal in Monte Carlo. It is both the advantage and the curse of Monte Carlo.
Suppose we are trying to compute an integral in dimensions using a th order rule using points. The error is
which will be very slow when is large. This will be a lot smaller than the error of Monte Carlo when
Importance sampling¶
Suppose we want to compute the integral of a highly concentrated function
We shall truncate to the interval , since the function is very small there.
f = lambda x: np.exp(-1e4*x*x)
xs = np.linspace(-1,1,1000)
plt.plot(xs,f(xs))
plt.show()
true_int = np.sqrt(np.pi)/100
ns = np.int32(np.logspace(0,5,20))
ntrial = 10
errs = []
for n in ns:
errs_samp = np.zeros(ntrial)
for k in range(ntrial):
xs = 2*np.random.rand(1,n)-1
errs_samp[k] = ((2*np.sum(f(xs))/n)-true_int)/true_int
errs.append(np.mean(errs_samp))
plt.loglog(ns,np.abs(errs),'o-')
plt.loglog(ns,1/np.sqrt(ns),'-')
plt.xlabel('n')
plt.ylabel('Relative error')
plt.show()


We can increase the accuracy, we should use Monte Carlo samples where is large. If the samples are drawn from the pobability distribution , then we compensate for this by
We shall choose to be the density for a normal distrubtion:
for sigma in [1e-2,1e-1, 1]:
p = lambda x: np.exp(-x*x/2/sigma/sigma)/np.sqrt(2*np.pi*sigma**2)
errs_import = []
for n in ns:
errs_samp = np.zeros(ntrial)
for k in range(ntrial):
xs = sigma * np.random.randn(1,n)
est = np.sum(f(xs)/p(xs)) / n
errs_samp[k] = (est-true_int)/true_int
errs_import.append(np.mean(errs_samp))
plt.loglog(ns,np.abs(errs_import),'o-',label='sigma={}'.format(sigma))
plt.loglog(ns,np.abs(errs),'*--',label='uniform')
plt.loglog(ns,1/np.sqrt(ns),':')
plt.xlabel('n')
plt.legend()
plt.ylabel('Relative error')
plt.show()
We see that concentrating the samples at the peak of the function significantly decreases the error. This method is called “importance sampling”.
Markov Chain Monte Carlo (MCMC)¶
Sometimes we wish to compute
but we can’t directly sample from . The idea will be to choose our samples to be samples from a Markov chain. i.e. is randomly generated from . If we run it long enough, then the ’s will be distributed according to the stationary distribution of the Markov chain.
Our task is therefore to design a Markov chain whose stationary distribution is .
Metropolis-Hastings¶
We shall generate a sample from . i.e. .
Then we set with probability and set otherwise.
You can check that the stationary probability is .
Note that we don’t need the value of , we only need the ratio, so need not be normalized.
Let’s use this to compute
where and . The corresponding .
Note that the samples are no longer independent. The covariance between samples will lower the effective number of samples, but the estimator will still converge.
def MC_step(x,p):
delta = 0.1
y = x + delta*np.random.randn()
prob = min(p(y)/p(x),1)
if np.random.rand() < prob:
x = y
return x
V = lambda x: x**4-x**2
p = lambda x: np.exp(-V(x))
nburn_in = 1000
x = 0
for i in range(nburn_in):
x = MC_step(x,p)
nsample = 10000
xs = [x]
for i in range(nsample):
x = MC_step(x,p)
xs.append(x)
xs = np.array(xs)
sum(xs*xs)/nsample
plt.hist(xs,bins=50)
plt.show()
xx = np.linspace(-1.5,1.5,200)
plt.plot(xx,p(xx),'.')
kde = scipy.stats.gaussian_kde(xs)
plt.plot(xx,kde(xx))
Such examples occur in statistical physics.