【Python】NumPy ufunc

2021-04-07 Views Python | ndarry | numpy | ufunc1733字8 min read

ufunc是universal function的缩写,这些函数能够作用于narray对象的每一个元素上,而不是针对narray对象操作,numpy提供了大量的ufunc的函数。这些函数在对narray进行运算的速度比使用循环或者列表推导式要快很多,但请注意,在对单个数值进行运算时,python提供的运算要比numpy效率高。

广播机制

广播(Broadcasting)是指不同形状的数组之间执行算术运算的方式,需要遵循4个原则:

  1. 让所有输入数组都向其中shape最长的数组看齐,shape中不足的部分都通过在前面加1(指1个维度)补齐;
  2. 输出数组的shape是输入数组shape的各个轴上的最大值;
  3. 如果各个输入数组的对应轴的长度相同或者其长度为1时,这样的数组之间能够用来计算,否则会出错;
  4. 当输入数组的某个轴的长度为1时,沿着此轴运算时,使用此轴上的第一组值。

注意: 当使用 ufunc 函数进行数组计算时, ufunc 函数会对两个数组的对应元素进行计算。进行这种计算的前提是两个数组的 shape 一致。若两个数组的 shape 不一致,则 NumPy 会实行广播机制。

一维ndarray广播

import numpy as np
arr1 = np.array([[0,0,0],[1,1,1],[2,2,2],[3,3,3]])
print(arr1)

arr2 = np.array([1,2,3])
print(arr2)

print(arr1 + arr2)
一维ndarray广播
计算两个ndarry的和的过程

二维ndarray广播

arr3 = np.arange(1,5).reshape(4,1)
print(arr3)

print(arr1+arr3)
二维ndarray广播

注意: 如果一维数组元素的数量与二维数组列的数量匹配,则将二维数组乘以一维数组将导致广播。
A two dimensional array multiplied by a one dimensional array results in broadcasting if number of 1-d array elements matches the number of 2-d array columns.

常用ufunc运算

注意: 对于计算单个元素,则建议采用Math库里对应的函数,np.sin( )对应math.sin( ),因为对于np.sin( )为了实现数组计算,底层做了很多复杂的处理,因此对于单个元素,math库里的函数计算速度快得多;而对于数组元素,则采用numpy库里的函数计算。
在读取计算结果时,通过下标获取数组元素的类型为Numpy中定义的类型,将其转换为python的标准类型需耗时间。针对此问题,可以采用数组提供的item( )方法,用来获取数组中单个元素,并直接返回标准的python数据类型,如:

>>> a = np.arange(6.0).reshape(2,3)
>>> a.item(1,2)  #与a[1,2]类似
5.0
>>> type(a.item(1,2))
<class 'float'>
>>> type(a[1,2])
<class 'numpy.float64'>

四则运算

四则运算
numpy提供的四则运算ufunc能够大大的提高计算效率,但如果运算式复杂,且参与运算的narray过大,会产生大量的中间结果,从而降低计算效率。例如:计算x=a*b+c时,实际上会按照如下方式计算:

t = a*b
x = t+c
del t

这会产生两次内存分配,一次时x,一次时t,所以按照

x = a*b
x = x+c

会节省一次内存的分配,从而提高效率。

比较和布尔运算

使用“==”、“>”等比较运算符比较两个数组,将返回一个布尔数组,它的每个元素的值是两个数组对应元素比较的结果,如:

>>> np.array([1,2,3])==np.array([3,2,1])
array([False,  True, False], dtype=bool)
>>> np.array([1,2,3])<np.array([3,2,1])
array([ True, False, False], dtype=bool)
比较和布尔运算

注意:
对两个bool数组进行逻辑运算时,将发生ValueError异常,因为bool值也是True和False,numpy无法确定运算目的,可以使用numpy.any()和numpy.all()函数,他们的使用方法和python的any()、all()函数用法相同。以bitwise_开头的函数时用于位运算,如(bitwise_and、bitwise_or)等,也可以使用& | ~ ^ 来进行运算。
除了numpy提供的内置ufunc函数,用户也可以编写自定义的ufunc函数,方式是:

  1. 编写对单个数值计算的目的函数;
  2. 利用np.frompyfunc(func, nin, nout)将其转换为ufunc函数,其中func是上面编写的目的函数,nin是输入的参数个数,nout是返回值的个数。
## 基本形式
u_func = np.frompyfunc(func,nin,nout)
ret = u_func(narray_obj,param1,param2..)
1
2
3

这里返回的ret是object类型,所以实际上需要用astype()转换为目的类型。numpy.vectorize()也实现了和numpy.frompyfunc()一样的功能,区别是前者可以t通过otypes指定返回值的类型,不用再用astype()进行转换。

## 基本形式
u_func = np.frompyfunc(func,otypes=[dtype1,dtype2..]
ret = u_func(narray_object,param1,param2..)

自定义ufunc函数

通过Numpy提供的标准ufunc函数可以满足大多要求,但有些特殊情况需要自定义函数来实现。这时,可以采用python来实现,然后使用frompyfunc( )函数将一个计算单个元素的函数转换为ufunc函数。例如,用一个分段函数来描述三角波,它的样子如图所示:
三角波
Python函数定义如下:

ef triangle_wave(x,c,c0,hc):
    x = x - int(x)   #周期为1,取小数部分计算
    if x>=c:
        r = 0.0
    elif x<c0:
        r = x/c0*hc
    else:
        r = (c-x)/(c-c0)*hc
    return r
1.通过列表推导计算

先使用列表推导计算一个列表,然后用array( )转换为数组,这种方法每次都要使用列表推导,而且对于多维数组,比较复杂,计算如下:

x = np.linspace(0,2,1000)
y1 = np.array([triangle_wave(t,0.6,0.4,1.0) for t in x])
2. fromnpyfunc( )函数计算

通过frompyfunc( )可以将计算单个值的函数转换为一个能对数组中每个元素计算的ufunc函数。frompyfunc( )的调用格式为:

frompyfunc(func,nin,nout)

其中,ufunc是计算单个元素的函数,nin是输入参数的个数,nout是func返回值的个数。如:

triangle_wave_ufunc = np.frompyfunc(triangle_wave,4,1)
y2 = triangle_wave_ufunc(x,0.6,0.4,1.0)

注意: triangle_wave_ufunc( )所返回数组的元素类型是object,因此还需要调用数组的astype()方法将其转换为双精度浮点数组

3. vectorize( )函数计算

使用vectorize( )可以实现和frompyfunc( )类似的功能,但他可以通过otypes参数指定返回数组的元素类型。otypes参数可以是一个表示元素类型的字符串,也可以是一个类型列表,使用列表可以描述多个返回数组的元素类型,如将上面的代码改成vectorize( ),则为:

triangle_wave_vec = np.vectorize(triangle_wave, otypes[np.float])
y3 = triangle_wave_vec(x,0.6,0.4,1.0)

参考资料

EOF