如何根据加权概率从 python 字典中选择键?



我有一个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`

鉴于项目与权重的这种相关性,我如何从 6.25% 的结果为"a"、32.25% 的结果为"b"、62.5% 的结果为"c"的d中选择键?

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 之间)落在插槽中,则返回该键

从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 ,说明它如何不可下标。

以下是文档中的相关代码段:

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

返回从填充中选择的元素的 k 大小列表,并进行替换。如果总体为空,则引发索引错误。

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

如果既未指定权重,也未指定cum_weights,则以相等的概率进行选择。如果提供了权重序列,则该序列的长度必须与总体序列相同。指定权重和cum_weights是一个类型错误。

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

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

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

如果您打算经常这样做,您可以使用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)

不确定您的用例是什么,但您可以查看 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()

如果你能够使用 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)

我的理解是:你需要一个简单的随机函数,它将在 0 到 1 之间均匀地生成一个随机数。如果值在0 to 0.0625之间,您将选择键a,如果它介于0.0625 and (0.0625 + 0.625)之间,那么您将选择键c等。这就是这个答案中实际提到的。

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

最新更新