如何有效地切片memmap?

Mic*_*hal 5 python numpy

目前,我正在处理一个非常适合我的内存的巨大数据集,因此我使用np.memmap。但是在某些时候,我必须将我的数据集分为训练和测试。当我想np.memmap使用一些索引数组切片时,我发现了这种情况:(下面可以找到代码和内存分配)

Line #    Mem usage    Increment   Line Contents
================================================
 7    29.340 MB     0.000 MB   def my_func2():
 8    29.340 MB     0.000 MB       ARR_SIZE = (1221508/4,430)
 9    29.379 MB     0.039 MB       big_mmap = np.memmap('big_mem_test.mmap',shape=ARR_SIZE, dtype=np.float64, mode='r')    
10    38.836 MB     9.457 MB       idx = range(ARR_SIZE[0])
11  2042.605 MB  2003.770 MB       sub = big_mmap[idx,:]
12  3046.766 MB  1004.160 MB       sub2 = big_mmap[idx,:]
13  3046.766 MB     0.000 MB       return  type(sub)
Run Code Online (Sandbox Code Playgroud)

但是,如果我想进行连续切片,我会使用以下代码:

Line #    Mem usage    Increment   Line Contents
================================================
15    29.336 MB     0.000 MB   def my_func3():
16    29.336 MB     0.000 MB       ARR_SIZE = (1221508/4,430)
17    29.375 MB     0.039 MB       big_mmap = np.memmap('big_mem_test.mmap',shape=ARR_SIZE, dtype=np.float64, mode='r')    
18    29.457 MB     0.082 MB       sub = big_mmap[0:1221508/4,:]
19    29.457 MB     0.000 MB       sub2 = big_mmap[0:1221508/4,:]  
Run Code Online (Sandbox Code Playgroud)

请注意,在第18,19行的第二个示例中,没有内存分配,整个操作要快得多。

在第11行的第一个示例中,有一个分配,因此big_mmap在切片期间会读取整个矩阵。但是第12行更令人惊讶的是还有另一个分配。进行更多此类操作,您很容易耗尽内存。

当我分割数据集时,索引是相当随机的并且不是连续的,所以我不能使用big_mmap[start:end,:]符号。

我的问题是:

还有其他方法可以让我切片memmap而不将整个数据读取到内存吗?

为什么在对索引进行切片时将整个矩阵读到内存中(示例一)?

为什么要再次读取并分配数据(第一个示例行12)?

bog*_*ron 6

您在第一个示例中看到的双重分配不是由于 memmap 行为;相反,这是由于__getitem__numpy 的 ndarray 类是如何实现的。当使用列表索引 ndarray 时(如在您的第一个示例中),数据将从源数组复制。当使用切片对象对其进行索引时,会在源数组中创建一个视图(不复制任何数据)。例如:

In [2]: x = np.arange(16).reshape((4,4))

In [3]: x
Out[3]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])

In [4]: y = x[[0, 2], :]

In [5]: y[:, :] = 100

In [6]: x
Out[6]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])
Run Code Online (Sandbox Code Playgroud)

y是数据的副本,x因此更改yx. 现在通过切片索引数组:

In [7]: z = x[::2, :]

In [8]: z[:, :] = 100

In [9]: x
Out[9]: 
array([[100, 100, 100, 100],
       [  4,   5,   6,   7],
       [100, 100, 100, 100],
       [ 12,  13,  14,  15]])
Run Code Online (Sandbox Code Playgroud)

关于您的第一个问题,我不知道有一种方法可以让您创建包含整个数组的任意切片,而无需将整个数组读入内存。您可能会考虑两个选项(除了您已经讨论过的 HDF5/PyTables 之类的选项):

  1. 如果您按顺序访问训练和测试集的元素(而不是将它们作为两个完整数组操作),您可以轻松编写一个小包装类,其__getitem__方法使用您的索引数组从 memmap 中提取适当的样本(即训练[i] 返回 big_mmap[training_ids[i]])

  2. 将您的数组拆分为两个单独的文件,其中仅包含训练或测试值。然后你可以使用两个单独的 memmap 对象。