Разделить массив numy на куски по максимальному размеру - PullRequest
0 голосов
/ 12 мая 2018

У меня есть несколько очень больших двумерных массивов numpy.Один набор данных - 55732 на 257659, что составляет более 14 миллиардов элементов.Поскольку некоторые операции мне нужно выполнить throw MemoryError s, я хотел бы попытаться разбить массив на куски определенного размера и запустить их против кусков.(Я могу агрегировать результаты после выполнения операции на каждом фрагменте.) Тот факт, что моя проблема MemoryErrors, означает, что важно как-то ограничить размер массивов, а не разбивать их на постоянное количество частей.

Для примера, давайте сгенерируем случайный массив 1009 на 1009:

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

Мои данные не обязательно могут быть разделены равномерно, и определенно не гарантируется возможность разделения по размеруЯ хочу.Поэтому я выбрал 1009, потому что он прост.

Скажем также, что я хочу, чтобы они были порциями не больше 50 на 50. Поскольку это просто для того, чтобы избежать ошибок с очень большими массивами, все в порядке, если результат нене точно.

Как я могу разделить это на нужные куски?

Я использую 64-битный Python 3.6 с numpy 1.14.3 (последний).

Связанные

Я видел эту функцию, которая использует reshape, но она не работает, если количество строк и столбцов не делит размер точно.

На этот вопрос (среди прочих похожих) есть ответы, объясняющие, как разбить на определенное количество кусков, но это не объясняет, как разбить на определенный размер.

Я также видел этот вопрос , так как это на самом деле моя точная проблема.Ответ и комментарии предлагают перейти на 64-битный (который у меня уже есть) и использовать numpy.memmap.Ни то, ни другое не помогло.

Ответы [ 2 ]

0 голосов
/ 27 мая 2018

Поскольку я не знаю, как ваши данные генерируются или будут обрабатываться, я могу предложить два подхода:

Включить использование изменения формы numpy

Дополните массив, чтобы позволить изменить его в размеры вашего чанка. Просто добавьте нули, чтобы каждый (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)]

показывает, что скорость компиляции списка примерно в 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 может быть расширен и для этого.

0 голосов
/ 12 мая 2018

Это можно сделать так, чтобы полученные массивы имели формы немного меньше желаемого максимума или чтобы они имели точно желаемый максимум, за исключением некоторого остатка в конце.

Основная логика - вычислитьпараметры для разделения массива, а затем используйте 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 разбивается только вдоль одной оси (или измерения) или массива за один раз.Итак, давайте начнем с первой оси.

  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 (количество запрошенных разделов) массивов-пустышек, которыев первом измерении они не превышают 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]
    

    Это даст нам список списков.Каждый подсписок содержит целые массивы нужного нам размера:

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

Полная функция

Адаптация рекурсивногофункция:

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

Я не тестировал скорость для массивов с более высокими размерами.

Фигуры меньше максимального

Обе эти функции работают правильно, если размер какого-либо размера меньше размерауказанный максимум.Это не требует специальной логики.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...