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