文章目录
- 1 基础
- 1.1 数据类型
- 1.1.1 整型数组与浮点型数组
- 1.1.2 元素同化
- 1.1.3 数组类型转换
- 1.2 数组维度
- 1.2.1 一维数组与二维数组
- 1.2.2 数组形状变换
- 2 创建数组
- 2.1 创建指定数组
- 2.2 创建递增数组
- 2.3 创建同值数组
- 2.4 创建随机数组
- 3 索引
- 3.1 访问数组元素
- 3.1.1 访问向量
- 3.1.2 访问矩阵
- 3.2 花式索引
- 3.2.1 向量花式索引
- 3.2.2 矩阵花式索引
- 3.3 数组切片
- 3.3.1 向量切片
- 3.3.2 矩阵切片
- 3.3.3 提取矩阵行
- 3.3.4 提取矩阵列
- 3.4 切片是视图!!!
- 3.4.1 切片是视图
- 3.4.2 拷贝切片
- 3.5 数组赋值是绑定
- 3.5.1 数组赋值是绑定
- 3.5.2 拷贝数组
- 4 数组的变形
- 4.1 数组转置
- 4.1.1 向量转置
- 4.1.2 矩阵转置
- 4.2 数组翻转
- 4.2.1 向量翻转
- 4.2.2 矩阵翻转
- 4.3 数组变形
- 4.3.1 向量变形
- 4.3.2 矩阵变形
- 4.4 数组拼接
- 4.4.1 向量拼接
- 4.4.2 矩阵拼接
- 4.5 数组拆分
- 4.5.1 向量拆分
- 4.5.2 矩阵拆分
- 5 运算
- 5.1 数组与标量运算
- 5.2 数组与数组运算
- 5.3 广播
- 5.3.1 向量广播
- 5.3.2 列矩阵广播
- 5.3.3 矩阵同时广播
- 6 数组函数
- 6.1 矩阵乘积
- 6.1.1 向量与向量
- 6.1.2 向量与矩阵
- 6.1.3 矩阵与向量
- 6.1.4 矩阵与矩阵
- 6.2 数学函数
- 6.3 聚合函数
- 7 布尔型数组
- 7.1 创建
- 7.2 布尔型数组中 True 的数量
- 7.3 布尔型数组作为掩码
- 7.4 满足条件的元素所在位置
- 8 数组与张量
本文使用的 Python 解释器与 NumPy 库的版本如下。
- Python 3.12.9
- NumPy 2.1.2
1 基础
1.1 数据类型
1.1.1 整型数组与浮点型数组
为克服 Python 内置列表的缺点,一个 NumPy 数组只有 1 种数据类型,以节约内存。

注意,使用 print 输出 NumPy 数组后,元素之间没有逗号。一来可以与 Python 内置列表区分,二来可以避免逗号与小数点之间的混淆。
1.1.2 元素同化
- 往整数型数组里插入浮点数,该浮点数会被自动截断为整数;
- 往浮点型数组里插入整数,该整数会被自动升级为浮点数。

1.1.3 数组类型转换
整数型数组和浮点型数组之间的界限十分严格,要将整数型数组和浮点型数组相互转换,规范的方法是使用 .astype() 方法。

此外,整数型数组在运算过程中也可升级为浮点型数组,示例如下。

整数型数组很好升级,但浮点型数组在运算过程中一般不会降级。
1.2 数组维度
1.2.1 一维数组与二维数组
深度学习三维及其以上的数组出现次数少,我们后续主要讲解。
NumPy 中的一维数组和二维数组,学了一维和二维后,很好类推到三维。
- 不同维度的数组之间,从外形上的本质区别是
- 一维数组使用1层中括号表示;
- 二维数组使用2层中括号表示;
- 三维数组使用3层中括号表示。
- 有些函数需要传入数组的形状参数,不同维度数组的形状参数为
- 一维数组的形状参数形如: x 或 (x,) ;
- 二维数组的形状参数形如: (x, y) ;
- 三维数组的形状参数形如: (x, y, z) 。
- 现在以同一个序列进行举例
- 当数组有 1 层中括号,如 [1 2 3],则其为一维数组,其形状是 3 或 (3, ) ;
- 当数组有 2 层中括号,如 [[1 2 3]],则其为二维数组,其形状是 (1, 3) ;
- 当数组有 3 层中括号,如 [[[1 2 3]]],则其为三维数组,其形状是 (1, 1, 3) ;

可以使用数组 .shape 属性查看 arr1 和 arr2 的形状。

1.2.2 数组形状变换
一维数组转二维数组,还是二维数组转一维数组,均要使用的是数组的重塑方法 .reshape() ,该方法需要传入重塑后的形状(shape)参数。
这个方法神奇的是,给定了其他维度的数值,剩下一个维度可以填 -1,让它自己去计算。
例如,把一个 5 行 6 列的矩阵重塑为 3 行 10 列的矩阵,当列的参数 10 告诉它,行的参数直接可以用 -1 来替代,它会自己去用 30 除以 10 来计算。

这里演示给 .reshape() 传入的形状参数是 (1, -1),正常情况下一般传入 (1, 10)。
(1, -1) 的含义是:行的参数是 1 行,列的参数 -1 让 NumPy 自己去除着算。

规定,将一维数组称为向量,二维数组称为矩阵。
2 创建数组
2.1 创建指定数组
当明确知道数组每一个元素的具体数值时,可以使用 np.array() 函数,将 Python 列表转化为 NumPy 数组。

2.2 创建递增数组
递增数组使用 np.arange() 函数进行创建(arange 全称是 array_range)。

2.3 创建同值数组
创建同值数组时,使用 np.zeros() 函数以及 np.ones() 函数,如示例。

示例中隐藏了一个细节——两个函数输出的并不是整数型的数组,这可能是为了避免插进去的浮点数被截断,所以将其设定为浮点型数组。
2.4 创建随机数组
创建随机数组,可以使用 np.random 系列函数,如示例所示。

3 索引
3.1 访问数组元素
与 Python 列表一致,访问 NumPy 数组元素时使用中括号,索引由 0 开始。
3.1.1 访问向量

3.1.2 访问矩阵

3.2 花式索引
花式索引(Fancy indexing)又名“花哨的索引”,这里的 Fancy 应取“华丽的、巧妙的、奢华的、时髦的”之义。
上一小节访问单个元素时,向量用 arr1[x],矩阵用 arr2[x,y]。逗号在矩阵里用于区分行与列。
这一小节,逗号新增一个功能,且不会与矩阵里的逗号混淆。
普通索引用一层中括号,花式索引用两层中括号。
3.2.1 向量花式索引

3.2.2 矩阵花式索引

根据以上实例,花式索引输出的仍然是一个向量。

3.3 数组切片
3.3.1 向量切片
向量与列表切片的操作完全一致。


3.3.2 矩阵切片

3.3.3 提取矩阵行
基于矩阵切片功能,可以提取其部分行。

所以,有时你可能看到诸如 arr[1][2] 这样的语法,这是先提取了第 1 行,再提取该行中第 2 个元素。
这种写法不是推荐。
3.3.4 提取矩阵列

提取某一个单独的列时,出来的结果是一个向量。这么做是为了省空间,因为列矩阵必须用两层中括号来存储。
形状为 1000 的向量,自然比形状为 (1000, 1) 的列矩阵更省空间(节约了 1000 对括号)。
如果想要提取一个列矩阵出来,示例如下。

3.4 切片是视图!!!
3.4.1 切片是视图
与 Python 列表和 Matlab 不同,NumPy 数组的切片仅仅是原数组的一个视图。
换言之,NumPy 切片并不会创建新的变量,示例如下。

习惯 Matlab 的用户可能无法理解,但其实这正是 NumPy 的精妙之处。
试想一个几百万条数据的数组,每次切片时都创建一个新变量,势必造成大量内存浪费。
因此,NumPy 的切片被设计为原数组的视图是极好的。
深度学习中为节省内存,将多次使用 arr[:] = <表达式> 来替代 arr = <表达式>。
3.4.2 拷贝切片

3.5 数组赋值是绑定
3.5.1 数组赋值是绑定
与 NumPy 数组的切片一样,NumPy 数组完整的赋值给另一个数组,也只是绑定。
换言之,NumPy 数组之间的赋值并不会创建新的变量,示例如下。

3.5.2 拷贝数组
如果真的需要赋给一个新数组,使用 .copy() 方法。

4 数组的变形
4.1 数组转置
数组的转置方法为 .T,其只对矩阵有效,因此遇到向量要先将其转化为矩阵。
4.1.1 向量转置

4.1.2 矩阵转置
行矩阵(向量)的转置刚演示了,列矩阵的转置如示例所示。

4.2 数组翻转
数组的翻转方法有两个,一个是上下翻转的 np.flipud() ,表示 up-down;
一个是左右翻转的 np.fliplr(),表示 left-right。
其中,向量只能使用 np.flipud()。在数学中,向量并不是横着排的,而是竖着排的。
4.2.1 向量翻转

4.2.2 矩阵翻转

4.3 数组变形
想要重塑数组的形状,需要用到 .reshape() 方法。
前面说过,把一个 5 行 6 列的矩阵重塑为 3 行 10 列的矩阵,当列的参数 10 告诉它,行的参数直接可以用 -1 来替代,它会自己去用 30 除以 10 来计算。
4.3.1 向量变形

4.3.2 矩阵变形

4.4 数组拼接
4.4.1 向量拼接
两个向量拼接,将得到一个新的加长版向量。

4.4.2 矩阵拼接
两个矩阵可以按不同的维度进行拼接,但拼接时必须注意维度的吻合。

最后要说明的是,向量和矩阵不能进行拼接,必须先把向量升级为矩阵。
4.5 数组拆分
4.5.1 向量拆分
向量分裂,将得到若干个更短的向量。

np.split() 函数中,给出的第二个参数 [2,8] 表示在索引 [2] 和索引 [8] 的位置截断。
4.5.2 矩阵拆分
矩阵的分裂同样可以按不同的维度进行,分裂出来的均为矩阵。

5 运算
5.1 数组与标量运算
Python 基础中,常用的运算符如表 5-1 所示,NumPy 的运算符与之相同。



5.2 数组与数组运算
同维度数组间的运算即对应元素之间的运算,这里仅以矩阵为例,向量与向量的操作与之相同。

乘法是遵循对应元素相乘的,你可以称之为“逐元素乘积”。
如何实现线性代数中的“矩阵级乘法”呢?6.1 会介绍到相关函数。
5.3 广播
不同形状的数组之间的运算有以下规则:
- 如果是向量与矩阵之间做运算,向量自动升级为行矩阵;
- 如果某矩阵是行矩阵或列矩阵,则其被广播,以适配另一个矩阵的形状。
5.3.1 向量广播
当一个形状为 (x, y) 的矩阵与一个向量做运算时,要求该向量的形状必须为 y,运算时向量会自动升级成形状为 (1, y) 的行矩阵,该形状为 (1, y) 的行矩阵再自动被广播为形状为 (x, y) 的矩阵,这样就与另一个矩阵的形状适配了。

5.3.2 列矩阵广播
当一个形状为 (x, y) 的矩阵与一个列矩阵做运算时,要求该列矩阵的形状必须为 (x, 1),该形状为 (x, 1) 的列矩阵再自动被广播为形状为 (x, y) 的矩阵,这样就与另一个矩阵的形状适配了。

5.3.3 矩阵同时广播
当一个形状为 (1, y) 的行矩阵与一个形状为 (x, 1) 的列矩阵做运算时,这俩矩阵都会被自动广播为形状为 (x, y) 的矩阵,这样就互相适配了。

6 数组函数
6.1 矩阵乘积
- 第五章中的乘法都是“逐元素相乘”,这里介绍线性代数中的矩阵乘积,本节只需要使用 np.dot() 函数。
- 当矩阵乘积中混有向量时,根据需求,其可充当行矩阵,也可充当列矩阵,但混有向量时输出结果必为向量。
6.1.1 向量与向量
设两个向量的形状按前后顺序分别是 5 以及 5 。从矩阵乘法的角度,有 (1, 5) * (5, 1) = (1, 1),因此输出的应该是形状为 1 的向量。

6.1.2 向量与矩阵
设向量的形状是 5,矩阵的形状是 (5, 3)。从矩阵乘法的角度,有 (1, 5) * (5, 3) = (1, 3),因此输出的应该是形状为 3 的向量。

6.1.3 矩阵与向量
设矩阵的形状是 (3, 5),向量的形状是 5。从矩阵乘法的角度,有 (3, 5) * (5, 1) = (3, 1),因此输出的应该是形状为 3 的向量。

6.1.4 矩阵与矩阵
设矩阵的形状是 (5, 2) 以及 (2, 8)。从矩阵乘法的角度,有 (5, 2) * (2, 8) = (5, 8),因此输出的应该是形状为 (5, 8) 的矩阵。

6.2 数学函数
NumPy 设计了很多数学函数,这里列举其中最重要、最常见的几个。


6.3 聚合函数
聚合很有用,这里用矩阵演示。向量与之一致,但没有 axis 参数。以下在注释中介绍了 6 个最重要的聚合函数,其用法完全一致,仅演示其中 3 个。

- 当 axis=0 时,最终结果与每一行的元素个数一致;
- 当 axis=1 时,最终结果与每一列的元素个数一致。
考虑到大型数组难免有缺失值,以上聚合函数碰到缺失值时会报错,因此出现聚合函数的安全版本,即计算时忽略缺失值:np.nansum()、np.nanprod() 、np.nanmean()、np.nanstd()、np.nanmax()、np.nanmin()。
7 布尔型数组
除了整数型数组和浮点型数组,还有一种有用的数组类型——布尔型数组。
7.1 创建
由于 NumPy 的主要数据类型是整数型数组或浮点型数组,因此布尔型数组的产生离不开:大于 >、大于等于 >=、等于 ==、不等号 !=、小于 <、小于等于 <=。
首先,我们将数组与系数作比较,以产生布尔型数组,示例如下。

其次,我们将同维数组作比较,以产生布尔型数组,示例如下。

最后,还可以同时比较多个条件。Python 里同时检查多个条件使用的与、或、非是 and、or、not。但 NumPy 中使用的与、或、非是 & 、 | 、 ~ 。

7.2 布尔型数组中 True 的数量
有三个关于 True 数量的有用函数,分别是 np.sum()、np.any()、np.all()。
np.sum() 数:统计布尔型数组里 True 的个数。示例如下。

从结果来看,arr1 与 arr2 里含有共同元素,那就是 5。
np.all() 函数:当布尔型数组里全是 True 时,才返回 True,示例如下。

从结果来看,尽管 3σ 准则告诉我们有 99.73% 的考生成绩高于 290 分,但仍然有最终成绩低于 250 分的裸考者。
7.3 布尔型数组作为掩码
若一个普通数组和一个布尔型数组的维度相同,可以将布尔型数组作为普通数组的掩码,这样可以对普通数组中的元素作筛选。给出两个示例。
第一个示例,筛选出数组中大于、等于或小于某个数字的元素。

注意,这个矩阵进行掩码操作后,退化为了向量。
第二个示例,筛选出数组逐元素比较的结果。

7.4 满足条件的元素所在位置
现在我们来思考一种情况:假设一个很长的数组,我想知道满足某个条件的元素们所在的索引位置,此时使用 np.where() 函数。

np.where() 函数的输出看起来比较怪异,它是输出了一个元组。
元组第一个元素是“满足条件的元素所在位置”;第二个元素是数组类型,可忽略掉。
8 数组与张量
PyTorch 作为当前首屈一指的深度学习库,其将 NumPy 的语法尽数吸收,作为自己处理数组的基本语法,且运算速度从使用 CPU 的数组进步到使用 GPU 的张量。
NumPy 和 PyTorch 的基础语法几乎一致,具体表现为:
- np 对应 torch;
- 数组 array 对应张量 tensor;
- NumPy 的 n 维数组对应着 PyTorch 的 n 阶张量。
数组与张量之间可以相互转换:
- 数组 arr 转为张量 ts:ts = torch.tensor(arr);
- 张量 ts 转为数组 arr:arr = np.array(ts)。
NumPy函数 | PyTorch函数 | 区别 |
---|---|---|
.astype() | .type() | 无 |
np.random.random() | torch.rand() | 无 |
np.random.randint() | torch.randint() | 不接纳一维张量 |
np.random.normal() | torch.normal() | 不接纳一维张量 |
np.random.randn() | torch.randn() | 无 |
.copy() | .clone() | 无 |
np.concatenate() | torch.cat() | 无 |
np.split() | torch.split() | 参数含义优化 |
np.dot() | torch.matmul() | 无 |
np.dot(v,v) | torch.dot() | 无 |
np.dot(m,v) | torch.mv() | 无 |
np.dot(m,m) | torch.mm() | 无 |
np.exp() | torch.exp() | 必须传入张量 |
np.log() | torch.log() | 必须传入张量 |
np.mean() | torch.mean() | 必须传入浮点型张量 |
np.std() | torch.std() | 必须传入浮点型张量 |