本文为 Jupyter Notebook 笔记转 Markdown,原
.ipynb
文件可点击此处下载
关于 Python 和 NumPy 的使用可以参考斯坦福大学CS231n
的教程:https://cs231n.github.io/python-numpy-tutorial/。
Numpy 是 Python 中科学计算的核心库。它提供了一个高性能的多维数组对象,以及用于处理这些数组的工具。
要想使用 NumPy,我们首先需要引入numpy
包:
1 | import numpy as np |
数组
NumPy 数组是所有相同类型的值的网格,并由非负整数元组索引。维数是数组的秩;数组的形状是一个整数元组,给出沿每个维度的数组大小。
我们可以通过嵌套的 Python 列表初始化 NumPy 数组,并使用方括号访问元素:
1 | a = np.array([1, 2, 3]) # 创建一个秩为1的数组 |
<class 'numpy.ndarray'>
(3,)
1 2 3
[5 2 3]
1 | b = np.array([[1,2,3], [4,5,6]]) # 创建一个秩为2的数组 |
[[1 2 3]
[4 5 6]]
(2, 3)
1 2 4
NumPy 也提供许多创建数组的函数:
1 | a = np.zeros((2, 2)) # 创建一个全零数组 |
[[0. 0.]
[0. 0.]]
1 | b = np.ones((1, 2)) # 创建一个全1数组 |
[[1. 1.]]
1 | c = np.full((2, 2), 7) # 创建一个常量数组 |
[[7 7]
[7 7]]
1 | d = np.eye(2) # 创建一个2x2的单位矩阵 |
[[1. 0.]
[0. 1.]]
1 | e = np.random.random((2, 2)) # 创建一个填充随机数的数组 |
[[0.23316062 0.89221292]
[0.15442143 0.18416244]]
更多创建数组的方法请见官方文档。
数组索引
NumPy 提供了几种索引数组的方式。
切片:类似于 Python 的列表,NumPy 数组可以被切片。由于数组可能是多维的,因此必须为数组的每个维度指定一个切片:
1 | # 创建这样一个秩为2、形状为(3, 4)的数组 |
[[2 3]
[6 7]]
数组的一个切片是对于同一数据的一个视图,因此修改它将修改原数组:
1 | print(a[0, 1]) |
2
77
你也可以将整数索引与切片索引混合使用。但是,这样做将产生比原始数组的秩低的数组:
1 | # 创建这样一个秩为2、形状为(3, 4)的数组 |
[[ 1 2 3 4]
[ 5 6 7 8]
[ 9 10 11 12]]
两种访问数组中间行数据的方式。将整数索引与切片混合使用会产生一个较低秩的数组,而仅使用切片会产生一个与原始数组等秩的数组:
1 | row_r1 = a[1, :] # a的第二行的秩为1的视图 |
[5 6 7 8] (4,)
[[5 6 7 8]] (1, 4)
[[5 6 7 8]] (1, 4)
在访问数组的列时,我们可以进行相同的区分:
1 | col_r1 = a[:, 1] |
[ 2 6 10] (3,)
[[ 2]
[ 6]
[10]] (3, 1)
整数数组索引:当使用切片对 NumPy 数组进行索引时,生成的数组视图将始终是原始数组的子数组。相反,整数数组索引允许你使用另一个数组中的数据构造任意数组:
1 | a = np.array([[1,2], [3, 4], [5, 6]]) |
[1 4 5]
[1 4 5]
使用整数数组索引时,可以重复使用源数组中的相同元素:
1 | print(a[[0, 0], [1, 1]]) |
[2 2]
[2 2]
整数数组索引的一个有用技巧是从矩阵的每一行中选择或更改一个元素:
1 | # 创建一个新数组,从中选择元素 |
[[ 1 2 3]
[ 4 5 6]
[ 7 8 9]
[10 11 12]]
[ 1 6 7 11]
[[11 2 3]
[ 4 5 16]
[17 8 9]
[10 21 12]]
布尔数组索引:布尔数组索引使您可以挑选出数组的任意元素。通常,这种类型的索引用于选择满足某些条件的数组元素:
1 | a = np.array([[1,2], [3, 4], [5, 6]]) |
[[False False]
[ True True]
[ True True]]
1 | # 我们使用布尔数组索引来构建一个秩为1的数组, |
[3 4 5 6]
[3 4 5 6]
更多关于 NumPy 数组索引的信息请见官方文档。
数据类型
每个 NumPy 数组都是相同类型的元素的网格。NumPy 提供了大量可用于构造数组的数据类型。在创建数组时,NumPy 会尝试猜测一个数据类型,但是构造数组的函数通常还包含一个可选参数来明确指定数据类型:
1 | x = np.array([1, 2]) # 让NumPy选择数据类型 |
int32
float64
int64
更多关于 NumPy 数据类型的信息请见官方文档。
数组运算
基本数学函数在数组上逐元素进行操作,并且可用作运算符重载和 NumPy 模块中的函数:
1 | x = np.array([[1,2],[3,4]], dtype=np.float64) |
[[ 6. 8.]
[10. 12.]]
[[ 6. 8.]
[10. 12.]]
1 | # 逐元素求差;都产生数组 |
[[-4. -4.]
[-4. -4.]]
[[-4. -4.]
[-4. -4.]]
1 | # 逐元素相乘;都产生数组 |
[[ 5. 12.]
[21. 32.]]
[[ 5. 12.]
[21. 32.]]
1 | # 逐元素相除;都产生数组 |
[[0.2 0.33333333]
[0.42857143 0.5 ]]
[[0.2 0.33333333]
[0.42857143 0.5 ]]
1 | # 逐元素求平方根;产生数组 |
[[1. 1.41421356]
[1.73205081 2. ]]
注意,*
是逐元素相乘,不是矩阵乘法。取而代之,用dot
函数计算向量的内积、向量与矩阵的乘积以及矩阵与矩阵的乘积。
1 | x = np.array([[1,2],[3,4]]) |
219
219
1 | # 矩阵/向量乘积;都产生秩为1的数组[29 67] |
[29 67]
[29 67]
1 | # 矩阵/矩阵乘积;都产生秩为2的数组 |
[[19 22]
[43 50]]
[[19 22]
[43 50]]
NumPy 提供了许多有用的函数来对数组执行计算。最有用的之一是sum
:
1 | x = np.array([[1,2],[3,4]]) |
10
[4 6]
[3 7]
NumPy 提供的数学函数的完整列表请见官方文档。
除了用数组计算数学函数外,我们经常需要整形或以其他方式处理数组中的数据。此类操作的最简单的例子是转置矩阵;要转置矩阵,只需使用数组对象的T
属性:
1 | print(x) |
[[1 2]
[3 4]]
[[1 3]
[2 4]]
1 | # 注意,对秩为1的数组求转置不会有任何变化: |
[1 2 3]
[1 2 3]
NumPy 提供了许多操作数组的函数,完整列表请见官方文档。
广播
广播是一种强大的机制,允许 NumPy 在执行算术运算时处理不同形状的数组。通常,我们有一个较小的数组和一个较大的数组,并且我们想多次使用较小的数组对较大的数组执行某些操作。
例如,假设我们要向矩阵的每一行添加一个常向量。我们可以这样做:
1 | # 我们将向量v加到矩阵x的每一行, |
[[ 2 2 4]
[ 5 5 7]
[ 8 8 10]
[11 11 13]]
这是可行的;但是当矩阵x
非常大时,在 Python 中计算显式循环可能会很慢。注意,将向量v
加到矩阵x
的每一行相当于:垂直堆叠v
的多个副本形成矩阵vv
,然后执行x
和vv
的逐元素求和。我们可以这样实现这种方法:
1 | vv = np.tile(v, (4, 1)) # 将v的4个副本堆叠在一起 |
[[1 0 1]
[1 0 1]
[1 0 1]
[1 0 1]]
1 | y = x + vv # x和vv逐元素相加 |
[[ 2 2 4]
[ 5 5 7]
[ 8 8 10]
[11 11 13]]
NumPy 广播使我们无需实际创建多个v的副本即可执行此计算。考虑使用广播的这个版本:
1 | # 我们将向量v加到矩阵x的每一行, |
[[ 2 2 4]
[ 5 5 7]
[ 8 8 10]
[11 11 13]]
即使x
的形状是(4, 3)
而v
的形状是(3,)
,但由于广播,y = x + v
这一行仍然可以工作;这一行的工作方式就好像v实际具有(4, 3)
的形状,其中每一行都是v
的一个副本,并且求和是逐元素进行的。
将两个阵列广播到一起遵循以下规则:
- 如果几个数组的秩不同,则在低秩数组的形状前面加1补齐,直到二者的形状具有相同的长度;
- 如果两个数组在某个维度上的大小相同,或者其中一个数组在该维度上的大小为1,则称这两个数组该维度上兼容;
- 如果几个数组在所有维度上都兼容,则它们可以广播到一起;
- 广播后的数组形状等于输入数组的形状在各个维度上的最大值;
- 在任一维度中,如果一个数组的大小为1而另一个数组的大小大于1,则第一个数组的行为就像是沿着该维度复制的一样。
如果上述解释难以理解,可以尝试阅读官方文档中的解释或这篇解释。
支持广播的函数称为通用函数。所有通用功能的列表请见官方文档。
以下是广播的一些应用:
1 | # 计算向量的张量积: |
[[ 4 5]
[ 8 10]
[12 15]]
1 | # 将向量加到矩阵的每一行: |
[[2 4 6]
[5 7 9]]
1 | # 将向量加到矩阵的每一列: |
[[ 5 6 7]
[ 9 10 11]]
[[ 5 6 7]
[ 9 10 11]]
1 | # 矩阵乘以常数: |
[[ 2 4 6]
[ 8 10 12]]
广播通常会使您的代码更简洁,更快捷,因此,您应尽可能使用它。
NumPy 文档
上文中简短的描述涉及到了您需要了解的有关 NumPy 的许多重要知识,但还远远不够。查看NumPy参考以了解更多关于 NumPy 的信息。
Matplotlib
Matplotlib 是一个绘图库。在本节中将简要介绍matplotlib.pyplot
模块,该模块提供了类似于 MATLAB 的绘图系统。
1 | import matplotlib.pyplot as plt |
通过运行这个特殊的 iPython 命令,我们将内联显示绘图:
1 | %matplotlib inline |
绘图
Matplotlib 中最重要的函数是plot
,它允许您绘制2D数据。这是一个简单的示例:
1 | # 计算正弦曲线上的点的x和y坐标 |
仅需一点点额外的工作,我们就可以轻松地一次绘制多条线,并添加标题、图例和轴标签:
1 | y_sin = np.sin(x) |
您可以在官方文档中阅读有关plot
函数的更多信息。
子图
可以使用subplot
函数在同一图中绘制不同的内容。下面是一个例子:
1 | # 计算正弦和余弦曲线上的点的x和y坐标 |
您可以在官方文档中阅读有关subplot
函数的更多信息。