ufunc函数
无灯可看。雨水从教正月半。探茧推盘。探得千秋字字看。
铜驼故老。说著宣和似天宝。五百年前。曾向杭州看上元。
ufunc是universal function的缩写,他是一种对数组的每个元素进行运算的函数。NumPy的内置许多函数都是用C语言实现的因此,他们的计算速度十分的快。
>>> x = np.linspace(0,2*np.pi,10) >>> x array([ 0. , 0.6981317 , 1.3962634 , 2.0943951 , 2.7925268 , 3.4906585 , 4.1887902 , 4.88692191, 5.58505361, 6.28318531]) >>> y = np.sin(x) >>> y array([ 0.00000000e+00, 6.42787610e-01, 9.84807753e-01, 8.66025404e-01, 3.42020143e-01, -3.42020143e-01, -8.66025404e-01, -9.84807753e-01, -6.42787610e-01, -2.44929360e-16])
可以查看函数的使用方法,得到使用方法。
下面我们比较一下np.sin()和math.sin()的时间复杂度。
#coding:utf-8 import math import datetime import numpy as np x = np.array([i* 0.001 for i in range(3000000)]) def sin_math(x): beg = datetime.datetime.now() for i,t in enumerate(x): # 返回下标和元素。 x[i] = math.sin(t) end = datetime.datetime.now() return end-beg def sin_np(x): beg = datetime.datetime.now() np.sin(x,x) end = datetime.datetime.now() return end-beg print(sin_math(x),sin_np(x))
最后得到的结构大概 np.sin()的速度比math.sin()快5倍。这得益于np.sin()的C语言级别的计算。
实际上标准的Python中可以用列表推导式的方法得到比for循环更快的计算效果。x = [math.sin(t) for t in x] ,但是列表推导式将产生一个新的列表,而不是修改原来的列表,相当于用 空间换了时间。
四则运算
NumPy提供了许多的ufunc函数,例如计算两个数组之和的add()函数。
>>> a+b array([ 5, 7, 9, 11]) >>> a = np.arange(0,4) >>> b = np.arange(5,9) >>> a+b array([ 5, 7, 9, 11]) >>> np.add(a,b) array([ 5, 7, 9, 11])
add()将返回一个数组,这个同样可以指定out参数,如果没有指定则创建一个新的数组来保存计算结果。
>>> np.add(a,b,a) array([ 5, 7, 9, 11]) >>> a array([ 5, 7, 9, 11])
NumPy位数组定义了各种数学运算操作符,因此计算两个数组想家可以简单的写成a+b而,而np.add(a,b,a)则可以用a += b来表示。下标列出了数组的运算符,以及与之对应的ufunc
数组对象支持操作符,极大地简化了算式的编写,但是需要注意,如果算式很复杂,要产生大量的中间结果这样就会降低程序的运算速度,例如堆a,b,c,三个数组采用算法进行 x = a * b + c 它相当于
t = a * b x = t + c del t
我们可以通过分解算式来将上述的一句话,变成两句话,且减少一次内存分配。
x = a * b
x += c
使用比较运算符堆两个数组进行运算的时候,将返回一个Bool 数组, 他的每个元素值都是两个数组对应元素的比较结果 eg:
>>> np.array([1,2,3,4]) < np.array([4,3,2,1])
array([ True, True, False, False], dtype=bool)
每个比较运算符都有一个ufunc 函数对应, 表2-2是比较运算符与ufunc 函数的对照表。
因为Python中的布尔运算使用,and,or和not等关键字,他们无法被重载,因此数组的布尔运算只能通过相应的ufunc函数进行,这些函数都以logical_开头,可以通过自动补齐来找到相应函数。
>>> a = np.arange(5) >>> a array([0, 1, 2, 3, 4]) >>> b = np.arange(4,-1,-1) >>> b array([4, 3, 2, 1, 0]) >>> print (a == b) [False False True False False] >>> print(a>b) [False False False True True] >>> print(np.logical_and(a,b)) [False True True True False] >>> print(a == b,a>b) [False False True False False] [False False False True True]
自定义ufunc函数
通过NumPy提供的标准ufunc函数,可以组合出复杂的表达式,在C语言级别堆数组的每个元素进行计算。但是这种表达式不易编写,而对元素进行计算的程序却很容易用Python实现,这时候可以通过frompyfunc()将计算单个元素的函数转换成ufunc函数,这样就可以很方便的用所产生的ufunc函数对数组进行计算了。
例如我们可以通过一个分段函数描述三角波,三角波的形状如图所示,它分为三段:上升段,下降段和平坦段。
根据图2-5,我们可以很容易的写出计算三角波上某点的Y坐标函数,显然triangle_wave()只能计算单个数值,不能直接堆数组进行处理。
import numpy as np import matplotlib.pyplot as plt def triangle_wave(x,c,c0,hc): x = x - int(x) #三角波的周期位1,因此只取x坐标的小数部分进行计算。 if x > c: r = 0.0 elif x < c0: r = x / c0 * hc else: r = (c - x)/(c - c0) * hc return r x = np.linspace(0,2,1000) y1 = np.array([triangle_wave(t,0.6,0.4,1.0) for t in x]) plt.plot(x,y1) plt.show()
通过frompyfunc()可以将计算单个值的函数转换为能对数组的每个元素进行计算的ufunc函数。frompyfunc的调用格式为:
frompyfunc(func,nin,nout)
其中func是计算单个元素的函数,nin是func的输入参数的个数,nout是func的返回值的个数。下面的程序使用frompyfunc()将triangle_wave()转换为ufunc函数对象triangle_ufunc1:
# -*- coding: utf-8 -*- """ Created on Tue Mar 14 13:24:50 2017 @author: x-power """ import numpy as np import matplotlib.pyplot as plt def triangle_wave(x,c,c0,hc): x = x - int(x) #三角波的周期位1,因此只取x坐标的小数部分进行计算。 if x > c: r = 0.0 elif x < c0: r = x / c0 * hc else: r = (c - x)/(c - c0) * hc return r x = np.linspace(0,2,1000) y1 = np.array([triangle_wave(t,0.6,0.4,1.0) for t in x]) triangle_ufunc = np.frompyfunc(triangle_wave,4,1) y2 = triangle_ufunc(x,0.6,0.3,1) print("y1的类型是:" + str(y1.dtype)) print("y2的类型是:" + str(y2.dtype)) y2 = y2.astype(np.float) print("转换之后的y2类型是:" + str(y2.dtype)) print("如果使用np.frompyfunc的话,返回值的类型是object很尴尬,如果需要方便的使用的话,需要转换类型。") #plt.plot(x,y1) #plt.plot(x,y2) # #plt.show()
广播
当使用ufunc函数堆两个数组进行计算的时候,ufunc函数会对这两个数组的对应元素进行计算,因此其要求两个数组的shape相同,不同的话会进行广播处理( broadcasting )处理:
1)让所有的输入数组都向其中维数最多的数组看齐,shape属性中不足的部分都通过向前面加1 的当时补齐。
2)输出的数组的shape属性是输入数组的shape属性的各个轴上的最大值。
3)如果输入数组的某个轴长度为 1 时,沿着此轴运算时都用此轴上的第一组值。
talk is cheap , show me your code !
import numpy as np import matplotlib.pyplot as plt a = np.arange(0,60,10).reshape(-1,1) b = np.arange(0,5) c = a + b print("a的形状是:" + str(a.shape)) print(a) print("b的形状是:" + str(b.shape)) print(a) print("c的形状是:" + str(c.shape)) print(c)
a的形状是:(6, 1) [[ 0] [10] [20] [30] [40] [50]] b的形状是:(5,) [[ 0] [10] [20] [30] [40] [50]] c的形状是:(6, 5) [[ 0 1 2 3 4] [10 11 12 13 14] [20 21 22 23 24] [30 31 32 33 34] [40 41 42 43 44] [50 51 52 53 54]]
由于a和b的维数不同,根据规则1),需要让b的shape属性向a 对齐,于是在b的 shape属性前 加 1,补齐位(1,5).相当于做了如下计算:
b.shape = 1,5
这样加法运算的两个输入数组的shape属性分别为(6,1)和(1,5),根据规则2)输出数组的各个轴的长度位输入数组的各个轴长度的最大值,可知输出数组的shape属性为(6,5)。
由于b的第0轴长度为1,而a的第0轴长度为6,为了让他们在第0轴上能够相加,需要将b的第0轴的长度扩展为6,这相当于:
b Out[16]: array([[0, 1, 2, 3, 4]]) b = b.repeat(6,0) b Out[18]: array([[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]])
这里的repeat()方法沿着axis参数指定的轴复制数组中的各个元素的值。由于a的第 1 轴的长度为 1 而b的第1轴的长度为5,为了能够让他们相加,需要将a的第一轴的长度扩展为5 这相当于:
a Out[19]: array([[ 0], [10], [20], [30], [40], [50]]) a = a.repeat(5,1) a Out[21]: array([[ 0, 0, 0, 0, 0], [10, 10, 10, 10, 10], [20, 20, 20, 20, 20], [30, 30, 30, 30, 30], [40, 40, 40, 40, 40], [50, 50, 50, 50, 50]])
经过上述处理就可以让 a 和 b进行相加计算了。可是这样不是很浪费内存么?由于这种广播计算使用频率比较高,所以一定是要有解决办法的。因此NumPy提供了ogrid对象,用于创建广播运算用的数组。
x,y = np.ogrid[:5,:5] print(x) print(y)
[[0] [1] [2] [3] [4]] [[0 1 2 3 4]]
此外,NumPy还提供了mgrid对象,它的用法和ogrid对象类似,但是它所返回的是进行广播之后的数组:
x,y = np.mgrid[:5,:5] print(x) print(y)
[[0 0 0 0 0] [1 1 1 1 1] [2 2 2 2 2] [3 3 3 3 3] [4 4 4 4 4]] [[0 1 2 3 4] [0 1 2 3 4] [0 1 2 3 4] [0 1 2 3 4] [0 1 2 3 4]]
ogrid是一个很有趣的对象,它像是多维数组一样,用切片元祖作为下标,返回的是一组可以用来广播计算的数组,其切片下标有两种形式:
- 开始值:结束值:步长,和np.arange(开始,结束,步长)类似
- 开始值:结束值:长度j,当第三个参数为虚数的时候,它表示所返回数组的长度和np.linspace(开始值,结束值,长度)类似。
x = np.arange(0,1,0.1) y = np.linspace(0,1,10) print(x) print(y)
x,y = np.ogrid[:1:4j,:1:3j] print(x) print(y)
x,y = np.ogrid[-2:2:20j,-2:2:20j]
z = x + np.exp(-x**2 - y**2)
下图则位使用ogrid()函数,计算二元函数在等间隔网格上的值,下面是绘制三维曲面(x,y)= xex^2-y^2的图形。
为了充分利用ufunc的广播功能,我们经常需要调整数组的形状,因此数组支持特殊的下标对象None,它表示在None对应的位置创建一个长度为1的新轴。例如堆一维数组a,a[None,:]和a.reshape(1,-1)等效,而a[:,None]和a.shape(-1,1)等效:
a = np.arange(4) print(a[:,None]) print("\n\n\n") print(a[None,:])
runfile('/home/x-power/untitled0.py', wdir='/home/x-power') [[0] [1] [2] [3]] [[0 1 2 3]]
下面的例子以None作为下标,实现广播运算。
x = np.array([0,1,4,10]) y = np.array([2,3,8]) print(x[None,:]) print(y[:,None]) print(x[None,:]+y[:,None])
[[ 0 1 4 10]] [[2] [3] [8]] [[ 2 3 6 12] [ 3 4 7 13] [ 8 9 12 18]]
真正的英雄都是在最后力挽狂澜的,这句话说的真他么的对,狗日的。
x = np.array([0,1,4,10]) y = np.array([2,3,8]) gy,gx = np.ix_(y,x) print(gy + gx)
[[ 2 3 6 12] [ 3 4 7 13] [ 8 9 12 18]]
在上面的例子中,通过ix_()将数组x和y转换成能进行广播运算的二维数组,注意数组y对应广播运算结果中的0轴,而数组x与第1轴相对应。