为什么random.sample比numpy的random.choice更快?

时间:2016-12-01 15:44:58

标签: python numpy random

我需要一种方法来取样而无需替换某个数组a。我尝试了两种方法(参见下面的MCVE),使用random.sample()np.random.choice

我认为numpy函数会更快,但事实证明它不是。在我的测试中random.samplenp.random.choice快〜15%。

这是正确的,还是我在下面的例子中做错了什么?如果这是正确的,为什么?

import numpy as np
import random
import time
from contextlib import contextmanager


@contextmanager
def timeblock(label):
    start = time.clock()
    try:
        yield
    finally:
        end = time.clock()
        print ('{} elapsed: {}'.format(label, end - start))


def f1(a, n_sample):
    return random.sample(range(len(a)), n_sample)


def f2(a, n_sample):
    return np.random.choice(len(a), n_sample, replace=False)


# Generate random array
a = np.random.uniform(1., 100., 10000)
# Number of samples' indexes to randomly take from a
n_sample = 100
# Number of times to repeat functions f1 and f2
N = 100000

with timeblock("random.sample"):
    for _ in range(N):
        f1(a, n_sample)

with timeblock("np.random.choice"):
    for _ in range(N):
        f2(a, n_sample)

1 个答案:

答案 0 :(得分:4)

TL; DR 您可以使用numpy.random.default_rng()对象代替numpy.random。特别是numpy.random.default_rng().choice(...)


如评论中所述,numpy中存在一个长期存在的问题,即与标准np.random.choice相比,k << n的实施对random.sample无效。

问题是np.random.choice(arr, size=k, replace=False)被实现为permutation(arr)[:k]。在大数组和小k的情况下,计算整个数组排列浪费时间和内存。标准的python示例工作更直接-只需采样就可以替换,而无需跟踪已采样的内容或要采样的内容。

在numpy v1.17.0中,引入了numpy.random软件包(linkwhat's newperformance)的返工和改进。就像在第一个链接中所说的那样,为了向后兼容,旧的numpy.random API使用了旧的实现,即,旧的API保持不变。

使用新API的无脑的新方法是使用numpy.random.default_rng()对象而不是numpy.random。因此,在您的情况下为np.random.default_rng().choice(...)。首先,它使用不同的生成器,在大多数情况下可能会更快。第二个-关于您的情况-choice变得更聪明,并且仅对足够大的数组(> 10000 elems)和相对较大的k(> 1/50的大小)使用整个数组排列。对于其他情况,它使用Floyd的采样算法(short descriptionnumpy implementation)。


这是我的笔记本电脑上的性能比较:

10000乘以10000个元素的数组中的100个样本:

random.sample elapsed: 0.8711776689742692
np.random.choice elapsed: 1.9704092079773545
np.random.default_rng().choice elapsed: 0.818919860990718

10000乘以10000个元素的数组中的1000个样本:

random.sample elapsed: 8.785315042012371
np.random.choice elapsed: 1.9777243090211414
np.random.default_rng().choice elapsed: 1.05490942299366

10000乘以10000个元素的数组中的10000个样本:

random.sample elapsed: 80.15063399000792
np.random.choice elapsed: 2.0218082449864596
np.random.default_rng().choice elapsed: 2.8596064270241186

以及我使用的代码:

import numpy as np
import random
from timeit import default_timer as timer
from contextlib import contextmanager


@contextmanager
def timeblock(label):
    start = timer()
    try:
        yield
    finally:
        end = timer()
        print ('{} elapsed: {}'.format(label, end - start))


def f1(a, n_sample):
    return random.sample(range(len(a)), n_sample)


def f2(a, n_sample):
    return np.random.choice(len(a), n_sample, replace=False)


def f3(a, n_sample):
    return np.random.default_rng().choice(len(a), n_sample, replace=False)


# Generate random array
a = np.random.uniform(1., 100., 10000)
# Number of samples' indexes to randomly take from a
n_sample = 100
# Number of times to repeat functions f1 and f2
N = 100000

print(f'{N} times {n_sample} samples')
with timeblock("random.sample"):
    for _ in range(N):
        f1(a, n_sample)

with timeblock("np.random.choice"):
    for _ in range(N):
        f2(a, n_sample)

with timeblock("np.random.default_rng().choice"):
    for _ in range(N):
        f3(a, n_sample)