重要性采样(importance sampling)
重要抽样主要为了解决一下几种问题:
1. 为了减小蒙特卡洛方法的方差
2. 为了对很少发生事件(rare event)进行有效采样,这类问题如果用传统蒙特卡洛采样会需要非常大的样本
3. 对于不容易抽样的样本进行抽样(有的样本遵循的概率分布函数
下边以一个例子对 rare event 的采样问题进行说明:1. 我们对事件
- 记:
- 我们想要得到蒙特卡洛对
的估计的标准差
和
是一个数量级的,即
,一般
首先,用蒙特卡洛对
事实上
利用蒙特卡洛方法对随机变量
根据蒙特卡洛采样的性质:
如果需要
如果
已知
%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从上边可以看出,用
重要抽样
对于蒙特卡洛抽样可以进行如下处理:
如果设定
公式中,
一般定义
这里针对上述
这里
## 指数概率分布 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个。
版权声明:本文为weixin_39618597原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。