【问题标题】:generating large number of random variates生成大量随机变量
【发布时间】:2017-06-12 13:28:49
【问题描述】:

我正在尝试找出在 python 中生成许多随机数的最佳方法。困难的部分是我不知道在运行之前我需要多少个数字

我有一个程序一次使用一个随机数,但它需要多次这样做。

到目前为止我尝试过的事情是:

  • 使用random.random()一次生成一个随机数
  • 使用np.random.rand()一次生成一个随机数
  • 使用np.random.rand(N)在一批N中生成随机数
  • 使用np.random.rand(N)在一批 N 中生成随机数,并在第一个 N 全部使用后创建一个新批次(我尝试了两种不同的实现,两者都比一次只生成一个数字慢)

在下面的脚本中,我比较了前三种方法(对于均匀分布和正态分布的随机数)。

我不知道p 函数是否真的有必要,但我想在每种情况下用随机数做等效的事情,这似乎是最简单的方法。

#!/bin/python3

import time
import random
import numpy as np

def p(x):
    pass

def gRand(n):
    for i in range(n):
        p(random.gauss(0,1))

def gRandnp1(n):
    for i in range(n):
        p(np.random.randn())

def gRandnpN(n):
    rr=np.random.randn(n)
    for i in rr:
        p(i)

def uRand(n):
    for i in range(n):
        p(random.random())

def uRandnp1(n):
    for i in range(n):
        p(np.random.rand())

def uRandnpN(n):
    rr=np.random.rand(n)
    for i in rr:
        p(i)

tStart=[]
tEnd=[]
N=1000000
for f in [uRand, uRandnp1, uRandnpN]:
    tStart.append(time.time())
    f(N)
    tEnd.append(time.time())

for f in [gRand, gRandnp1, gRandnpN]:
    tStart.append(time.time())
    f(N)
    tEnd.append(time.time())

print(np.array(tEnd)-np.array(tStart))

这个计时脚本输出的一个代表例子是:
[ 0.26499939 0.45400381 0.19900227 1.57501364 0.49000382 0.23000193]
前三个数字用于 [0,1) 上的均匀随机数,接下来的三个数字用于正态分布数字(mu=0,sigma=1)。

对于任何一种类型的随机变量,最快的方法(这三种)是一次生成所有随机数,将它们存储在一个数组中,然后遍历数组。问题是在我运行程序之前我不知道我需要多少这些数字。

我想做的是大批量生成随机数。然后,当我在一批中使用所有数字时,我将重新填充存储它们的对象。问题是我不知道实现这一点的干净方法。我想出的一种解决方案如下:

N=1000000
numRepop=4
N1=N//numRepop
__rands__=[]
irand=-1

def repop():
    global __rands__
    __rands__=np.random.rand(N1)

repop()

def myRand():
    global irand
    try:
        irand += 1
        return __rands__[irand]
    except:
        irand=1
        repop()
        return __rands__[0]

但这实际上比任何其他选项都慢。

如果我将 numpy 数组转换为列表,然后弹出元素,我将获得类似于使用 numpy 一次生成一个随机变量的性能:

__r2__=[]

def repop2():
    global __r2__
    rr=np.random.rand(N1)
    __r2__=rr.tolist()

repop2()

def myRandb():
    try:
        return __r2__.pop()
    except:
        repop2()
        return __r2__.pop()

有没有更好的方法来做到这一点?

编辑:“更好”是指更快。我也更喜欢确定性(伪)随机数

【问题讨论】:

  • (1) 小心那种基准测试Return the time in seconds since the epoch as a floating point number. Note that even though the time is always returned as a floating point number, not all systems provide time with a better precision than 1 second. While this function normally returns non-decreasing values, it can return a lower value than a previous call if the system clock has been set back between the two calls. (2) 为什么要弹出,如果你只需要移动一个索引来选择位置。无需移除对象。
  • “更好的方法”是什么意思?除了性能,您还有其他要求吗?出于某些目的,您需要加密安全的随机数,或者您可能需要超过默认的 32 位随机数。
  • @Håken Lid 所说的“更好”我的意思是更快
  • @sascha (1) 这是一个我没有意识到的好点,但我所做的时间安排给出了相当一致的结果,所以我现在并不太担心。 (2) 起初我尝试过索引(使用repopmyRand),但这比pop()(使用repop2myRandb)慢。
  • 在 *nix 上,您可以阅读 /dev/random/dev/urandom,具体取决于您是否想要更多随机或更快...

标签: python performance numpy random


【解决方案1】:

如果一次生成大量数字更快,您可以制作一个缓存批次的生成器。这适用于 python 3.5

def randoms(batchsize=10000):
    while True:
        yield from numpy.random.rand(batchsize)

不知道它是否比您的其他实现更快,但它是一个永无止境的生成器。

您可以像使用任何迭代器一样使用它:

prng = randoms()
for _ in range(1000000):
    foo(next(prng))

或者像这样(但循环永远不会退出):

for x in randoms():
    foo(x)

编辑:

我尝试自己对此进行基准测试,我认为差异主要是因为 python 中函数调用的额外成本。我试图通过在所有情况下循环 range 来使基准测试更具可比性,并且使用预生成数组的优势更小。

我通过使用一个微优化技巧获得了几乎同样好的速度,其中numpy.random.rand 被分配给一个局部变量,这大大加快了函数调用的速度。

我还包括生成器方法以进行比较。

def randoms(batchsize):
    rand = numpy.random.rand
    while True:
        yield from rand(batchsize)
​
def test_generator(times):
    rand = randoms(1000).__next__
    for n in range(times):
        rand()

def test_rand(times):
    for n in range(times):
        numpy.random.rand() 

def test_rand_micro_opt(times):
    rand = numpy.random.rand
    for n in range(times):
        rand()

def test_array(times):
    array = numpy.random.rand(times)
    for n in range(times):
        array[n]
​
# ipython / jupyter magic %timeit command        
%timeit -n 1000 test_generator(10000)
%timeit -n 1000 test_rand(10000)
%timeit -n 1000 test_rand_micro_opt(10000)
%timeit -n 1000 test_array(10000)
​
1000 loops, best of 3: 2.09 ms per loop
1000 loops, best of 3: 2.93 ms per loop
1000 loops, best of 3: 1.74 ms per loop
1000 loops, best of 3: 1.57 ms per loop

【讨论】:

  • 我现在做了一些测试,这比简单地使用 random 模块的 random 函数花费的时间多两倍多。测试是 timeit(lambda: [next(prng) for _ in range(1000000)], number=10)timeit(lambda: [random() for _ in range(1000000)], number=10)。还尝试将prng.__next__ 存储在一个变量中并使用它,但这并没有太大帮助。
  • 是的。它基于这样的假设,即生成批量数字将节省大量时间,正如 OP 的基准测试所显示的那样。但我认为速度上的差异主要是因为直接在数组上循环时不会得到昂贵的函数调用。有了生成器,函数调用就不会少了。
  • 我也对生成器版本进行了基准测试,但它仅比我编辑的答案中最慢的测试稍快。
  • 在答案中包含该测试仍然会很好,最好使用rand = prng.__next__
  • 你是对的,这确实有所作为。不过,更简单的微优化仍然更快。
【解决方案2】:

您可以通过不一直查找模块及其功能来显着加快速度。

def uRand_2(n):
    r = random.random
    for i in range(n):
        p(r())

def uRandnp1_2(n):
    r = np.random.rand
    for i in range(n):
        p(r())

你的版本在我的电脑上计时:

[ 0.14439154  0.24865651  0.13786387  0.85637093  0.28924942  0.13338685]

我上面的两个版本(对应你的前两个):

[ 0.10629296  0.15638423]

哦,我不明白打电话给p 有什么意义。我认为这只会增加噪音并影响实际随机数生成的速度。这是我没有调用p的时间,即,只是做r()

[ 0.04560113  0.1083169]

【讨论】:

  • 我想知道为什么numpy函数这么慢。还有numpy.random.random() 在生成单个值时似乎比numpy.random.rand() 快两倍。不过,标准库版本仍然更快。
  • @HåkenLid 可能是参数。我刚刚尝试了timeit('f()', 'def f(size=None): pass'),它的速度大约是timeit('f()', 'def f(**args): pass') 的两倍。
【解决方案3】:

不是超级漂亮,但应该可以:

import numpy as np

class BatchedPRNG(object):
    def __init__(self, seed=0, batch_size=10000, dist='uniform'):
        self.prng = np.random.RandomState(seed)         # own random-stream !
        self.batch_size = batch_size
        self.dist = dist
        self.index = 0
        if self.dist == 'uniform':
            self.pool = self.prng.random_sample(size=self.batch_size)
        else:
            self.pool = self.prng.normal(size=self.batch_size)

    def sample_one(self):
        if self.index < self.batch_size:
            self.index += 1
            return self.pool[self.index-1]
        else:
            self.index = 1
            if self.dist == 'uniform':
                self.pool = self.prng.random_sample(size=self.batch_size)
            else:
                self.pool = self.prng.normal(size=self.batch_size)
            return self.pool[self.index-1]

dist = BatchedPRNG()
for i in range(11):
    print(dist.sample_one())

这遵循封装/面向对象方法的思想,每次您需要一个新样本时都会以函数调用为代价。它还使用自己的 PRNG-Stream,因此在代码的其他部分对 np.random.X 的全局调用不会更改此对象的内部状态。

如果您想使用其他发行版或需要其他功能,显然您需要对此进行修改。

遗憾的是,您也必须注意基准测试。

编辑:出奇的慢

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-04
    相关资源
    最近更新 更多