【问题标题】:Generate random size-k subset from size-N (probability-weighted) set从大小为 N(概率加权)的集合中生成随机大小为 k 的子集
【发布时间】:2014-07-25 06:46:04
【问题描述】:

这个问题源于一个音乐训练游戏,我必须从 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 个权重 [5,3,2] 生成 5000 个随机对 输出记录每个权重出现的次数 应该是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] -> ln(weight[i])' 或类似的东西,这样可以得到正确的结果?

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

【问题讨论】:

    标签: python random probability combinatorics weighted


    【解决方案1】:

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

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

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

    【讨论】:

    • 他似乎想要np.random.choice(2, size=1000, replace=False, p=[0.5, 0.3, 0.2]),即3个项目中的两个单独的选择。
    • 感谢 Emre,Numpy 充满了惊喜!不幸的是,我将使用不支持 numpy 的 IronPython。
    【解决方案2】:

    不替换权重的抽样是一个棘手的问题。

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

    要执行我怀疑您要求的操作,您应该执行条件泊松采样之类的操作。我不知道有一个 Python 模块可以做到这一点,尽管几乎可以肯定有一个。 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))
    

    编辑: 好的,上面的算法很糟糕。我会尽量记住如何正确操作。

    【讨论】:

    • 是的,我内心的数学家一直在试图解决抽象问题。你是对的,在我的特殊情况下,我不需要比我拥有的更精确的东西。不过我很好奇!
    • 如果您在大学或有机会接触大学,您可以看看 Yves Tille 的“采样算法”。它会告诉你如何计算概率(它并不简单,但它会逗你内在的数学家)
    猜你喜欢
    • 1970-01-01
    • 2017-09-09
    • 1970-01-01
    • 2018-09-06
    • 1970-01-01
    • 1970-01-01
    • 2022-01-24
    • 1970-01-01
    • 2012-11-25
    相关资源
    最近更新 更多