如何对截断的多项式分布进行采样?


9

我需要一种算法来采样截断的多项式分布。那是,

x1Zp1x1pkxkx1!xk!

其中是归一化常数,\ vec x具有k个正分量,并且\ sum x_i = n。我只考虑\ vec {x}\ vec a \ le \ vec x \ le \ vec b范围内的值X ķ Σ X = Ñ X 一个XbZxkxi=nxaXb

如何采样这个截断的多项式分布?

注意:有关对非截断的多项式分布进行采样的算法,请参见Wikipedia。有没有办法使该算法适应截短的分布?

统一版本:问题的一个简单版本是使所有p一世等于p一世=1个/ķ。如果您至少可以设计一种算法来采样这种情况下的截短分布,请发布该算法。尽管不是一般性的答案,但这将帮助我解决当前的其他实际问题。

Answers:


9

如果我对您的理解正确,那么您想从多项式分布中以概率采样值使,但是您希望对分布进行截断以便代表所有。p 1... p ķ Σ X = Ñ 一个X b X x1,,xkp1,,pkixi=naixibixi

我看到了三种解决方案(都不像未截断的情况那样优雅):

  1. 接受拒绝。来自非截断多项式的样本,如果样本符合截断边界,则接受样本,否则拒绝并重复该过程。它速度很快,但效率可能非常低。
rtrmnomReject <- function(R, n, p, a, b) {
  x <- t(rmultinom(R, n, p))
  x[apply(a <= x & x <= b, 1, all) & rowSums(x) == n, ]
}
  1. 直接模拟。以类似于数据生成过程的方式进行采样,即从随机的中采样单个大理石,并重复此过程,直到总共采样了大理石,但是当您从给定的deploy中部署大理石总数时(已经等于),然后停止从这种中抽水。我在下面的脚本中实现了这一点。X b nxibi
# single draw from truncated multinomial with a,b truncation points
rtrmnomDirect <- function(n, p, a, b) {
  k <- length(p)

  repeat {
    pp <- p         # reset pp
    x <- numeric(k) # reset x
    repeat {
      if (sum(x<b) == 1) { # if only a single category is left
        x[x<b] <- x[x<b] + n-sum(x) # fill this category with reminder
        break
      }
      i <- sample.int(k, 1, prob = pp) # sample x[i]
      x[i] <- x[i] + 1  
      if (x[i] == b[i]) pp[i] <- 0 # if x[i] is filled do
      # not sample from it
      if (sum(x) == n) break    # if we picked n, stop
    }
    if (all(x >= a)) break # if all x>=a sample is valid
    # otherwise reject
  }

  return(x)
}
  1. Metropolis算法。最后,第三种也是最有效的方法是使用Metropolis算法。通过使用直接仿真(但可以不同地初始化)以绘制第一个样本来初始化算法。在以下步骤中,反复进行:建议值以概率 接受为,否则将值取为在这里,。作为建议,我使用了函数,该函数采用值并将其从0随机翻转到个案数,并将其移至另一个类别。 Ÿ = q X - 1X ˚F Ý / ˚F X - 1X - 1个 ˚F X α Π p X / X q X i 1X1y=q(Xi1)Xif(y)/f(Xi1)Xi1个FX一世p一世X一世/X一世qX一世-1个step
# draw R values
# 'step' parameter defines magnitude of jumps
# for Meteropolis algorithm
# 'init' is a vector of values to start with
rtrmnomMetrop <- function(R, n, p, a, b,
                          step = 1,
                          init = rtrmnomDirect(n, p, a, b)) {

  k <- length(p)
  if (length(a)==1) a <- rep(a, k)
  if (length(b)==1) b <- rep(b, k)

  # approximate target log-density
  lp <- log(p)
  lf <- function(x) {
    if(any(x < a) || any(x > b) || sum(x) != n)
      return(-Inf)
    sum(lp*x - lfactorial(x))
  }

  step <- max(2, step+1)

  # proposal function
  q <- function(x) {
    idx <- sample.int(k, 2)
    u <- sample.int(step, 1)-1
    x[idx] <- x[idx] + c(-u, u)
    x
  }

  tmp <- init
  x <- matrix(nrow = R, ncol = k)
  ar <- 0

  for (i in 1:R) {
    proposal <- q(tmp)
    prob <- exp(lf(proposal) - lf(tmp))
    if (runif(1) < prob) {
      tmp <- proposal
      ar <- ar + 1
    }
    x[i,] <- tmp
  }

  structure(x, acceptance.rate = ar/R, step = step-1)
}

该算法从开始,然后在分布的不同区域徘徊。显然,它比以前的要快,但是您需要记住,如果使用它来采样少量案例,那么最终可能会得出彼此接近的平局。另一个问题是,您需要确定大小,即算法应进行多大的跳跃-太小可能导致移动缓慢,太大可能导致提出太多无效建议并拒绝它们。您可以在下面查看其用法示例。在图上可以看到:第一行的边际密度,第二行的轨迹图,以及显示变量对的后续跳跃的图。X1个step

n <- 500
a <- 50
b <- 125
p <- c(1,5,2,4,3)/15
k <- length(p)
x <- rtrmnomMetrop(1e4, n, p, a, b, step = 15)

cmb <- combn(1:k, 2)

par.def <- par(mfrow=c(4,5), mar = c(2,2,2,2))
for (i in 1:k)
  hist(x[,i], main = paste0("X",i))
for (i in 1:k)
  plot(x[,i], main = paste0("X",i), type = "l", col = "lightblue")
for (i in 1:ncol(cmb))
  plot(jitter(x[,cmb[1,i]]), jitter(x[,cmb[2,i]]),
       type = "l", main = paste(paste0("X", cmb[,i]), collapse = ":"),
       col = "gray")
par(par.def)

在此处输入图片说明

从这种分布进行抽样的问题是,通常描述了一种非常低效的抽样策略。想象一下,和,和与接近,在这种情况下,您希望对具有不同概率的类别进行采样频率到底。在极端情况下,请想象两类分布,其中和,p1个pķ一个1个==一个ķb1个=bķ一个一世b一世p1个p2一个1个一个2b1个b2,在这种情况下,您预计会发生一些非常罕见的事件(这种分布的现实示例是研究人员重复采样,直到他发现与假设相符的采样为止,因此与作弊相比,与随机采样更多有关) 。

如果将其定义为Rukhin(2007,2008),则分配问题就少得多,在该示例中,您对每个类别的案例进行抽样,即与成比例地抽样ñp一世p一世的。


Rukhin,AL(2007)。处理分配问题中的正态统计和几何随机变量总和。统计和概率字母,77(12),1312-1321。

Rukhin,AL(2008)。平衡分配问题中的停止规则:精确分布和渐近分布。序列分析,27(3),277-292。


ÿ一世=X一世-一个一世=ñ-一世一个一世ÿ一世b一世-一个一世X一个一个

@becko如果将这种方法与我描述的方法进行比较,您会发现它们提供了不同的解决方案。
蒂姆

我不明白他们之间有什么不同?我所做的只是更改变量。
becko

@becko您的起点就是全部x[i] >= a。假设您扔了一个偏头硬币,正面概率为0.9。您扔硬币直到获得至少10个正面和10个反面。在停止点,您的平均头要多于尾。从开始x[1] = ... = x[k] = a意味着您忽略了以下事实:每个的起点x[i]由于的不同而不同p[i]
蒂姆

我明白你的意思了。我唯一不喜欢您的解决方案的地方是,我认为对于特定的参数选择而言,它可能效率很低。
becko

1

这是我尝试将Tim的R代码转换为Python的努力。由于我花了一些时间来理解这个问题并用Python编写了算法,因此我想在这里分享它们,以防人们感兴趣。

  1. 接受拒绝算法
def sample_truncated_multinomial_accept_reject(k, pVec, a, b):
    x = list(np.random.multinomial(k, pVec, size=1)[0])
    h = [x[i] >= a[i] and x[i] <= b[i] for i in range(len(x))]
    while sum(h) < len(h):
        x = list(np.random.multinomial(k, pVec, size=1)[0])
        h = [x[i] >= a[i] and x[i] <= b[i] for i in range(len(x))]
    return x
  1. 直接模拟
def truncated_multinomial_direct_sampling_from_urn(k, pVec, a, b):
    n = len(pVec)
    while True:
        pp = pVec 
        x = [0 for _ in range(n)] 
        while True:
            if sum([x[h] < b[h] for h in range(n)])==1:
                indx = [h for h in range(n) if x[h] < b[h]][0]
                x[indx] = k - sum(x)
                break
            i = np.random.choice(n, 1, p=pp)[0]
            x[i] += 1
            if x[i] == b[i]:
                pp = [pp[j]/(1-pp[i]) for j in range(n)]
                pp[i] = 0 
            if sum(x) == k:
                break  
        if sum([x[h] < a[h] for h in range(n)]) == 0:
            break 
    return x 
  1. 都会算法
def compute_log_function(x, pVec, a, b):
    x_less_a = sum([x[i] < a[i] for i in range(len(pVec))])
    x_more_a = sum([x[i] > b[i] for i in range(len(pVec))])
    if x_less_a or x_more_a or sum(x) != k:
        return float("-inf")
    return np.sum(np.log(pVec)*x - np.array([math.lgamma(h+1) for h in x]))
def sampling_distribution(original, pVec, a, b, step):
    x = copy.deepcopy(original) 
    idx = np.random.choice(len(x), 2, replace=False)
    u = np.random.choice(step, 1)[0]
    x[idx[0]] -= u
    x[idx[1]] += u
    x_less_a = sum([x[i] < a[i] for i in range(len(pVec))])
    x_more_a = sum([x[i] > b[i] for i in range(len(pVec))])
    while x_less_a or x_more_a or sum(x) != k:
        x = copy.deepcopy(original)  
        idx = np.random.choice(len(x), 2, replace=False)
        u = np.random.choice(step, 1)[0]
        x[idx[0]] -= u
        x[idx[1]] += u
        x_less_a = sum([x[i] < a[i] for i in range(len(pVec))])
        x_more_a = sum([x[i] > b[i] for i in range(len(pVec))])
    return x 
def sample_truncated_multinomial_metropolis_hasting(k, pVec, a, b, iters, step=1):
    tmp=sample_truncated_multinomial_accept_reject(k, pVec, a, b)[0]
    step = max(2, step)
    for i in range(iters):
        proposal = sampling_distribution(tmp, pVec, a, b, step)
        if compute_log_function(proposal, pVec, a, b) == float("-inf"):
            continue             
        prob = np.exp(np.array(compute_log_function(proposal, pVec, a, b)) -\
                      np.array(compute_log_function(tmp, pVec, a, b)))
        if np.random.uniform() < prob:
            tmp = proposal 
        step -= 1 
    return tmp

有关此代码的完整实现,请参阅我的Github存储库,网址为

https://github.com/mohsenkarimzadeh/sampling

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.