通过追加数据构建Numpy数组(事先不知道完整大小)
问题描述
我有许多文件,每个文件都以(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更好地执行此操作,避免在内存中多次重写整个数据集?
nb:最终的大块块数组可能需要10 GB。
解决方案
使用NumPy数组的本机list
,然后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
。
编辑以解决已编辑的问题:
假设数据总大小为20 GB。串联时, 系统仍必须保留20 GB(对于每个单独的阵列) 还要为新的串联数组分配20 GB,因此需要40 GB GB的RAM(数据集的两倍)。如何在不要求的情况下完成此操作 RAM的翻倍?(例如:如果我们只有32 GB,有没有解决方案 内存?)
我将按照当前答案中提出的一样,分阶段地解决这个问题。dataset_np1 = np.concatenate(half1_of_data)
、dataset_np2 = np.concatenate(half2_of_data)
只需要150%的内存(而不是200%)。这可以递归地扩展,但以速度为代价,直到这成为问题中的命题的极限。我只能假设像dask这样的人能更好地处理这一点,但我自己还没有测试过。
伪码:
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%。这只是一个 实现细节,但我希望大意是明确的。
换一种说法,如果数据集是1000 GB,那么另一种方法将声明&qot;怎么办?那么,任何麻木数组都不会起作用。这就是数据库存在的原因,使用tools可以非常高效地查询它们。但同样,这在某种程度上是一个研究问题,在很大程度上取决于您的特定需求。作为非常不知情的预感,我会查看dask。
这类库显然会将这类问题作为它们所做工作的子集来处理,我建议您不要自己实现这些事情,因为您将花费的总时间将远远超过选择和学习库的时间。
另一方面,我想知道这是否真的必须是一个如此巨大的数组,maybe a slightly different design or formulation of the problem可以让我们完全摆脱这个技术问题。
相关文章