关于 numpy.random.choice 的工作原理

About how numpy.random.choice works

我正在尝试编写一个模拟 Coupon collector's problem 的程序。这是问题的快速参考:

Given n coupons, how many coupons do you expect you need to draw with replacement before having drawn each coupon at least once.

我想出了两段代码:

(1)

n = 50
coupon = np.arange(0, n)
def collect(coupon):
    number = 49
    collection = np.array([])
    while len(set(collection)) != n:
          number +=1
          collection = np.random.choice(coupon, replace = True, size = number)
    return number

并得到 10 次 collect(coupon) 迭代的结果,平均值为:

[175. 151. 128. 132. 169. 118. 134. 138. 150. 135.]
143.0

(2)

n = 50
coupon = np.arange(0,n)
def collect(coupon):
    collection = set()
    number = 0
    while len(collection) != n:
          number +=1
          got = np.random.choice(coupon)
          collection.add(got)
    return number

运行 10 次 collect(coupon) 迭代的平均值的结果是:

[184, 119, 286, 196, 172, 370, 163, 267, 238, 199]
219.4

我已经尝试过大量交互,代码(1) 和代码(2) 产生了截然不同的结果。

我知道收集所有 50 张优惠券的期望值的正确答案是 225,代码 (2) 是正确的。另一方面,我找不到代码 (1) 为什么失败的合理解释?为什么 numpy.random.choice 在这个例子中不起作用?

Numpy 问题(或没有)

您的代码看起来不错,包括您对 np.random.choice 的使用。您可能会感到困惑的一个地方与 replace 参数的默认值有关。 replace 默认为 True,因此您无需在代码块 (1) 中显式将 replace = True 传递给 choice

除了那个非常小的问题,您的代码没有明显的问题。因此,问题可能出在数学和概率上。

概率问题

225 是绘制尺寸为 1 时的预期值。在您的代码 (1) 中,绘制尺寸随着每次迭代而增加。这样想:随着抽奖规模的增加,在一次抽奖中获得所有优惠券的几率开始变得相当大。我不知道确切的数字(编辑:我找到了一些确切的数字。它们现在在下面的 "Deeper investigation" 部分),但是说得到所有的概率单次抽取 100 张中的 50 张优惠券是 0.01。当您抽到 143 时,获得所有优惠券至少一次的累积几率应该至少为 0.4(可能更大)。

深入调查

通过一些调整,您的代码可用于估计在单次抽奖中看到所有 50 张优惠券的几率 x:

def collectx(coupon, x, reps):
    x = np.asarray(x)
    n = coupon.size

    counts = np.zeros((x.size, reps), dtype=int)
    for i,xsub in enumerate(x):
        for j in range(reps):
            count = 1
            while np.unique(np.random.choice(coupon, size=xsub)).size < n:
                count += 1
            counts[i, j] = count

    return counts

许多不同 x 值的概率可以通过传入一个序列来立即估计。现在,要估计所有开奖大小为 120-143 的概率,您可以 运行:

n = 50
coupon = np.arange(0, n)
counts = collectx(coupon, np.arange(120,144), 100)

这会产生 counts,这是一个形状为 (24, 100) 的数组。绘图大小随行而变化,估计的复制 ID 随列而变化。您可以通过对估计重复取平均值并将结果除以 1:

来获得 "see every coupon in a single draw" 概率
probx = (1/counts.mean(axis=1))

看起来像:

[0.00418971 0.00563    0.00661288 0.00694493 0.00690799 0.00854774
 0.00909339 0.01050531 0.01207875 0.01344086 0.01485222 0.0155642
 0.02004008 0.02115059 0.02015723 0.02377556 0.02639916 0.02379819
 0.02856327 0.03941663 0.04145937 0.03162555 0.03601008 0.04821601]

对于 120 的抽奖规模,概率仍然低于 0.005,但它迅速增加,并且在 143 的抽奖规模下,看到每张优惠券的概率接近 0.05。由于抽奖规模低于 120 的概率很小,因此对抽奖规模 120-143 的概率求和可以合理估计代码块 (1) 在一次抽奖结束时看到所有优惠券的累积概率绘制尺寸 143:

print('%.3f' % probx.sum())

输出:

0.475

所以您的代码块 (1) 很可能在 n==143 时没有看到每张优惠券。这与您观察到的结果完全一致。所以没有 Numpy 问题,只是概率问题。