将多个numpy数组实时写入文件

7
这与将多个numpy数组写入文件不同,因为我需要能够流式传输内容,而不是一次性写入所有内容。
我需要将多个压缩的numpy数组以二进制形式写入文件。我不能在写入之前将所有数组存储在内存中,因此更像是将numpy数组流式传输到文件中。
目前,这在文本中可以正常工作。
file = open("some file")
while doing stuff: file.writelines(somearray + "\n") 其中somearray是每次循环的新实例
但是,如果我尝试将数组以二进制形式写入,则无法正常工作。
数组以30hz创建并增长得太大,无法保存在内存中。它们也不能分别存储到一堆单个数组文件中,因为那只会浪费空间并导致混乱。
因此,我希望每个会话只有一个文件,而不是每个会话有10k个文件。

所有的数组形状和数据类型都相同吗?你说:“...但是如果我尝试将数组写成二进制,这种方法行不通”,但你没有解释你如何尝试做到这一点,以及出了什么问题。请提供更多关于你尝试过的内容的细节。 - Warren Weckesser
它们的形状不相同。 出错的原因是文件为空。 - dtracers
2个回答

4
一个NPZ文件只是一个zip归档文件,因此您可以将每个数组保存到临时NPY文件中,将该NPY文件添加到zip归档文件中,然后删除临时文件。
例如,
import os
import zipfile
import numpy as np


# File that will hold all the arrays.
filename = 'foo.npz'

with zipfile.ZipFile(filename, mode='w', compression=zipfile.ZIP_DEFLATED) as zf:
    for i in range(10):
        # `a` is the array to be written to the file in this iteration.
        a = np.random.randint(0, 10, size=20)

        # Name for the temporary file to which `a` is written.  The root of this
        # filename is the name that will be assigned to the array in the npz file.
        # I've used 'arr_{}' (e.g. 'arr_0', 'arr_1', ...), similar to how `np.savez`
        # treats positional arguments.
        tmpfilename = "arr_{}.npy".format(i)

        # Save `a` to a npy file.
        np.save(tmpfilename, a)

        # Add the file to the zip archive.
        zf.write(tmpfilename)

        # Delete the npy file.
        os.remove(tmpfilename)

这里有一个例子,展示了如何运行该脚本,并使用np.load读取数据:
In [1]: !ls
add_array_to_zip.py

In [2]: run add_array_to_zip.py

In [3]: !ls
add_array_to_zip.py foo.npz

In [4]: foo = np.load('foo.npz')

In [5]: foo.files
Out[5]: 
['arr_0',
 'arr_1',
 'arr_2',
 'arr_3',
 'arr_4',
 'arr_5',
 'arr_6',
 'arr_7',
 'arr_8',
 'arr_9']

In [6]: foo['arr_0']
Out[6]: array([0, 9, 3, 7, 2, 2, 7, 2, 0, 5, 8, 1, 1, 0, 4, 2, 5, 1, 8, 2])

你需要在自己的系统上测试一下,看看它是否能够跟上数组生成过程。


另一种选择是使用类似HDF5的东西,可以使用h5pypytables


你如何持续将数组添加到.npz文件中? - Sebastian Mendez
我的回答是如何做到这一点的示例,不是吗? - Warren Weckesser
1
使用您的答案,我该如何将十个新数组附加到 foo.npz 中?我认为您想使用 mode='a' - Sebastian Mendez
1
啊,我明白了。我的回答假设你将创建文件,然后重复向其中添加数组。是的,如果你想要添加到现有文件中,你需要使用mode='a'来打开zipfile。你还需要确保在NPZ文件中使用的每个数组名称都不存在。 - Warren Weckesser
1
我相信那是他想要的。假设数组是从可能无限的发生器中产生的,可以随时停止和恢复;这就是我认为的“流式传输”。 - Sebastian Mendez
问题中缺少太多细节,让我无法猜测他想要什么。该问题涉及在“会话”中生成一堆数组。我的答案假设“会话”是程序的一次运行,并且为该会话创建一个文件。 - Warren Weckesser

3

一种选择是使用pickle将数组保存到以追加二进制打开的文件中:

import numpy as np
import pickle
arrays = [np.arange(n**2).reshape((n,n)) for n in range(1,11)]
with open('test.file', 'ab') as f:
    for array in arrays:
        pickle.dump(array, f)

new_arrays = []        
with open('test.file', 'rb') as f:
    while True:
        try:
            new_arrays.append(pickle.load(f))
        except EOFError:
            break
assert all((new_array == array).all() for new_array, array in zip(new_arrays, arrays))

这可能不是最快的,但应该足够快。看起来这似乎会占用更多的数据,但比较一下这些:
x = 300
y = 300
arrays = [np.random.randn(x, y) for x in range(30)]

with open('test2.file', 'ab') as f:
    for array in arrays:
        pickle.dump(array, f)

with open('test3.file', 'ab') as f:
    for array in arrays:
        f.write(array.tobytes())

with open('test4.file', 'ab') as f:
    for array in arrays:
        np.save(f, array)

你会发现文件大小分别为1,025 KB、1,020 KB和1,022 KB。


你如何重新加载这些数组?如果我使用 np.load('test4.file'),我只能获取第一个。 - Thomas Ahle
1
我的第一个片段描述了如何加载数组;第8-14行是从第1-6行所写的内容中加载的。 - Sebastian Mendez
有点巧妙,但相当高效 :) - Thomas Ahle
Warren提出的另一种解决方案更符合使用numpy结构的预期,但问题在于每个新数组都意味着读取所有旧数组; 因此,从n个数组的流创建文件将需要O(n ^ 2)时间。 使用二进制附加,从n个数组构建文件只需要O(n)时间。 - Sebastian Mendez
不错的观点,尽管n^2的行为只会在您不断关闭/打开文件时发生。如果您像Warren的代码一样不断添加到文件中,应该会得到线性行为。但是再次强调,有时您可能需要关闭文件,所以这个观点很好。 - Thomas Ahle

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