人口蒙特卡罗实现

3
我正在尝试使用Python实现Population Monte Carlo算法,该算法在这篇论文中有描述(参见第78页图3),用于一个只有一个参数的简单模型(请参见函数model())。不幸的是,算法无法正常工作,我无法弄清楚问题所在。请查看下面的代码实现。实际函数名为abc()。所有其他函数都可以视为辅助函数,并且似乎工作正常。
为了检查算法是否有效,我首先使用模型的唯一参数param = 8生成观察数据。因此,由ABC算法产生的后验应该集中在8周围。但事实并非如此,我想知道原因。
我将非常感谢任何帮助或评论。
# imports

from math import exp
from math import log
from math import sqrt
import numpy as np
import random
from scipy.stats import norm


# globals
N = 300              # sample size
N_PARTICLE = 300      # number of particles
ITERS = 5            # number of decreasing thresholds
M = 10               # number of words to remember
MEAN = 7             # prior mean of parameter
SD = 2               # prior sd of parameter


def model(param):
  recall_prob_all = 1/(1 + np.exp(M - param))
  recall_prob_one_item = np.exp(np.log(recall_prob_all) / float(M))
  return sum([1 if random.random() < recall_prob_one_item else 0 for item in range(M)])

## example
print "Output of model function: \n" + str(model(10)) + "\n"

# generate data from model
def generate(param):
  out = np.empty(N)
  for i in range(N):
    out[i] = model(param)
  return out

## example

print "Output of generate function: \n" + str(generate(10)) + "\n"


# distance function (sum of squared error)
def distance(obsData,simData):
  out = 0.0
  for i in range(len(obsData)):
    out += (obsData[i] - simData[i]) * (obsData[i] - simData[i])
  return out

## example

print "Output of distance function: \n" + str(distance([1,2,3],[4,5,6])) + "\n"


# sample new particles based on weights
def sample(particles, weights):
  return np.random.choice(particles, 1, p=weights)

## example

print "Output of sample function: \n" + str(sample([1,2,3],[0.1,0.1,0.8])) + "\n"


# perturbance function
def perturb(variance):
  return np.random.normal(0,sqrt(variance),1)[0]

## example 

print "Output of perturb function: \n" + str(perturb(1)) + "\n"

# compute new weight
def computeWeight(prevWeights,prevParticles,prevVariance,currentParticle):
  denom = 0.0
  proposal = norm(currentParticle, sqrt(prevVariance))
  prior = norm(MEAN,SD)
  for i in range(len(prevParticles)):
    denom += prevWeights[i] * proposal.pdf(prevParticles[i])
  return prior.pdf(currentParticle)/denom


## example 

prevWeights = [0.2,0.3,0.5]
prevParticles = [1,2,3]
prevVariance = 1
currentParticle = 2.5
print "Output of computeWeight function: \n" + str(computeWeight(prevWeights,prevParticles,prevVariance,currentParticle)) + "\n"


# normalize weights
def normalize(weights):
  return weights/np.sum(weights)


## example 

print "Output of normalize function: \n" + str(normalize([3.,5.,9.])) + "\n"


# sampling from prior distribution
def rprior():
  return np.random.normal(MEAN,SD,1)[0]

## example 

print "Output of rprior function: \n" + str(rprior()) + "\n"


# ABC using Population Monte Carlo sampling
def abc(obsData,eps):
  draw = 0
  Distance = 1e9
  variance = np.empty(ITERS)
  simData = np.empty(N)
  particles = np.empty([ITERS,N_PARTICLE])
  weights = np.empty([ITERS,N_PARTICLE])

  for t in range(ITERS):
    if t == 0:
      for i in range(N_PARTICLE):
        while(Distance > eps[t]):
          draw = rprior()
          simData = generate(draw)
          Distance = distance(obsData,simData)

        Distance = 1e9
        particles[t][i] = draw
        weights[t][i] = 1./N_PARTICLE

      variance[t] = 2 * np.var(particles[t])
      continue


    for i in range(N_PARTICLE):
      while(Distance > eps[t]):
        draw = sample(particles[t-1],weights[t-1])
        draw += perturb(variance[t-1])
        simData = generate(draw)
        Distance = distance(obsData,simData)

      Distance = 1e9
      particles[t][i] = draw
      weights[t][i] = computeWeight(weights[t-1],particles[t-1],variance[t-1],particles[t][i])


    weights[t] = normalize(weights[t])  
    variance[t] = 2 * np.var(particles[t])

  return particles[ITERS-1]



true_param = 9
obsData = generate(true_param)
eps = [15000,10000,8000,6000,3000]
posterior = abc(obsData,eps)
#print posterior

1
一个不熟悉MC工作方式的人很难理解你的代码。你能确认哪一部分是按预期工作的吗?另外,你引用了哪篇文章和其中的哪些部分也不是完全清晰的。或许你可以在代码注释中进行交叉引用。 - aldanor
谢谢你的评论。我已经添加了论文链接,其中介绍了该算法的伪代码。 - beginneR
这篇论文顺便说一下并不是免费提供的。 - ayhan
@ayhan 抱歉,我可能以某种方式登录了。该算法也在此处提供(请参见第5页底部)。https://arxiv.org/pdf/0805.2256v9.pdf - beginneR
1个回答

3

我在寻找Python实现PMC算法的过程中偶然发现了这个问题,因为恰巧我正在将这篇论文中的技术应用到自己的研究中。

你能发布一下你得到的结果吗?我的猜测是,1)你选择的距离函数(和/或相似性阈值)很差,或者2)你没有使用足够的粒子。我可能错了(我对样本统计不是很熟悉),但你的距离函数暗示着你随机抽取的顺序很重要。我需要再思考一下是否它实际上对收敛特性产生任何影响(可能没有),但为什么不直接使用平均值或中位数作为你的样本统计量呢?

我使用1000个粒子和真实参数值为8来运行您的代码,同时使用样本均值之间的绝对差作为距离函数,在三次迭代中使用[0.5、0.3、0.1]的epsilon;我的估计后验分布的峰值似乎在每次迭代中都接近8,同时人口方差也有所减少。请注意,仍然存在明显的向右偏差,但这是由于您的模型的不对称性造成的(小于或等于8的参数值永远不会导致多于8个观察到的成功,而所有大于8的参数值都可以导致右偏分布)。以下是我的结果图:

Particle evolution over three iterations, converging to the true value of 8, and demonstrating a slight asymmetry in the tendency towards higher parameter values.


你说得对,谢谢!问题出在距离函数上。就在你写这个之前的几天,我也改成了一个简单的“均值绝对差”距离函数,然后它就好了。 - beginneR
目前,我在处理多个参数时,正在苦苦挣扎计算权重。对我来说,舍弃或接受一组参数是有意义的。因此,与其基于它们的权重选择参数的单个值,我想知道是否存在每组参数一个权重的情况。您知道哪个版本是正确的吗? - beginneR
我认为我们正在从参数的完整联合后验分布中抽样,因此每个粒子具有每个单独参数的一个采样值和一个重要性采样权重。对于每个N个粒子:1)使用上一次迭代的粒子权重从先前迭代中重新取样粒子;2)在每个随机抽样粒子中,每个参数值都使用建议分布(例如多元正态分布)扰动;3)针对每个新粒子重复步骤2,直到满足新的epsilon;4)计算并归一化新的权重。 - jbb
谢谢!但是假设你想要估计正态分布的均值和方差。什么是适当的联合先验分布?必须指定联合先验才能计算粒子的权重。 - beginneR
另一个问题是,为了计算新权重,您需要计算先前粒子的方差。但是,如果一个粒子是一个包含每个参数采样值的列表/元组,那么如何计算粒子的方差呢? - beginneR
我在文献中一直很难找到明确解释PMC在ABC应用中的详细实现方式。不过,我发现Joel Akaret的这个包特别有帮助,你可能会发现他的代码很有用。关于您的先验概率,由您指定。如果您假设参数独立,则可以将先验概率相乘以获得联合先验概率。也许可以尝试使用正态先验分布来计算均值和Gamma先验分布来计算(正定)方差。 - jbb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接