我需要一种方法来取样而无需替换某个数组a
。我尝试了两种方法(参见下面的MCVE),使用random.sample()
和np.random.choice
。
我认为numpy
函数会更快,但事实证明它不是。在我的测试中random.sample
比np.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)
答案 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
软件包(link,what's new,performance)的返工和改进。就像在第一个链接中所说的那样,为了向后兼容,旧的numpy.random API使用了旧的实现,即,旧的API保持不变。
使用新API的无脑的新方法是使用numpy.random.default_rng()
对象而不是numpy.random
。因此,在您的情况下为np.random.default_rng().choice(...)
。首先,它使用不同的生成器,在大多数情况下可能会更快。第二个-关于您的情况-choice
变得更聪明,并且仅对足够大的数组(> 10000 elems)和相对较大的k(> 1/50的大小)使用整个数组排列。对于其他情况,它使用Floyd的采样算法(short description,numpy 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)