从变量权重中随机生成组合

时间:2015-06-24 07:00:23

标签: php algorithm probability

非常重要的编辑:所有 i 唯一

问题

我有 n 唯一 对象的 A 列表。每个对象 A i 具有可变百分比 P i

我想创建一个算法,生成 k 对象的新列表 B k &lt; n / 2 ,在大多数情况下, k 明显少于 n / < sub> 2 。例如,n = 231,k = 21 )。列表 B 应该没有重复项,并且将使用源自列表 A 的对象填充,但具有以下限制:

  

对象 A i 出现在 B 中的概率为 P i

我尝试过什么

(这些片段在PHP中仅用于测试目的) 我首先列出了 A

$list = [
    "A" => 2.5, 
    "B" => 2.5, 
    "C" => 2.5, 
    "D" => 2.5, 
    "E" => 2.5, 
    "F" => 2.5, 
    "G" => 2.5, 
    "H" => 2.5, 
    "I" => 5,   
    "J" => 5,   
    "K" => 2.5, 
    "L" => 2.5, 
    "M" => 2.5, 
    "N" => 2.5, 
    "O" => 2.5, 
    "P" => 2.5, 
    "Q" => 2.5, 
    "R" => 2.5, 
    "S" => 2.5, 
    "T" => 2.5, 
    "U" => 5,   
    "V" => 5,   
    "W" => 5,   
    "X" => 5,   
    "Y" => 5,   
    "Z" => 20   
];

首先,我尝试了以下两个算法(这些仅仅是为了测试目的而在PHP中):

$result = [];

while (count($result) < 10) {
    $rnd = rand(0,10000000) / 100000;

    $sum = 0;
    foreach ($list as $key => $value) {
        $sum += $value;
        if ($rnd <= $sum) {
            if (in_array($key,$result)) {
                break;
            } else {
                $result[] = $key;
                break;
            }
        }
    }
}

$result = [];

while (count($result) < 10) {
    $sum = 0;
    foreach ($list as $key => $value) {
        $sum += $value;
    }

    $rnd = rand(0,$sum * 100000) / 100000;

    $sum = 0;
    foreach ($list as $key => $value) {
        $sum += $value;
        if ($rnd <= $sum) {
            $result[] = $key;
            unset($list[$key]);
            break;
        }
    }
}

两种算法之间的唯一区别是,当遇到重复时,会再次尝试,并且在拾取时会删除对象表单 A 。事实证明,这两种算法具有相同的概率输出。

我运行了第二个算法100,000次并跟踪每个字母被挑选的次数。以下数组列出了基于100,000次测试的任何列表 B 中拾取字母的百分比。

[A] => 30.213
[B] => 29.865
[C] => 30.357
[D] => 30.198
[E] => 30.152
[F] => 30.472
[G] => 30.343
[H] => 30.011
[I] => 51.367
[J] => 51.683
[K] => 30.271
[L] => 30.197
[M] => 30.341
[N] => 30.15
[O] => 30.225
[P] => 30.135
[Q] => 30.406
[R] => 30.083
[S] => 30.251
[T] => 30.369
[U] => 51.671
[V] => 52.098
[W] => 51.772
[X] => 51.739
[Y] => 51.891
[Z] => 93.74

回顾算法时,这是有道理的。该算法错误地将原始百分比解释为对任何给定位置挑选对象的百分比,而不是任何列表 B 。因此,例如,实际上,在列表 B 中选择Z的几率为93%,但是为索引选择Z的机会 B n 是20%。这不是我想要的。我希望在 B 列表中选择Z的几率为20%。

这甚至可能吗?怎么办呢?

编辑1

我试过简单地将所有 P i = k的总和,如果所有 P i ,这都有效是相同的,但在修改它们的价值后,它开始变得越来越错。

初始概率

$list= [
    "A" => 8.4615,
    "B" => 68.4615,
    "C" => 13.4615,
    "D" => 63.4615,
    "E" => 18.4615,
    "F" => 58.4615,
    "G" => 23.4615,
    "H" => 53.4615,
    "I" => 28.4615,
    "J" => 48.4615,
    "K" => 33.4615,
    "L" => 43.4615,
    "M" => 38.4615,
    "N" => 38.4615,
    "O" => 38.4615,
    "P" => 38.4615,
    "Q" => 38.4615,
    "R" => 38.4615,
    "S" => 38.4615,
    "T" => 38.4615,
    "U" => 38.4615,
    "V" => 38.4615,
    "W" => 38.4615,
    "X" => 38.4615,
    "Y" =>38.4615,
    "Z" => 38.4615
];

10,000次运行后的结果

Array
(
    [A] => 10.324
    [B] => 59.298
    [C] => 15.902
    [D] => 56.299
    [E] => 21.16
    [F] => 53.621
    [G] => 25.907
    [H] => 50.163
    [I] => 30.932
    [J] => 47.114
    [K] => 35.344
    [L] => 43.175
    [M] => 39.141
    [N] => 39.127
    [O] => 39.346
    [P] => 39.364
    [Q] => 39.501
    [R] => 39.05
    [S] => 39.555
    [T] => 39.239
    [U] => 39.283
    [V] => 39.408
    [W] => 39.317
    [X] => 39.339
    [Y] => 39.569
    [Z] => 39.522
)

3 个答案:

答案 0 :(得分:3)

让我们分析一下。 使用替换 :(不是您想要的,但更容易分析)。

给定大小为L的列表k,以及元素a_ia_i在列表中的概率由您的值{{1}表示}。

让我们检查p_i在列表中的某个索引a_i处的概率。我们将该概率表示为j。请注意,对于列表中的任何索引q_i,j t - 我们只需说q_i,j = q_i,t

a_i将在列表中的任何位置的概率表示为:

q_i_1=q_i_2=...=q_i_k=q_i

但它也是1-(1-q_i)^k - 所以我们需要解决等式

p_i

一种方法是newton-raphson method

在计算每个元素的概率后,检查它是否确实是一个可推广空间(总和为1,所有概率都在[0,1]中)。如果不是 - 则无法对给定的概率和1-(1-q_i)^k = pi 1 - (1-q_i)^k -pi = 0 进行处理。

无需替换:这比较复杂,因为现在k(选项不是i.i.d)。这里概率的计算会比较棘手,我现在还不确定如何计算它们,我想在创建列表时需要在运行时完成。

(删除了我几乎肯定会有偏见的解决方案)。

答案 1 :(得分:3)

我们必须sum_i P_i = k,否则我们就无法成功。

如前所述,这个问题有点容易,但你可能不喜欢这个答案,理由是它不够随意&#34;

Sample a uniform random permutation Perm on the integers [0, n)
Sample X uniformly at random from [0, 1)
For i in Perm
    If X < P_i, then append A_i to B and update X := X + (1 - P_i)
    Else, update X := X - P_i
End

您希望使用定点算术而不是浮点近似计算涉及实数的计算。

缺失的条件是该分布具有称为&#34;最大熵的技术属性&#34;。像amit一样,我想不出一个好方法。这是一种笨拙的方式。

我解决此问题的第一个(也是错误的)本能是将A_i中的每个B独立地包含在概率P_i中并重试,直到B为正确的长度(因为你可以问问数学的原因,不会有太多的重试。问题是条件会扰乱概率。如果P_1 = 1/3P_2 = 2/3以及k = 1,则结果为

{}: probability 2/9
{A_1}: probability 1/9
{A_2}: probability 4/9
{A_1, A_2}: probability 2/9,

并且1/5的条件概率实际为A_14/5的{​​{1}}实际为A_2

相反,我们应该替换产生适当条件分布的新概率Q_i。我不知道Q_i的封闭表单,因此我建议使用像gradient descent这样的数值优化算法来查找它们。初始化Q_i = P_i(为什么不呢?)。使用动态编程,可以找到Q_i的当前设置,给定l元素的结果,A_i是其中一个元素的概率。 (我们只关心l = k条目,但我们需要其他条目才能使重复发生。)通过更多工作,我们可以获得整个渐变。对不起,这太粗略了。

在Python中,使用似乎总是收敛的非线性求解方法(同时将每个q_i更新为其边缘正确的值并进行标准化):

#!/usr/bin/env python3
import collections
import operator
import random


def constrained_sample(qs):
    k = round(sum(qs))
    while True:
        sample = [i for i, q in enumerate(qs) if random.random() < q]
        if len(sample) == k:
            return sample


def size_distribution(qs):
    size_dist = [1]
    for q in qs:
        size_dist.append(0)
        for j in range(len(size_dist) - 1, 0, -1):
            size_dist[j] += size_dist[j - 1] * q
            size_dist[j - 1] *= 1 - q
    assert abs(sum(size_dist) - 1) <= 1e-10
    return size_dist


def size_distribution_without(size_dist, q):
    size_dist = size_dist[:]
    if q >= 0.5:
        for j in range(len(size_dist) - 1, 0, -1):
            size_dist[j] /= q
            size_dist[j - 1] -= size_dist[j] * (1 - q)
        del size_dist[0]
    else:
        for j in range(1, len(size_dist)):
            size_dist[j - 1] /= 1 - q
            size_dist[j] -= size_dist[j - 1] * q
        del size_dist[-1]
    assert abs(sum(size_dist) - 1) <= 1e-10
    return size_dist


def test_size_distribution(qs):
    d = size_distribution(qs)
    for i, q in enumerate(qs):
        d1a = size_distribution_without(d, q)
        d1b = size_distribution(qs[:i] + qs[i + 1:])
        assert len(d1a) == len(d1b)
        assert max(map(abs, map(operator.sub, d1a, d1b))) <= 1e-10


def normalized(qs, k):
    sum_qs = sum(qs)
    qs = [q * k / sum_qs for q in qs]
    assert abs(sum(qs) / k - 1) <= 1e-10
    return qs


def approximate_qs(ps, reps=100):
    k = round(sum(ps))
    qs = ps
    for j in range(reps):
        size_dist = size_distribution(qs)
        for i, p in enumerate(ps):
            d = size_distribution_without(size_dist, qs[i])
            d.append(0)
            qs[i] = p * d[k] / ((1 - p) * d[k - 1] + p * d[k])
        qs = normalized(qs, k)
        print(qs)
    return qs


def test(ps, reps=100000):
    qs = approximate_qs(ps)
    counter = collections.Counter()
    for j in range(reps):
        counter.update(constrained_sample(qs))
    test_size_distribution(qs)
    print(size_distribution(qs))
    print('p', 'Actual', sep='\t')
    for i, p in enumerate(ps):
        print(p, counter[i] / reps, sep='\t')


if __name__ == '__main__':
    test([i / 25 for i in range(26)])

答案 2 :(得分:0)

除非我的数学技能比我想的要弱很多,否则列表B中你的例子中列表A的元素的平均机会应该是10/26 = 0.38。
如果你降低任何物体的机会,必须有其他人有更高的机会。 此外,列表A中的概率无法计算:它们太低:您无法填充列表/您没有足够的元素可供选择。

假设上述内容正确(或足够正确),这意味着在您的列表A中,您的平均权重必须是随机选择的平均机会。反过来,这意味着列表a中的概率总和不超过100。

除非我完全错了,否则就是......