1.5 NumPy

优质
小牛编辑
115浏览
2023-12-01

在深度学习的实现中,经常出现数组和矩阵的计算。NumPy 的数组类(numpy.array )中提供了很多便捷的方法,在实现深度学习时,我们将使用这些方法。本节我们来简单介绍一下后面会用到的 NumPy。

1.5.1 导入 NumPy

NumPy 是外部库。这里所说的外部是指不包含在标准版 Python 中。因此,我们首先要导入 NumPy 库。


>>> import numpy as np

Python 中使用 import 语句来导入库。这里的 import numpy as np ,直译的话就是将 numpy 作为 np 导入的意思。通过写成这样的形式,之后 NumPy 相关的方法均可通过 np 来调用。

1.5.2 生成 NumPy 数组

要生成 NumPy 数组,需要使用 np.array() 方法。np.array() 接收 Python 列表作为参数,生成 NumPy 数组(numpy.ndarray )。


>>> x = np.array([1.0, 2.0, 3.0])
>>> print(x)
[ 1. 2. 3.]
>>> type(x)
<class 'numpy.ndarray'>

1.5.3 NumPy 的算术运算

下面是 NumPy 数组的算术运算的例子。


>>> x = np.array([1.0, 2.0, 3.0])
>>> y = np.array([2.0, 4.0, 6.0])
>>> x + y

  # 对应元素的加法
array([ 3.,  6., 9.])
>>> x - y
array([ -1.,  -2., -3.])
>>> x * y

  # element-wise product
array([  2.,   8.,  18.])
>>> x / y
array([ 0.5,  0.5,  0.5])

这里需要注意的是,数组 x 和数组 y 的元素个数是相同的(两者均是元素个数为 3 的一维数组)。当 x 和 y 的元素个数相同时,可以对各个元素进行算术运算。如果元素个数不同,程序就会报错,所以元素个数保持一致非常重要。另外,对应元素的的英文是 element-wise,比如对应元素的乘法就是 element-wise product。

NumPy 数组不仅可以进行 element-wise 运算,也可以和单一的数值(标量)组合起来进行运算。此时,需要在 NumPy 数组的各个元素和标量之间进行运算。这个功能也被称为广播(详见后文)。


>>> x = np.array([1.0, 2.0, 3.0])
>>> x / 2.0
array([ 0.5,  1. ,  1.5])

1.5.4 NumPy 的 N 维数组

NumPy 不仅可以生成一维数组(排成一列的数组),也可以生成多维数组。比如,可以生成如下的二维数组(矩阵)。


>>> A = np.array([[1, 2], [3, 4]])
>>> print(A)
[[1 2]
 [3 4]]
>>> A.shape
(2, 2)
>>> A.dtype
dtype('int64')

这里生成了一个 2 x 2 的矩阵 A 。另外,矩阵 A 的形状可以通过 shape 查看,矩阵元素的数据类型可以通过 dtype 查看。下面,我们来看一下矩阵的算术运算。


>>> B = np.array([[3, 0],[0, 6]])
>>> A + B
array([[ 4,  2],
     [ 3, 10]])
>>> A * B
array([[ 3,  0],
     [ 0, 24]])

和数组的算术运算一样,矩阵的算术运算也可以在相同形状的矩阵间以对应元素的方式进行。并且,也可以通过标量(单一数值)对矩阵进行算术运算。这也是基于广播的功能。


>>> print(A)
[[1 2]
 [3 4]]
>>> A * 10
array([[ 10, 20],
     [ 30, 40]])

 NumPy 数组(np.array )可以生成 N 维数组,即可以生成一维数组、二维数组、三维数组等任意维数的数组。数学上将一维数组称为向量 ,将二维数组称为矩阵 。另外,可以将一般化之后的向量或矩阵等统称为张量 (tensor)。本书基本上将二维数组称为矩阵,将三维数组及三维以上的数组称为张量多维数组

1.5.5 广播

NumPy 中,形状不同的数组之间也可以进行运算。之前的例子中,在 2x2 的矩阵 A 和标量 10 之间进行了乘法运算。在这个过程中,如图 1-1 所示,标量 10 被扩展成了 2 x 2 的形状,然后再与矩阵 A 进行乘法运算。这个巧妙的功能称为广播 (broadcast)。

图 1-1 广播的例子:标量 10 被当作 2 x 2 的矩阵

我们通过下面这个运算再来看一个广播的例子。


>>> A = np.array([[1, 2], [3, 4]])
>>> B = np.array([10, 20])
>>> A * B
array([[ 10, 40],
     [ 30, 80]])

在这个运算中,如图 1-2 所示,一维数组 B 被巧妙地变成了和二位数组 A 相同的形状,然后再以对应元素的方式进行运算。

图 1-2 广播的例子 2

综上,因为 NumPy 有广播功能,所以不同形状的数组之间也可以顺利地进行运算。

1.5.6 访问元素

元素的索引从 0 开始。对各个元素的访问可按如下方式进行。


>>> X = np.array([[51, 55], [14, 19], [0, 4]])
>>> print(X)
[[51 55]
 [14 19]
 [ 0 4]]
>>> X[0]

  # 第0行
array([51, 55])
>>> X[0][1]

 # (0,1)的元素
55

也可以使用 for 语句访问各个元素。


>>> for row in X:
...   print(row)
...
[51 55]
[14 19]
[0 4]

除了前面介绍的索引操作,NumPy 还可以使用数组访问各个元素。


>>> X = X.flatten()

     # 将X转换为一维数组
>>> print(X)
[51 55 14 19  0  4]
>>> X[np.array([0, 2, 4])]

 # 获取索引为0、2、4的元素
array([51, 14,  0])

运用这个标记法,可以获取满足一定条件的元素。例如,要从 X 中抽出大于 15 的元素,可以写成如下形式。


>>> X > 15
array([ True,  True, False,  True, False, False], dtype=bool)
>>> X[X>15]
array([51, 55, 19])

对 NumPy 数组使用不等号运算符等(上例中是 X > 15 ),结果会得到一个布尔型的数组。上例中就是使用这个布尔型数组取出了数组的各个元素(取出 True 对应的元素)。

 Python 等动态类型语言一般比 C 和 C++ 等静态类型语言(编译型语言)运算速度慢。实际上,如果是运算量大的处理对象,用 C/C++ 写程序更好。为此,当 Python 中追求性能时,人们会用 C/C++ 来实现处理的内容。Python 则承担中间人的角色,负责调用那些用 C/ C++ 写的程序。NumPy 中,主要的处理也都是通过 C 或 C++ 实现的。因此,我们可以在不损失性能的情况下,使用 Python便利的语法。