📌 原文链接: https://aistudio.baidu.com/...
1.7 附录:NumPy介绍
NumPy(Numerical Python的简称)是高性能科学计算和数据分析的基础包。使用飞桨构建神经网络模型时,通常会使用NumPy实现数据预处理和一些模型指标的计算,飞桨中的Tensor数据可以很方便的和ndarray数组进行相互转换。
NumPy具有如下功能:
- ndarray数组:一个具有矢量算术运算和复杂广播能力的多维数组,具有快速且节省空间的特点。
- 对整组数据进行快速运算的标准数学函数(无需编写循环)。
- 线性代数、随机数生成以及傅里叶变换功能。
- 读写磁盘数据、操作内存映射文件。
本质上,NumPy期望用户在执行“向量”操作时,像使用“标量”一样轻松。读者可以先在本机上运行如下代码,感受一下NumPy的便捷。
In [3]
>>> import numpy as np >>> a = np.array([1,2,3,4]) >>> b = np.array([10,20,30,40]) >>> c = a + b >>> print (c)
[11 22 33 44]
1.7.1 ndarray数组
ndarray数组是NumPy的基础数据结构,可以灵活、高效地处理多个元素的操作。本节主要从如下五部分展开介绍:
(1)为什么引入ndarray数组
(2)如何创建ndarray数组
(3)ndarray数组的基本运算
(4)ndarray数组的切片和索引
(5)ndarray数组的统计运算
1.7.1.1 为什么引入ndarray数组
Python中的List列表也可以非常灵活的处理多个元素的操作,但效率却非常低。与之比较,ndarray数组具有如下特点:
- ndarray数组中所有元素的数据类型相同、数据地址连续,批量操作数组元素时速度更快。而list列表中元素的数据类型可能不同,需要通过寻址方式找到下一个元素。
- ndarray数组支持广播机制,矩阵运算时不需要写for循环。
- NumPy底层使用C语言编写,内置并行计算功能,运行速度高于Python代码。
下面通过几个案例体会一下,在完成同一个任务时,使用ndarray数组和List列表的差异。
案例1:实现a+1的计算
In [3]
# Python原生的list # 假设有两个list a = [1, 2, 3, 4, 5] b = [2, 3, 4, 5, 6] # 完成如下计算 # 对a的每个元素 + 1 # a = a + 1 不能这么写,会报错 # a[:] = a[:] + 1 也不能这么写,也会报错 for i in range(5): a[i] = a[i] + 1 a
[2, 3, 4, 5, 6]
In [4]
# 使用ndarray import numpy as np a = np.array([1, 2, 3, 4, 5]) a = a + 1 a
array([2, 3, 4, 5, 6])
案例2:实现c=a+b的计算
In [5]
# 计算 a和b中对应位置元素的和,是否可以这么写? a = [1, 2, 3, 4, 5] b = [2, 3, 4, 5, 6] c = a + b # 检查输出发现,不是想要的结果 c
[1, 2, 3, 4, 5, 2, 3, 4, 5, 6]
In [6]
# 使用for循环,完成两个list对应位置元素相加 c = [] for i in range(5): c.append(a[i] + b[i]) c
[3, 5, 7, 9, 11]
In [7]
# 使用numpy中的ndarray完成两个ndarray相加 import numpy as np a = np.array([1, 2, 3, 4, 5]) b = np.array([2, 3, 4, 5, 6]) c = a + b c
array([ 3, 5, 7, 9, 11])
通过案例1和案例2可以看出,在不写for循环的情况下,ndarray数组就可以非常方便的完成数学计算。在编写矢量或者矩阵的程序时,可以像编写普通数值一样,使得代码极其简洁。另外,ndarray数组还提供了广播机制,它会按一定规则自动对数组的维度进行扩展以完成计算,如案例3所示,1维数组和2维数组进行相加操作,ndarray数组会自动扩展1维数组的维度,然后再对每个位置的元素分别相加。
案例3:实现1维数组和2维数组相加的操作
In [8]
# 自动广播机制,1维数组和2维数组相加 # 二维数组维度 2x5 # array([[ 1, 2, 3, 4, 5], # [ 6, 7, 8, 9, 10]]) d = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]) # c是一维数组,维度5 # array([ 4, 6, 8, 10, 12]) c = np.array([ 4, 6, 8, 10, 12]) e = d + c e
array([[ 5, 8, 11, 14, 17],
[10, 13, 16, 19, 22]])
1.7.1.2 创建ndarray数组
创建ndarray数组最简单的方式就是使用array
函数,它接受一切序列型的对象(包括其他数组),然后产生一个新的含有传入数据的NumPy数组。下面通过示例展示array
、arange
、zeros
和ones
四个主要函数的实现方法。
array
:创建嵌套序列(比如由一组等长列表组成的列表),并转换为一个多维数组。
In [9]
# 导入numpy import numpy as np # 从list创建array a = [1,2,3,4,5,6] # 创建简单的列表 b = np.array(a) # 将列表转换为数组 b
array([1, 2, 3, 4, 5, 6])
arange
:创建元素从0到10依次递增2的数组。
In [10]
# 通过np.arange创建 # 通过指定start, stop (不包括stop),interval来产生一个1维的ndarray a = np.arange(0, 10, 2) a
array([0, 2, 4, 6, 8])
zeros
:创建指定长度或者形状的全0数组。
In [79]
# 创建全0的ndarray a = np.zeros([3,3]) a
array([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
ones
:创建指定长度或者形状的全1数组。
In [80]
# 创建全1的ndarray a = np.ones([3,3]) a
array([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]])
1.7.1.3 查看ndarray数组的属性
ndarray的属性包括shape
、dtype
、size
和ndim
等,通过如下代码可以查看ndarray数组的属性。
shape
:数组的形状 ndarray.shape,1维数组 ( N , ) (N, ) ,2维数组 ( M , N ) (M, N) , 3 维数组( M , N , K ) 3维数组(M, N, K) 。dtype
:数组的数据类型。size
:数组中包含的元素个数ndarray.size
,其大小等于各个维度的长度的乘积。ndim
:数组的维度大小ndarray.ndim
, 其大小等于ndarray.shape
所包含元素的个数。
In [81]
a = np.ones([3, 3]) print('a, dtype: {}, shape: {}, size: {}, ndim: {}'.format(a.dtype, a.shape, a.size, a.ndim))
a, dtype: float64, shape: (3, 3), size: 9, ndim: 2
In [82]
import numpy as np b = np.random.rand(10, 10) b.shape
(10, 10)
In [83]
b.size
In [84]
b.ndim
In [85]
b.dtype
dtype('float64')
1.7.1.4 改变ndarray数组的数据类型和形状
创建ndarray之后,可以对其数据类型或形状进行修改,代码实现如下:
In [86]
# 转化数据类型 b = a.astype(np.int64) print('b, dtype: {}, shape: {}'.format(b.dtype, b.shape)) # 改变形状 c = a.reshape([1, 9]) print('c, dtype: {}, shape: {}'.format(c.dtype, c.shape))
b, dtype: int64, shape: (3, 3)
c, dtype: float64, shape: (1, 9)
1.7.1.5 ndarray数组的基本运算
ndarray数组可以像普通的数值型变量一样进行加减乘除操作,主要包含如下两种运算:标量和ndarray数组之间的运算、两个ndarray数组之间的运算。
(1)标量和ndarray数组之间的运算
标量和ndarray数组之间的运算主要包括除法、乘法、加法和减法运算,代码实现如下:
In [87]
# 标量除以数组,用标量除以数组的每一个元素 arr = np.array([[1., 2., 3.], [4., 5., 6.]]) 1. / arr
array([[1. , 0.5 , 0.33333333],
[0.25 , 0.2 , 0.16666667]])
In [88]
# 标量乘以数组,用标量乘以数组的每一个元素 arr = np.array([[1., 2., 3.], [4., 5., 6.]]) 2.0 * arr
array([[ 2., 4., 6.],
[ 8., 10., 12.]])
In [89]
# 标量加上数组,用标量加上数组的每一个元素 arr = np.array([[1., 2., 3.], [4., 5., 6.]]) 2.0 + arr
array([[3., 4., 5.],
[6., 7., 8.]])
In [90]
# 标量减去数组,用标量减去数组的每一个元素 arr = np.array([[1., 2., 3.], [4., 5., 6.]]) 2.0 - arr
array([[ 1., 0., -1.],
[-2., -3., -4.]])
(2)两个ndarray数组之间的运算
两个ndarray数组之间的运算主要包括减法、加法、乘法、除法和开根号运算,代码实现如下:
In [91]
# 数组 减去 数组, 用对应位置的元素相减 arr1 = np.array([[1., 2., 3.], [4., 5., 6.]]) arr2 = np.array([[11., 12., 13.], [21., 22., 23.]]) arr1 - arr2
array([[-10., -10., -10.],
[-17., -17., -17.]])
In [92]
# 数组 加上 数组, 用对应位置的元素相加 arr1 = np.array([[1., 2., 3.], [4., 5., 6.]]) arr2 = np.array([[11., 12., 13.], [21., 22., 23.]]) arr1 + arr2
array([[12., 14., 16.],
[25., 27., 29.]])
In [93]
# 数组 乘以 数组,用对应位置的元素相乘 arr1 * arr2
array([[ 11., 24., 39.],
[ 84., 110., 138.]])
In [94]
# 数组 除以 数组,用对应位置的元素相除 arr1 / arr2
array([[0.09090909, 0.16666667, 0.23076923],
[0.19047619, 0.22727273, 0.26086957]])
In [95]
# 数组开根号,将每个位置的元素都开根号 arr ** 0.5
array([[1. , 1.41421356, 1.73205081],
[2. , 2.23606798, 2.44948974]])
1.7.1.6 ndarray数组的索引和切片
在编写模型过程中,通常需要访问或者修改ndarray数组某个位置的元素,则需要使用ndarray数组的索引。有些情况下可能需要访问或者修改一些区域的元素,则需要使用ndarray数组的切片。
ndarray数组的索引和切片的使用方式与Python中的list类似。通过[ -n , n-1 ]的下标进行索引,通过内置的slice
函数,设置其start
,stop
和step
参数进行切片,从原数组中切割出一个新数组。ndarray数组的索引是一个内容丰富的主题,因为选取数据子集或单个元素的方式有很多。下面从一维数组和多维数组两个维度介绍索引和切片的方法。
(1)1维ndarray数组的索引和切片
从表面上看,一维数组跟Python列表的功能类似,它们重要区别在于:数组切片产生的新数组,还是指向原来的内存区域,数据不会被复制,视图上的任何修改都会直接反映到源数组上。将一个标量值赋值给一个切片时,该值会自动传播到整个选区。
In [96]
# 1维数组索引和切片 a = np.arange(30) a[10]
In [97]
a = np.arange(30) b = a[4:7] b
array([4, 5, 6])
In [98]
# 将一个标量值赋值给一个切片时,该值会自动传播到整个选区。 a = np.arange(30) a[4:7] = 10 a
array([ 0, 1, 2, 3, 10, 10, 10, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29])
In [99]
# 数组切片产生的新数组,还是指向原来的内存区域,数据不会被复制。 # 视图上的任何修改都会直接反映到源数组上。 a = np.arange(30) arr_slice = a[4:7] arr_slice[0] = 100 a, arr_slice
(array([ 0, 1, 2, 3, 100, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29]),
array([100, 5, 6]))
In [100]
# 通过copy给新数组创建不同的内存空间 a = np.arange(30) arr_slice = a[4:7] arr_slice = np.copy(arr_slice) arr_slice[0] = 100 a, arr_slice
(array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]),
array([100, 5, 6]))
(2)多维ndarray数组的索引和切片
多维ndarray数组的索引和切片具有如下特点:
- 在多维数组中,各索引位置上的元素不再是标量而是多维数组。
- 以逗号隔开的索引列表来选取单个元素。
- 在多维数组中,如果省略了后面的索引,则返回对象会是一个维度低一点的ndarray。
多维ndarray数组的索引代码实现如下所示。
In [101]
# 创建一个多维数组 a = np.arange(30) arr3d = a.reshape(5, 3, 2) arr3d
array([[[ 0, 1],
[ 2, 3],
[ 4, 5]],
[[ 6, 7],
[ 8, 9],
[10, 11]],
[[12, 13],
[14, 15],
[16, 17]],
[[18, 19],
[20, 21],
[22, 23]],
[[24, 25],
[26, 27],
[28, 29]]])
In [102]
# 只有一个索引指标时,会在第0维上索引,后面的维度保持不变 arr3d[0]
array([[0, 1],
[2, 3],
[4, 5]])
In [103]
# 两个索引指标 arr3d[0][1]
array([2, 3])
In [104]
# 两个索引指标 arr3d[0, 1]
array([2, 3])
多维ndarray数组的切片代码如下所示。
In [105]
# 创建一个数组 a = np.arange(24) a
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23])
In [106]
# reshape成一个二维数组 a = a.reshape([6, 4]) a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]])
In [107]
# 使用for语句生成list [k for k in range(0, 6, 2)]
[0, 2, 4]
In [108]
# 结合上面列出的for语句的用法 # 使用for语句对数组进行切片 # 下面的代码会生成多个切片构成的list # k in range(0, 6, 2) 决定了k的取值可以是0, 2, 4 # 产生的list的包含三个切片 # 第一个元素是a[0 : 0+2], # 第二个元素是a[2 : 2+2], # 第三个元素是a[4 : 4+2] slices = [a[k:k+2] for k in range(0, 6, 2)] slices
[array([[0, 1, 2, 3],
[4, 5, 6, 7]]),
array([[ 8, 9, 10, 11],
[12, 13, 14, 15]]),
array([[16, 17, 18, 19],
[20, 21, 22, 23]])]
In [109]
slices[0]
array([[0, 1, 2, 3],
[4, 5, 6, 7]])
1.7.1.7 ndarray数组的统计方法
可以通过数组上的一组数学函数对整个数组或某个轴向的数据进行统计计算。主要包括如下统计方法:
mean
:计算算术平均数,零长度数组的mean为NaN。std
和var
:计算标准差和方差,自由度可调(默认为n)。sum
:对数组中全部或某轴向的元素求和,零长度数组的sum为0。max
和min
:计算最大值和最小值。argmin
和argmax
:分别为最大和最小元素的索引。cumsum
:计算所有元素的累加。cumprod
:计算所有元素的累积。
说明:
sum
、mean
以及标准差std
等聚合计算既可以当做数组的实例方法调用,也可以当做NumPy函数使用。
In [110]
# 计算均值,使用arr.mean() 或 np.mean(arr),二者是等价的 arr = np.array([[1,2,3], [4,5,6], [7,8,9]]) arr.mean(), np.mean(arr)
(5.0, 5.0)
In [111]
# 求和 arr.sum(), np.sum(arr)
(45, 45)
In [112]
# 求最大值 arr.max(), np.max(arr)
(9, 9)
In [113]
# 求最小值 arr.min(), np.min(arr)
(1, 1)
In [114]
# 指定计算的维度 # 沿着第1维求平均,也就是将[1, 2, 3]取平均等于2,[4, 5, 6]取平均等于5,[7, 8, 9]取平均等于8 arr.mean(axis = 1)
array([2., 5., 8.])
In [115]
# 沿着第0维求和,也就是将[1, 4, 7]求和等于12,[2, 5, 8]求和等于15,[3, 6, 9]求和等于18 arr.sum(axis=0)
array([12, 15, 18])
In [116]
# 沿着第0维求最大值,也就是将[1, 4, 7]求最大值等于7,[2, 5, 8]求最大值等于8,[3, 6, 9]求最大值等于9 arr.max(axis=0)
array([7, 8, 9])
In [117]
# 沿着第1维求最小值,也就是将[1, 2, 3]求最小值等于1,[4, 5, 6]求最小值等于4,[7, 8, 9]求最小值等于7 arr.min(axis=1)
array([1, 4, 7])
In [118]
# 计算标准差 arr.std()
2.581988897471611
In [119]
# 计算方差 arr.var()
6.666666666666667
In [120]
# 找出最大元素的索引 arr.argmax(), arr.argmax(axis=0), arr.argmax(axis=1)
(8, array([2, 2, 2]), array([2, 2, 2]))
In [121]
# 找出最小元素的索引 arr.argmin(), arr.argmin(axis=0), arr.argmin(axis=1)
(0, array([0, 0, 0]), array([0, 0, 0]))
1.7.2 随机数np.random
主要介绍创建ndarray随机数组以及随机打乱顺序、随机选取元素等相关操作的方法。
1.7.2.1 创建随机ndarray数组
创建随机ndarray数组主要包含设置随机种子、均匀分布和 正态分布三部分内容,代码如下所示。
(1)设置随机数种子
In [122]
# 可以多次运行,观察程序输出结果是否一致 # 如果不设置随机数种子,观察多次运行输出结果是否一致 np.random.seed(10) a = np.random.rand(3, 3) a
array([[0.77132064, 0.02075195, 0.63364823],
[0.74880388, 0.49850701, 0.22479665],
[0.19806286, 0.76053071, 0.16911084]])
(2)均匀分布
In [123]
# 生成均匀分布随机数,随机数取值范围在[0, 1)之间 a = np.random.rand(3, 3) a
array([[0.08833981, 0.68535982, 0.95339335],
[0.00394827, 0.51219226, 0.81262096],
[0.61252607, 0.72175532, 0.29187607]])
In [124]
# 生成均匀分布随机数,指定随机数取值范围和数组形状 a = np.random.uniform(low = -1.0, high = 1.0, size=(2,2)) a
array([[ 0.83554825, 0.42915157],
[ 0.08508874, -0.7156599 ]])
(3)正态分布
In [125]
# 生成标准正态分布随机数 a = np.random.randn(3, 3) a
array([[ 1.484537 , -1.07980489, -1.97772828],
[-1.7433723 , 0.26607016, 2.38496733],
[ 1.12369125, 1.67262221, 0.09914922]])
In [126]
# 生成正态分布随机数,指定均值loc和方差scale a = np.random.normal(loc = 1.0, scale = 1.0, size = (3,3)) a
array([[2.39799638, 0.72875201, 1.61320418],
[0.73268281, 0.45069099, 1.1327083 ],
[0.52385799, 2.30847308, 1.19501328]])
1.7.2.2 随机打乱ndarray数组顺序
(1)随机打乱1维ndarray数组顺序
In [127]
# 生成一维数组 a = np.arange(0, 30) print('before random shuffle: ', a) # 打乱一维数组顺序 np.random.shuffle(a) print('after random shuffle: ', a)
before random shuffle: [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
24 25 26 27 28 29]
after random shuffle: [14 3 20 21 9 26 25 1 22 23 0 11 17 10 27 16 6 19 13 15 8 2 28 18
29 7 4 5 12 24]
从输出结果看,所有元素位置都被打乱了。
(2)随机打乱2维ndarray数组顺序
In [128]
# 生成一维数组 a = np.arange(0, 30) # 将一维数组转化成2维数组 a = a.reshape(10, 3) print('before random shuffle: \n{}'.format(a)) # 打乱一维数组顺序 np.random.shuffle(a) print('after random shuffle: \n{}'.format(a))
从输出结果看,只有行的顺序被打乱了,列顺序不变。
1.7.2.3 随机选取元素
In [129]
# 随机选取部分元素 a = np.arange(30) b = np.random.choice(a, size=5) b
array([23, 3, 29, 16, 20])