前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >NumPy高效使用逻辑,11个角度理顺它!

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

作者头像
double
发布2019-11-18 21:26:29
1.1K0
发布2019-11-18 21:26:29
举报
文章被收录于专栏:算法channel算法channel

1 Numpy更高效

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

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

2 导入Numpy

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

代码语言:javascript
复制
from numpy import *

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

3 生成numpy数组

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

代码语言:javascript
复制
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中最主要的数据结构之一

代码语言:javascript
复制
In [6]: type(v),type(m)
Out[6]: (numpy.ndarray, numpy.ndarray)

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

代码语言:javascript
复制
In [7]: shape(v),shape(m)
Out[7]: ((4,), (2, 2))

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

代码语言:javascript
复制
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, 我们能看到一个数组内元素的类型:

代码语言:javascript
复制
In [9]: m.dtype
Out[9]: dtype('int32')

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

代码语言:javascript
复制
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赋值:

代码语言:javascript
复制
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更多取值:int, float, complex, bool, object, 我们还可以显示的定义数据位数的类型,如:int64, int16, float128, complex128

5 通过函数生成数组

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

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

代码语言:javascript
复制
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 函数:起始点,终点,分割份数;包括终点

代码语言:javascript
复制
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

代码语言:javascript
复制
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 函数,实际工作中也很有用,在这里我列举一个

代码语言:javascript
复制
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个点的两两间距离:

代码语言:javascript
复制
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)]]

特殊点的矩阵,对角阵:

代码语言:javascript
复制
In [22]: diag([1,2,3])
Out[22]:
array([[1, 0, 0],
       [0, 2, 0],
       [0, 0, 3]])

主对角线偏移1的矩阵:

代码语言:javascript
复制
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]])

零阵:

代码语言:javascript
复制
In [24]: zeros((3,3))
Out[24]:
array([[0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.]])

1阵:

代码语言:javascript
复制
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)的三维数组:

代码语言:javascript
复制
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,)的一维数组:

代码语言:javascript
复制
In [38]: random.randn(5)
Out[38]: array([ 0.51924157,  1.05994257, -0.492707  ,  0.87205736, -0.14581919])

7 主要属性

生成1个三维数组:

代码语言:javascript
复制
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  ]]])

每个元素的字节数:

代码语言:javascript
复制
In [28]: M.itemsize
Out[28]: 8

M的总字节数:

代码语言:javascript
复制
In [30]: M.nbytes
Out[30]: 192

M的维数:

代码语言:javascript
复制
In [29]: M.ndim
Out[29]: 3

8 索引数组

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

代码语言:javascript
复制
In [38]: M[1,1,2]
Out[38]: 0.7518069979719579

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

代码语言:javascript
复制
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 ]])

也可方便的切片:

代码语言:javascript
复制
In [47]: M[:,:2,1:3]
Out[47]:
array([[[1.        , 0.95927478],
        [1.        , 0.72604572]],

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

直接赋值:

代码语言:javascript
复制
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类型的数组:

代码语言:javascript
复制
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:

代码语言:javascript
复制
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 呢

代码语言:javascript
复制
In [52]: x>5

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

代码语言:javascript
复制
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 :

代码语言:javascript
复制
In [53]: where(x>5)
Out[53]: (array([11, 12, 13, 14, 15, 16, 17, 18, 19], dtype=int64),)

9 元素级操作

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

代码语言:javascript
复制
In [55]: v1
Out[55]: array([0, 1, 2, 3, 4])

v1+2:

代码语言:javascript
复制
In [57]: v1+2
Out[57]: array([2, 3, 4, 5, 6])

v1 * v1:

代码语言:javascript
复制
In [58]: v1 * v1
Out[58]: array([ 0,  1,  4,  9, 16])

10 矩阵运算

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

dot 操作:

代码语言:javascript
复制
# 数值[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类

代码语言:javascript
复制
In [81]: matrix(v1)*matrix(v2)
Out[81]: matrix([[28, 29]])

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

代码语言:javascript
复制
In [83]: matrix(v1).shape
Out[83]: (1, 5)

而在没有包装前:

代码语言:javascript
复制
In [84]: v1.shape
Out[84]: (5,)

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

代码语言:javascript
复制
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 统计变量

求平均值:

代码语言:javascript
复制
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.        ])

求标准差:

代码语言:javascript
复制
In [97]: v3.std()
Out[97]: 2.8588178511708016

求方差:

代码语言:javascript
复制
In [98]: v3.var()
Out[98]: 8.17283950617284

求最大值:

代码语言:javascript
复制
In [99]: v3.max(axis=1)
Out[99]: array([8, 8, 8])

求最小值:

代码语言:javascript
复制
In [106]: v3.min(axis=1)
Out[106]: array([5, 0, 1])

求和:

代码语言:javascript
复制
In [107]: v3.sum(axis=1)
Out[107]: array([19, 12, 12])

求累乘:

代码语言:javascript
复制
In [108]: v3.cumprod(axis=1)
Out[108]:
array([[  6,  30, 240],
       [  8,  32,   0],
       [  3,  24,  24]], dtype=int32)

求累和:

代码语言:javascript
复制
In [109]: v3.cumsum(axis=1)
Out[109]:
array([[ 6, 11, 19],
       [ 8, 12, 12],
       [ 3, 11, 12]], dtype=int32)

求迹:

代码语言:javascript
复制
In [111]: v3.trace()
Out[111]: 11

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

12 改变Shape

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

代码语言:javascript
复制
In [117]: v4 = v3.reshape((1,9))
In [118]: v4
Out[118]: array([[6, 5, 8, 8, 4, 0, 3, 8, 1]])

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

代码语言:javascript
复制
# 修改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]

代码语言:javascript
复制
In [128]: v5 = v3[:,:,newaxis]
In [129]: v5
Out[129]:
array([[[ 6],
        [10],
        [ 8]],

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

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

13 数组由小变大

NumPy中的函数 repeat, tile, vstack, hstack, 和concatenate 具备变换小数组为大数组的能力。

repeat复制元素

代码语言:javascript
复制
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 复制块:

代码语言:javascript
复制
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竖直方向合并数组:

代码语言:javascript
复制
In [18]: c = array([[-1,-2]])
Out[20]:
array([[ 1,  2],
       [ 3,  4],
       [-1, -2]])

hstack 水平方向合并数组:

代码语言:javascript
复制
b = array([[40],[12]])
hstack((a,b))
array([[ 1,  2, 40],
       [ 3,  4, 12]])

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

代码语言:javascript
复制
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平时经常使用的函数,希望对大家有用。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员郭震zhenguo 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 Numpy更高效
  • 2 导入Numpy
  • 3 生成numpy数组
  • 4 为什么要用numpy?
  • 5 通过函数生成数组
  • 6 随机函数
  • 7 主要属性
  • 8 索引数组
  • 9 元素级操作
  • 10 矩阵运算
  • 11 统计变量
  • 12 改变Shape
  • 13 数组由小变大
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档