python 2 的高性能加权随机选择?

High performance weighted random choice for python 2?

我有以下python方法,它从序列"seq"中选择一个加权运行dom元素运行domly由其他序列加权,其中包含权重对于 seq 中的每个元素:

def weighted_choice(seq, weights):
    assert len(seq) == len(weights)

    total = sum(weights)
    r = random.uniform(0, total)
    upto = 0
    for i in range(len(seq)):
        if upto + weights[i] >= r:
            return seq[i]
        upto += weights[i]
    assert False, "Shouldn't get here"

如果我用 1000 个元素的序列调用上面一百万次,如下所示:

seq = range(1000)
weights = []
for i in range(1000):
    weights.append(random.randint(1,100))

st=time.time()
for i in range(1000000):
    r=weighted_choice(seq, weights)
print (time.time()-st)

it 运行s 在 cpython 2.7 中大约 45 秒,在 cpython 3.6 中大约 70 秒。 在 pypy 5.10 中它在 2.3 秒左右完成,这对我来说很好,遗憾的是由于某些原因我不能使用 pypy。

关于如何在 cpython 上加速这个函数有什么想法吗?我对其他实现(算法上或通过外部库,如 numpy)也很感兴趣,如果它们表现更好的话。

ps: python3 有 random.choices 有权重,它 运行s 大约 23 秒,比上面的函数好,但仍然正好是十比 pypy 慢 1 倍 运行.

我已经用 numpy 试过了:

weights=[1./1000]*1000
st=time.time()
for i in range(1000000):
    #r=weighted_choice(seq, weights)
    #r=random.choices(seq, weights)
    r=numpy.random.choice(seq, p=weights)
print (time.time()-st)

它 运行 70 秒。

你可以使用numpy.random.choicep参数是权重)。通常 numpy 函数是向量化的,因此 运行 速度 near-C。

实施为:

def weighted_choice(seq, weights):
    w = np.asarray(weights)
    p = w / w.sum()  # can skip if weights always sum to 1
    return np.random.choice(seq, p=w)

编辑:

时间:

%timeit np.random.choice(x, p=w)  # len(x) == 1_000_000
13 ms ± 238 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit np.random.choice(y, p=w)  # len(y) == 100_000_000
1.28 s ± 18.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

您可以在 numpy 中采用这种方法。如果你简化 for 循环,你可以通过索引你需要的位置来获得 numpy 的真正力量

#Untimed since you did not
seq = np.arange(1000)
weights = np.random.randint(1,100,(1000,1))


def weights_numpy(seq,weights,iterations):
    """
    :param seq: Input sequence
    :param weights: Input Weights
    :param iterations: Iterations to run
    :return: 
    """
    r = np.random.uniform(0,weights.sum(0),(1,iterations)) #create array of choices
    ar = weights.cumsum(0) # get cumulative sum
    return seq[(ar >= r).argmax(0)] #get indeces of seq that meet your condition

和时间 (python 3,numpy '1.14.0')

%timeit weights_numpy(seq,weights,1000000)
4.05 s ± 256 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

比 PyPy 慢一点,但几乎...