0

"Выполнение отображения (mapping) массива NumPy на месте"

9

Возможна ли карта NumPy массива на месте? Если да, то как?

У меня есть 2D массив a_values, и сейчас я использую следующий код, чтобы выполнить преобразование элементов:

for row in range(len(a_values)):
    for col in range(len(a_values[0])):
        a_values[row][col] = dim(a_values[row][col])

Однако это выглядит довольно неаккуратно, и у меня есть подозрение, что в NumPy должна быть функция, которая позволяет сделать то же самое, что-то вроде:

a_values.map_in_place(dim)

Но если что-то подобное существует, я пока не смог его найти.

3 ответ(ов)

0

Вопрос: Возможно ли выполнить отображение массива NumPy на месте?

Ответ: Да, но не с помощью одного метода массива. Вам нужно будет написать собственный код.

Ниже представлен скрипт, который сравнивает различные реализации, обсуждаемые в теме:

import timeit
from numpy import array, arange, vectorize, rint

# ПОДГОТОВКА
get_array = lambda side: arange(side**2).reshape(side, side) * 30
dim = lambda x: int(round(x * 0.67328))

# ТАЙМЕР
def best(fname, reps, side):
    global a
    a = get_array(side)
    t = timeit.Timer('%s(a)' % fname, setup='from __main__ import %s, a' % fname)
    return min(t.repeat(reps, 3))  # малое число, так как на месте --> сходимость к 1

# ФУНКЦИИ
def mac(array_):
    for row in range(len(array_)):
        for col in range(len(array_[0])):
            array_[row][col] = dim(array_[row][col])

def mac_two(array_):
    li = range(len(array_[0]))
    for row in range(len(array_)):
        for col in li:
            array_[row][col] = int(round(array_[row][col] * 0.67328))

def mac_three(array_):
    for i, row in enumerate(array_):
        array_[i][:] = [int(round(v * 0.67328)) for v in row]

def senderle(array_):
    array_ = array_.reshape(-1)
    for i, v in enumerate(array_):
        array_[i] = dim(v)

def eryksun(array_):
    array_[:] = vectorize(dim)(array_)

def ufunc_ed(array_):
    multiplied = array_ * 0.67328
    array_[:] = rint(multiplied)

# ОСНОВНОЕ
r = []
for fname in ('mac', 'mac_two', 'mac_three', 'senderle', 'eryksun', 'ufunc_ed'):
    print('\nТестируем `%s`...' % fname)
    r.append(best(fname, reps=50, side=50))
    # Следующий код необходим для визуальной проверки, что функции возвращают одинаковые результаты
    tmp = get_array(3)
    eval('%s(tmp)' % fname)
    print(tmp)
tmp = min(r) / 100
print('\n===== ...И ПОБЕДИТЕЛЬ... =========================')
print('  mac (как в вопросе)        :  %.4fms [%.0f%%]' % (r[0] * 1000, r[0] / tmp))
print('  mac (оптимизированный)     :  %.4fms [%.0f%%]' % (r[1] * 1000, r[1] / tmp))
print('  mac (срез-присвоение)      :  %.4fms [%.0f%%]' % (r[2] * 1000, r[2] / tmp))
print('  senderle                   :  %.4fms [%.0f%%]' % (r[3] * 1000, r[3] / tmp))
print('  eryksun                    :  %.4fms [%.0f%%]' % (r[4] * 1000, r[4] / tmp))
print('  срез-присвоение с ufunc    :  %.4fms [%.0f%%]' % (r[5] * 1000, r[5] / tmp))
print('=======================================================\n')

Вывод приведенного выше скрипта - по крайней мере, на моей системе - следующий:

  mac (как в вопросе)        :  88.7411ms [74591%]
  mac (оптимизированный)     :  86.4639ms [72677%]
  mac (срез-присвоение)      :  79.8671ms [67132%]
  senderle                   :  85.4590ms [71832%]
  eryksun                    :  13.8662ms [11655%]
  срез-присвоение с ufunc    :  0.1190ms [100%]

Как вы можете заметить, использование ufunc в NumPy увеличивает скорость более чем на 2 и почти на 3 порядка по сравнению с вторым лучшим и худшим альтернативами соответственно.

Если использование ufunc невозможно, вот сравнение других альтернатив:

  mac (как в вопросе)        :  91.5761ms [672%]
  mac (оптимизированный)     :  88.9449ms [653%]
  mac (срез-присвоение)      :  80.1032ms [588%]
  senderle                   :  86.3919ms [634%]
  eryksun                    :  13.6259ms [100%]

Надеюсь, это поможет!

0

Использование реализации на numpy с применением трюка out_ может быть привлекательным, но как показывают ваши результаты, есть более быстрые способы достижения аналогичных результатов. Время выполнения функции fmilo, которая, судя по всему, оптимизирована для вашей задачи, составляет всего 0.0620 мс, что гораздо быстрее, чем другие методы.

Давайте проанализируем:

  1. Использование np_round с out= — это действительно хороший способ избежать создания дополнительных массивов, так как вы перенаправляете результат непосредственно в входной массив. Однако использование этого подхода не всегда ведет к максимальной производительности, как показывает ваш тест.

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

  3. Сравнение с другими методами — в вашем тесте функции с использованием срезов и векторизации показывают значительно худшие результаты, что указывает на то, что более прямолинейные реализации могут иметь преимущества в скорости.

Таким образом, хотя использование numpy и трюков с out_ может быть оправдано в некоторых случаях, оптимизация конкретной функции, как fmilo, может дать существенно лучшие результаты. Выбор подхода должен основываться на потребностях вашего приложения и требованиях к производительности.

0

Если использование ufunc не представляется возможным, возможно, стоит рассмотреть вариант с Cython. Он легко интегрируется и может привести к значительному ускорению в конкретных задачах, связанных с массивами NumPy.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь