生成一个包含随机布尔值的大型numpy数组的高效内存方式

17

我需要创建一个大的numpy数组,其中包含随机布尔值,而不会导致交换。

我的笔记本电脑有8 GB的RAM。创建一个(1200, 2e6)的数组只需不到2秒钟,并使用2.29 GB的RAM:

>>> 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 MB的数组:

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

但是,如果我将数组的大小增加一倍至(1200, 800e3),就会遇到交换问题,并且创建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甚至需要更长的时间(约8分钟),并且还会使用交换:

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

使用np.random.randint创建一个(1200, 2e6)的随机数数组会产生一个MemoryError错误。

有没有更有效的方法来创建一个(1200, 2e6)的随机布尔数组?

1个回答

14

使用np.random.randint的一个问题是它生成64位整数,而numpy的 np.bool dtype 仅使用8位来表示每个布尔值。因此,你正在分配一个比必要大8倍的中间数组。

避免中间64位dtypes的一种解决方法是使用np.random.bytes生成随机字节串,然后使用np.fromstring将其转换为8位整数数组。这些整数可以转换为布尔值,例如通过测试它们是否小于 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)

基准测试:

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

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

一个重要的限制是概率将会被舍入到最接近 1/256 的倍数(对于如 p=1/2 这样的精确的 1/256 倍数,这不会影响准确性)。


更新:

一种更快的方法是利用一个事实,即您只需要为输出数组中的每个 0 或 1 生成单个随机位。因此,您可以创建一个随机数组,其中包含 8 位整数,其大小为最终输出的 1/8,然后使用 np.unpackbits 将其转换为 np.bool

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

1
如果你使用.view(np.bool)或者.astype(np.bool, copy=False),而不是.astype(np.bool),那么你的最后一个解决方案会更快,因为这两种方法都可以避免复制整个数组。 - Jaime
@Jaime 谢谢 - 我总是忘记 .astype() 默认返回副本。 - ali_m
谢谢@ali_m,这个随机布尔数组是在numpy广播问题的背景下:http://stackoverflow.com/q/34496409/3313834 - user3313834
1
“生成字节并与255*p进行比较”的策略存在一个限制,即概率被舍入为1/256的倍数,而不总是“正确”的1/256倍数。 - user2357112
@user2357112 你说得对 - 我已经编辑了我的答案,提到了这个警告,尽管我不确定是否可能在不分配更大的中间数组的情况下做得更好。 - ali_m

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接