【问题标题】:How to choose keys from a python dictionary based on weighted probability? [duplicate]如何根据加权概率从 python 字典中选择键? [复制]
【发布时间】:2017-04-17 01:40:35
【问题描述】:

我有一个 Python 字典,其中键代表某些项目,值代表所述项目的某些(标准化)权重。例如:

d = {'a': 0.0625, 'c': 0.625, 'b': 0.3125}
# Note that sum([v for k,v in d.iteritems()]) == 1 for all `d`

鉴于项目与权重的这种相关性,我如何从d 中选择一个键,以使 6.25% 的时间结果为“a”,32.25% 的时间为“b”,以及 62.5%结果是'c'?

【问题讨论】:

  • 你看过this吗?

标签: python random probability


【解决方案1】:
def weighted_random_by_dct(dct):
    rand_val = random.random()
    total = 0
    for k, v in dct.items():
        total += v
        if rand_val <= total:
            return k
    assert False, 'unreachable'

应该做的伎俩。遍历每个键并保持运行总和,如果随机值(介于 0 和 1 之间)落在插槽中,则返回该键

【讨论】:

    【解决方案2】:

    从 Python 3.6 开始,您可以使用内置的 random.choices() 而不必使用 Numpy。

    那么,如果我们想从您的字典中采样(替换)25 个键,其中的值是被采样的权重/概率,我们可以简单地写:

    import random
    random.choices(list(my_dict.keys()), weights=my_dict.values(), k=25)
    

    这会输出一个采样键列表:

    ['c', 'b', 'c', 'b', 'b', 'c', 'c', 'c', 'b', 'c', 'b', 'c', 'b', 'c', 'c', 'c', 'c', 'c', 'a', 'b']
    

    如果您只需要一个键,请将 k 设置为 1 并从 random.choices 返回的列表中提取单个元素:

    random.choices(list(my_dict.keys()), weights=my_dict.values(), k=1)[0]
    

    (如果您不将 my_dict.keys() 转换为列表,您将收到关于它如何不可下标的 TypeError。)

    这是来自docs的相关sn-p:

    random.choices(population, weights=None, *, cum_weights=None, k=1)

    返回一个 k 大小的元素列表,该列表是从具有替换的总体中选择的。如果人口为空,则引发 IndexError。

    如果指定了权重序列,则根据相对权重进行选择。或者,如果给出了 cum_weights 序列,则根据累积权重进行选择(可能使用 itertools.accumulate() 计算)。例如,相对权重 [10, 5, 30, 5] 等价于累积权重 [10, 15, 45, 50]。在内部,相对权重会在进行选择之前转换为累积权重,因此提供累积权重可以节省工作量。

    如果既没有指定权重也没有指定 cum_weights,则以相等的概率进行选择。如果提供了权重序列,则它必须与总体序列的长度相同。同时指定权重和 cum_weights 是 TypeError。

    权重或 cum_weights 可以使用与 random() 返回的浮点值互操作的任何数字类型(包括整数、浮点数和分数,但不包括小数)。权重假定为非负数。

    对于给定的种子,具有相同权重的choices() 函数通常会产生与重复调用choice() 不同的序列。 Choices() 使用的算法使用浮点算法来实现内部一致性和速度。 choice() 使用的算法默认为整数运算,重复选择,以避免舍入误差产生的小偏差。

    根据https://stackoverflow.com/a/39976962/5139284 的 cmets,random.choices 对于小型阵列更快,numpy.random.choice 对于大型阵列更快。 numpy.random.choice 还提供了无需替换即可采样的选项,而没有内置的 Python 标准库函数。

    【讨论】:

      【解决方案3】:

      如果您打算经常这样做,您可以使用numpy 从使用np.random.choice() 的加权概率列表中选择您的密钥。下面的示例将使用加权概率选择您的密钥 10,000 次。

      import numpy as np
      
      probs = [0.0625, 0.625, 0.3125]
      keys = ['a', 'c', 'b']
      
      choice_list = np.random.choice(keys, 10000, replace=True, p=probs)
      

      【讨论】:

        【解决方案4】:

        不确定您的用例在这里,但您可以查看 NLTK 包中的频率分布/概率分布类,它们处理所有细节。

        FreqDist 是计数器的扩展,可以传递给ProbDistI 接口。 ProbDistI 接口公开了一个“generate()”方法,可用于对分布进行采样,以及一个“prob(sample)”方法,可用于获取给定键的概率。

        对于您的情况,您希望使用最大似然估计,因此使用 MLEProbDist。如果你想平滑分布,你可以试试 LaplaceProbDist 或 SimpleGoodTuringProbDist。

        例如:

        from nltk.probability import FreqDist, MLEProbDist
        
        d = {'a': 6.25, 'c': 62.5, 'b': 31.25}
        freq_dist = FreqDist(d)
        prob_dist = MLEProbDist(freq_dist)
        
        print prob_dist.prob('a')
        print prob_dist.prob('b')
        print prob_dist.prob('c')
        print prob_dist.prob('d')
        

        将打印“0.0625 0.3125 0.625 0.0”。

        要生成新样本,您可以使用:

        prob_dist.generate()
        

        【讨论】:

          【解决方案5】:

          如果你能够使用 numpy,你可以使用numpy.random.choice 函数,如下所示:

          import numpy as np
          
          d = {'a': 0.0625, 'c': 0.625, 'b': 0.3125}
          
          def pick_by_weight(d):
              d_choices = []
              d_probs = []
              for k,v in d.iteritems():
                d_choices.append(k)
                d_probs.append(v)
              return np.random.choice(d_choices, 1, p=d_probs)[0]
          
          
          d = {'a': 0.0625, 'c': 0.625, 'b': 0.3125}
          choice = pick_by_weight(d)
          

          【讨论】:

            【解决方案6】:

            保留一个“倒置”字典可能很有用,其中键是权重值,值是您可以获得的键的列表。这样一来,在更多键具有相同权重的情况下更容易分发它:

            from collections import defaultdict
            import random
            
            dict = {'a': 0.0625, 'd': 0.0625, 'c': 0.625, 'b': 0.3125}
            
            inverted_dict = defaultdict(list)
            
            for k, v in dict.items():
                inverted_dict[v].append(k)
            
            # Here first you get a random value between 0 and 1, which is your weigth
            # Then, you choose a random value from the list of keys that have the same weight
            print(random.choice(inverted_dict[random.choice(inverted_dict.keys())]))
            

            【讨论】:

              【解决方案7】:

              我的理解是:你需要一个简单的随机函数,它会在 0 和 1 之间均匀生成一个随机数。如果值在 0 to 0.0625 之间,你将选择键 a,如果它在在0.0625 and (0.0625 + 0.625) 之间,然后您将选择键c 等。这就是answer 中实际提到的内容。

              由于随机数将统一生成,因此预计与其他键相比,与较大权重相关联的键将被更多地选择。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2014-12-15
                • 2015-08-22
                • 2012-09-14
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-09-19
                相关资源
                最近更新 更多