通过maxmimum大小将numpy数组拆分为块

时间:2018-05-12 11:52:32

标签: python numpy

我有一些非常大的二维numpy数组。一个数据集是55732乘257659,超过140亿个元素。因为我需要执行一些操作抛出MemoryError,所以我想尝试将数组拆分成一定大小的块并对着块运行它们。 (我可以在每个部分运行操作后聚合结果。)我的问题是MemoryErrors的事实意味着我可以以某种方式限制数组的大小,而不是将它们分成常数件。

举个例子,让我们生成一个1009乘1009的随机数组:

a = numpy.random.choice([1,2,3,4], (1009,1009))

我的数据不一定可以均匀分割,并且绝对不能保证按我想要的大小分割。所以我选择了1009,因为它是主要的。

我们还要说我希望它们的大小不超过50比50。因为这只是为了避免极大数组的错误,所以如果结果不准确就没关系。

如何将其拆分为所需的块?

我正在使用Python 3.6 64位和numpy 1.14.3(最新版)。

相关

我见过this function that uses reshape,但如果行数和列数没有完全划分大小,则无效。

This question(以及其他类似的)有答案解释如何分成一定数量的块,但这并不能解释如何分割成一定的大小。

我也看到this question,因为这实际上是我的确切问题。答案和评论建议切换到64位(我已经拥有)并使用numpy.memmap。没有帮助。

3 个答案:

答案 0 :(得分:2)

可以这样做,以便得到的数组的形状略小于所需的最大值,或者除了最后的一些余数外,它们具有完全所需的最大值。

基本逻辑是计算拆分数组的参数,然后使用array_split沿阵列的每个轴(或维度)拆分数组。

我们需要numpymath模块以及示例数组:

import math
import numpy

a = numpy.random.choice([1,2,3,4], (1009,1009))

略小于最大

逻辑

首先在每个维度中存储最终块大小的形状,然后将其拆分为元组:

chunk_shape = (50, 50)

array_split一次只能沿一个轴(或维度)或一个数组分割。所以,让我们从第一个轴开始。

  1. 计算将数组拆分为以下所需的部分数量:

    num_sections = math.ceil(a.shape[0] / chunk_shape[0])
    

    在我们的示例中,这是21(1009 / 50 = 20.18)。

  2. 现在拆分它:

    first_split = numpy.array_split(a, num_sections, axis=0)
    

    这给我们提供了21个(请求的部分数量)numpy数组的列表,这些数组被分割,因此它们在第一维中不大于50:

    print(len(first_split))
    # 21
    print({i.shape for i in first_split})
    # {(48, 1009), (49, 1009)}
    # These are the distinct shapes, so we don't see all 21 separately
    

    在这种情况下,他们沿着那条轴走48和49.

  3. 我们可以对第二维的每个新数组做同样的事情:

    num_sections = math.ceil(a.shape[1] / chunk_shape[1])
    second_split = [numpy.array_split(a2, num_sections, axis=1) for a2 in first_split]
    

    这为我们提供了一份清单清单。每个子列表都包含我们想要的大小的numpy数组:

    print(len(second_split))
    # 21
    print({len(i) for i in second_split})
    # {21}
    # All sublists are 21 long
    print({i2.shape for i in second_split for i2 in i})
    # {(48, 49), (49, 48), (48, 48), (49, 49)}
    # Distinct shapes
    
  4. 完整功能

    我们可以使用递归函数为任意维度实现这个:

    def split_to_approx_shape(a, chunk_shape, start_axis=0):
        if len(chunk_shape) != len(a.shape):
            raise ValueError('chunk length does not match array number of axes')
    
        if start_axis == len(a.shape):
            return a
    
        num_sections = math.ceil(a.shape[start_axis] / chunk_shape[start_axis])
        split = numpy.array_split(a, num_sections, axis=start_axis)
        return [split_to_approx_shape(split_a, chunk_shape, start_axis + 1) for split_a in split]
    

    我们称之为:

    full_split = split_to_approx_shape(a, (50,50))
    print({i2.shape for i in full_split for i2 in i})
    # {(48, 49), (49, 48), (48, 48), (49, 49)}
    # Distinct shapes
    

    精确形状加上余数

    逻辑

    如果我们想成为一个小小的发烧友并且所有新阵列都完全指定的大小(除了尾随的剩余数组),我们可以通过传递一个索引列表来分割到array_split

    1. 首先建立索引数组:

      axis = 0
      split_indices = [chunk_shape[axis]*(i+1) for i  in range(math.floor(a.shape[axis] / chunk_shape[axis]))]
      

      这使用了一个索引列表,每个索引都是50个:

      print(split_indices)
      # [50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950, 1000]
      
    2. 然后拆分:

      first_split = numpy.array_split(a, split_indices, axis=0)
      print(len(first_split))
      # 21
      print({i.shape for i in first_split})
      # {(9, 1009), (50, 1009)}
      # Distinct shapes, so we don't see all 21 separately
      print((first_split[0].shape, first_split[1].shape, '...', first_split[-2].shape, first_split[-1].shape))
      # ((50, 1009), (50, 1009), '...', (50, 1009), (9, 1009))
      
    3. 然后再次为第二轴:

      axis = 1
      split_indices = [chunk_shape[axis]*(i+1) for i  in range(math.floor(a.shape[axis] / chunk_shape[axis]))]
      second_split = [numpy.array_split(a2, split_indices, axis=1) for a2 in first_split]
      print({i2.shape for i in second_split for i2 in i})
      # {(9, 50), (9, 9), (50, 9), (50, 50)}
      
    4. 完整功能

      调整递归函数:

      def split_to_shape(a, chunk_shape, start_axis=0):
          if len(chunk_shape) != len(a.shape):
              raise ValueError('chunk length does not match array number of axes')
      
          if start_axis == len(a.shape):
              return a
      
          split_indices = [
              chunk_shape[start_axis]*(i+1)
              for i in range(math.floor(a.shape[start_axis] / chunk_shape[start_axis]))
          ]
          split = numpy.array_split(a, split_indices, axis=start_axis)
          return [split_to_shape(split_a, chunk_shape, start_axis + 1) for split_a in split]
      

      我们称之为完全相同:

      full_split = split_to_shape(a, (50,50))
      print({i2.shape for i in full_split for i2 in i})
      # {(9, 50), (9, 9), (50, 9), (50, 50)}
      # Distinct shapes
      

      额外备注

      性能

      这些功能似乎非常快。我能够将我的示例数组(超过140亿个元素)分成1000个1000个形状的片段(产生超过14000个新阵列),在0.05秒内完成任一功能:

      print('Building test array')
      a = numpy.random.randint(4, size=(55000, 250000), dtype='uint8')
      chunks = (1000, 1000)
      numtests = 1000
      print('Running {} tests'.format(numtests))
      print('split_to_approx_shape: {} seconds'.format(timeit.timeit(lambda: split_to_approx_shape(a, chunks), number=numtests) / numtests))
      print('split_to_shape: {} seconds'.format(timeit.timeit(lambda: split_to_shape(a, chunks), number=numtests) / numtests))
      

      输出:

      Building test array
      Running 1000 tests
      split_to_approx_shape: 0.035109398348040485 seconds
      split_to_shape: 0.03113800323300747 seconds
      

      我没有用更高维度的数组测试速度。

      小于最大值

      的形状

      如果任何尺寸的尺寸小于指定的最大值,这些功能都能正常工作。这不需要特殊的逻辑。

答案 1 :(得分:1)

由于我不知道您的数据是如何生成或将被处理的,我可以建议两种方法:

允许使用numpy&s>重塑

填充数组以允许将其重新整形为块大小。只需用零填充,这样每个(axis_size%chunk_size)== 0.每个轴的chunk_size可能不同。

填充像这样的多维数组会创建一个(略大)副本。为了避免复制,请删除'最大的可分块阵列,重新整形并分别处理左边界。

根据您的数据处理方式,这可能非常不切实际。

使用列表组合

我认为拆分实现有更简单/可读的版本。使用numpy.split()或只是花哨的索引。

import numpy as np

a = np.arange(1009)

chunk_size = 50

%timeit np.split(a, range(chunk_size, a.shape[0], chunk_size))
%timeit [a[i:i+chunk_size] for i in range(0, a.shape[0], chunk_size)]

显示列表comp的速度是〜3倍,同时返回相同的结果:

36.8 µs ± 1.66 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
10.4 µs ± 2.48 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)

我想列表理解的加速应该直接转换为更高维数组。 array_split的numpy's implementation基本上就是这样,但是还允许在任意轴上进行分块。但是,列表补偿也可以扩展到这样做。

答案 2 :(得分:0)

只需使用np.array_split和操作员//的楼层划分,我们就可以相对容易地做到这一点。

import numpy as np

max_size = 15
test = np.arrange(101)

result = np.array_split(test, len(test) // max_size)