Использование векторизации Numpy для функций, возвращающих векторы
numpy.vectorize
принимает функцию f: a- > b и превращает ее в g: a [] → b [].
Это отлично работает, когда a
и b
являются скалярами, но я не могу думать о причине, почему он не будет работать с b как ndarray
или list, то есть f: a- > b [ ] и g: a [] → b [] []
Например:
import numpy as np
def f(x):
return x * np.array([1,1,1,1,1], dtype=np.float32)
g = np.vectorize(f, otypes=[np.ndarray])
a = np.arange(4)
print(g(a))
Это дает:
array([[ 0. 0. 0. 0. 0.],
[ 1. 1. 1. 1. 1.],
[ 2. 2. 2. 2. 2.],
[ 3. 3. 3. 3. 3.]], dtype=object)
Хорошо, так что это дает правильные значения, но неправильный тип dtype. И еще хуже:
g(a).shape
дает:
(4,)
Итак, этот массив практически бесполезен. Я знаю, что могу преобразовать его:
np.array(map(list, a), dtype=np.float32)
чтобы дать мне то, что я хочу:
array([[ 0., 0., 0., 0., 0.],
[ 1., 1., 1., 1., 1.],
[ 2., 2., 2., 2., 2.],
[ 3., 3., 3., 3., 3.]], dtype=float32)
но это не является ни эффективным, ни питоновым. Может ли кто-нибудь из вас найти более чистый способ сделать это?
Спасибо заранее!
Ответы
Ответ 1
np.vectorize
- просто удобная функция. На самом деле он делает код более быстрым. Если использовать np.vectorize
не удобно, просто напишите свою собственную функцию, которая работает по вашему желанию.
Цель np.vectorize
состоит в том, чтобы преобразовывать функции, которые не являются numpy-aware (например, принимать float как входные и возвращаемые float как выходные данные) в функции, которые могут работать с (и возвращать) массивы numpy.
Ваша функция f
уже имеет значение numpy-aware - она использует массив numpy в своем определении и возвращает массив numpy. Поэтому np.vectorize
не подходит для вашего использования.
Таким образом, решение состоит в том, чтобы просто свернуть свою собственную функцию f
, которая работает так, как вы хотите.
Ответ 2
import numpy as np
def f(x):
return x * np.array([1,1,1,1,1], dtype=np.float32)
g = np.vectorize(f, otypes=[np.ndarray])
a = np.arange(4)
b = g(a)
b = np.array(b.tolist())
print(b)#b.shape = (4,5)
c = np.ones((2,3,4))
d = g(c)
d = np.array(d.tolist())
print(d)#d.shape = (2,3,4,5)
Это должно устранить проблему, и она будет работать независимо от размера вашего ввода. "Карта" работает только для одного размера входных данных. Использование ".tolist()" и создание нового ndarray решает проблему более полно и красиво (я полагаю). Надеюсь, это поможет.
Ответ 3
Новый параметр signature
в 1.12.0 делает именно то, что вам нужно.
def f(x):
return x * np.array([1,1,1,1,1], dtype=np.float32)
g = np.vectorize(f, signature='()->(n)')
Тогда g(np.arange(4)).shape
даст (4L, 5L)
.
Здесь указана подпись f
. (n)
- это форма возвращаемого значения, а ()
- форма скалярного параметра. И параметры могут быть массивами тоже. Для более сложных сигнатур см. Обобщенный универсальный функциональный API.
Ответ 4
Я написал функцию, которая, похоже, подходит вам.
def amap(func, *args):
'''array version of build-in map
amap(function, sequence[, sequence, ...]) -> array
Examples
--------
>>> amap(lambda x: x**2, 1)
array(1)
>>> amap(lambda x: x**2, [1, 2])
array([1, 4])
>>> amap(lambda x,y: y**2 + x**2, 1, [1, 2])
array([2, 5])
>>> amap(lambda x: (x, x), 1)
array([1, 1])
>>> amap(lambda x,y: [x**2, y**2], [1,2], [3,4])
array([[1, 9], [4, 16]])
'''
args = np.broadcast(None, *args)
res = np.array([func(*arg[1:]) for arg in args])
shape = args.shape + res.shape[1:]
return res.reshape(shape)
Попробуйте
def f(x):
return x * np.array([1,1,1,1,1], dtype=np.float32)
amap(f, np.arange(4))
Выходы
array([[ 0., 0., 0., 0., 0.],
[ 1., 1., 1., 1., 1.],
[ 2., 2., 2., 2., 2.],
[ 3., 3., 3., 3., 3.]], dtype=float32)
Вы можете также обернуть его лямбдой или частичным для удобства
g = lambda x:amap(f, x)
g(np.arange(4))
Обратите внимание, что docstring vectorize
говорит
Функция vectorize
предоставляется в первую очередь для удобства, а не для представление. Реализация по существу является циклом for.
Таким образом, мы ожидаем, что amap
будет иметь схожую производительность, как vectorize
. Я не проверял, любые тесты производительности приветствуются.
Если производительность действительно важна, вы должны рассмотреть что-то еще, например. вычисление прямого массива с помощью reshape
и broadcast
, чтобы избежать цикла в чистом питоне (как vectorize
, так и amap
- это более поздний случай).
Ответ 5
Лучшим способом решения этого вопроса будет использование 2-D массива NumPy (в данном случае массива столбцов) в качестве ввода исходной функции, который затем генерирует двухмерный вывод с результатами, которые, как я полагаю, вам ожидали.
Вот как это выглядит в коде:
import numpy as np
def f(x):
return x*np.array([1, 1, 1, 1, 1], dtype=np.float32)
a = np.arange(4).reshape((4, 1))
b = f(a)
# b is a 2-D array with shape (4, 5)
print(b)
Это гораздо более простой и менее подверженный ошибкам способ завершить операцию. Вместо того, чтобы пытаться преобразовать функцию с numpy.vectorize, этот метод основан на естественной способности NumPy к широковещательным массивам. Трюк состоит в том, чтобы убедиться, что по меньшей мере одно измерение имеет равную длину между массивами.