2017-01-26 4 views
3

Я пытаюсь найти лучший способ генерации множества случайных чисел в python. Трудная часть заключается в том, что я не буду знать, сколько цифр мне нужно до выполнения.генерирует большое количество случайных чисел

У меня есть программа, которая использует случайные числа по одному, но это нужно делать много раз.

То, что я пытался до сих пор являются:

  • генерации случайных чисел по одному, используя random.random()
  • генерации случайных чисел по одному, используя np.random.rand()
  • генерации случайных чисел в пакете из N, используя np.random.rand(N)
  • генерировать случайные числа в партии N, используя np.random.rand(N), и создавать новую партию после первого использования N (я пробовал две разные реализации и бот h медленнее, чем просто генерация одного числа за раз)

В следующем сценарии я сравниваю первые три из этих методов (как для равномерных, так и для нормально распределенных случайных чисел).

Я не знаю, действительно ли нужна функция 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() 

Есть ли лучший способ сделать это?

Редактировать: от "better" Я просто имею в виду быстрее.Я также предпочитаю детерминированный (псевдо) случайных чисел

+0

(1) Будьте осторожны с такого рода бенчмаркинг '' '' Возвращает время в секундах с начала эпохи в виде числа с плавающей точкой. Обратите внимание: хотя время всегда возвращается как число с плавающей запятой, не все системы обеспечивают время с лучшей точностью, чем 1 секунду. Хотя эта функция обычно возвращает неубывающие значения, она может вернуть более низкое значение, чем предыдущий вызов, если системные часы были установлены обратно между двумя вызовами. '' '(2) Почему поп, если все, что вам нужно, перемещает индекс для выбора позиции. Нет необходимости удалять объекты. – sascha

+0

Что вы подразумеваете под «лучшим способом»? Есть ли у вас какие-либо другие требования, кроме производительности? Для некоторых целей вам нужны криптографически безопасные случайные числа, или вам может потребоваться больше 32 бит случайности. –

+0

@ Håken Lid by «better» Я просто хочу сказать быстрее – kevin

ответ

0

Не супер красивый, но это должно работать:

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-поток, так что глобальные вызовы np.random.X в других частях вашего кода не изменяют внутреннее состояние этого объекта.

Очевидно, что вам необходимо изменить это, если вы хотите использовать другие дистрибутивы или вам нужны другие функции.

К сожалению, вы также должны заботиться о бенчмаркинге.

Edit: удивительно медленно

2

Если это быстрее, чтобы генерировать много цифр в то время, вы могли бы сделать генератор, который будет кэшировать пакеты. Это работает в 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) 

EDIT:

Я попытался бенчмарка это я, и я думаю, что разница в основном связана с дополнительной стоимостью вызовов функций в 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 
+0

Я провел некоторое тестирование, и это заняло более двух раз, просто используя «случайную» функцию «случайного» модуля. Тест был «timeit (lambda: [next (prng) для _ в диапазоне (1000000)], number = 10)' vs 'timeit (lambda: [random() для _ в диапазоне (1000000)], number = 10)' , Также попытался сохранить 'prng .__ next__' в переменной и использовать это, но это мало помогло. –

+0

Да. Это было основано на предположении, что сбор партий чисел сэкономит много времени, как показали показания OP. Но я считаю, что разница в скорости в основном связана с дорогостоящими вызовами функций, которые вы не получаете, когда цикл происходит непосредственно над массивом. С генератором существует меньше вызовов функций. –

+0

Я также сравнивал версию генератора, но это только немного быстрее, чем самый медленный тест в моем отредактированном ответе. –

1

Вы можете сделать это значительно быстрее, не просматривая модуль и его функцию все время.

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] 
+1

Интересно, почему функция numpy работает так медленно. Также существует 'numpy.random.random()', который, кажется, примерно в два раза быстрее, чем 'numpy.random.rand()' при генерации одиночных значений. Однако стандартная версия библиотеки все же быстрее. –

+0

@ HåkenLid Могут быть параметрами. Я просто попробовал 'timeit ('f()', 'def f (size = None): pass')', который примерно в два раза быстрее, чем 'timeit ('f()', 'def f (** args): передать) '. –