2019-11-18 10:58:08

NumPy高效使用逻辑,11个角度理顺它!

29 / 0 / 0 / 0

作者: zhenguo 来源: Python与算法社区

1 Numpy更高效

使用Python的地方,就能看到Numpy,尤其是需要数值计算的地方,Numpy的高性能更是体现的淋漓尽致。

它基于Python,提供远高于Python的高性能向量、矩阵和更高维度的数据结构。之所以性能高是由于它在密集型计算任务中,向量化操作是用C和Fortran代码实现。

2 导入Numpy

只需要一行代码就能导入:

from numpy import *  

在numpy包中,描述向量,矩阵和更高维度的数据集使用的术语是array.

3 生成numpy数组

有许多方法能初始化一个新的numpy数组,例如:arangelinspace等,从文件中读入数据,从python的lists等都能生成新的向量和矩阵数组。例:

In [1]: from numpy import *  

In [2]: v = array([1,2,3,4])  

In [3]: v  
Out[3]: array([1, 2, 3, 4])  

In [4]: m = array([[1,2],[3,4]])  

In [5]: m  
Out[5]:  
array([[1, 2],  
       [3, 4]])  

v和m的类型都是ndarray,这是numpy中最主要的数据结构之一

In [6]: type(v),type(m)  
Out[6]: (numpy.ndarray, numpy.ndarray)  

v和m的不同仅仅是它们的形状(shape), 我们能通过ndarray.shape属性发现它们的形状信息,shape属性很有用,尤其在深度学习模型调试中:

In [7]: shape(v),shape(m)  
Out[7]: ((4,), (2, 2))  

numpy中获取元素个数通过size:

In [8]: size(v),size(m)  
Out[8]: (4, 4)  

4 为什么要用numpy?

到此,numpy.ndarray看起来非常像Python的list, 那我们为什么不用Python的list计算,干嘛非要创造一个新的数组(array)类型呢?

有多个原因:

  • Python的list是一个通用结构。Python的list能包括任意类型的对象,并且是动态类型, 它们不支持一些数学函数,比如矩阵的点乘,实现如此的函数对于Python的list而言,不会高效,因为它是动态类型

  • Numpy的array是静态类型和同质的,当array被创建时,元素的类型就确定

  • Numpy的array更节省内存

  • 由于是静态类型,一些数学函数实现起来会更快,例如array间的加减乘除能够用C和Fortran实现

使用ndarray.dtype, 我们能看到一个数组内元素的类型:

In [9]: m.dtype  
Out[9]: dtype('int32')  

如果我们尝试用str类型赋值给m,会报错:

In [10]: m[0,0]='hello'  
---------------------------------------------------------------------------  
ValueError                                Traceback (most recent call last)  
<ipython-input-10-8d5580112ac6> in <module>  
----> 1 m[0,0]='hello'  

ValueError: invalid literal for int() with base 10: 'hello'  

创建数组时,能指定类型,通过为dtype赋值:

In [11]: mc = array([[1,2,],[3,4]],dtype=complex)  

In [12]: mc  
Out[12]:  
array([[1.+0.j, 2.+0.j],  
       [3.+0.j, 4.+0.j]])  

dtype更多取值:intfloatcomplexboolobject, 我们还可以显示的定义数据位数的类型,如:int64int16float128complex128

5 通过函数生成数组

对于更大的数组,手动初始化数据是不现实的,比如使用python的list. 我们得用numpy提供的函数才能生成不同形式的数组。比如更通用的:

arange 函数:起始点,终点,步长;不包括终点

In [2]: x = arange(0,10,1)  
In [3]: x  
Out[3]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])  

In [4]: x = arange(-1, 1, 0.1)  
In [5]: x  
Out[5]:  
array([-1.00000000e+00, -9.00000000e-01, -8.00000000e-01, -7.00000000e-01,  
       -6.00000000e-01, -5.00000000e-01, -4.00000000e-01, -3.00000000e-01,  
       -2.00000000e-01, -1.00000000e-01, -2.22044605e-16,  1.00000000e-01,  
        2.00000000e-01,  3.00000000e-01,  4.00000000e-01,  5.00000000e-01,  
        6.00000000e-01,  7.00000000e-01,  8.00000000e-01,  9.00000000e-01])  

linspace 函数:起始点,终点,分割份数;包括终点

In [5]: linspace(0,10,5)  
Out[5]: array([ 0. ,  2.5,  5. ,  7.5, 10. ])  

logspace 函数:如下例子,各项分别为 e^1,e^2, e^3,…e^10

In [17]: logspace(1, 10, 10, base=e)  
Out[17]:  
array([2.71828183e+00, 7.38905610e+00, 2.00855369e+01, 5.45981500e+01,  
       1.48413159e+02, 4.03428793e+02, 1.09663316e+03, 2.98095799e+03,  
       8.10308393e+03, 2.20264658e+04])  

mgrid 函数,实际工作中也很有用,在这里我列举一个

In [18]: x,y = mgrid[0:5,0:5]  

In [19]: x  
Out[19]:  
array([[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]])  

In [20]: y  
Out[20]:  
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]])  

这是基本用法,完全看不出干啥。如果我有10个点,想要得出这10个点的两两间距离:

x,y = mgrid[0:5,0:5]  
In [28]: list(map(lambda xe,ye: [(ex,ey) for ex, ey in zip(xe, ye)], x,y))  
Out[28]:  
[[(0, 0), (0, 1), (0, 2), (0, 3), (0, 4)],  
 [(1, 0), (1, 1), (1, 2), (1, 3), (1, 4)],  
 [(2, 0), (2, 1), (2, 2), (2, 3), (2, 4)],  
 [(3, 0), (3, 1), (3, 2), (3, 3), (3, 4)],  
 [(4, 0), (4, 1), (4, 2), (4, 3), (4, 4)]]  

特殊点的矩阵,对角阵:

In [22]: diag([1,2,3])  
Out[22]:  
array([[1, 0, 0],  
       [0, 2, 0],  
       [0, 0, 3]])  

主对角线偏移1的矩阵:

In [23]: diag([1,2,3],k=1)  
Out[23]:  
array([[0, 1, 0, 0],  
       [0, 0, 2, 0],  
       [0, 0, 0, 3],  
       [0, 0, 0, 0]])  

零阵:

In [24]: zeros((3,3))  
Out[24]:  
array([[0., 0., 0.],  
       [0., 0., 0.],  
       [0., 0., 0.]])  

1阵:

In [25]: ones((3,3))  
Out[25]:  
array([[1., 1., 1.],  
       [1., 1., 1.],  
       [1., 1., 1.]])  

6 随机函数

Numpy提供的random使用起来更友好,如下:

获得取值(0,1)上的均匀分布,生成shape为(1,2,3)的三维数组:

In [36]: random.rand(1,2,3)  
Out[36]:  
array([[[0.22120906, 0.80388812, 0.52726328],  
        [0.37277153, 0.88388754, 0.76586961]]])  

满足均值为0,方差为1的高斯分布得到随机数据,生成shape为(5,)的一维数组:

In [38]: random.randn(5)  
Out[38]: array([ 0.51924157,  1.05994257, -0.492707  ,  0.87205736, -0.14581919])  

7 主要属性

生成1个三维数组:

In [32]: M = random.rand(2,3,4)  
In [33]: M  
Out[33]:  
array([[[0.92362312, 0.68582456, 0.95927478, 0.40126712],  
        [0.13534077, 0.42983011, 0.72604572, 0.64202846],  
        [0.96822191, 0.8332197 , 0.64065175, 0.8979606 ]],  

       [[0.62970371, 0.22183503, 0.58752818, 0.20008916],  
        [0.21533794, 0.20558915, 0.751807  , 0.07743367],  
        [0.09854002, 0.55408343, 0.8663579 , 0.150306  ]]])  

每个元素的字节数:

In [28]: M.itemsize  
Out[28]: 8  

M的总字节数:

In [30]: M.nbytes  
Out[30]: 192  

M的维数:

In [29]: M.ndim  
Out[29]: 3  

8 索引数组

索引数组的元素,可以使用方括号和下标,M是三维,下标索引的方法:

In [38]: M[1,1,2]  
Out[38]: 0.7518069979719579  

使用 表示全部此维度的所有元素都要获取:

In [41]: M[0,:,:]  
Out[41]:  
array([[0.92362312, 0.68582456, 0.95927478, 0.40126712],  
       [0.13534077, 0.42983011, 0.72604572, 0.64202846],  
       [0.96822191, 0.8332197 , 0.64065175, 0.8979606 ]])  

也可方便的切片:

In [47]: M[:,:2,1:3]  
Out[47]:  
array([[[1.        , 0.95927478],  
        [1.        , 0.72604572]],  

       [[0.22183503, 0.58752818],  
        [0.20558915, 0.751807  ]]])  

直接赋值:

In [43]: M[0,:,1]=1.0  
In [44]: M  
Out[44]:  
array([[[0.92362312, 1.        , 0.95927478, 0.40126712],  
        [0.13534077, 1.        , 0.72604572, 0.64202846],  
        [0.96822191, 1.        , 0.64065175, 0.8979606 ]],  

       [[0.62970371, 0.22183503, 0.58752818, 0.20008916],  
        [0.21533794, 0.20558915, 0.751807  , 0.07743367],  
        [0.09854002, 0.55408343, 0.8663579 , 0.150306  ]]])  

更有用的掩码索引,对应一个bool类型的数组:

In [49]: M[0,:,[False,True,True,False]]  
Out[49]:  
array([[1.        , 1.        , 1.        ],  
       [0.95927478, 0.72604572, 0.64065175]])  

M的维数:2*3*4,结果维度预期:1*3*2,但是实际结果维度:1*2*3

掩码索引,这一特性对于带条件的选取元素很重要。例如,使用arange生成一维数组x:

In [51]: x  
Out[51]:  
array([0. , 0.5, 1. , 1.5, 2. , 2.5, 3. , 3.5, 4. , 4.5, 5. , 5.5, 6. ,  
       6.5, 7. , 7.5, 8. , 8.5, 9. , 9.5])  

判断x 中哪些元素大于5 呢

In [52]: x>5  

结果返回的一堆:True, False:

Out[52]:  
array([False, False, False, False, False, False, False, False, False,  
       False, False,  True,  True,  True,  True,  True,  True,  True,  
        True,  True])  

然而有时候,我们想知道x 中哪些位置的元素大于5,此时要借助 where :

In [53]: where(x>5)  
Out[53]: (array([11, 12, 13, 14, 15, 16, 17, 18, 19], dtype=int64),)  

9 元素级操作

NumPy中两个数组加减乘除等,默认都是对应元素的操作:

In [55]: v1  
Out[55]: array([0, 1, 2, 3, 4])  

v1+2:

In [57]: v1+2  
Out[57]: array([2, 3, 4, 5, 6])  

v1 * v1:

In [58]: v1 * v1  
Out[58]: array([ 0,  1,  4,  9, 16])  

10 矩阵运算

线性代数中,矩阵的相乘操作在NumPy 中怎么实现,两种方法:dot函数 和 包装为matrix对象

dot 操作:

# 数值[1,10)内,生成shape为(5,2)的随机整数数组  
v2 = random.randint(1,10,(5,2))  
In [77]: v2  
Out[77]:  
array([[8, 6],  
       [9, 4],  
       [4, 6],  
       [1, 3],  
       [2, 1]])  
# v1:1行5列,v2:5行2列,结果1行2列  
In [78]: dot(v1,v2)  
Out[78]: array([28, 29])  

另一种方法包装为matrix类

In [81]: matrix(v1)*matrix(v2)  
Out[81]: matrix([[28, 29]])  

经过包装后,shape取值也会变得更符合线代中的常识:

In [83]: matrix(v1).shape  
Out[83]: (1, 5)  

而在没有包装前:

In [84]: v1.shape  
Out[84]: (5,)  

求矩阵的行列式,要求数组的最后两个维度相等,因此重新构造一个矩阵:

In [88]: v3 = random.randint(0,10,(3,3))  

In [89]: v3  
Out[89]:  
array([[6, 5, 8],  
       [8, 4, 0],  
       [3, 8, 1]])  

In [90]: v3m = matrix(v3)  

In [91]: v3m  
Out[91]:  
matrix([[6, 5, 8],  
        [8, 4, 0],  
        [3, 8, 1]])  

In [92]: linalg.det(v3m)  
Out[92]: 399.9999999999999  

11 统计变量

求平均值:

In [93]: v3  
Out[93]:  
array([[6, 5, 8],  
       [8, 4, 0],  
       [3, 8, 1]])  

In [94]: v3.mean()  
Out[94]: 4.777777777777778  
In [95]: v3.mean(axis=1)  
Out[95]: array([6.33333333, 4.        , 4.        ])  

求标准差:

In [97]: v3.std()  
Out[97]: 2.8588178511708016  

求方差:

In [98]: v3.var()  
Out[98]: 8.17283950617284  

求最大值:

In [99]: v3.max(axis=1)  
Out[99]: array([8, 8, 8])  

求最小值:

In [106]: v3.min(axis=1)  
Out[106]: array([5, 0, 1])  

求和:

In [107]: v3.sum(axis=1)  
Out[107]: array([19, 12, 12])  

求累乘:

In [108]: v3.cumprod(axis=1)  
Out[108]:  
array([[  6,  30, 240],  
       [  8,  32,   0],  
       [  3,  24,  24]], dtype=int32)  

求累和:

In [109]: v3.cumsum(axis=1)  
Out[109]:  
array([[ 6, 11, 19],  
       [ 8, 12, 12],  
       [ 3, 11, 12]], dtype=int32)  

求迹:

In [111]: v3.trace()  
Out[111]: 11  

In [112]: diag(v3).sum()  
Out[112]: 11  

12 改变Shape

NumPy数组的shape 被修改而无需复制原有数据,这使它更为高效。

In [117]: v4 = v3.reshape((1,9))  
In [118]: v4  
Out[118]: array([[6, 5, 8, 8, 4, 0, 3, 8, 1]])  

我们验证下v4 是否真的没有被复制:

# 修改v4的第一行第2列的元素5,为10  
In [120]: v4[0,1]=10  
In [121]: v4  
Out[121]: array([[ 6, 10,  8,  8,  4,  0,  3,  8,  1]])  
# 查看v3,发现对应元素也变为10  
In [122]: v3  
Out[122]:  
array([[ 6, 10,  8],  
       [ 8,  4,  0],  
       [ 3,  8,  1]])  

所以验证得出:v4仅仅是v3的视图,未发生复制。

NumPy 提供的flatten 函数也有改变shape 的能力,将高维数组变为向量。但是,值得注意的是它会发生数组复制行为,因此不是高效的

有时,我们需要增加维度,此时可使用newaxis,它会插入1个维度,如下在第三个维度插入,v5的shape变为: [3,3,1]

In [128]: v5 = v3[:,:,newaxis]  
In [129]: v5  
Out[129]:  
array([[[ 6],  
        [10],  
        [ 8]],  

       [[ 8],  
        [ 4],  
        [ 0]],  

       [[ 3],  
        [ 8],  
        [ 1]]])  

13 数组由小变大

NumPy中的函数 repeattilevstackhstack, 和concatenate 具备变换小数组为大数组的能力。

repeat复制元素

In [132]: a = array([[1,2],[3,4]])  

In [137]: repeat(a,2,axis=1)  
Out[137]:  
array([[1, 1, 2, 2],  
       [3, 3, 4, 4]])  

In [138]: repeat(a,2,axis=0)  
Out[138]:  
array([[1, 2],  
       [1, 2],  
       [3, 4],  
       [3, 4]])  

tile 复制块:

In [5]: tile(a,2)  
Out[5]:  
array([[1, 2, 1, 2],  
       [3, 4, 3, 4]])  

In [6]: tile(a,(2,3))  
Out[6]:  
array([[1, 2, 1, 2, 1, 2],  
       [3, 4, 3, 4, 3, 4],  
       [1, 2, 1, 2, 1, 2],  
       [3, 4, 3, 4, 3, 4]])  

vstack竖直方向合并数组:

In [18]: c = array([[-1,-2]])  
Out[20]:  
array([[ 1,  2],  
       [ 3,  4],  
       [-1, -2]])  

hstack 水平方向合并数组:

b = array([[40],[12]])  
hstack((a,b))  
array([[ 1,  2, 40],  
       [ 3,  4, 12]])  

concatenate指定在哪个轴向上合作数组:

In [26]: concatenate((a,c),axis=0) # 效果等于vstack  
Out[26]:  
array([[ 1,  2],  
       [ 3,  4],  
       [-1, -2]])  

In [27]: concatenate((a,b),axis=1) # 效果等于hstack  
Out[27]:  
array([[ 1,  2, 40],  
       [ 3,  4, 12]])  

以上全部手码,利用两个周的课余时间整理出来,从十几个角度汇总NumPy平时经常使用的函数,希望对大家有用。

PS: 如本文对您有疑惑,可加QQ:1752338621 进行讨论。

0 条评论

0
0
官方
微信
官方微信
Q Q
咨询
意见
反馈
返回
顶部