Использование Numpy Vectorize для функций, которые возвращают векторы
numpy.vectorize
берет функцию f:a->b и превращает ее в g:a[]->b[].
Это прекрасно работает, когда a
а также b
скаляры, но я не могу придумать причину, почему это не будет работать с б как ndarray
или список, то есть 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)
но это не эффективно и не питонно. Кто-нибудь из вас, ребята, может найти более чистый способ сделать это?
Заранее спасибо!
5 ответов
np.vectorize
это просто удобная функция. Это на самом деле не делает код работать быстрее. Если это не удобно использовать np.vectorize
Просто напишите свою собственную функцию, которая работает так, как вы хотите.
Цель np.vectorize
заключается в преобразовании функций, не учитывающих numpy (например, принимает числа с плавающей точкой в качестве входных и возвращаемых значений с плавающей точкой в качестве выходных данных) в функции, которые могут работать (и возвращать) массивы с нулевыми значениями.
Ваша функция f
уже осведомлен о NumPy - он использует массив NUMPY в своем определении и возвращает массив NUMPY. Так np.vectorize
не подходит для вашего случая использования.
Поэтому решение состоит в том, чтобы просто свернуть свою собственную функцию f
это работает так, как вы хотите.
Новый параметр 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 обобщенных универсальных функций.
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 решает проблему более полно и красиво (я считаю). Надеюсь это поможет.
Вы хотите векторизовать функцию
import numpy as np
def f(x):
return x * np.array([1,1,1,1,1], dtype=np.float32)
Предполагая, что вы хотите стать холостым np.float32
массивы как результат, вы должны указать это как otype
. Однако в своем вопросе вы указалиotypes=[np.ndarray]
что означает, что вы хотите, чтобы каждый элемент был np.ndarray
. Таким образом, вы правильно получите результатdtype=object
.
Правильный звонок был бы
np.vectorize(f, signature='()->(n)', otypes=[np.float32])
Однако для такой простой функции лучше использовать numpy
функции; np.vectorize
просто зацикливается на нем. Поэтому в вашем случае просто перепишите свою функцию как
def f(x):
return np.multiply.outer(x, np.array([1,1,1,1,1], dtype=np.float32))
Это быстрее и приводит к менее скрытым ошибкам (учтите, однако, что результаты dtype
будет зависеть от x
если вы передадите комплексное число или число с четырехкратной точностью, то будет результат).
Я написал функцию, кажется, соответствует вашим потребностям.
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))
Обратите внимание на строку документации vectorize
говорит
vectorize
Функция предоставляется в первую очередь для удобства, а не для производительности. Реализация по сути является циклом for.
Таким образом, мы ожидаем amap
здесь имеют такую же производительность, как vectorize
, Я не проверял, приветствуются любые тесты производительности.
Если производительность действительно важна, вы должны рассмотреть что-то еще, например, прямой расчет массива с reshape
а также broadcast
чтобы избежать петли в чистом питоне (оба vectorize
а также amap
более поздний случай).
Лучший способ решить эту проблему - использовать двумерный массив 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 транслировать массивы. Хитрость заключается в том, чтобы убедиться, что хотя бы одно измерение имеет одинаковую длину между массивами.