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 предоставляет математические функции, к которым встроенное приложение обращается чаще всего:
Тригонометрия –
sin(),cos(),tan(),asin(),acos(),atan(),arctan2(),sinh(),cosh(),tanh(),asinh(),acosh(),atanh(),sinc().Экспоненты и логарифмы –
exp(),expm1(),log(),log10(),log2(),sqrt().
Каждая функция обрабатывает весь массив за один вызов библиотеки. Ускорение по сравнению со списковым включением 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-совместимые операции с массивами.