6.9. 通用函数

通用函数(ufunc)是一种在一次调用中作用于数组每个元素的数学函数。上一页中的算术运算符就是披着运算符语法外衣的通用函数;本页则是涵盖三角函数、指数 / 对数、舍入等的具名通用函数目录。

每个 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 提供了嵌入式应用最常用到的数学函数:

每个函数都在一次库调用中处理整个数组。相比逐个元素调用 math.sin() 的 Python 列表推导式,在典型缓冲区上能获得 10-30 倍的加速。

6.9.2. out= 关键字

每次 ufunc 调用通常都会分配一个新的结果数组来保存其输出。在每秒运行多次的循环中,这些分配会累积起来并浪费 RAM。传入 out=(一个已存在的、与输入形状相同的浮点数组)会把结果写入该数组,而不是分配新的数组:

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

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

如果 out 的 dtype 或形状不对,函数会抛出异常。本页的每个 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:

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

以 dB 表示的功率谱:

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

6.9.5. np.vectorize

普通的 Python 函数可以通过 vectorize() 提升为类似 ufunc 形式的函数。所得的可调用对象接受标量、可迭代对象或 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])

默认情况下结果的 dtype 为 floatotypes= 可以覆盖它:

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% 的适度加速,而非真正通用函数的 30 倍。当同一个名字下的某个函数必须同时作用于标量、列表数组时,它才是正确的工具——而当目标是追求原始速度时则不然。

上面列出的每个通用函数的完整调用签名,请参阅 numpy --- 兼容 numpy 的数组运算