通过附加数据构建 Numpy 数组(事先不知道完整大小)
Building a Numpy array by appending data (without knowing the full size in advance)
我有很多文件,每个文件都被读取为形状为 (n, 1000)
的矩阵,其中 n 可能因文件而异。
我想将它们全部连接成一个大的 Numpy 数组。我目前这样做:
dataset = np.zeros((100, 1000))
for f in glob.glob('*.png'):
x = read_as_numpyarray(f) # custom function; x is a matrix of shape (n, 1000)
dataset = np.vstack((dataset, x))
但效率很低,因为我通过将现有数组与读取的下一个文件堆叠在一起来重新定义 dataset
很多次。
如何使用 Numpy 以更好的方式做到这一点,避免整个数据集在内存中多次重写?
注意:最终的大 Numpy 数组可能需要 10 GB。
预先分配您的阵列。循环遍历要预先添加的文件,然后将要从每个文件中检索的数据量相加。然后创建具有您需要的总大小的 dataset
数组。最后再次遍历文件,将数据插入到已经分配的数组中。这将比分配新数组并为每个附加文件一遍又一遍地从以前的文件复制数据更有效。
或者不构建 10GB 阵列。考虑修改您的操作,以便它们在较小的数据块上兼容,并按需读入更易于管理的数据集。
使用原生 list
numpy 数组,然后 np.concatenate
。
本机list
会在需要时增加(by ~1.125)大小,所以不会发生太多的重新分配,而且,它只会保存指向分散(内存中不连续)的指针np.arrays
持有实际数据。
只调用 concatenate
一次即可解决您的问题。
伪代码
dataset = []
for f in glob.glob('*.png'):
x = read_as_numpyarray(f) # custom function; x is a matrix of shape (n, 1000)
dataset.append(x)
dataset_np = np.concatenate(dataset)
注意 vstack
内部使用 concatenate
.
编辑以解决编辑后的问题:
Let's say the total size of data is 20 GB. When concatenating, the
system will have to still keep 20 GB (for each individual array) and
also allocate 20 GB for the new concatenated array, thus requiring 40
GB of RAM (double of the dataset). How to do this without requiring
the double of RAM? (Example: is there a solution if we only have 32 GB
of RAM?)
我会首先分阶段按照当前答案中的建议来解决这个问题。 dataset_np1 = np.concatenate(half1_of_data)
、dataset_np2 = np.concatenate(half2_of_data)
将只需要 150% RAM(而不是 200%)。这可以以牺牲速度为代价递归地扩展,直到它成为问题中的命题的极限。我只能假设 dask 之类的人可以更好地处理这个问题,但我自己还没有测试过。
澄清一下,在你拥有 dataset_np1 之后,你不再需要所有分片小数组的列表,并且可以释放它。只有这样你才开始加载另一半。因此,您只需要在内存中保留额外 50% 的数据。
伪代码:
def load_half(buffer: np.array, shard_path: str, shard_ind: int):
half_dataset = []
for f in glob.glob(f'{shard_path}/*.png'):
x = read_as_numpyarray(f) # custom function; x is a matrix of shape (n, 1000)
half_dataset.append(x)
half_dataset_np = np.concatenate(half_dataset) # see comment *
buffer[:buffer.shape[0] // 2 * (shard_ind + 1), ...] = half_dataset_np
half1_path = r"half1" # preprocess the shards to be found by glob or otherwise
half2_path = r"half2"
assert os.path.isdir(half1_path)
assert os.path.isdir(half2_path)
buffer = np.zeros(size_shape)
half1_np = load_half(half1_path, buffer, 0) # only 50% of data temporarily loaded, then freed [can be done manually if needed]
half2_np = load_half(half2_path, buffer, 1) # only 50% of data temporarily loaded, then freed
人们可以(容易地或不那么容易地)将其概括为四分之一、八分之一或递归地任何所需的分数,以牺牲速度为代价来减少内存成本,无穷大的极限是问题中的原始命题。
- 重要注释(代码中见“见注释*”):
人们可能会注意到 half_dataset_np = np.concatenate(half_dataset)
实际上分配了数据集的 50%,另外分配了 50%
在碎片中,显然没有为我们节省任何东西。这是正确的,我可以
找不到连接到缓冲区的方法。然而,实施这一
按照建议递归地(并且未在伪代码中显示)将保存
内存,因为四分之一每次只会使用 2* 25%。这只是一个
实现细节,但我希望要点是清楚的。
换句话说,另一种方法会声明“如果数据集是 1000GB 怎么办”?那么没有 numpy 数组会做。这就是数据库存在的原因,并且可以使用 tools. But again, this is somewhat a research question, and depends heavily on your specific needs. As a very uninformed hunch, I would check out dask.
非常有效地查询它们
这样的图书馆显然会解决像这样的问题,作为他们所做工作的一个子集,我建议不要自己实施这些事情,因为你花费的总时间将远远超过选择和学习图书馆的时间。
另一方面,我想知道这是否真的需要这么大的数组,maybe a slightly different design or formulation of the problem 可以让我们完全摆脱这个技术问题。
我想你需要mmap的概念。 Numpy 内置了对 mmap 的支持。我喜欢@Gulzar 提到的分片来分块处理文件的方式。我没有使用过 mmap(),但如果您看到他们的文档,您似乎不需要将整个文件放在内存中。您可以通过追加模式对其进行写入,并且可以通过这种方式对其进行操作。此外,如果您担心此操作的时间限制,因为分片会不断增加处理时间,您应该考虑此过程的分布式计算架构。这样,您可以使用 10 台机器,每台机器的内存较小,但将块附加到较大的内存中。我知道这不是一个完整的答案,这就是为什么我最后会提供一些与我刚才提到的想法相关的资源。此外,还有 hdf5 和 zarr,您可以将其用作此过程的变通方法。
我讲的算法思路:
(1) 开始
(2) 解析文件 1 到 10:添加它们,作为 mmap 引用对象存储在硬盘中。
对于从 1 到 100 的所有 n:
(3) 解析n*10+1到(n+1)*10个文件:将它们添加到内存中,将它们附加到硬盘中的mmap引用对象。
(4) 您将 numpy 数组存储在内存中。
参考文献:
(1) mmap vs zarr vs hdf5
(2) numpy mmap support
(3) what is mmap actually
我有很多文件,每个文件都被读取为形状为 (n, 1000)
的矩阵,其中 n 可能因文件而异。
我想将它们全部连接成一个大的 Numpy 数组。我目前这样做:
dataset = np.zeros((100, 1000))
for f in glob.glob('*.png'):
x = read_as_numpyarray(f) # custom function; x is a matrix of shape (n, 1000)
dataset = np.vstack((dataset, x))
但效率很低,因为我通过将现有数组与读取的下一个文件堆叠在一起来重新定义 dataset
很多次。
如何使用 Numpy 以更好的方式做到这一点,避免整个数据集在内存中多次重写?
注意:最终的大 Numpy 数组可能需要 10 GB。
预先分配您的阵列。循环遍历要预先添加的文件,然后将要从每个文件中检索的数据量相加。然后创建具有您需要的总大小的 dataset
数组。最后再次遍历文件,将数据插入到已经分配的数组中。这将比分配新数组并为每个附加文件一遍又一遍地从以前的文件复制数据更有效。
或者不构建 10GB 阵列。考虑修改您的操作,以便它们在较小的数据块上兼容,并按需读入更易于管理的数据集。
使用原生 list
numpy 数组,然后 np.concatenate
。
本机list
会在需要时增加(by ~1.125)大小,所以不会发生太多的重新分配,而且,它只会保存指向分散(内存中不连续)的指针np.arrays
持有实际数据。
只调用 concatenate
一次即可解决您的问题。
伪代码
dataset = []
for f in glob.glob('*.png'):
x = read_as_numpyarray(f) # custom function; x is a matrix of shape (n, 1000)
dataset.append(x)
dataset_np = np.concatenate(dataset)
注意 vstack
内部使用 concatenate
.
编辑以解决编辑后的问题:
Let's say the total size of data is 20 GB. When concatenating, the system will have to still keep 20 GB (for each individual array) and also allocate 20 GB for the new concatenated array, thus requiring 40 GB of RAM (double of the dataset). How to do this without requiring the double of RAM? (Example: is there a solution if we only have 32 GB of RAM?)
我会首先分阶段按照当前答案中的建议来解决这个问题。 dataset_np1 = np.concatenate(half1_of_data)
、dataset_np2 = np.concatenate(half2_of_data)
将只需要 150% RAM(而不是 200%)。这可以以牺牲速度为代价递归地扩展,直到它成为问题中的命题的极限。我只能假设 dask 之类的人可以更好地处理这个问题,但我自己还没有测试过。
澄清一下,在你拥有 dataset_np1 之后,你不再需要所有分片小数组的列表,并且可以释放它。只有这样你才开始加载另一半。因此,您只需要在内存中保留额外 50% 的数据。
伪代码:
def load_half(buffer: np.array, shard_path: str, shard_ind: int):
half_dataset = []
for f in glob.glob(f'{shard_path}/*.png'):
x = read_as_numpyarray(f) # custom function; x is a matrix of shape (n, 1000)
half_dataset.append(x)
half_dataset_np = np.concatenate(half_dataset) # see comment *
buffer[:buffer.shape[0] // 2 * (shard_ind + 1), ...] = half_dataset_np
half1_path = r"half1" # preprocess the shards to be found by glob or otherwise
half2_path = r"half2"
assert os.path.isdir(half1_path)
assert os.path.isdir(half2_path)
buffer = np.zeros(size_shape)
half1_np = load_half(half1_path, buffer, 0) # only 50% of data temporarily loaded, then freed [can be done manually if needed]
half2_np = load_half(half2_path, buffer, 1) # only 50% of data temporarily loaded, then freed
人们可以(容易地或不那么容易地)将其概括为四分之一、八分之一或递归地任何所需的分数,以牺牲速度为代价来减少内存成本,无穷大的极限是问题中的原始命题。
- 重要注释(代码中见“见注释*”):
人们可能会注意到half_dataset_np = np.concatenate(half_dataset)
实际上分配了数据集的 50%,另外分配了 50% 在碎片中,显然没有为我们节省任何东西。这是正确的,我可以 找不到连接到缓冲区的方法。然而,实施这一 按照建议递归地(并且未在伪代码中显示)将保存 内存,因为四分之一每次只会使用 2* 25%。这只是一个 实现细节,但我希望要点是清楚的。
换句话说,另一种方法会声明“如果数据集是 1000GB 怎么办”?那么没有 numpy 数组会做。这就是数据库存在的原因,并且可以使用 tools. But again, this is somewhat a research question, and depends heavily on your specific needs. As a very uninformed hunch, I would check out dask.
非常有效地查询它们这样的图书馆显然会解决像这样的问题,作为他们所做工作的一个子集,我建议不要自己实施这些事情,因为你花费的总时间将远远超过选择和学习图书馆的时间。
另一方面,我想知道这是否真的需要这么大的数组,maybe a slightly different design or formulation of the problem 可以让我们完全摆脱这个技术问题。
我想你需要mmap的概念。 Numpy 内置了对 mmap 的支持。我喜欢@Gulzar 提到的分片来分块处理文件的方式。我没有使用过 mmap(),但如果您看到他们的文档,您似乎不需要将整个文件放在内存中。您可以通过追加模式对其进行写入,并且可以通过这种方式对其进行操作。此外,如果您担心此操作的时间限制,因为分片会不断增加处理时间,您应该考虑此过程的分布式计算架构。这样,您可以使用 10 台机器,每台机器的内存较小,但将块附加到较大的内存中。我知道这不是一个完整的答案,这就是为什么我最后会提供一些与我刚才提到的想法相关的资源。此外,还有 hdf5 和 zarr,您可以将其用作此过程的变通方法。
我讲的算法思路:
(1) 开始
(2) 解析文件 1 到 10:添加它们,作为 mmap 引用对象存储在硬盘中。
对于从 1 到 100 的所有 n:
(3) 解析n*10+1到(n+1)*10个文件:将它们添加到内存中,将它们附加到硬盘中的mmap引用对象。
(4) 您将 numpy 数组存储在内存中。
参考文献:
(1) mmap vs zarr vs hdf5
(2) numpy mmap support
(3) what is mmap actually