保存在磁盘上的 numpy 数组中的随机访问

Posted

技术标签:

【中文标题】保存在磁盘上的 numpy 数组中的随机访问【英文标题】:Random access in a saved-on-disk numpy array 【发布时间】:2021-09-13 07:41:39 【问题描述】:

我有一个大的 numpy 数组 A 形状为 (2_000_000, 2000)dtype float64,需要 32 GB。

(或者将相同的数据分成10个形状(200_000、2000)的数组,序列化可能更容易?)。

我们如何将其序列化到磁盘,以便我们可以快速随机读取数据的任何部分?

更准确地说,我需要能够从A 以随机起始索引i 读取一万个形状(16、2 000)的窗口:

L = []
for i in range(10_000):
    i = random.randint(0, 2_000_000 - 16):
    window = A[i:i+16, :]         # window of A of shape (16, 2000) starting at a random index i
    L.append(window)
WINS = np.concatenate(L)   # shape (10_000, 16, 2000) of float64, ie: ~ 2.4 GB

假设我只有 8 GB 的 RAM 可用于此任务;将整个 32 GB 的 A 加载到 RAM 中是完全不可能的。

我们如何在磁盘上序列化的 numpy 数组中读取此类窗口?(.h5 格式或任何其他格式)

注意:读取是在随机起始索引处完成的这一事实很重要。

【问题讨论】:

这能回答你的问题吗? ***.com/questions/29209293/… 谢谢@orlp,它是相关的,但它没有回答它,因为这里的具体部分是我需要能够随机启动原始大数组的窗口/切片点。这些细节使其更加具体。 你可以试试 NumPy 内存映射:numpy.memmap(filename, dtype=<class 'numpy.ubyte'>, mode='r+', offset=0, shape=None, order='C') @RyanPepper 我试过了,但这是我遇到的问题:***.com/questions/68209831/… @orlp 建议将数据写入 HDF5 文件是一个很好的解决方案。一旦数组在文件中,就很容易从数据集中读取切片。 h5py 包支持 NumPy 索引(包括一些花哨的索引技术)。 【参考方案1】:

此示例展示了如何将 HDF5 文件用于您描述的过程。

首先,使用 shape(2_000_000, 2000)dtype=float64 值的数据集创建一个 HDF5 文件。我为尺寸使用了变量,所以你可以修改它。

import numpy as np
import h5py
import random

h5_a0, h5_a1 = 2_000_000, 2_000

with h5py.File('SO_68206763.h5','w') as h5f:
    dset = h5f.create_dataset('test',shape=(h5_a0, h5_a1))
    
    incr = 1_000
    a0 = h5_a0//incr
    for i in range(incr):
        arr = np.random.random(a0*h5_a1).reshape(a0,h5_a1)
        dset[i*a0:i*a0+a0, :] = arr       
    print(dset[-1,0:10])  # quick dataset check of values in last row

接下来,以读取模式打开文件,读取 10_000 个形状为 (16,2_000) 的随机数组切片并附加到列表 L。最后,将列表转换为数组WINS。请注意,默认情况下,该数组将有 2 个轴 - 如果您希望每个评论有 3 个轴,则需要使用 .reshape()(还显示了重塑)。

with h5py.File('SO_68206763.h5','r') as h5f:
    dset = h5f['test']
    L = []
    ds0, ds1 = dset.shape[0], dset.shape[1]
    for i in range(10_000):
        ir = random.randint(0, ds0 - 16)
        window = dset[ir:ir+16, :]  # window from dset of shape (16, 2000) starting at a random index i
        L.append(window)
    WINS = np.concatenate(L)   # shape (160_000, 2_000) of float64,
    print(WINS.shape, WINS.dtype)
    WINS = np.concatenate(L).reshape(10_0000,16,ds1)   # reshaped to (10_000, 16, 2_000) of float64
    print(WINS.shape, WINS.dtype)

上面的过程不是内存效率的。您最终得到了随机切片数据的 2 个副本:在列表 L 和数组 WINS 中。如果内存有限,这可能是个问题。为避免中间复制,将数据的随机幻灯片直接读取到数组中。这样做可以简化代码,并减少内存占用。该方法如下图所示(WINS2为2轴阵列,WINS3为3轴阵列)。

with h5py.File('SO_68206763.h5','r') as h5f:
    dset = h5f['test']
    ds0, ds1 = dset.shape[0], dset.shape[1]
    WINS2 = np.empty((10_000*16,ds1))
    WINS3 = np.empty((10_000,16,ds1))
    for i in range(10_000):
        ir = random.randint(0, ds0 - 16)
        WINS2[i*16:(i+1)*16,:] = dset[ir:ir+16, :]
        WINS3[i,:,:] = dset[ir:ir+16, :]

【讨论】:

【参考方案2】:

我尝试过的 h5py 数据集的替代解决方案是使用 memmap,正如 @RyanPepper 的评论中所建议的那样。

将数据写入二进制

import numpy as np
with open('a.bin', 'wb') as A:
    for f in range(1000):
        x =  np.random.randn(10*2000).astype('float32').reshape(10, 2000)
        A.write(x.tobytes())
        A.flush()

稍后以memmap 打开

A = np.memmap('a.bin', dtype='float32', mode='r').reshape((-1, 2000))
print(A.shape)  # (10000, 2000)
print(A[1234:1234+16, :])  # window

【讨论】:

以上是关于保存在磁盘上的 numpy 数组中的随机访问的主要内容,如果未能解决你的问题,请参考以下文章

numpy的文件存储,读取 .npy .npz 文件

从使用 numpy.save(...) 保存的文件中将 numpy 数组加载到 C 中

在磁盘上保留 numpy 数组的最佳方法

Numpy数组的保存与读取方法

将 Numpy 数组保存为图像

Numpy | 23 IO