Эффективный для памяти способ генерации большого массива numpy, содержащего случайные логические значения

Мне нужно создать массив с большим количеством, содержащий случайные логические значения без попадания в swap.

Мой ноутбук имеет 8 ГБ оперативной памяти. Создание массива (1200, 2e6) занимает менее 2 с и использует 2,29 ГБ оперативной памяти:

>>> dd = np.ones((1200, int(2e6)), dtype=bool)
>>> dd.nbytes/1024./1024
2288.818359375

>>> dd.shape
(1200, 2000000)

Для относительно небольшого (1200, 400e3), np.random.randint все еще довольно быстро, беря приблизительно 5 секунд для создания массива 458 МБ:

db = np.array(np.random.randint(2, size=(int(400e3), 1200)), dtype=bool)
print db.nbytes/1024./1024., 'Mb'

Но если я удвою размер массива до (1200, 800e3), я попал в swap, и для создания db требуется ~ 2,7 мин. (

cmd = """
import numpy as np
db = np.array(np.random.randint(2, size=(int(800e3), 1200)), dtype=bool)
print db.nbytes/1024./1024., 'Mb'"""

print timeit.Timer(cmd).timeit(1)

Использование random.getrandbits занимает еще больше времени (~ 8min), а также использует swap:

from random import getrandbits
db = np.array([not getrandbits(1) for x in xrange(int(1200*800e3))], dtype=bool)

Использование np.random.randint для a (1200, 2e6) просто дает MemoryError.

Существует ли более эффективный способ создания случайного логического массива (1200, 2e6)?

Ответы

Ответ 1

Одна проблема с использованием np.random.randint заключается в том, что он генерирует 64-битные целые числа, тогда как numpy np.bool dtype использует только 8 бит для представления каждого логического значения. Поэтому вы выделяете промежуточный массив размером 8x больше необходимого.

Обходной путь, который позволяет избежать промежуточных 64-разрядных типов, состоит в том, чтобы сгенерировать строку случайных байтов, используя np.random.bytes, которая может быть преобразована в массив из 8-битных целых чисел, используя np.fromstring. Эти целые числа затем могут быть преобразованы в логические значения, например, путем проверки, меньше ли они 255 * p, где p - желаемая вероятность того, что каждый элемент имеет значение True:

import numpy as np

def random_bool(shape, p=0.5):
    n = np.prod(shape)
    x = np.fromstring(np.random.bytes(n), np.uint8, n)
    return (x < 255 * p).reshape(shape)

Benchmark:

In [1]: shape = 1200, int(2E6)

In [2]: %timeit random_bool(shape)
1 loops, best of 3: 12.7 s per loop

Одно важное предостережение состоит в том, что вероятность будет округлена до ближайшего кратного 1/256 (для точного кратного 1/256, такого как p = 1/2, это не должно влиять на точность).


Обновление:

Еще более быстрый метод заключается в том, чтобы использовать тот факт, что вам нужно всего лишь создать один случайный бит на 0 или 1 в вашем выходном массиве. Поэтому вы можете создать случайный массив из 8-битных целых чисел размером 1/8 размера конечного вывода, а затем преобразовать его в np.bool с помощью np.unpackbits:

def fast_random_bool(shape):
    n = np.prod(shape)
    nb = -(-n // 8)     # ceiling division
    b = np.fromstring(np.random.bytes(nb), np.uint8, nb)
    return np.unpackbits(b)[:n].reshape(shape).view(np.bool)

Например:

In [3]: %timeit fast_random_bool(shape)
1 loops, best of 3: 5.54 s per loop