能否将内存映射文件 np.concatenate 连接起来?

20

我使用 np.save() 保存了几个 numpy 数组,它们的总大小非常大。

是否可以将它们全部作为内存映射文件加载,然后通过连接和切片来处理所有数组,而无需将任何内容加载到内存中?


可能是重复问题:不复制地连接Numpy数组 - Jonas Schäfer
当然,我已经尝试过简单地使用np.concatenate()连接内存映射数组的元组,结果是加载到内存中并迅速使我的系统崩溃。 - vedran
阅读另一个线程,我认为你想要实现的目标似乎对我来说相当不可能。尽管我确实能看到它的用处。如果只是关于切片,我有一两个想法,但这些方法在其他numpy工具中无法使用。 - Jonas Schäfer
我想在这种特定情况下,我只能不使用切片了,但是你当然可以分享你所拥有的想法。 - vedran
1
h5py 对你来说是一个可能性吗?在那里,你可以很好地切片而不必加载整个内容。 - cronos
3个回答

24

使用 numpy.concatenate 方法会将数组加载到内存中。为了避免这种情况,可以轻松地在新文件中创建第三个 memmap 数组,并从要连接的数组中读取值。更有效的方式是,您还可以将新数组附加到磁盘上已存在的文件中。

无论哪种情况,您都必须选择正确的数组顺序(行主序或列主序)。

以下示例说明了如何沿着轴 0 和轴 1 进行连接。


1) 沿着 axis=0 进行连接

a = np.memmap('a.array', dtype='float64', mode='w+', shape=( 5000,1000)) # 38.1MB
a[:,:] = 111
b = np.memmap('b.array', dtype='float64', mode='w+', shape=(15000,1000)) # 114 MB
b[:,:] = 222

你可以定义一个第三个数组,以与要连接的第一个数组(这里是a)相同的文件为基础,在模式r+(读取并追加)下进行操作,但是它的形状应该是你想要在拼接后获得的最终数组的形状,例如:

c = np.memmap('a.array', dtype='float64', mode='r+', shape=(20000,1000), order='C')
c[5000:,:] = b

沿着axis=0进行连接并不需要传递order='C',因为这已经是默认顺序。


2)沿着axis=1进行连接

a = np.memmap('a.array', dtype='float64', mode='w+', shape=(5000,3000)) # 114 MB
a[:,:] = 111
b = np.memmap('b.array', dtype='float64', mode='w+', shape=(5000,1000)) # 38.1MB
b[:,:] = 222
在磁盘上保存的数组实际上是扁平化的,所以如果您使用mode=r+shape=(5000,4000)创建c时不更改数组顺序,则来自a第二行的前1000个元素将进入c中的第一行。但是,您可以通过向memmap传递order='F'(列优先)来轻松避免这种情况:
c = np.memmap('a.array', dtype='float64', mode='r+',shape=(5000,4000), order='F')
c[:, 3000:] = b

这里有一个更新后的文件'a.array',其中包含连接结果。您可以重复此过程以成对地连接。

相关问题:


5

也许有另一种解决方案,但我有一个跨多个文件的单个多维数组,我只想要读取它。我用dask concatenation解决了这个问题。

import numpy as np
import dask.array as da
 
a = np.memmap('a.array', dtype='float64', mode='r', shape=( 5000,1000))
b = np.memmap('b.array', dtype='float64', mode='r', shape=(15000,1000))

c = da.concatenate([a, b], axis=0)

这种方法避免了繁琐的附加文件处理。然后可以对 dask 数组进行切片和操作,几乎像任何 numpy 数组一样,并且在计算结果时调用 compute

请注意有两个注意事项:

  1. 无法进行原地重新赋值,例如 c[::2] = 0 不可行,因此在这些情况下需要采用创造性的解决方案。
  2. 这也意味着原始文件不能再更新。为了保存结果,应使用 dask 的 store 方法。此方法还可以接受一个 memmapped 数组。

0
如果你使用order='F',会导致另一个问题,即下次加载文件时,即使通过order='F',也会变得一团糟。因此,我的解决方案如下,经过多次测试,效果非常好。
fp = your old memmap...
shape = fp.shape
data = your ndarray...
data_shape = data.shape
concat_shape = data_shape[:-1] + (data_shape[-1] + shape[-1],)
print('cancat shape:{}'.format(concat_shape))
new_fp = np.memmap(new_file_name, dtype='float32', mode='r+', shape=concat_shape)
if len(concat_shape) == 1:
    new_fp[:shape[0]] = fp[:]
    new_fp[shape[0]:] = data[:]
if len(concat_shape) == 2:
    new_fp[:, :shape[-1]] = fp[:]
    new_fp[:, shape[-1]:] = data[:]
elif len(concat_shape) == 3:
    new_fp[:, :, :shape[-1]] = fp[:]
    new_fp[:, :, shape[-1]:] = data[:]
fp = new_fp
fp.flush()

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