在Numpy中创建笛卡尔积时的MemoryError

问题描述 投票:8回答:3

我有3个numpy阵列,需要在它们之间形成笛卡尔积。阵列的尺寸不固定,因此它们可以采用不同的值,一个例子可以是A =(10000,50),B =(40,50),C =(10000,50)。

然后,我执行一些处理(如a + b-c)以下是我用于产品的功能。

def cartesian_2d(arrays, out=None):

    arrays = [np.asarray(x) for x in arrays]
    dtype = arrays[0].dtype

    n = np.prod([x.shape[0] for x in arrays])
    if out is None:
        out = np.empty([n, len(arrays), arrays[0].shape[1]], dtype=dtype)

    m = n // arrays[0].shape[0]
    out[:, 0] = np.repeat(arrays[0], m, axis=0)
    if arrays[1:]:
        cartesian_2d(arrays[1:], out=out[0:m, 1:, :])
        for j in range(1, arrays[0].shape[0]):
            out[j * m:(j + 1) * m, 1:] = out[0:m, 1:]
    return out

a = [[ 0, -0.02], [1, -0.15]]
b = [[0, 0.03]]

result = cartesian_2d([a,b,a])

// array([[[ 0.  , -0.02],
    [ 0.  ,  0.03],
    [ 0.  , -0.02]],

   [[ 0.  , -0.02],
    [ 0.  ,  0.03],
    [ 1.  , -0.15]],

   [[ 1.  , -0.15],
    [ 0.  ,  0.03],
    [ 0.  , -0.02]],

   [[ 1.  , -0.15],
    [ 0.  ,  0.03],  
    [ 1.  , -0.15]]])

输出与itertools.product相同。但是,我正在使用我的自定义函数来利用numpy矢量化操作,与我的情况下的itertools.product相比,它运行良好。

在此之后,我做到了

result[:, 0, :] + result[:, 1, :] - result[:, 2, :]

//array([[ 0.  ,  0.03],
       [-1.  ,  0.16],
       [ 1.  , -0.1 ],
       [ 0.  ,  0.03]])

所以这是最终的预期结果。

只要我的数组适合内存,该函数就可以正常工作。但是我的用例要求我处理大量数据,并且我在np.empty()行得到一个MemoryError,因为它无法分配所需的内存。我目前正处理大约20GB的数据,这可能会在未来增加。

这些数组代表向量,必须存储在float中,所以我不能使用int。此外,它们是密集阵列,因此使用sparse不是一种选择。

我将使用这些数组进行进一步处理,理想情况下我不想在这个阶段将它们存储在文件中。所以memmap / h5py格式可能无济于事,虽然我不确定。

如果还有其他方法可以形成这个产品,那也没关系。

我确信有些应用程序的数据集大于此,我希望有人之前遇到过这样的问题,并希望知道如何处理这个问题。请帮忙。

python numpy out-of-memory itertools cartesian-product
3个回答
3
投票

如果至少你的结果适合记忆

以下产生您的预期结果,而不依赖于结果大小的三倍的中间值。它使用广播。

请注意,几乎所有NumPy操作都可以这样播放,因此在实践中可能不需要明确的笛卡尔积:

#shared dimensions:
sh = a.shape[1:]
aba = (a[:, None, None] + b[None, :, None] - a[None, None, :]).reshape(-1, *sh)
aba
#array([[ 0.  ,  0.03],
#       [-1.  ,  0.16],
#       [ 1.  , -0.1 ],
#       [ 0.  ,  0.03]])

按'ID'处理结果行

你可以考虑省略reshape。这将允许您通过组合索引来处理结果中的行。如果你的组件ID只是0,1,2,...就像在你的例子中那样,它将与组合ID相同。例如,aba [1,0,0]将对应于作为第一行b的第二行获得的行 - a的第一行。

一点解释

广播:例如,当添加两个阵列时,它们的形状不必相同,仅因广播而兼容。从某种意义上说,广播是向数组添加标量的概括:

    [[2],                 [[7],   [[2],
7 +  [3],     equiv to     [7], +  [3],
     [4]]                  [7]]    [4]]

广播:

              [[4],            [[1, 2, 3],   [[4, 4, 4],
[[1, 2, 3]] +  [5],  equiv to   [1, 2, 3], +  [5, 5, 5],
               [6]]             [1, 2, 3]]    [6, 6, 6]]

为此,每个操作数的每个维度必须为1或等于每个其他操作数中的相应维度,除非它是1.如果操作数的维度小于其他操作数,则其形状用左边的填充。请注意,未明确创建插图中显示的等值数组。

如果结果也不合适

在那种情况下,我不知道你怎么可能避免使用存储,所以h5py或类似的东西。

从每个操作数中删除第一列

这只是切片的问题:

a_no_id = a[:, 1:]

请注意,与Python列表不同,切片时的NumPy数组不会返回副本而是返回视图。因此效率(内存或运行时)不是问题。


1
投票

另一种解决方案是create a cartesian product of indices(这更容易,因为存在1D阵列的笛卡尔积的解决方案):

idx = cartesian_product(
    np.arange(len(a)),
    np.arange(len(b)) + len(a),
    np.arange(len(a))
)

然后使用花式索引来创建输出数组:

x = np.concatenate((a, b))
result = x[idx.ravel(), :].reshape(*idx.shape, -1)

1
投票

在磁盘上有效地写入结果

首先要考虑结果数据的大小。

结果数据的大小

size_in_GB = A.shape[0]**2*A.shape[1]*B.shape[0]*(size_of_datatype)/1e9

在你的问题中你提到A.shape =(10000,50),B =(40,50)。使用float64,您的结果将大约为1600 GB。如果你有足够的磁盘空间,这可以毫无问题地完成,但是你必须考虑接下来要对数据做什么。也许这只是一个中间结果,并且可以在块中处理数据。

如果不是这种情况,这里是一个如何有效处理1600GB数据的示例(RAM使用量约为200 MB)。对于实际数据,吞吐量应该在200 MB / s左右。

计算结果的代码来自@PaulPanzer。

import numpy as np
import tables #register blosc
import h5py as h5
import h5py_cache as h5c

a=np.arange(500*50).reshape(500, 50)
b=np.arange(40*50).reshape(40, 50)

# isn't well documented, have a look at https://github.com/Blosc/hdf5-blosc
compression_opts=(0, 0, 0, 0, 5, 1, 1)
compression_opts[4]=9 #compression level 0...9
compression_opts[5]=1 #shuffle
compression_opts[6]=1 #compressor (I guess that's lz4)

File_Name_HDF5='Test.h5'
f = h5.File(File_Name_HDF5, 'w',chunk_cache_mem_size=1024**2*300)
dset = f.create_dataset('Data', shape=(a.shape[0]**2*b.shape[0],a.shape[1]),dtype='d',chunks=(a.shape[0]*b.shape[0],1),compression=32001,compression_opts=(0, 0, 0, 0, 9, 1, 1), shuffle=False)

#Write the data
for i in range(a.shape[0]):
  sh = a.shape[1:]
  aba = (a[i] + b[:, None] - a).reshape(-1, *sh)
  dset[i*a.shape[0]*b.shape[0]:(i+1)*a.shape[0]*b.shape[0]]=aba

f.close()

读数据

File_Name_HDF5='Test.h5'
f = h5c.File(File_Name_HDF5, 'r',chunk_cache_mem_size=1024**2*300)
dset=f['Data']
chunks_size=500
for i in range(0,dset.shape[0],chunks_size):
  #Iterate over the first column
  data=dset[i:i+chunks_size,:] #avoid excessive calls to the hdf5 library
  #Do something with the data

f.close()

f = h5c.File(File_Name_HDF5, 'r',chunk_cache_mem_size=1024**2*300)
dset=f['Data']
for i in range(dset.shape[1]):
  # Iterate over the second dimension
  # fancy indexing e.g.[:,i] will be much slower
  # use np.expand_dims or in this case np.squeeze after the read operation from the dset
  # if you wan't to have the same result than [:,i] (1 dim array)
  data=dset[:,i:i+1] 
  #Do something with the data

f.close()

在这个测试示例中,我的写入吞吐量约为550 M / s,读取吞吐量约为(500 M / s第一次调暗,1000M / s秒暗淡),压缩率为50. Numpy memmap仅提供可接受的速度你在最快的方向读取或写入数据(在C的最后一个维度),HDF5使用的分块数据格式在这里,这根本不是问题。 Numpy memmap也无法进行压缩,导致文件更大,速度更慢。

请注意,压缩过滤器和块状必须根据您的需要进行设置。这取决于您以后如何读取数据和实际数据。

如果你做了一些完全错误的事情,那么与正确的方法相比,性能可能会慢10-100倍(例如,可以针对第一个或第二个读取示例优化块形状)。

© www.soinside.com 2019 - 2024. All rights reserved.