保存在磁盘上的 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 数组中的随机访问的主要内容,如果未能解决你的问题,请参考以下文章