Python 全栈 400 之NumPy数值计算练习

240 NumPy 数值计算更高效的案列

Python 已经提供了很多丰富的内置包,我们为什么还要学习 NumPy 呢?

先看一个例子,找寻学习 NumPy 的必要性和重要性。

打开 IPython,创建 Python 的列表 a 对象。然后,使用列表生成式,创建一个元素都为原来两倍的新列表 a2,并统计这一行的用时为 95.7 ms .

In [76]: a = list(range(1000000))

In [77]: %time a2 = [i*2 for i in a]
Wall time: 95.7 ms

使用 NumPy,创建同样大小和取值的数组 na。然后,对每个元素乘以 2,返回一个新数组 na2,用时为 2 ms

In [78]: import numpy as np

In [79]: na = np.array(range(1000000))

In [80]: %time na2 = na * 2
Wall time: 2 ms

完成同样的都对元素乘以 2 的操作,NumPy 比 Python 快了 45 倍之多。

这就是我们要学好 NumPy 的一个重要理由,它在处理更大数据量时,处理效率明显快于 Python ;并且内置的向量化运算和广播机制,使得使用 NumPy 更加简洁,会少写很多嵌套的 for 循环,因此代码的可读性大大增强。

252 NumPy 计算为什么如此快?

有多个原因:

  • Python的 list 是一个通用结构。它能包括任意类型的对象,并且是动态类型。
  • NumPy 的 ndarray 是静态、同质的类型,当 ndarray 对象被创建时,元素的类型就确定。由于是静态类型,所以 ndarray 间的加、减、乘、除用 C 和 Fortran 实现才成为可能,所以运行起来就会更快。根据官当介绍,底层代码用 C 语言和 Fortran 语言实现,实现性能无限接近 C 的处理效率。

由此可见,NumPy 就非常适合做大规模的数值计算和数据分析。

今天,我们一起学习 NumPy 的基本使用,借助实际的 iris 数据集,使用例子帮助大家最快掌握 NumPy 那些最高频使用的函数。

241 创建 NumPy 数组五种常用方法

创建一个 ndarray 数组对象,有很多种方法。array 函数能创建新的数组;arange, linspace 等方法;从文件中读入数据返回一个 ndarray 对象;多个 ndarray 对象又能构造生成一个新的 ndarray 对象。

1) 通过构造函数 array 创建一维 array

import numpy as np

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

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

2) 创建二维 array

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)

3) arange 数组

In [94]: ara = np.arange(1,10)

In [95]: ara
Out[95]: array([1, 2, 3, 4, 5, 6, 7, 8, 9])

4) linspace 数组,15 个元素:

In [97]: np.linspace(1,10,15)
Out[97]:
array([ 1.        ,  1.64285714,  2.28571429,  2.92857143,  3.57142857,
        4.21428571,  4.85714286,  5.5       ,  6.14285714,  6.78571429,
        7.42857143,  8.07142857,  8.71428571,  9.35714286, 10.        ])

5) 组合 ndarray 对象:

如下创建一个 ndarray 对象 a

In [98]: a = np.arange(10).reshape(2,-1)

In [99]: a
Out[99]:
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])

找出 a 中大于 3 的元素索引,使用 where 方法,返回一个元组,带有 2 个 ndarray 对象,分别表示大于 3 的元素第一维、第二维度中的位置:

In [100]: np.where(a>3)
Out[100]:
(array([0, 1, 1, 1, 1, 1], dtype=int64),
 array([4, 0, 1, 2, 3, 4], dtype=int64))

where 方法返回值可读性不强,我们把它拼接为一个 ndarray 对象:

In [101]: np.array(np.where(a>3))
Out[101]:
array([[0, 1, 1, 1, 1, 1],
       [4, 0, 1, 2, 3, 4]], dtype=int64)

然后,再转置,使用 np.transpose 方法:

tuple_to_array = np.array(np.where(a>3))
np.transpose(tuple_to_array)

结果,这回一看就明白了,[0,4] 表示在原 ndarray 对象 a 上的索引:

Out[102]:
array([[0, 4],
       [1, 0],
       [1, 1],
       [1, 2],
       [1, 3],
       [1, 4]], dtype=int64)
242 NumPy 数组之shape,size,dtype

1) shape 属性

数组的形状信息,非常重要。在深度学习中,构建网络模型,调试多维数组运算代码时,shape 的作用更加凸显。

shape 属性返回数组的形状信息,是一个元组对象。

如下,分别创建一维数组 v ,二维数组 m

In [108]: v = np.zeros(10)

In [109]: m = np.ones((3,4))

打印它们的 shape 信息,(10,) 表示为一维数组,且第一维的长度为 10. 当元组只有一个元素时,为什么写成这样,我们在前面的 Python 基础部分、进阶部分都提到过。

In [110]: v.shape
Out[110]: (10,)

In [111]: m.shape
Out[111]: (3, 4)

size 属性获取数组内元素个数:

In [112]: m.size
Out[112]: 12

dtype 属性获取数组内元素的类型:

In [113]: m.dtype
Out[113]: dtype('float64')

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

In [10]: m[0,0]='hello'

ValueError: could not convert string to float: 'hello'

创建数组时,还可以通过为 dtype 赋值,指定元素类型:

In [117]: m = np.array([1,2,3],dtype='float')

In [118]: m
Out[118]: array([1., 2., 3.])

dtype 更多取值:int, complex, bool, object,还可以显示的定义数据位数的类型,如:int64, int16, float128, complex128

243 NumPy 之 arange 函数

起始点,终点,步长;不包括终点。

In [94]: ara = np.arange(1,10)

In [95]: ara
Out[95]: array([1, 2, 3, 4, 5, 6, 7, 8, 9])
244 NumPy 之 linspace 函数

起始点,终点,分割份数;包括终点。

In [5]: np.linspace(0,10,5)
Out[5]: array([ 0. ,  2.5,  5. ,  7.5, 10. ])
245 NumPy 之 logspace 函数

创建以 e 为底,指数为 1,2,…,10 的数组:

In [17]: np.logspace(1, 10, 10, base=np.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])
246 NumPy 之 创建对角数组

创建对角数组:

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

主对角线偏移 1 的数组:

In [23]: np.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]])
247 NumPy 之创建全零数组

创建元素全都为 0 的数组:

In [24]: np.zeros((3,3))
Out[24]:
array([[0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.]])
248 NumPy之创建全一数组

创建元素全都为 1 的数组:

In [25]: np.ones((3,3))
Out[25]:
array([[1., 1., 1.],
       [1., 1., 1.],
       [1., 1., 1.]])
249 NumPy 之创建随机数组

np.random 模块生成随机数组,更加方便。

生成 0~1 ,shape 为 (3,5) 的随机数数组:

In [2]: np.random.rand(3,5)
Out[2]:
array([[0.25366147, 0.18996607, 0.01599463, 0.08113353, 0.99794258],
       [0.38813147, 0.33669704, 0.97040282, 0.0836301 , 0.55533133],
       [0.17767781, 0.94982834, 0.52045864, 0.58504198, 0.40904079]])
250 NumPy 之索引案例

NumPy 索引,功能强大,不仅支持切片操作,还支持布尔型按条件筛选操作。

In [2]: m = np.arange(18).reshape(2,3,3)

In [3]: m
Out[3]:
array([[[ 0,  1,  2],
        [ 3,  4,  5],
        [ 6,  7,  8]],

       [[ 9, 10, 11],
        [12, 13, 14],
        [15, 16, 17]]])

: 表示此维度的所有元素全部获取:

In [4]: m[:,1:3,:]
Out[4]:
array([[[ 3,  4,  5],
        [ 6,  7,  8]],

       [[12, 13, 14],
        [15, 16, 17]]])
In [5]: m[:,1:3,:1]
Out[5]:
array([[[ 3],
        [ 6]],

       [[12],
        [15]]])

按照维度赋值:

In [7]: m[:,0,:] = -1

In [8]: m
Out[8]:
array([[[-1, -1, -1],
        [ 3,  4,  5],
        [ 6,  7,  8]],

       [[-1, -1, -1],
        [12, 13, 14],
        [15, 16, 17]]])
In [11]: m[:,:,0]
Out[11]:
array([[-1,  3,  6],
       [-1, 12, 15]])

NumPy 还支持掩码索引,用于元素筛选,非常方便。

判断上面切片 m[:,:,0] 中大于 5 的元素,写法简洁,无需写 for 循环。

In [12]: mt = m[:,:,0]

In [13]: mt[mt>5]
Out[13]: array([ 6, 12, 15])
251 NumPy之数据归一化案例

1) 下载数据

使用 NumPy,下载 iris 数据集。

仅提取 iris 数据集的第二列 usecols = [1]

import numpy as np

url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data'
wid = np.genfromtxt(url, delimiter=',', dtype='float', usecols=[1])

展示数据

array([3.5, 3. , 3.2, 3.1, 3.6, 3.9, 3.4, 3.4, 2.9, 3.1, 3.7, 3.4, 3. ,
       3. , 4. , 4.4, 3.9, 3.5, 3.8, 3.8, 3.4, 3.7, 3.6, 3.3, 3.4, 3. ,
       3.4, 3.5, 3.4, 3.2, 3.1, 3.4, 4.1, 4.2, 3.1, 3.2, 3.5, 3.1, 3. ,
       3.4, 3.5, 2.3, 3.2, 3.5, 3.8, 3. , 3.8, 3.2, 3.7, 3.3, 3.2, 3.2,
       3.1, 2.3, 2.8, 2.8, 3.3, 2.4, 2.9, 2.7, 2. , 3. , 2.2, 2.9, 2.9,
       3.1, 3. , 2.7, 2.2, 2.5, 3.2, 2.8, 2.5, 2.8, 2.9, 3. , 2.8, 3. ,
       2.9, 2.6, 2.4, 2.4, 2.7, 2.7, 3. , 3.4, 3.1, 2.3, 3. , 2.5, 2.6,
       3. , 2.6, 2.3, 2.7, 3. , 2.9, 2.9, 2.5, 2.8, 3.3, 2.7, 3. , 2.9,
       3. , 3. , 2.5, 2.9, 2.5, 3.6, 3.2, 2.7, 3. , 2.5, 2.8, 3.2, 3. ,
       3.8, 2.6, 2.2, 3.2, 2.8, 2.8, 2.7, 3.3, 3.2, 2.8, 3. , 2.8, 3. ,
       2.8, 3.8, 2.8, 2.8, 2.6, 3. , 3.4, 3.1, 3. , 3.1, 3.1, 3.1, 2.7,
       3.2, 3.3, 3. , 2.5, 3. , 3.4, 3. ])

单变量(univariate),长度为 150 的一维 NumPy 数组。

2) 归一化

求出最大值、最小值

smax = np.max(wid)
smin = np.min(wid)

In [51]: smax,smin
Out[51]: (4.4, 2.0)

`

归一化公式:

s = (wid - smin) / (smax - smin)

还有一个更简便的方法,使用 ptp 方法,它直接求出最大值与最小值的差

s = (wid - smin) / wid.ptp()

3) NumPy 的打印设置

只打印小数点后三位的设置方法:

np.set_printoptions(precision=3)  

归一化结果:

array([0.625, 0.417, 0.5  , 0.458, 0.667, 0.792, 0.583, 0.583, 0.375,
       0.458, 0.708, 0.583, 0.417, 0.417, 0.833, 1.   , 0.792, 0.625,
       0.75 , 0.75 , 0.583, 0.708, 0.667, 0.542, 0.583, 0.417, 0.583,
       0.625, 0.583, 0.5  , 0.458, 0.583, 0.875, 0.917, 0.458, 0.5  ,
       0.625, 0.458, 0.417, 0.583, 0.625, 0.125, 0.5  , 0.625, 0.75 ,
       0.417, 0.75 , 0.5  , 0.708, 0.542, 0.5  , 0.5  , 0.458, 0.125,
       0.333, 0.333, 0.542, 0.167, 0.375, 0.292, 0.   , 0.417, 0.083,
       0.375, 0.375, 0.458, 0.417, 0.292, 0.083, 0.208, 0.5  , 0.333,
       0.208, 0.333, 0.375, 0.417, 0.333, 0.417, 0.375, 0.25 , 0.167,
       0.167, 0.292, 0.292, 0.417, 0.583, 0.458, 0.125, 0.417, 0.208,
       0.25 , 0.417, 0.25 , 0.125, 0.292, 0.417, 0.375, 0.375, 0.208,
       0.333, 0.542, 0.292, 0.417, 0.375, 0.417, 0.417, 0.208, 0.375,
       0.208, 0.667, 0.5  , 0.292, 0.417, 0.208, 0.333, 0.5  , 0.417,
       0.75 , 0.25 , 0.083, 0.5  , 0.333, 0.333, 0.292, 0.542, 0.5  ,
       0.333, 0.417, 0.333, 0.417, 0.333, 0.75 , 0.333, 0.333, 0.25 ,
       0.417, 0.583, 0.458, 0.417, 0.458, 0.458, 0.458, 0.292, 0.5  ,
       0.542, 0.417, 0.208, 0.417, 0.583, 0.417])

分布可视化

import seaborn as sns
sns.distplot(s,kde=False,rug=True)

频率分布直方图:

sns.distplot(s,hist=True,kde=True,rug=True)

带高斯密度核函数的直方图:

252 创建一个 [3,5] 所有元素为 True 的数组
In [15]: np.ones((3,5),dtype=bool)
Out[15]:
array([[ True,  True,  True,  True,  True],
       [ True,  True,  True,  True,  True],
       [ True,  True,  True,  True,  True]])
253 一维数组转二维
In [18]: a =  np.linspace(1,5,10)

In [19]: a.reshape(5,2)
Out[19]:
array([[1.        , 1.44444444],
       [1.88888889, 2.33333333],
       [2.77777778, 3.22222222],
       [3.66666667, 4.11111111],
       [4.55555556, 5.        ]])
254 数组所有奇数替换为 -1
In [14]: m = np.arange(10).reshape(2,5)

In [16]: m[m%2==1] = -1
In [17]: m
Out[17]:
array([[ 0, -1,  2, -1,  4],
       [-1,  6, -1,  8, -1]])
255 提取出数组中所有奇数
In [18]: m = np.arange(10).reshape(2,5)

In [19]: m[m%2==1]
Out[19]: array([1, 3, 5, 7, 9])
256 求 2 个 NumPy 数组的交集
In [21]: m ,n = np.arange(10), np.arange(1,15,3)

In [22]: np.intersect1d(m,n)
Out[22]: array([1, 4, 7])
257 求 2 个 NumPy 数组的差集
In [21]: m ,n = np.arange(10), np.arange(1,15,3)

In [23]: np.setdiff1d(m,n)
Out[23]: array([0, 2, 3, 5, 6, 8, 9])
258 筛选出指定区间内的所有元素

注意: ( m >2 ),必须要添加一对括号

In [21]: m = np.arange(10).reshape(2,5)
In [34]: m[(m > 2) & (m < 7)]
Out[34]: array([3, 4, 5, 6])  
259 二维数组交换 2 列
In [21]: m = np.arange(10).reshape(2,5)
In [37]: m
Out[37]:
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])

In [36]: m[:,[1,0,2,3,4]]
Out[36]:
array([[1, 0, 2, 3, 4],
       [6, 5, 7, 8, 9]])

可以一次交换多列:

In [38]: m[:,[1,0,2,4,3]]
Out[38]:
array([[1, 0, 2, 4, 3],
       [6, 5, 7, 9, 8]])
260 二维数组反转行
In [39]: m = np.arange(10).reshape(2,5)

In [40]: m
Out[40]:
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])

In [41]: m[::-1]
Out[41]:
array([[5, 6, 7, 8, 9],
       [0, 1, 2, 3, 4]])
261 生成数值 5~10 ,shape 为 (3,5) 的随机浮点数:
In [9]: np.random.seed(100)

In [42]: np.random.randint(5,10,(3,5)) + np.random.rand(3,5)
Out[42]:
array([[9.31623868, 5.68431289, 9.5974916 , 5.85600452, 9.3478736 ],
       [5.66356114, 7.78257215, 7.81974462, 6.60320117, 7.17326763],
       [7.77318114, 6.81505713, 9.21447171, 5.08486345, 8.47547692]])
262 揭秘 Shape

一个一维数组,长度为 12,为什么能变化为二维 (12,1)(2,6)等,三维 (12,1,1)(2,3,2)等,四维 (12,1,1,1)(2,3,1,2)等。总之,能变化为任意多维度。

reshape 是如何做到的?使用了什么魔法数据结构和算法吗?

这篇文章对于 reshape 方法的原理解释,会很独到,尽可能让朋友们弄明白数组 reshape 的魔法。

如同往常一样,导入 NumPy 包:

import numpy as np

创建一个一维数组 a,从 0 开始,间隔为 2 ,含有 12 个元素的数组:

a = np.arange(0,24,2)

打印数组 a

In [48]: a
Out[48]: array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18, 20, 22])

如上数组 a, NumPy 会将其解读成两个结构,一个 buffer,还有一个 view

buffer 的示意图如下所示:

view 是解释 buffer 的一个结构,比如数据类型,flags信息等:

In [50]: a.dtype
Out[50]: dtype('int32')

In [51]: a.flags
Out[51]:
  C_CONTIGUOUS : True
  F_CONTIGUOUS : True
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False

使用 a[6] 访问数组 a 中 index 为 6 的元素。从背后实现看, NumPy 会辅助一个轴,轴的取值为 0 到 11 。

从概念上看,它的示意图如下所示:

所以,借助这个轴 ia[6] 就会被索引到元素 12,如下所示:

至此,大家要建立一个轴的概念。

接下来,做一次 reshape 变化,变化数组 a 的 shape 为 (2,6):

b = a.reshape(2,6)

打印 b:

In [53]: b
Out[53]:
array([[ 0,  2,  4,  6,  8, 10],
       [12, 14, 16, 18, 20, 22]])

此时,NumPy 会建立两个轴,假设为 i ,ji 的取值为 0 到 1,j 的取值为 0 到 5,示意图如下:

使用 b[1][2] 获取元素到 16

In [54]: b[1][2]
Out[54]: 16

两个轴的取值分为 1,2,如下图所示,定位到元素 16

平时,有些读者朋友可能会混淆两个 shape,(12,) 和 (12,1) ,其实前者一个轴,后者两个轴,示意图分别如下:

一个轴,取值从 0 到 11

两个轴,i 轴取值从 0 到 11, j 轴取值从 0 到 0

至此,大家要建立两个轴的概念。

并且,通过上面几幅图看到,无论 shape 如何变化,变化的是视图,底下的 buffer 始终未变。

接下来,上升到三个轴,变化数组 a 的 shape 为 (2,3,2)

c = a.reshape(2,3,2)

打印 c:

In [55]: c = a.reshape(2,3,2)

In [56]: c
Out[56]:
array([[[ 0,  2],
        [ 4,  6],
        [ 8, 10]],

       [[12, 14],
        [16, 18],
        [20, 22]]])

数组 c 有三个轴,取值分别为 0 到 1, 0 到 2, 0 到 1,示意图如下所示:

读者们注意体会,ijk 三个轴,其值的分布规律。如果去掉 i 轴取值为 1 的单元格后,

实际就对应到数组 c 的前半部分元素:

array([[[ 0,  2],
        [ 4,  6],
        [ 8, 10]],

也就是如下的索引组合 :

至此,三个轴的 reshape 已经讲完,再说一个有意思的问题。

还记得,原始的一维数组 a 吗?它一共有 12 个元素,后来,我们变化它为数组 c ,shape 为 (2,3,2),那么如何升级为 4 维或 任意维呢?

4 维可以为:(1,2,3,2),示意图如下:

看到,轴 i 索引取值只有 0,它被称为自由维度,可以任意插入到原数组的任意轴间。

比如,5 维可以为:(1,2,1,3,2):

至此,你应该完全理解 reshape 操作后的魔法:

  • buffer 是个一维数组,永远不变;
  • 变化的 shape 通过 view 传达;
  • 取值仅有 0 的轴为自由轴,它能变化出任意维度。

关于 reshape 操作,最后再说一点,reshape 后的数组,仅仅是原来数组的视图 view,并没有发生复制元素的行为,这样才能保证 reshape 操作更为高效。

In [58]: v1 = np.arange(10)

In [59]: v2 = v1.reshape(2,5)

In [60]: v2
Out[60]:
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])

改变 v2 的第一个元素:

In [61]: v2[0,0] = 10

In [62]: v2
Out[62]:
array([[10,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9]])

如果 v2 是 v1 的视图,那么 v1 也会改变,如下,v1 的第一个元素也发生相应改变,所以得证。

In [63]: v1
Out[63]: array([10,  1,  2,  3,  4,  5,  6,  7,  8,  9])

在了解完 reshape 操作的奥秘后,相信大家都建立轴和多轴的概念,这对灵活使用高维数组很有帮助。

263 元素级操作

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

In [59]: v1 = np.arange(5)

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

执行,v1+2 操作,按照元素顺序逐个加 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])
264 矩阵运算

线性代数中,矩阵的乘法操作在 NumPy 中怎么实现?

常见两种方法:使用dot 函数,另一种是转化为 matrix 对象。

dot 操作:

# 数值[1,10)内,生成shape为(5,2)的随机整数数组
In [1]: import numpy as np

In [2]: v1 = np.arange(5)

In [3]: v2 = np.random.randint(1,10,(5,2))

In [4]: np.dot(v1,v2)
Out[4]: array([49, 51])

另一种方法,将 v1v2 分别转化为 matrix 对象:

In [6]: np.matrix(v1)*np.matrix(v2)
Out[6]: matrix([[49, 51]])

需要注意,数组v1 经过 matrix 转化后,shape 由原来 (5,) 变化为 (1,5) ,的确变得更像线性代数中的矩阵:

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

首先,导入与求行列式相关的模块 linalg,求矩阵的行列式,要求数组的最后两个维度相等。

In [10]: from numpy import linalg 

In [11]: v1 = np.arange(12)
In [12]: v2 = v1.reshape(3,2,2)

In [13]: linalg.det(v2)
Out[13]: array([-2., -2., -2.])

In [14]: v3 = np.arange(9).reshape(3,3)
In [15]: linalg.det(v3)
Out[15]: 0.0
265 NumPy 求 9 大统计变量

NumPy 能方便的求出统计学常见的描述性统计量。

1) 求平均值

In [21]: m1 = np.random.randint(1,10,(3,4))

In [22]: m1
Out[22]:
array([[8, 4, 8, 2],
       [4, 9, 2, 1],
       [7, 7, 7, 5]])

In [23]: m1.mean() # 默认求出数组所有元素的平均值
Out[23]: 5.333333333333333

In [24]: m1.sum() / 12
Out[24]: 5.333333333333333

若想求某一维度的平均值,设置 axis 参数,求 axis 等于 1 的平均值:

In [26]: m1.mean(axis = 1)
Out[26]: array([5.5, 4. , 6.5])

2) 求标准差

如下,分别求所有元素的标准差、某一维度上的标准差:

In [28]: m1.std()
Out[28]: 2.592724864350674

In [29]: m1.std(axis=1)
Out[29]: array([2.59807621, 3.082207  , 0.8660254 ])

3) 求方差

如下,分别求所有元素的方差、某一维度上的方差:

In [30]: m1.var()
Out[30]: 6.722222222222221

In [31]: m1.var(axis=1)
Out[31]: array([6.75, 9.5 , 0.75])

4) 求最大值

如下,分别求所有元素的最大值、某一维度上的最大值:

In [34]: m1.max()
Out[34]: 9

In [35]: m1.max(axis=1)
Out[35]: array([8, 9, 7])

5) 求最小值

如下,分别求所有元素的最小值、某一维度上的最小值:

In [36]: m1.min()
Out[36]: 1

In [37]: m1.min(axis=1)
Out[37]: array([2, 1, 5])

6) 求和

如下,分别求所有维度上元素的和、某一维度上的元素和:

In [38]: m1.sum()
Out[38]: 64

In [39]: m1.sum(axis=1)
Out[39]: array([22, 16, 26])

7) 求累乘

如下,分别求所有维度上元素的累乘、某一维度上的累乘:

In [22]: m1
Out[22]:
array([[8, 4, 8, 2],
       [4, 9, 2, 1],
       [7, 7, 7, 5]])

In [40]: m1.cumprod()
Out[40]:
array([       8,       32,      256,      512,     2048,    18432,
          36864,    36864,   258048,  1806336, 12644352, 63221760],
      dtype=int32)

In [42]: m1.cumprod(axis=1)
Out[42]:
array([[   8,   32,  256,  512],
       [   4,   36,   72,   72],
       [   7,   49,  343, 1715]], dtype=int32)

8) 求累和

如下,分别求所有维度上元素的累加和、某一维度上的累加和:

In [43]: m1.cumsum()
Out[43]: array([ 8, 12, 20, 22, 26, 35, 37, 38, 45, 52, 59, 64], dtype=int32)

In [44]: m1.cumsum(axis=1)
Out[44]:
array([[ 8, 12, 20, 22],
       [ 4, 13, 15, 16],
       [ 7, 14, 21, 26]], dtype=int32)

9) 求迹

对角线上元素的和:

In [22]: m1
Out[22]:
array([[8, 4, 8, 2],
       [4, 9, 2, 1],
       [7, 7, 7, 5]])

In [45]: m1.trace()
Out[45]: 24
266 NumPy 之 flatten 函数

NumPy 的 flatten 函数也有改变 shape 的能力,它将高维数组变为向量。但是,它会发生数组复制行为。

In [68]: v1 = np.random.randint(1,10,(2,3))

In [69]: v1
Out[69]:
array([[3, 8, 5],
       [2, 3, 4]])

In [70]: v2 = v1.flatten()

In [71]: v2
Out[71]: array([3, 8, 5, 2, 3, 4])

v2[0] 被修改为 30 后,原数组 v1 没有任何改变。

In [73]: v2[0] = 30

In [74]: v1
Out[74]:
array([[3, 8, 5],
       [2, 3, 4]])
267 NumPy 之 newaxis

使用 newaxis 增加一个维度,维度的索引只有 0,本篇的开头已经详细解释过,不再赘述。

In [81]: v1 = np.arange(10) # shape 为一维, (10, )
In [82]: v2 = v1[:,np.newaxis] # shape 为二维,(10,1)
268 NumPy 之 repeat

repeat 操作,实现某一维上的元素复制操作。

在维度 0 上复制元素 2 次:

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

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

在维度 1 上复制元素 2 次:

In [137]: np.repeat(a,2,axis=1)
Out[137]:
array([[1, 1, 2, 2],
       [3, 3, 4, 4]])
269 NumPy 之 tile 按块复制元素

tile 实现按块复制元素:

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

In [89]: np.tile(a,3)
Out[89]:
array([[1, 2, 1, 2, 1, 2],
       [3, 4, 3, 4, 3, 4]])

In [6]: np.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]])
270 NumPy 之 vstack

vstack :vertical stack,沿竖直方向合并多个数组:

In [4]: a = np.array([[1,2],[3,4]])
In [5]: b = np.array([[-1,-2]])
In [6]: c = np.vstack((a,b)) # 注意参数类型:元组
Out[5]:
array([[ 1,  2],
       [ 3,  4],
       [-1, -2]])
271 NumPy 之 hstack

hstack 沿水平方向合并多个数组。

值得注意,不管是 vstack,还是 hstack ,沿着合并方向的维度,其元素的长度要一致。

In [4]: a = np.array([[1,2],[3,4]])
In [5]: b = np.array([[5,6,7],[8,9,10]])
In [4]: c = np.hstack((a,b)) 
In [5]: c
Out[5]:
array([[ 1,  2,  5,  6,  7],
       [ 3,  4,  8,  9, 10]])
272 NumPy 之 concatenate

concatenate 指定在哪个维度上合作数组。

In [4]: a = np.array([[1,2],[3,4]])
In [5]: b = np.array([[-1,-2]])
In [6]: np.concatenate((a,b),axis=0) # 效果等于vstack
Out[6]:
array([[ 1,  2],
       [ 3,  4],
       [-1, -2]])

In [7]:  c = np.array([[5,6,7],[8,9,10]])
In [108]: np.concatenate((a,c),axis=1) # 效果等于hstack
Out[108]:
array([[ 1,  2,  5,  6,  7],
       [ 3,  4,  8,  9, 10]])

NumPy 还有一些小 track,比如 r_ 类,c_ 类,也能实现合并操作。

In [4]: a = np.array([[1,2],[3,4]])
In [5]: b = np.array([[-1,-2]])    
In [6]: np.r_[a,b] # r_类
Out[6]:
array([[ 1,  2],
       [ 3,  4],
       [-1, -2]])    
In [7]: a = np.array([[1,2],[3,4]])
In [8]: c = np.array([[5,6,7],[8,9,10]])
In [9]: np.c_[a,c] # c_ 类
Out[9]:
array([[ 1,  2,  5,  6,  7],
       [ 3,  4,  8,  9, 10]])    
In [10]: np.r_[a,c[:,:2]]
Out[10]:
array([[1, 2],
       [3, 4],
       [5, 6],
       [8, 9]])
273 NumPy 之 argmax,argmin

argmax 返回数组中某个维度的最大值索引,当未指明维度时,返回 buffer 中最大值索引。如下所示:

In [131]: a = np.random.randint(1,10,(2,3))

In [132]: a
Out[132]:
array([[8, 1, 4],
       [5, 4, 3]])

In [133]: a.argmax()
Out[133]: 0

In [134]: a.argmax(axis = 0)
Out[134]: array([0, 1, 0], dtype=int64)

In [135]: a.argmax(axis = 1)
Out[135]: array([0, 0], dtype=int64)
274 初步认识 NumPy 的广播

广播,英文 broadcasting,有些读者是在使用 NumPy 时,从报错信息中第一次见到 broadcasting。

那么,什么是广播?广播的规则又是怎样的?在 NumPy 中,下列操作是有效的:

In [1]: import numpy as np

In [2]: v1 = np.arange(10).reshape(2,5) # v1 shape: (2,5)

In [3]: v2 = np.array([2]) # v2 shape: (1,)

In [4]: v1 + v2
Out[4]:
array([[ 2,  3,  4,  5,  6],
       [ 7,  8,  9, 10, 11]])

v1 的 shape 为 (2,5),v2 的 shape 为 (1,) ,一个为二维,一个为一维。如果没有广播机制,一定会抛(出,维数不等无法相加的异常。

但是,因为广播机制的存在,v2 数组会适配 v1 数组,按照第 0 ,1 维度,分别发生一次广播,广播后的 v2 变为:

v2 = np.tile(2,(2,5))
Out[10]:
array([[2, 2, 2, 2, 2],
       [2, 2, 2, 2, 2]])

然后,执行再执行加法操作时,因为 v1, v2 的 shape 变得完全一致,所以就能实现相加操作了。

但是,如果 v2 的 shape 为 (2,),如下, 是否 v2 广播后,能实现 v1 + v2 操作?

v2 = np.array([1,2])

执行 v1 + v2 后,抛出 shapes (2,5) (2,) 无法广播到一起的异常。

In [11]: v2 = np.array([1,2])

In [12]: v1 + v2

ValueError: operands could not be broadcast together with shapes (2,5) (2,)

因为 v1 , v2 按照广播的规则,无法达成一致的 shape,所以抛出异常。下面了解广播的具体规则。

275 NumPy 之广播的规则

以上看到,不是任意 shape 的多个数组,操作时都能广播到一起,必须满足一定的约束条件。

NumPy 首先会比较最靠右的维度,如果最靠右的维度相等或其中一个为 1,则认为此维度相等;

那么,再继续向左比较,如果一直满足,则认为两者兼容;

最后,分别在对应维度上发生广播,以此补齐直到维度一致。

如下,两个数组 a, b, shape 分别为 (2,1,3) , (4,3), 它们能否广播兼容? 我们来分析下。

a = np.arange(6).reshape(2,1,3) # shape: (2,1,3)
b = np.arange(12).reshape(4,3) # shape: (4,3)
  • 1 按照规则,从最右侧维度开始比较,数组 a, b 在此维度上的长度都为 3, 相等;

  • 2 继续向左比较,a 在此维度上长度为 1, b 长度为 4,根据规则,也认为此维度是兼容的;

  • 3 继续比较,但是数组 b 已到维度终点,停止比较。

结论,数组 a 和 b 兼容,通过广播能实现 shape 一致。

276 NumPy 广播实施的具体步骤拆分案例

下面看看,数组 a 和 b 广播操作实施的具体步骤。

In [36]: a # 初始 a
Out[36]:
array([[[0, 1, 2]],

       [[3, 4, 5]]])

In [39]: b # 初始 b
Out[39]:
array([[ 0,  1,  2],
       [ 3,  4,  5],
       [ 6,  7,  8],
       [ 9, 10, 11]])

维度编号从 0 开始,数组 a 在维度 1 上发生广播,复制 4 次:

```python a = np.repeat(a,4,axis=1)

打印 a :

python Out[35]: array([[[0, 1, 2], [0, 1, 2], [0, 1, 2], [0, 1, 2]],

   [[3, 4, 5],
    [3, 4, 5],
    [3, 4, 5],
    [3, 4, 5]]])
此时,数组 a 和 b 在后两个维度一致,但是数组 b 维度缺少一维,所以 b 也会广播一次:

python b = b[np.newaxis,:,:] # 首先增加一个维度 b = np.repeat(b,2,axis=0) # 在维度 0 上复制 2 次

经过以上操作,数组 a 和 b 维度都变为 (2,4,3),至此广播完成,做个加法操作:

python In [55]: a + b Out[55]: array([[[ 0, 2, 4], [ 3, 5, 7], [ 6, 8, 10], [ 9, 11, 13]],

   [[ 3,  5,  7],
    [ 6,  8, 10],
    [ 9, 11, 13],
    [12, 14, 16]]])
验证我们自己实现的广播操作,是否与 NumPy 中的广播操作一致,直接使用原始的 a 和 b 数组相加,看到与上面得到的结果一致。

python a = np.arange(6).reshape(2,1,3) # shape: (2,1,3) b = np.arange(12).reshape(4,3) # shape: (4,3) a + b Out[56]: array([[[ 0, 2, 4], [ 3, 5, 7], [ 6, 8, 10], [ 9, 11, 13]],

   [[ 3,  5,  7],
    [ 6,  8, 10],
    [ 9, 11, 13],
    [12, 14, 16]]])
至此,广播规则总结完毕。

建议大家都好好理解广播机制,因为接下来使用 NumPy 函数,或者查看文档时,再遇到 broadcast ,就知道它的规则,加快对函数的理解。

并且,即便遇到广播不兼容的 bug 时,相信也能很快解决。

#### 277 返回有规律的数组

已知数组:

python a = np.array([1,2,3])

返回如下数组:

python array([1, 1, 1, 2, 2, 2, 3, 3, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3])

分析

数组前半部分 `1, 1, 1, 2, 2, 2, 3, 3, 3` 通过 repeat 函数复制 3 次, 后面部分通过 tile 函数复制 3 次,体会二者区别。然后合并数据。

python In [59]: np.hstack((np.repeat(a,3), np.tile(a,3))) Out[59]: array([1, 1, 1, 2, 2, 2, 3, 3, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3])

#### 278 Python 实现向量化

借助 NumPy 的 `vectorize` 实现操作向量化

原生的 Python 列表不支持向量化操作,两个列表相加默认不是逐个元素相加:

python a = [1,3,5] b = [2,4,6] a + b # 默认实现的不是逐个元素相加操作

但是,借助 vectorize 能实现矢量相加:

python def add(x,y): return x+y addv = np.vectorize(add)

addv 函数就能实现两个数组相加:

python In [67]: addv(a,b) Out[67]: array([ 3, 7, 11])

#### 279 NumPy 限制打印元素的个数

使用 `set_printoptions` 限制打印元素的个数

python np.set_printoptions(threshold=5)

#### 280 NumPy 求中位数

求如下三维数组 a,沿 axis = 1 的中位数。

使用 median 方法,因为 axis 为 1 的数组元素长度为 4, 所以中位数为中间两个数的平均数。

如切片 a[0,:,0] 为 `[4,8,5,3]`,排序后的中间两个元素为 `[4,5]`,平均值为 4.5.

python In [75]: a array([[[4, 2, 4], [8, 2, 7], [5, 3, 6], [3, 2, 3]],

   [[2, 6, 1],
    [5, 9, 8],
    [9, 7, 1],
    [2, 1, 1]]])

In [73]: ma = np.median(a,axis = 1) Out[74]: array([[4.5, 2. , 5. ], [3.5, 6.5, 1. ]])

#### 281 NumPy 计算 softmax 得分值

已知数组 a ,求 softmax 得分值。

python In [81]: a Out[81]: array([0.07810512, 0.12083313, 0.23554504, 0.62057901, 0.3437597 , 0.10876455, 0.08338525, 0.28873765, 0.54033942, 0.71941148])

定义 softmax 函数:

python def softmax(a): ea = np.exp(a - np.max(a)) return ea / e_a.sum(axis=0)

调用 softmax ,得到每个元素的得分,因为 softmax 单调递增函数,所以输入值越大,得分值越高。

python In [85]: sm Out[85]: array([0.07694574, 0.08030473, 0.0900658 , 0.13236648, 0.10035914, 0.07934139, 0.0773531 , 0.09498634, 0.12216039, 0.14611689])

sum(sm) 等于 1

#### 282 NumPy 求任意分位数

已知数组 a,求 20 分位数,80 分位数

python a = np.arange(11)

使用 percentile 函数,q 为分位数列表

python In [95]: np.percentile(a,q=[20,80]) Out[95]: array([2., 8.])

#### 283 找到 NumPy 中缺失值

NumPy 使用 `np.nan` 标记缺失值,给定如下数组 a,求出缺失值的索引。

如下使用 where 函数,返回满足条件的位置索引:

python In [119]: a = np.array([ 0., 1., np.nan, 3., np.nan, np.nan, 6., 7., 8., 9.]) In [123]: np.where(np.isnan(a)) Out[123]: (array([2, 4, 5], dtype=int64),)

#### 284 NumPy 返回无缺失值的行

给定数组,找出没有任何缺失值的行,

python a = np.array([[ 0., np.nan, 2., 3.], [ 4., 5., np.nan, 7.], [ 8., 9., 10., 11.], [12., 13., np.nan, 15.], [16., 17., np.nan, 19.], [20., 21., 22., 23.]])

求解方法:

python In [135]: m = np.sum(np.isnan(a), axis = 1) == 0

In [136]: m Out[136]: array([False, False, True, False, False, True])

In [137]: a[m] Out[137]: array([[ 8., 9., 10., 11.], [20., 21., 22., 23.]])

#### 285 NumPy 求相关系数

求如下二维数组 a 的相关系数

python a = array([[ 2, 12, 21, 10], [ 1, 20, 8, 22], [ 7, 1, 5, 1], [ 7, 10, 14, 14], [12, 13, 13, 14], [ 0, 12, 21, 2]])

如下使用 corrcoef 方法,求得两列的相关系数,相关系数为 0.242

python In [149]: np.corrcoef(a[:,1],a[:,2]) Out[149]: array([[1. , 0.24230838], [0.24230838, 1. ]])

#### 286 缺失值默认用0填充

如下数组,含有缺失值,使用 0 填充:

python a = np.array([[ 0., np.nan, 2., 3.], [ 4., 5., np.nan, 7.], [ 8., 9., 10., 11.], [12., 13., np.nan, 15.], [16., 17., np.nan, 19.], [20., 21., 22., 23.]])

一行代码,np.isnan(a) 逐元素检查,若为空则为 True,否则为 False,得到一个与原来 shape 相同的值为 True 和 False 的数组。

python In [157]: a[np.isnan(a)] = 0

In [158]: a Out[158]: array([[ 0., 0., 2., 3.], [ 4., 5., 0., 7.], [ 8., 9., 10., 11.], [12., 13., 0., 15.], [16., 17., 0., 19.], [20., 21., 22., 23.]])

#### 287 使用 NumPy 处理 fashion-mnist 数据集  

fashion-mnist 是一个与手写字一样经典的数据集,与服饰相关。

导入数据特征数 785,我们先提取 0 到 784 ,然后 reshape 为 28 * 28 的二维数组。

 首先导入 NumPy,截取前 784 个元素,reshape 为 28 * 28 的元素。大家自行下载此数据集,下载并导入后,train_data 的 shape 为 (*,785).

python import numpy as np traindata = fashionmnisttrain.tonumpy() # Pandas DataFrame 转 numpy 对象 row0 = train_data[0,:784].reshape(28,-1)

导入 matplotlib ,使用 imshow 绘制 784 个像素(取值为 0-255)

python import matplotlib.pyplot as plt plt.imshow(row0) plt.show()

展示的图像,如下所示:

![image-20200315221039608](https://images.gitbook.cn/a8f0d7f0-66c8-11ea-8540-bdc623044264)

依次展示前 10 幅图:

python for i in range(10): print('图%d'%(i,)) plt.imshow(train_data[i,:784].reshape(28,-1)) plt.colorbar() plt.show() ```

image-20200315221301481

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值