我经常发现自己处于需要对数组或列表进行随机索引的位置,其中索引的概率不是均匀分布的,而是根据某些正权重.什么是快速获得它们的方法?我知道我可以将权重传递给numpy.random.choice作为可选参数p,但是函数似乎很慢,并且构建一个传递它的范围也不理想.权重之和可以是任意正数,并且不保证为1,这使得该方法在(0,1)中生成随机数,然后减去权重条目,直到结果为0或更不可能.
虽然有关于如何以简单的方式实现类似的东西(主要不是获取数组索引,但相应的元素)的答案,例如Weighted choice short and simple,我正在寻找一个快速的解决方案,因为适当的函数经常执行.我的权重经常变化,因此构建类似别名掩码的开销(详细介绍可以在http://www.keithschwarz.com/darts-dice-coins/中找到)应该被视为计算时间的一部分.
解决方法:
累积求和和二等分
在任何一般情况下,似乎建议计算权重的累积和,并使用bisect模块中的bisect在结果排序数组中查找随机点
def weighted_choice(weights):
cs = numpy.cumsum(weights)
return bisect.bisect(cs, numpy.random.random() * cs[-1])
如果速度是一个问题.下面给出更详细的分析.
注意:如果数组不是平的,可以使用numpy.unravel_index将平面索引转换为形状索引,如https://stackoverflow.com/a/19760118/1274613所示
实验分析
使用numpy内置函数有四种或多或少的明显解决方案.使用timeit比较所有这些结果会得到以下结果:
import timeit
weighted_choice_functions = [
"""import numpy
wc = lambda weights: numpy.random.choice(
range(len(weights)),
p=weights/weights.sum())
""",
"""import numpy
# Adapted from https://stackoverflow.com/a/19760118/1274613
def wc(weights):
cs = numpy.cumsum(weights)
return cs.searchsorted(numpy.random.random() * cs[-1], 'right')
""",
"""import numpy, bisect
# Using bisect mentioned in https://stackoverflow.com/a/13052108/1274613
def wc(weights):
cs = numpy.cumsum(weights)
return bisect.bisect(cs, numpy.random.random() * cs[-1])
""",
"""import numpy
wc = lambda weights: numpy.random.multinomial(
1,
weights/weights.sum()).argmax()
"""]
for setup in weighted_choice_functions:
for ps in ["numpy.ones(40)",
"numpy.arange(10)",
"numpy.arange(200)",
"numpy.arange(199,-1,-1)",
"numpy.arange(4000)"]:
timeit.timeit("wc(%s)"%ps, setup=setup)
print()
结果输出是
178.45797914802097
161.72161589498864
223.53492237901082
224.80936180002755
1901.6298267539823
15.197789980040397
19.985687876993325
20.795070077001583
20.919113760988694
41.6509403079981
14.240949985047337
17.335801470966544
19.433710905024782
19.52205040602712
35.60536142199999
26.6195822560112
20.501282756973524
31.271995796996634
27.20013752405066
243.09768892999273
这意味着numpy.random.choice非常慢,甚至专用的numpy searchsorted方法也比类型天真的bisect变种慢. (这些结果是使用Python 3.3.5和numpy 1.8.1获得的,因此对于其他版本可能会有所不同.)基于numpy.random.multinomial的函数对于大权重的效率低于基于累积求和的方法.据推测,argmax必须迭代整个阵列并进行比较,每个步骤都起着重要作用,从增加和减少重量列表之间的四秒差异可以看出.
标签:python,algorithm,random