Numpy数组的计算:通用函数
Numpy数组的计算有时非常快,有时也非常慢,使Numpy变快的关键是利用向量化的操作,通常在Numpy的通用函数中实现,提高数组元素的重复计算的效率
缓慢的循环
Pythom的默认实现(被称为Cpython)处理某种操作时非常慢,一部分原因是该语言的动态性和解释性-数据类型的灵活特性决定了序列操作不能像C语言和Fortan语言一样被编译成有效的机器码
Python的相对缓慢通常出现在很多小操作需要不断重复的时候,比如对数组的每个元素做循环操作时。
事实上处理的瓶颈并不是运算本身,而是Cpython在每次循环时都必须做数据类型的检查和函数的调度,并没有静态语言运行前编译这样更有效率
通用函数介绍
Numpy为很多类型的操作提供了非常方便的,静态类型的,可编译程序的接口。也被称作向量操作,可以通过简单地对数组进行操作,这样会用于数组的每一个元素
Numpy的向量操作是通过通用函数实现的,通用函数的主要目的是对Numpy数组中的值执行更快的重复操作
1 np.arange(5) / np.arange(1, 6) 2 Out[85]: array([0. , 0.5 , 0.66666667, 0.75 , 0.8 ])
不仅限于一维数组,可以进行多维数组的计算
1 x = np.arange(9).reshape((3,3)) 2 3 2 ** x 4 Out[87]: 5 array([[ 1, 2, 4], 6 [ 8, 16, 32], 7 [ 64, 128, 256]], dtype=int32)
通过通用函数用向量的方式进行计算几乎总比用Python循环实现的计算更有效率,尤其是数组很大时。
Numpy的通用函数
通用函数有两种存在形式:一元通用函数对单个输入操作,二元通用函数对两个输入操作
1.数组的计算
1 x = np.arange(4) 2 3 print("x =", x) 4 x = [0 1 2 3] 5 6 print("x + 5", x + 5) 7 x + 5 [5 6 7 8] 8 9 print("x - 5", x - 5) 10 x - 5 [-5 -4 -3 -2] 11 12 print("x * 2", x * 2) 13 x * 2 [0 2 4 6] 14 15 print("x / 2", x / 2) 16 x / 2 [0. 0.5 1. 1.5] 17 18 print("x // 2", x // 2) 19 x // 2 [0 0 1 1] 20 21 print("-x =", -x) 22 -x = [ 0 -1 -2 -3] 23 24 print("x **2", x ** 2) 25 x **2 [0 1 4 9] 26 27 print("x % x", x % 2) 28 x % x [0 1 0 1]
可以任意将这些运算符组合使用,当然需要考虑优先级
1 - (0.5 * x + 1) ** 2 2 Out[98]: array([-1. , -2.25, -4. , -6.25])
所有这些算数运算符都是Numpy内置函数的简单封装器,例如+运算符就是add函数的封装器:
1 np.add(x, 2) 2 Out[99]: array([2, 3, 4, 5])
Numpy实现的算术运算符
1 运算符 对应的通用函数 2 + np.add 3 - no.subtract 4 - np.negative 5 / np.divide 6 // np.floor_divide 7 ** np.power 8 % np.mod
2.绝对值
1 x = np.array([-2, -1, 0, 1, 2]) 2 3 abs(x) 4 Out[101]: array([2, 1, 0, 1, 2]) 5 6 x 7 Out[102]: array([-2, -1, 0, 1, 2])
对应的Numpy通用函数是np.absolute,该函数也可以用别名np.abs来访问:
1 np.absolute(x) 2 Out[103]: array([2, 1, 0, 1, 2]) 3 4 np.abs(x) 5 Out[104]: array([2, 1, 0, 1, 2])
这个通用函数也可以处理复数,当处理复数时,绝对值返回的是该复数的模
1 x = np.array([3 - 4j, 4 - 3j, 2 + 0j, 0 + 1j]) 2 3 np.abs(x) 4 Out[106]: array([5., 5., 2., 1.])
3.三角函数
首先定义一个角度数组:
1 theta = np.linspace(0, np.pi, 3)
对这些值进行三角函数运算:
1 print("theta =", theta) 2 theta = [0. 1.57079633 3.14159265] 3 4 print("sin(theta) =", np.sin(theta)) 5 sin(theta) = [0.0000000e+00 1.0000000e+00 1.2246468e-16] 6 7 print("cos(theta) =", np.cos(theta)) 8 cos(theta) = [ 1.000000e+00 6.123234e-17 -1.000000e+00] 9 10 print("tan(theta) =", np.tan(theta)) 11 tan(theta) = [ 0.00000000e+00 1.63312394e+16 -1.22464680e-16]
逆三角函数同样可以使用:
1 x = [-1, 0, 1] 2 3 print("x =", x) 4 x = [-1, 0, 1] 5 6 print("arcsin(x) =", np.arcsin(x)) 7 arcsin(x) = [-1.57079633 0. 1.57079633] 8 9 print("arccos(x) =", np.arccos(x)) 10 arccos(x) = [3.14159265 1.57079633 0. ] 11 12 print("arctan(x) =", np.arctan(x)) 13 arctan(x) = [-0.78539816 0. 0.78539816]
4.指数和对数
Numpy中另一个常用的运算通用函数是指数运算
1 x = [1, 2, 3] 2 3 print("x =", x) 4 x = [1, 2, 3] 5 6 print("e^2 =", np.exp(x)) 7 e^2 = [ 2.71828183 7.3890561 20.08553692] 8 9 print("2^x =", np.exp2(x)) 10 2^x = [2. 4. 8.] 11 12 print("3^x =", np.power(3, x)) 13 3^x = [ 3 9 27]
指数运算的逆运算,即取对数也是可用的
最基本的np.log给出的是以自然常数(e)为底数的对数
1 x = [1, 2, 4, 10] 2 3 print("x =", x) 4 x = [1, 2, 4, 10] 5 6 print("ln(x) =", np.log(x)) 7 ln(x) = [0. 0.69314718 1.38629436 2.30258509] 8 9 print("log2(x) =", np.log2(x)) 10 log2(x) = [0. 1. 2. 3.32192809] 11 12 print("log10(x) =", np.log10(x)) 13 log10(x) = [0. 0.30103 0.60205999 1. ]
还有一些特殊版本,对非常小的输入值可以保持较好的精度:
1 x = [0, 0.001, 0.01, 0.1] 2 3 print("exp(x) - 1 =", np.expm1(x)) 4 exp(x) - 1 = [0. 0.0010005 0.01005017 0.10517092] 5 6 print("log(1 + x) =", np.log1p(x)) 7 log(1 + x) = [0. 0.0009995 0.00995033 0.09531018]
当x的值很小时,以上函数给出的值比np.log和np.exp的计算更精确
5.专用的通用函数
介绍一个更加专用,也更加晦涩的通用函数优异来源是子模块scipy.special,scipy.special中包含了很多的计算函数(包含统计学用到的函数)
可以在官网看到,搜索"gamma function python"
高级的通用函数特性
1. 指定输出
在进行大量运算时,有时候需要指定一个用于存放运算结果的数组是有用的,不同于创建临时数组,可以使用这个特性将计算结果直接写入期望的存储位置
所有的通用函数都可以通过out参数来指定计算结果的存放位置
1 x = np.arange(5) 2 3 y = np.empty(5) 4 5 np.multiply(x, 10, out=y) 6 Out[138]: array([ 0., 10., 20., 30., 40.]) 7 8 print(y) 9 [ 0. 10. 20. 30. 40.]
这个特性也可以被用作数组视图,例如将计算结果写入指定数组的每隔一个元素位置:
1 y = np.zeros(10) 2 3 np.power(2, x, out=y[::2]) 4 Out[141]: array([ 1., 2., 4., 8., 16.]) 5 6 print(y) 7 [ 1. 0. 2. 0. 4. 0. 8. 0. 16. 0.]
如果这里写的是y[::2] = 2 ** x,那么结果将是创建一个临时数组,该数组存放的是 2 ** x的结果,并且接下来会将这些值复制到y数组中,对于比较小的计算量来说,两种方式差别不大,对于较大数组,通过慎重使用out参数将能够有效节约内存
2.聚合
二元通用函数存在聚合功能,这些聚合可以直接在对象上计算。
例如,使用reduce方法作用一个数组,一个reduce方法会对给定的元素和操作重复执行,直到得到单个结果
举例:
对add通用函数调用reduce方法会返回数组中所有元素的和
1 x = np.arange(1, 6) 2 3 np.add.reduce(x) 4 Out[145]: 15
对multiply通用函数调用reduce方法会返回数组中所有元素的乘积
1 np.multiply.reduce(x) 2 Out[146]: 120
需要存储每次计算的中间结果,可以使用accumulate
1 np.add.accumulate(x) 2 Out[147]: array([ 1, 3, 6, 10, 15], dtype=int32) 3 4 np.multiply.accumulate(x) 5 Out[148]: array([ 1, 2, 6, 24, 120], dtype=int32)
还有其他专用函数(np.sum,np.prod,np.cumsum....),也可以实现以上的reduce功能
3.外积
任何通用函数都可以使用outer方法获得两个不同输入数组所有元素对的函数计算结果
意味着可以使用一行代码实现乘法表:
1 x = np.arange(1, 6) 2 3 np.multiply.outer(x, x) 4 Out[150]: 5 array([[ 1, 2, 3, 4, 5], 6 [ 2, 4, 6, 8, 10], 7 [ 3, 6, 9, 12, 15], 8 [ 4, 8, 12, 16, 20], 9 [ 5, 10, 15, 20, 25]])
通用函数另一个非常有用的特性是它能操作不同大小和形状的数组,一组这样的操作被称为广播