【问题标题】:How to implement roulette wheel for picking random objects in python?如何实现轮盘赌以在 python 中选择随机对象?
【发布时间】:2016-01-09 19:02:20
【问题描述】:

在 python 中,我有一个对象列表(列表大小 >= 2),其中有一个名为 score 的属性,其值是 >= 0(无上限)的任何浮点数。

我想从列表中随机选择 2 个不同的对象,但要确保选择得分更高的值的机会更高。基本上,分数越高,被选中的机会就越大。我正在考虑做一个轮盘赌风格,我将所有分数相加,然后对每个项目进行完美的计算,百分比是它的分数除以总分。

但是我如何仍然选择这 2 个对象?

谢谢

【问题讨论】:

  • 一旦选择了第一个对象,是否会在列表中被替换,即是否可以再次选择第二次?

标签: python random probability


【解决方案1】:

这个问题不是很清楚,但我想我明白你的意思。 您应该使用逆 CDF 类型方法。下面的示例将为您的分数列表返回一个索引,然后使用它来获取您想要的值。显然,从编程的角度来看,有更聪明的方法可以做到这一点,但您显然需要了解该方法,我认为这个示例会对您有很大帮助。

>>> import numpy as np
>>> import pandas as pd
>>> scores = [100, 200, 300, 400, 500]
>>> scores = np.array(scores)
>>> sum = scores.sum()
>>> sum
1500
>>> percentages = scores/float(sum)
>>> percentages
array([ 0.06666667,  0.13333333,  0.2       ,  0.26666667,  0.33333333])
>>> cdf = percentages.cumsum()
>>> cdf
array([ 0.06666667,  0.2       ,  0.4       ,  0.66666667,  1.        ])
>>> cdf = np.concatenate([np.array([0]), cdf])
>>> cdf
array([ 0.        ,  0.06666667,  0.2       ,  0.4       ,  0.66666667,  1.        ])
>>>def returnobj(cdf, randnum):
>>>    for i in range(len(cdf)-1):
>>>        if((randnum>cdf[i])&(randnum<=cdf[i+1])):
>>>            return i
##########
#This Bit is just to show how the results look, and to show it favours higher scores
>>>for i in range(10000):
>>>    results.append(returnobj(cdf, np.random.random()))
>>>results=pd.DataFrame(results)
>>>results[results.columns[0]] = results[results.columns[0]].astype(str)
>>>results['a'] = results[0]
>>>print results.groupby(0).count()
0   639
1  1375
2  2039
3  2678
4  3269

【讨论】:

    【解决方案2】:

    我会使用静态(类)变量来跟踪单个对象的最高分数。然后我会有一个非静态方法来评估实例分数与最高分数的百分比。 (基本上就是你说的)

    然后,在我的主程序中,我将使用普通的randrange 来选择您的一个对象的index。然后我会调用该对象的百分比函数并在 0-1 之间创建另一个随机变量。如果变量大于函数的结果,则选择对象,否则不要选择它。重复此操作,直到您选择了所需数量的对象。

    这很可能不是最好和灵活的方法,但它应该会给你想要的比例。

    【讨论】:

      【解决方案3】:

      这是一个“简单”的代码来做我认为你想要的。

      诀窍是选择一个,将其从列表中删除,然后重试。

      此解决方案适用于您想要挑选的任意数量的物品...

      import random
      
      class Obj(object):
      
          def __init__(self, score):
              self.score = score
      
      
          def __repr__(self):
              return "<Obj %.2f>"%self.score
      
      
      def get_items(l, num=2):
          if num==0: return []
          total = sum(o.score for o in l)
          for obj in l:
              obj._chance = obj.score / total
          l = sorted(l, key=lambda o: o._chance)
          selected = l[0] #float problems resolved ;)
          r = random.random()
          chance = 0.0
          for o in l:
              chance += o._chance
              if chance > r:
                  selected = o
                  break
          l.remove(selected)
          return [selected] + get_items(l, num-1)
      
      l = [Obj(1.0),Obj(2.0),Obj(3.0)]
      
      print (get_items(l, num=2))  
      

      【讨论】:

        【解决方案4】:

        我会假设一个项目的分数不会改变,并且项目是唯一的。

        获得随机加权项的最佳方法是索引到累积权重列表中。

        要获取样本,请继续选择随机项目,直到您有足够的唯一值。

        代码:

        from bisect import bisect_left
        from itertools import accumulate
        from random import random, shuffle
        
        class WeightedRandom:
            def __init__(self, items, weights):
                self.items    = list(items)
                self.weights  = list(weights)
                self.cum_wt   = list(accumulate(weights))
                self.total_wt = self.cum_wt[-1]
        
            def random(self):
                value = random() * self.total_wt
                index = bisect_left(self.cum_wt, value)
                return self.items[index]
        
            def sample(self, n):
                items = set()
                while len(items) < n:
                    items.add(self.random())
                # Note: casting from set to list introduces
                #   an ordering bias; we have to remove this bias.
                items = list(items)
                shuffle(items)
                return items
        

        然后

        >>> roulette = WeightedRandom("ABCDEFGHIJ", range(1, 11))
        >>> for i in range(10):
        ...     print(roulette.sample(2))
        ['J', 'D']
        ['J', 'C']
        ['I', 'F']
        ['C', 'H']
        ['J', 'E']
        ['A', 'G']
        ['J', 'H']
        ['E', 'I']
        ['I', 'D']
        ['E', 'I']
        

        但请注意,如果前 (n - 1) 个项目的总和占 total_wt 的很大一部分,sample 可能需要很长时间才能运行!

        【讨论】:

          【解决方案5】:

          有几种方法可以使用 numpy 的 tensorflow 之类的库:

          tf.random.categorical([tf.math.log(probs)], 1).numpy()[0][0]
          

          或:

          np.random.multinomial(1, probs/sum_probs).argmax()
          

          但是使用 random 模块的简单函数应该可以解决问题:

          def roulette_wheel(re: random.Random, probs: List[float], sum_probs: float):
              """Returns the index of chosen element, with probability proportionate to its value"""
          
              n = re.uniform(0, sum_probs)
              for i, p in enumerate(probs):
                  if n <= p:
                      return i
                  n -= p
              raise ValueError(f"{sum_probs} should be equal to {sum(probs)}")
          

          【讨论】:

            猜你喜欢
            • 2021-06-02
            • 2018-07-04
            • 2012-05-06
            • 2015-08-14
            • 1970-01-01
            • 2019-07-06
            • 2014-05-25
            • 1970-01-01
            • 2010-09-15
            相关资源
            最近更新 更多