Это можно сделать так, чтобы полученные массивы имели формы немного меньше желаемого максимума или чтобы они имели точно желаемый максимум, за исключением некоторого остатка в конце.
Основная логика - вычислитьпараметры для разделения массива, а затем используйте array_split
, чтобы разделить массив вдоль каждой оси (или измерения) массива.
Нам понадобятся numpy
и math
Модули и массив примеров:
import math
import numpy
a = numpy.random.choice([1,2,3,4], (1009,1009))
Чуть меньше, чем max
Логика
Сначала сохраните форму окончательного размера фрагмента по каждому измерению, которое вы хотите разделитьэто в кортеже:
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 (количество запрошенных разделов) массивов-пустышек, которыев первом измерении они не превышают 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]
Это даст нам список списков.Каждый подсписок содержит целые массивы нужного нам размера:
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
.
Сначала создайте массив индексов:
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
Дополнительные примечания
Производительность
Эти функциикажется, довольно быстро.Мне удалось разделить мой примерный массив (с более чем 14 миллиардами элементов) на 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
Я не тестировал скорость для массивов с более высокими размерами.
Фигуры меньше максимального
Обе эти функции работают правильно, если размер какого-либо размера меньше размерауказанный максимум.Это не требует специальной логики.