Numpy: ОШИБКА: функции np.vectorize () дважды работают с первым элементом (как видно, когда функция изменяет изменяемый объект)

Созданный на 8 мар. 2017  ·  7Комментарии  ·  Источник: numpy/numpy

У меня есть список словарей. Я пытаюсь использовать np.vectorize для применения функции, которая изменяет элементы словаря для каждого словаря в списке. Результаты, кажется, показывают, что векторизация дважды воздействует на первый элемент. Это ошибка, которую можно исправить? (Возможно, связано с тем, что векторизация проверяет тип для первого элемента?) Ниже приведены некоторые примеры случаев и результаты:

Простой тестовый пример без изменений словаря:

def fcn1(x):
    return x['b']
a = [{'b': 1} for _ in range(3) ]
print(a)
print(np.vectorize(fcn1)(a))
print(a, '\n\n')

вывод:

[{'b': 1}, {'b': 1}, {'b': 1}]
[1 1 1]
[{'b': 1}, {'b': 1}, {'b': 1}]

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

def fcn2(x):
    x['b'] += 1
    return x['b']
a = [{'b': 1} for _ in range(3) ]
print(a)
print(np.vectorize(fcn2)(a))
print(a, '\n\n')

вывод:

[{'b': 1}, {'b': 1}, {'b': 1}]
[3 2 2]
[{'b': 3}, {'b': 2}, {'b': 2}]  

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

def fcn3(x):
    x['b'] *= 2
    return x['b']
a = [{'b': 1} for _ in range(3) ]
print(a)
print(np.vectorize(fcn3)(a))
print(a, '\n\n')

вывод:

[{'b': 1}, {'b': 1}, {'b': 1}]
[4 2 2]
[{'b': 4}, {'b': 2}, {'b': 2}]    

Вы можете сделать то же самое, фактически не предоставляя возвращаемое значение (именно так я пытаюсь использовать его в моем случае использования):

def fcn4(x):
    x['b'] += 1
a = [{'b': 1} for _ in range(3) ]
print(a)
np.vectorize(fcn4)(a)
print(a, '\n\n')

вывод:

[{'b': 1}, {'b': 1}, {'b': 1}]
[{'b': 3}, {'b': 2}, {'b': 2}]

И, кстати, в списке длиной 3 нет ничего особенного, вы можете изменить это и увидеть то же поведение только первого элемента, который подвергается двойному изменению.

Я подтвердил поведение, используя как Numpy версии 1.11.3, так и 1.12.0

РЕДАКТИРОВАТЬ:
Я нашел обходной путь, который также подтверждает, что это проблема «тестирования типа на первом элементе». Если вы укажете аргумент otypes , первый элемент не попадет дважды:

def fcn(x):
    x['b'] += 1
    return x['b']
a = [{'b': 1} for _ in range(3)]
print a
print np.vectorize(fcn, otypes=[dict])(a)
print a, '\n\n'

вывод:

[{'b': 1}, {'b': 1}, {'b': 1}]
[2 2 2]
[{'b': 2}, {'b': 2}, {'b': 2}]
00 - Bug

Самый полезный комментарий

«Если otypes не указан, то вызов функции с первым аргументом будет использоваться для определения количества выходов. Результаты этого вызова будут кэшироваться, если cache имеет значение True, чтобы предотвратить вызов функции дважды. Однако для реализации кэш, исходная функция должна быть обернута, что замедлит последующие вызовы, поэтому делайте это только в том случае, если ваша функция дорогая. "
https://docs.scipy.org/doc/numpy/reference/generated/numpy.vectorize.html

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

Все 7 Комментарий

Я только что отредактировал свой пост с помощью нового теста, который, похоже, подтверждает, что проверка первого типа элемента IS вызывает проблему

Более простой тестовый пример:

a = np.array([1, 2, 3])
def f(x):
    print('got', x)
    return x
fv = np.vectorize(f)
y = fv(a)

Дает:

got 1
got 1
got 2
got 3

«Если otypes не указан, то вызов функции с первым аргументом будет использоваться для определения количества выходов. Результаты этого вызова будут кэшироваться, если cache имеет значение True, чтобы предотвратить вызов функции дважды. Однако для реализации кэш, исходная функция должна быть обернута, что замедлит последующие вызовы, поэтому делайте это только в том случае, если ваша функция дорогая. "
https://docs.scipy.org/doc/numpy/reference/generated/numpy.vectorize.html

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

ах, очевидно, я недостаточно хорошо прочитал все документы. Это решает проблему двойных вызовов, но за счет более медленного выполнения. Итак, я полагаю, что нет способа предотвратить этот двойной вызов, сохранив при этом производительность?

например, в numpy/lib/function_base.py в vectorize class function _get_ufunc_and_otypes() я бы наивно подумал, что вы можете изменить эти строки:

inputs = [arg.flat[0] for arg in args]
outputs = func(*inputs)

кому:

#earlier
import copy

...

inputs = copy.deepcopy([arg.flat[0] for arg in args])
outputs = func(*inputs)

И тогда вам не нужно будет использовать кеширование или указывать типы, но я думаю, вы избегаете дважды нажимать фактические изменяемые элементы. Но я не знаю, насколько сильно снизится производительность по сравнению с кешированием.

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

Я думаю, что для векторизованных операций с массивами очень больших объектов создание глубокой копии первого элемента может оказаться более затратным с точки зрения вычислений, чем применение функции. Так что было бы неплохо иметь эту функциональность, если пользователь не указывает otype , но затем говорит в документации, что производительность может снизиться, если otype не указано для массивов большого размера. объекты. @ Эрик-Визер, что ты думаешь?

copy.deepcopy() небезопасен для многих входов, поэтому, к сожалению, это не жизнеспособный вариант.

Единственный способ исправить это - переписать ядро vectorize , которое в настоящее время использует numpy.frompyfunc для создания фактически numpy ufunc. Потребуется создать альтернативный внутренний цикл, аналогичный тому, который мы используем для np.apply_along_axis . К сожалению, чтобы избежать снижения производительности, я думаю, что нам нужно будет выполнить цикл на C (как frompyfunc в ufunc, который он создает).

Была ли эта страница полезной?
0 / 5 - 0 рейтинги