【问题标题】:Python: Randomly draw several objects in a listPython:在列表中随机绘制多个对象
【发布时间】:2013-11-08 06:53:11
【问题描述】:

我正在寻找最有效的方法来随机抽取nelements 在一个列表中给定一个概率列表,说明每个元素被选中的概率。

aList = [3,4,2,1,4,3,5,7,6,4]

MyProba = [0.1,0.1,0.2,0,0.1,0,0.2,0,0.2,0.1]

这意味着在每次抽奖时,第一个元素(即3)有0.1的概率被抽奖。当然,

sum(MyProba) == 1 # 总是返回 True len(aList) == len(MyProba) # 总是返回 True

到目前为止,我做了以下工作:

def random_pick(some_list, proba):
    x = random.uniform(0, 1)
    cumulative_proba = 0.0
    for item, item_proba in zip(some_list, proba):
        cumulative_proba += item_proba
        if x < cumulative_proba:
            break
    return item

nb_draws = 10
list_of_drawn_elements = []
for one_draw in range(nb_draws):
    list_of_drawn_elements.append(random_pick(aList, MyProba))

它可以工作,但是对于长列表和较大的 nb_draws 值非常慢。 如何提高此过程的速度?

注意:在我面临的特殊情况下,nb_draws 总是等于aList 的长度。

【问题讨论】:

  • 你试过使用 Numpy 的多项式类吗?它可以满足您的需求,并且如果用 C 实现可能会更快(我不知道。您必须对其进行测试。)请参见此处:docs.scipy.org/doc/numpy/reference/generated/…
  • numpy.random.choice 更合适。所有繁重的工作都在 C 中完成,算法应该比您使用的更高效。
  • 您可能有兴趣在 Python 中阅读 Bendersky 在 weighted random choice 上的页面——它列出了许多可能的方法并提供了性能比较。

标签: python list random performance


【解决方案1】:

一般的想法(正如其他人的回答所概述的那样)是您的方法效率低下,因为每次抽取样本时都会进行预处理(累积分布的计算),尽管这样做就足够了采样前一次,然后使用预处理后的数据进行采样。

使用Walker's alias method 可以有效地完成预处理采样。我已经实施了一段时间;看看source code。 (对不起外部链接,但我认为在这里发布它太长了)。我的版本需要 NumPy;如果您不想使用 NumPy,还有一个 NumPy-free alternative(我的版本基于此)。

编辑:Walker 的别名方法的解释可以在我提供的第一个链接中找到。简而言之,假设您以某种方式设法构建了一个矩形“飞镖板”,该“飞镖板”被细分为多个部分,每个部分对应于您的原始项目之一,并且每个部分的面积与选择相应的所需概率成正比元素。然后,您可以开始在飞镖板上随机投掷飞镖(通过生成两个随机数来指定飞镖结束位置的水平和垂直坐标)并检查飞镖击中的区域。与区域对应的项目将是您选择的项目。 Walker 的别名方法只是构造飞镖板的线性时间预处理。然后可以在恒定时间内完成每个元素的绘制。最后,从 n 中抽取 m 个元素将需要 O(n) 的预处理成本和 O(m) 用于生成样本,总复杂度为 O(n + m)。

【讨论】:

  • 我还没有真正尝试理解它的作用,但是在运行源代码时我收到此错误消息“第 97 行:列表索引必须是整数,而不是 numpy.string_”
  • 抱歉,要点有误;第 95 行应该是 nrand = defaultdict(int) 而不是 rand = defaultdict(int)。还在我的答案中添加了对该方法的简短说明。
【解决方案2】:

这是我的惰性方法...为所需分布构建一个包含预期值数量的列表,并使用random.choice() 从列表中选择一个值。

>>> import random
>>>
>>> value_probs = dict(zip([3,4,2,1,4,3,5,7,6,4], [0.1,0.1,0.2,0,0.1,0,0.2,0,0.2,0.1]))
>>> expected_dist = sum([[i] * int(prob * 100) for i, prob in value_probs.iteritems()], [])
>>> random.choice(expected_dist)

【讨论】:

    【解决方案3】:

    您可能会尝试预先计算每个元素的累积概率范围,并根据这些间隔制作一棵树。然后你会得到一个对数复杂度来查找与生成概率相对应的元素,而不是你现在拥有的线性复杂度。

    【讨论】:

    【解决方案4】:

    每次调用 random_pick 时,您都在计算 cumulative_proba。我建议在方法之外进行计算,使用更好的数据结构来存储,比如二叉搜索树,这样可以将时间复杂度从 O(n) 降低到 O(lgn)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-10-06
      • 1970-01-01
      • 2012-11-01
      • 2013-10-25
      • 2012-07-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多