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 提供了嵌入式应用最常用到的数学函数:
三角函数 ——
sin()、cos()、tan()、asin()、acos()、atan()、arctan2()、sinh()、cosh()、tanh()、asinh()、acosh()、atanh()、sinc()。
每个函数都在一次库调用中处理整个数组。相比逐个元素调用 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 为 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% 的适度加速,而非真正通用函数的 30 倍。当同一个名字下的某个函数必须同时作用于标量、列表和数组时,它才是正确的工具——而当目标是追求原始速度时则不然。
上面列出的每个通用函数的完整调用签名,请参阅 numpy --- 兼容 numpy 的数组运算。