python - 按最大大小将 numpy 数组拆分为块
问题描述
我有一些非常大的二维 numpy 数组。一个数据集是 55732 x 257659,包含超过 140 亿个元素。因为我需要执行一些操作 throw MemoryError
s,所以我想尝试将数组拆分为一定大小的块并针对这些块运行它们。(我可以在每个片段上运行操作后汇总结果。)我的问题是MemoryErrors
意味着重要的是我可以以某种方式限制数组的大小,而不是将它们分成恒定数量的片段。
例如,让我们生成一个 1009 x 1009 的随机数组:
a = numpy.random.choice([1,2,3,4], (1009,1009))
我的数据不一定可以均匀拆分,也绝对不能保证可以按我想要的大小进行拆分。所以我选择了 1009,因为它是素数。
还假设我希望它们以不大于 50 x 50 的块的形式出现。由于这只是为了避免非常大的数组出现错误,因此如果结果不准确也没关系。
我怎样才能把它分成所需的块?
我正在使用带有 numpy 1.14.3(最新)的 Python 3.6 64 位。
有关的
我见过这个使用 的函数reshape
,但是如果行数和列数不能完全划分大小,它就不起作用。
这个问题(在其他类似问题中)有解释如何分割成一定数量的块的答案,但这并没有解释如何分割成一定的大小。
我也看到了这个问题,因为这实际上是我的确切问题。答案和评论建议切换到 64 位(我已经拥有)并使用numpy.memmap
. 都没有帮助。
解决方案
这样做可以使生成的数组的形状略小于所需的最大值,或者使它们恰好具有所需的最大值,除了最后的一些剩余部分。
基本逻辑是计算分割数组的参数,然后array_split
沿数组的每个轴(或维度)分割数组。
我们需要numpy
andmath
模块和示例数组:
import math
import numpy
a = numpy.random.choice([1,2,3,4], (1009,1009))
略低于最大值
逻辑
首先将最终块大小的形状沿要拆分的每个维度存储在一个元组中:
chunk_shape = (50, 50)
array_split
一次仅沿一个轴(或维度)或数组拆分。所以让我们从第一个轴开始。
计算我们需要将数组拆分为的部分数:
num_sections = math.ceil(a.shape[0] / chunk_shape[0])
在我们的示例中,这是 21 (
1009 / 50 = 20.18
)。现在拆分它:
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。
我们可以对第二维的每个新数组做同样的事情:
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
精确的形状加上余数
逻辑
如果我们想要更漂亮一点,并且除了尾随的剩余数组之外,所有新数组都完全符合指定的大小,我们可以通过传递一个索引列表来拆分 at to 来做到这一点array_split
。
首先建立索引数组:
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]
然后拆分:
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))
然后再次针对第二个轴:
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 x 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
我没有用更高维数组测试速度。
小于最大值的形状
如果任何维度的大小小于指定的最大值,这些函数都可以正常工作。这不需要特殊的逻辑。
推荐阅读
- jquery - Jquery不会让我在css中使用计数器?
- php - 无法使用 CI 控制器将文件上传到 codeigniter 文件夹
- python - 在 Python 中通过 TCP 发送/接收多条消息
- sql - SQL。将 datediff 小时数转换为 dd.hh:mm:ss
- java - xpath 每天都在变化
- node.js - 更新 package.json 以反映已安装的包
- javascript - Jquery-timeago Uncaught TypeError:无法读取未定义的属性“替换”
- laravel-5 - 当点击提交错误发生时
- android - 是否可以从活动堆栈中删除几个活动
- c++ - 如何在 C++ 中将值拆分为单个十进制整数