从大小N(概率加权)集生成随机大小k子集

时间:2014-06-04 14:38:49

标签: python random probability combinatorics weighted

这个问题源于一个音乐训练游戏,我必须从12个可用的音高等级中选择一个随机的3音符和弦,但某些音符比其他音符更可能(这样用户可以为较弱的音符训练更多)。

我认为这个问题很简单:将每个重量视为一个线段,将所有线段一个接一个地放置成一个长段,在这个长段上选择一个随机点,记录它所在的重量,冲洗并重复,直到我们有k个项目。

以下Python代码演示了此技术无法产生正确的结果:

# Choose k items from a set of weights
# return set of winning indices
def Choose(W,k):
    import random

    cumulative = [ sum(W[:i+1]) for i in xrange(len(W)) ]
    totalWeight = cumulative[-1]

    winners = set()
    while len(winners) < k:
        rnd = random.uniform(0.0, totalWeight)

        # Returns first element of cumulative that is >= rnd
        w = next( i for i in xrange(len(cumulative)) if cumulative[i] >= rnd )
        winners.add( w )

    return winners

def Test(N):
    x = [ list(Choose( [5,3,2], 2 )) for i in xrange(int(N/2))]
    y = sum(x, [])
    z = [y.count(i) for i in (0,1,2) ]

    print z

for i in range(10):
    Test(10000)

我从3个权重生成5000个随机对[5,3,2] 输出记录每个重量出现的次数 它应该是5000,3000,2000

为了更好的衡量,我进行了10次实验:

python test.py 
[4173, 3331, 2496]
[4180, 3367, 2453]
[4193, 3393, 2414]
[4228, 3375, 2397]
[4207, 3388, 2405]
[4217, 3377, 2406]
[4173, 3438, 2389]
[4172, 3378, 2450]
[4174, 3371, 2455]
[4208, 3322, 2470]

所以~4200 vs 3300 vs 2400 不是5000 vs 3000 vs 2000

有没有一种简单的方法可以理解为什么这不起作用?

是否有某种方法可以改变权重,可能是'weight [i] - &gt; ln(weight [i])'或类似的东西会产生正确的结果吗?

如何获得正确的结果? (我更关心代码的清晰度而不是最佳效率)

2 个答案:

答案 0 :(得分:2)

numpy.random.choice与p参数一起使用:

np.random.choice(3, size=1000, p=[0.5, 0.3, 0.2])

现在再试一次,看看你得到了什么。

答案 1 :(得分:1)

没有用重物替换是一个棘手的问题。

首先,考虑您的直观解决方案。您生成5000对,并且您希望这些对中的5000对包含1.这意味着每对必须包含1.我怀疑这不是您想要或期望的。要获得您期望的分布,您可以先选择1,然后分别选择概率为.6或.4的2或3。

要做我怀疑你要求的事情,你应该做一些像条件泊松采样这样的事情。我不知道有一个Python模块可以做到这一点,尽管几乎肯定有一个。 &#39;采样&#39; R中的包就可以了。我知道网上没有温和的介绍。

从实际的角度来看,只需做你正在做的事情并调整权重,使概率接近你想要的。对于你想要做的事情,似乎没有必要采用精确的概率。

如果你想要一个简单的方法(效率非常低效)来实现你想要的东西:

1)标准化权重,使所有权重的总和加起来为所需的样本大小。用你的例子.5 + .3 + .2 = 2,因此标准化权重将是[1.,.6,.4]。

2)让p_i为第i个权重视为概率(它们都必须小于或等于1或问题是不可能的。通过选择具有概率p_i的第i个元素来选择样本

3)如果绘制的样本的大小正确输出,否则再次绘制

这是一个快速代码示例

import random
def sample(weights, sample_size):
    w = float(sum(weights))
    normweights = [x * sample_size / w for x in weights]
    samp = [random.random() < pi for pi in normweights]
    while sum(samp) != sample_size:
        samp = [random.random() < pi for pi in normweights]
    return [i for i,b in enumerate(samp) if b]

print(sample([.5,.3,.2], 2))

编辑: 好的,上面的算法很糟糕。我会尝试记住如何正确地做到这一点。

相关问题