6.9. Универсальные функции

Универсальная функция (ufunc) – это математическая функция, которая применяется к каждому элементу массива за один вызов. Арифметические операторы на предыдущей странице – это универсальные функции в синтаксисе операторов; эта страница – каталог именованных функций, охватывающих тригонометрию, exp / log, округление и некоторые другие.

Каждая ufunc принимает скаляр, итерируемый объект Python или ndarray и возвращает либо одно число с плавающей точкой (когда вход был скаляром), либо ndarray чисел с плавающей точкой:

from ulab import numpy as np

np.exp(2.0)                    # 7.389...
np.sin(range(4))               # 1-D float ndarray
np.sqrt([1, 4, 9, 16])         # array([1.0, 2.0, 3.0, 4.0])

a = np.arange(9).reshape((3, 3))
np.exp(a)                      # 3x3 float ndarray

6.9.1. Каталог

numpy предоставляет математические функции, к которым встроенное приложение обращается чаще всего:

Каждая функция обрабатывает весь массив за один вызов библиотеки. Ускорение по сравнению со списковым включением Python, которое вызывает math.sin() поэлементно, составляет 10-30x на типичном буфере.

6.9.2. Ключевое слово out=

Каждый вызов ufunc обычно выделяет свежий результирующий массив для хранения вывода. В цикле, выполняющемся много раз в секунду, эти выделения накапливаются и расходуют ОЗУ. Передача out= – уже существующего массива с плавающей точкой той же формы, что и вход – записывает результат в этот массив вместо выделения нового:

x = np.linspace(0, 2 * np.pi, num=256)
y = np.zeros(256)

while True:
    np.sin(x, out=y)
    # use y ...

Если out имеет неверный тип данных или форму, функция вызывает исключение. Это ключевое слово поддерживается каждой ufunc на этой странице; это самый чистый способ сохранить потоковый цикл обработки сигналов без выделений памяти.

6.9.3. Ufunc с двумя аргументами

arctan2() – единственная истинная ufunc с двумя аргументами в списке выше – она возвращает учитывающий квадрант арктангенс y / x и транслирует два операнда:

y = np.array([1, 2.2, 33.33, 444.444])
np.arctan2(y, 1.0)             # against a scalar
np.arctan2(1.0, y)             # the other way
np.arctan2(y, y)               # against another array

6.9.4. Композиция универсальных функций

Универсальные функции компонуются как любое другое выражение над массивами. Несколько паттернов, которые возникают на камере:

Гамма-коррекция (в пространстве с плавающей точкой):

gamma = 0.5
out = 255.0 * (frame / 255.0) ** gamma

Простой сглаживающий фильтр нижних частот (alpha близко к 1.0 означает медленное обновление):

alpha = 0.95
filtered = alpha * filtered + (1.0 - alpha) * sample

Сигмоида:

sigmoid = 1.0 / (1.0 + np.exp(-x))

Спектр мощности в дБ:

spectrum = 20.0 * np.log10(np.abs(real) + 1e-12)

6.9.5. np.vectorize

Обычная функция Python может быть преобразована в функцию в форме ufunc с помощью vectorize(). Получившийся вызываемый объект принимает скаляры, итерируемые объекты или значения ndarray:

def f(x):
    return x * x

vf = np.vectorize(f)

vf(44.0)                          # array([1936.0])
vf(np.array([1, 2, 3, 4]))        # array([1.0, 4.0, 9.0, 16.0])
vf([2, 3, 4])                     # array([4.0, 9.0, 16.0])

По умолчанию тип данных результата – float. otypes= переопределяет его:

vf_u8 = np.vectorize(f, otypes=np.uint8)
vf_u8([1, 2, 3, 4])
# array([1, 4, 9, 16], dtype=uint8)

Функция Python должна принимать один аргумент и возвращать одно число.

vectorize() в основном синтаксическая – обёрнутая функция Python всё равно должна выполняться один раз на элемент, так что большая часть поэлементной стоимости интерпретатора, которую избегает истинная ufunc, возвращается. Ожидайте умеренное ускорение 30%-50% по сравнению со списковым включением, а не 30x истинной универсальной функции. Это подходящий инструмент, когда одна функция должна работать со скалярами, списками и массивами под одним именем – но не когда целью является чистая скорость.

Полные сигнатуры вызова каждой из перечисленных выше универсальных функций см. в numpy — numpy-совместимые операции с массивами.