繁体   English   中英

为什么 numpy.random.choice 这么慢?

[英]Why is numpy.random.choice so slow?

在编写脚本时,我发现了 numpy.random.choice function。 我实现了它,因为它比等效的 if 语句要干净得多。 但是,在运行脚本后,我意识到它if 语句慢得多。

以下是MWE。 第一种方法需要 0.0 秒,而第二种方法需要 7.2 秒。 如果你扩大i循环,你会看到 random.choice 变慢的速度。

谁能评论为什么 random.choice 这么慢?

import numpy as np
import numpy.random as rand
import time as tm

#-------------------------------------------------------------------------------

tStart = tm.time()
for i in xrange(100):
    for j in xrange(1000):
        tmp = rand.rand()
        if tmp < 0.25:
            var = 1
        elif tmp < 0.5:
            var = -1
print('Time: %.1f s' %(tm.time() - tStart))

#-------------------------------------------------------------------------------

tStart = tm.time()
for i in xrange(100):
    for j in xrange(1000):
        var = rand.choice([-1, 0, 1], p = [0.25, 0.5, 0.25])
print('Time: %.1f s' %(tm.time() - tStart))

你用错了。 对操作进行矢量化,否则 numpy 将没有任何好处:

var = numpy.random.choice([-1, 0, 1], size=1000, p=[0.25, 0.5, 0.25])

计时数据:

>>> timeit.timeit('''numpy.random.choice([-1, 0, 1],
...                                      size=1000,
...                                      p=[0.25, 0.5, 0.25])''',
...               'import numpy', number=10000)
2.380380242513752

>>> timeit.timeit('''
... var = []
... for i in xrange(1000):
...     tmp = rand.rand()
...     if tmp < 0.25:
...         var.append(1)
...     elif tmp < 0.5:
...         var.append(-1)
...     else:
...         var.append(0)''',
... setup='import numpy.random as rand', number=10000)
5.673041396894519

由于通过np.random.choice进行随机密钥采样,我花了很长时间才发现我的数据生成器非常慢。

如果不需要非均匀分布,那么这里就是可行的解决方案,我发现。

代替

def get_random_key(a_huge_key_list):
    return np.random.choice(a_huge_key_list)

def get_random_key(a_huge_key_list):
    L = len(a_huge_key_list)
    i = np.random.randint(0, L)
    return a_huge_key_list[i]

这提供了 x60 的加速。

这个具有累积分数的解决方案快了大约 25 倍:

def choice(options,probs):
    x = np.random.rand()
    cum = 0
    for i,p in enumerate(probs):
        cum += p
        if x < cum:
            break
    return options[i]


options = ['a','b','c','d']
probs = [0.2,0.6,0.15,0.05]
runs = 100000


now = time.time()
temp = []
for i in range(runs):
    op = choice(options,probs)
    temp.append(op)
temp = Counter(temp)
for op,x in temp.items():
    print(op,x/runs)
print(time.time()-now)

print("")
now = time.time()
temp = []
for i in range(runs):
    op = np.random.choice(options,p = probs)
    temp.append(op)
temp = Counter(temp)
for op,x in temp.items():
    print(op,x/runs)
print(time.time()-now)

运行它我得到:

b 0.59891
a 0.20121
c 0.15007
d 0.04981
0.16232800483703613

b 0.5996
a 0.20138
c 0.14856
d 0.05046
3.8451428413391113

我怀疑np.random.choice正在减慢它的速度,对于小样本比大样本更是如此。

if版本的粗略矢量化是:

def foo(n):
    x = np.random.rand(n)
    var = np.zeros(n)
    var[x<.25] = -1
    var[x>.75] = 1
    return var

ipython运行我得到:

timeit np.random.choice([-1,0,1],size=1000,p=[.25,.5,.25])
1000 loops, best of 3: 293 us per loop

timeit foo(1000)
10000 loops, best of 3: 83.4 us per loop

timeit np.random.choice([-1,0,1],size=100000,p=[.25,.5,.25])
100 loops, best of 3: 11 ms per loop

timeit foo(100000)
100 loops, best of 3: 8.12 ms per loop

因此,对于1000大小, choice慢 3-4 倍,但对于更大的向量,差异开始消失。

其他答案至少涉及以下一项:

1- 使用 python 列表作为 numpy.random.choice 的输入并创建开销。

2-使用len(array)将为3的先验知识。

3-分布均匀。

对于任意长度的列表,最快的算法之一会在每一步将列表分成 2 个。 例如,以下代码适用于一般情况。

def my_random_function(collection, p):
    miles = []
    current = 0
    for prob in p:
        miles.append(current)
        current += prob
    if not math.isclose(current,1):
        raise ValueError()
    x = random.random()
    _all = list(zip(collection,miles))
    while(len(_all)!= 1):
        if _all[len(_all)//2][1] < x:
            _all = _all[len(_all)//2:]
        else:
            _all = _all[0: len(_all)//2]
    return _all[0][0]

为了比较差异,我准备了两个案例:

small_list = list(range(3))
small_array = np.arange(3)
#create a random probability list
small_p = [random.random() for i in range(3)]
small_p = [prob/sum(small_p) for prob in small_p]
small_p_np = np.array(small_p)

large_list = list(range(10000))
large_array = np.arange(10000)
#create a random probability list
large_p = [random.random() for i in range(10000)]
large_p = [prob/sum(large_p) for prob in large_p]
large_p_np = np.array(large_p)

结果如下:

%timeit np.random.choice(small_array, p= small_p_np)

68.1 µs ± 196 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

%timeit my_random_function(small_list, small_p)

5.13 µs ± 26.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%timeit np.random.choice(large_array, p= large_p_np)

279 µs ± 1.15 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit my_random_function(large_list, large_p)

3.26 ms ± 5.82 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

结果显示 numpy.random.choice 对于小型 collections 需要 x10 多倍的时间,但当元素更多时,它很快成为更好的选择。 似乎这个 function 的开销很大,最好避免在代码的性能关键部分中使用小列表。

对于偶然发现这个问题并且没有抽取 1,000 个样本 10,000 次但 1 个样本 10,000 次的其他人,自 Python 3.6 以来存在更快的替代方案。 function random.choices 比 numpy.random.choice 快约 20 倍。

timeit("random.choices([-1, 0, 1], k=1, weights=[.25, .5, .25])", 
setup="import random", number=10000)
# >>> 0.018841781999981322

对比

timeit("numpy.random.choice([-1, 0, 1], size=1, p=[.25, .5, .25])", 
setup="import numpy", number=10000)
# >>> 0.40612822200000664

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM