蒙特卡洛采样_重要性采样 importance sampling(1)

重要性采样(importance sampling)

重要抽样主要为了解决一下几种问题:

1. 为了减小蒙特卡洛方法的方差

2. 为了对很少发生事件(rare event)进行有效采样,这类问题如果用传统蒙特卡洛采样会需要非常大的样本

3. 对于不容易抽样的样本进行抽样(有的样本遵循的概率分布函数

比较复杂,不易抽样)

下边以一个例子对 rare event 的采样问题进行说明:1. 我们对事件

进行采样,已知
发生的概率为
,2. 我们想用常规的蒙特卡洛方法来估计
  • 记:
  • 我们想要得到蒙特卡洛对
    的估计的标准差
    是一个数量级的,即
    ,一般

首先,用蒙特卡洛对

估计等价于计算"标记函数"
的期望

事实上

遵循伯努利分布,
(当
)

利用蒙特卡洛方法对随机变量

进行估计,则有

根据蒙特卡洛采样的性质:

,

如果需要

,则需要

如果

,那么
,采样成本极高。可以从下面例子看出:

已知

,想用传统蒙特卡洛方法估计

%matplotlib inline
## 导入所需要的库
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.mlab as mlab
import matplotlib
import scipy.stats as stats

# Sympy Stuff
import sympy
from sympy import erf
from sympy.utilities.lambdify import lambdify
## 首先计算问题的解析解
xs=sympy.Symbol('xs')
fz = sympy.exp(-xs**2/2)/sympy.sqrt(2*sympy.pi)
Pz = sympy.integrals.integrate(fz,(xs,4.5,sympy.oo)).evalf()
print("Analytical solution")
print(Pz)
Analytical solution
3.39767312476808e-6
# 定义蒙特卡洛sample 函数,为后续准备
def monte_carlo(num_samples, sample_generator, g_evaluator, cumsum=False):
    """
    函数详情可以参考 https://zhuanlan.zhihu.com/p/250282313
    输入:
    -----
    num_samples: 定义样本数量
    sample_generator: 根据给定的概率分布生成样本,对应上文的X,
    调用方式 sample_generator(num_samples),返回samples,可以是多个维度,
    但是第一个维度必须是num_samples    
    g_evaluator(samples): 计算 g(X)
    cumsum:如果 cumsum=True, 那么对于1个,2个,……n个样本,都进行monte_carlo估计
    输出
    estimator:蒙特卡洛估计值(对应给定的num_samples)
    samples: 对应的样本 Xs
    evaluations: 对应的 g(X)s    
    """
    samples = sample_generator(num_samples)
    evaluations = g_evaluator(samples)
    if cumsum is False:
        estimate = np.sum(evaluations,axis=0)/float(num_samples)
    else:
        estimate = np.sum(evaluations,axis=0)/np.arange(1,num_samples+1,dtype=np.float)

    return estimate, samples, evaluations
# 利用蒙特卡洛方法对 Pz 进行估计
sampler = np.random.randn
gfun_mc = lambda x:x>4.5 #定义了 ”标记函数“(indicator function 1(Z>4.5)
num_samples = 10000
Pz_mc,_,_ = monte_carlo(num_samples,sampler,gfun_mc)
print('''Monte Carlo estimation of {:d} samples of P(Z>4.5)={:5E}
      nt Truth: {:5E}'''.format(num_samples,Pz_mc,Pz))
Monte Carlo estimation of 10000 samples of P(Z>4.5)=0.000000E+00

     Truth: 3.39767312476808E-6

从上边可以看出,用

进行估计时,得到的
经常是0。这是因为
实在太小了,
的样本数量足以采集到足够多的满足
的样本。

重要抽样

对于蒙特卡洛抽样可以进行如下处理:

如果设定

也是一个概率分布函数的话,则有:

公式中,

在概率密度函数
下的期望
在在概率密度函数
下的期望。

一般定义

,视为权重。 通过选取合适的概率密度函数
可以减小
的采样难度,或者降低原来蒙特卡洛问题的标准差。

这里针对上述

的问题,进行类似的处理。首先选取另外一个概率分布函数
,满足指数概率分布:

这里

是归一化常数。
## 指数概率分布 q_X()可以由scipy的stats.expon定义
## 对比一下 q_X(x) 与 f_X(x)
fig,(ax1,ax2) = plt.subplots(1,2,figsize=(14,5))
plt.xticks(fontsize=14)
plt.yticks(fontsize=14)
x = np.linspace(4.5,10,1000)
qx = stats.expon.pdf(x)
fx = stats.norm.pdf(x)
ax1.plot(x,qx,label='Exponential')
ax1.plot(x,fx,label='Standard Normal')
ax1.set_xlabel('Z',fontsize=14)
ax1.set_ylabel('PDF',fontsize=14)
ax1.set_title('normal scale')

ax2.semilogy(x,qx,label='Exponential')
ax2.semilogy(x,fx,label='Standard Normal')
ax2.set_xlabel('Z',fontsize=14)
ax2.set_ylabel('PDF',fontsize=14)
ax2.set_title('log scale')
plt.show()

从上边可以看出,选定的

比原有的
有数量级上的差异。
## 利用 q_X(x)对问题进行重点抽样(important sampling, 简记为is)
sampler = lambda ns: stats.expon.rvs(size=ns,loc=4.5)
gfun_is = lambda x:gfun_mc(x)*stats.norm.pdf(x)/stats.expon.pdf(x,loc=4.5)

Pz_is,samples,weights = monte_carlo(num_samples,sampler,gfun_is)
print('''Important sample with {:d} samples Pz_is={:5E}
        nt True is {:5E}'''.format(num_samples,Pz_is,Pz))
Important sample with 10000 samples Pz_is=3.356084E-06

     True is 3.39767312476808E-6

从上可以看出,通过选取合适的

,可以显著提高传统蒙特卡洛估计的准确性
fig,(ax1,ax2)=plt.subplots(1,2,figsize=(15,5))
ax1.semilogy(samples,weights,'o')
ax1.set_xlabel('Z',fontsize=14)
ax1.set_ylabel(r'$f_X(x)/q_X(x)$, (weight)',fontsize=14)
ax2.hist(weights,bins=50,density=True)
ax2.set_title('Histogram of weight')
plt.show()

传统蒙特卡洛采样方差和重点采样方差的对比

在文章开篇说到,传统蒙特卡洛的方差:

而采用了重点采样之后,由于我们其实是在计算:

,但是

下面我们可以对比一下两个方差:

import math
var_mc = (Pz*(1-Pz))/num_samples
std_mc = math.sqrt(var_mc)
print('Standard deviation of MC sample:{:5E}'.format(std_mc))
Standard deviation of MC sample:1.843275E-05

从上边可以看出蒙特卡洛采样的标准差比

都大……
## 通过上文计算的 weights估计重点采样的标准差
var_is = np.var(weights) # 因为上边计算中,根据指数概率分布定义,只有 Z>4.5 才产生 weight,所以隐含了indactor 函数
std_is = math.sqrt(var_is)
print('Standard deviation of important sample:{0}'.format(std_is))
Standard deviation of important sample:4.398853583360912e-06

可见重点采样的标准差比传统蒙特卡洛标准差小了将近一个数量级

下边估算一下两种采样需要的样本数量:

如文章开题所述,如果想要让采样标准差等于

,则有:

epsilon=0.1
require_num_samples = Pz*(1-Pz)/(epsilon*Pz)**2
print(require_num_samples)
print('Required {0} samples for Monte Carlo sampling to have standard deviation=epsilon*p'
      .format(require_num_samples))
29431807.1693590
Required 29431807.1693590 samples for Monte Carlo sampling to have standard deviation=epsilon*p

如果采用重点采样,则有:

require_num_samples_is = var_is/(epsilon*Pz)**2
print(require_num_samples_is)
print('Required {0} samples for Monte Carlo sampling to have standard deviation=epsilon*p'
      .format(require_num_samples_is))
167.616135443252
Required 167.616135443252 samples for Monte Carlo sampling to have standard deviation=epsilon*p

从上边可以看出,通过重点采样,如果想达到相同的标准差,需要的样本数量会从29431807降低到167个。

你可能感兴趣的:(蒙特卡洛采样)