按最大大小将numpy数组拆分为块

siv3szwd  于 2023-08-05  发布在  其他
关注(0)|答案(3)|浏览(98)

我有一些 * 非常 * 大的二维numpy数组。一个数据集是55732乘257659,这是超过140亿个元素。因为我需要执行一些操作throw MemoryError s,所以我想尝试将数组拆分为特定大小的块,并对这些块运行它们。(我可以在对每个部分运行操作后聚合结果。)我的问题是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。都没有帮助。

yebdmbv4

yebdmbv41#

这可以这样做,使得所得到的阵列具有略小于期望的最大值的形状,或者使得它们除了在末端处的一些剩余部分之外完全具有期望的最大值。
基本逻辑是计算用于分割数组的参数,然后使用array_split沿着数组的每个轴(或维度)分割数组。
我们需要numpymath模块以及示例数组:

import math
import numpy

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

字符串

略小于max

逻辑

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

chunk_shape = (50, 50)


array_split一次仅沿着一个轴(或维度)或数组拆分。让我们从第一个轴开始。
1.计算我们需要将数组拆分成的部分的数量:

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


在我们的示例中,这是21(1009 / 50 = 20.18)。
1.现在拆分它:

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。
1.我们可以对第二维的每个新数组做同样的事情:

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

完整功能

我们可以使用递归函数对任意维度实现这一点:

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]


1.然后拆分:

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))


1.对于第二个轴:

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)}

完整功能

调整递归函数:

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

额外说明

性能

这些功能看起来相当快。我能够在0.05秒内将示例数组(超过140亿个元素)拆分为1000 × 1000个形状的片段(产生超过14000个新数组):

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


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

小于max的形状

如果任何尺寸的大小小于指定的最大值,则这两个函数都可以正常工作。这不需要特殊的逻辑。

piztneat

piztneat2#

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

启用numpy的reshape

填充数组,以便将其重塑为块维度。简单地用零填充,这样每个(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)]

字符串
显示list 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)


我想列表理解的加速应该直接转化为更高维的数组。numpy的array_split实现基本上做到了这一点,但还允许在任意轴上进行分块。然而,列表comp也可以扩展到这样做。

kjthegm6

kjthegm63#

通过简单地使用np.array_split和天花板除法,我们可以相对容易地做到这一点。

import numpy as np
import math

max_size = 15
test = np.arrange(101)

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

字符串

相关问题