Python 科学计算入门
0.1 NumPy
Numpy
库是 Python 科学计算的核心。
Numpy Array 是一个存放相同数据类型的数组(类型 numpy.ndarray
),可以使用非负元组(Non-negative tuple)来索引。
我们称 Rank 为数组的维数,Shape 表示数组的各个维度的大小,使用整型元组表示。
0.1.1 Array Creation
初始化 Numpy Array 的方法如下:
1 | import numpy as np |
类似 Matlab,提供多种方法创建数组:
1 | a = np.zeros((2, 2)) # 2x2 0-matrix |
更多创建方法用到再说。
0.1.2 Array Indexing
索引 Numpy Array 的方法和 Python 原生数组类似:
- 整型索引 + slice
:
切片;
1 | a = np.array( |
注意引用传递的问题。
还可以将 整型索引 和 slice 混合使用:
1 | a = np.array( |
此外,Numpy 还支持:
- 整型数组索引 + 布尔数组索引(更类似 Matlab):仍然是引用传递。需要
copy()
来 copy construct;
1 | a = np.array( |
1 | a = np.array([ |
0.1.3 Data Types
Numpy 中提供来多种数据类型,可以用来构建数组等操作。默认情况下构建 array,在不指定 dtype
参数时,Numpy 会猜测数组的数据类型。
1 | x = np.array([1, 2]) |
Python 原生类型,如
int / bool / float / complex / bytes / str / object
,分别对应int_ / bool_ / float64 / complex128 / bytes_ / str_ / object_
;
当数据类型比较复杂时(例如结构体数组),我们可以手动创建 dtype
并指定,例如:
1 | dt = np.dtype([ |
更多类型的使用、dtype
信息,请参见 Array DTypes - Numpy Doc;
0.1.4 Array Math
数组运算,在 Numpy 中也是向量 / 矩阵运算。
在 Matlab 中,我们知道这些运算可以是向量整体点积/叉积、矩阵整体乘法/取逆、向量矩阵乘法;也可以是 element-wise(逐元素)的运算。
逐元素的四则运算、开方运算如下(会产生新对象):
1 | x = np.array([[1,2],[3,4]], dtype=np.float64) |
向量点积(内积)/ 矩阵整体乘法 / 向量矩阵乘法:
1 | # matrices |
我们发现,在 Numpy 中,通过 array()
定义的 rank-1 array 很灵活,既可以作行向量,又可以作列向量。
也正因如此,我们下面介绍的 转置操作 对 rank-1 array 毫无影响:
1 | x = np.array([[1,2], [3,4]]) |
此外,除了 element-wise 求和,Numpy 和 Matlab 一样提供了整体求和的方法 sum
:
1 | x = np.array([[1,2],[3,4]]) |
更多运算方法用到再说。
0.1.5 Broadcasting
广播(broadcasting)是 Numpy 运算中相当重要的性质之一,它为不同维度的 array 间的运算提供了更高效的方式。下面几种常用的手段:
将 rank-1 array 加到 matrix 的每一行上
假设现在有个需求,将 v
向量加到 x
的每一行上,最后放到 y
中。
1 | x = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) |
如果不用 broadcasting 就应该这么做:
1 | for i in range(x.shape[1]): |
或者使用 tile
来堆叠重复向量:
1 | vv = np.tile(v, (4, 1)) # Stack 4 copies of v on top of each other |
第一种方法虽然能实现需求,但是如果 x
相当大,那么 Python 自己的循环就非常慢,因此我们需要借助 broadcasting(写在 Numpy Library 中,C/C++ 实现):
1 | # 不同维度的 array 通过 broadcast 直接运算 |
但是如果使用不当会出现 shape mismatch 的情况。因此我们需要知道 broadcast 的算法:
如果参与运算的 arrays 的 rank 不同,那么向 rank 小的 array 的 shape 中
prepend
数字 1(可以在不添加元素的前提下扩展维度),直至它们的 rank(就是 shape 元组的长度)相等;例如
[1, 2, 3]
(shape(3,)
)扩展一次维度后变成[[1, 2, 3]]
(shape(1, 3)
);定义:两个 arrays 在某个维度上 compatible(匹配),当且仅当它们的 shape 在这个维度上的值相同,或者一方为 1;
因此,第一步就是尝试让两个参与运算的 array 在每个维度上都匹配。数学上只有两个 array 在每个维度上都匹配,才能进行 broadcasting 操作。
两个 arrays 运算时,如果在某个维度上一方为 1,另一方大于 1,那么说明需要在这个维度上 broadcasting,具体做法就是让这个维度上为 1 的 array,在当前维度上重复多次直至与另一个 array 的相等;
如果两个 arrays 是匹配的,那么在最终运算结束时,结果是二者 shapes 的 element-wise 的最大值。
思考 1:为什么 broadcasting 会将向量加到矩阵的每一行,而不是每一列?
答:由 broadcasting 算法决定。上面提到,会向 shape 中 prepend
数字 1,这就是原因(如果算法是 append 的话就是加到列上去了)。
思考 2:那么我们如何利用 broadcasting 将向量加到每一列上?
答:先用转置,然后最后再转置回去就行。或者使用 reshape
来手动预处理 shape(向它 append 1);
1 | x = np.array([[1, 2, 3], [4, 5, 6]]) |
常量 Element-wise 运算
这应该是广播最为通俗易懂的形式。
我们直接用常量(相当于扩展前的 shape (1,)
)向矩阵乘:
1 | x = np.array([[1, 2, 3], [4, 5, 6]]) |
计算向量叉积
注:可以使用自带方法
numpy.outer(a, b)
,这里只是展示如何用 broadcasting 完成叉积(外积)。
本质上就是将被乘向量的每个元素乘到每个列上:
1 | v = np.array([1, 2, 3]) |
关于 Numpy 的基础用法掌握这么多就够了。如果希望更多的信息,请参见 Numpy 官方文档。
0.2 SciPy
SciPy
库提供了一些高性能方法来计算和掌控多维数组,对科学计算和工程有很大用处。
最好入门的方法是阅读这个文档:Scipy - Reference。这里我们尽快入门为主,有需要再自行翻阅。
0.2.1 Image Operations
Scipy 库提供了图像处理的基本函数。例如:
- 从磁盘的图片中读入到 Numpy 数组;
- 将 Numpy 数组写成图片;
下面是使用 Scipy resize 图片的例子:
1 | # 注:这个写法在 scipy 1.2.0 以后被弃用。 |
0.2.2 Matlab Files
我们可以从 Matlab 中加载通用的矩阵文件。例如下面的例子是从 Matlab 的矩阵文件中加载复数矩阵:
1 | from scipy.io import loadmat |
0.2.3 Distance between points
Scipy 求两点间距如下:
1 | import numpy as np |
还有一个 cdist
求的是两个点集间的距离(指定两个点集,而不像上面是在一个点集内),自动输出为矩阵形式。
0.3 Matplotlib
介绍一些模板用法。
0.3.1 并列折线图
1 | import matplotlib.pyplot as plt |
0.3.2 并列直方图
1 | import matplotlib.pyplot as plt |
0.3.3 堆叠条形图
1 | import matplotlib.pyplot as plt |
0.3.4 折线-直方组合图 & 对数坐标
1 | import matplotlib.pyplot as plt |
0.3.3 样条曲线和插值拟合
1 | # File: fit_interp_1d.py |
1 | import math |
0.3.4 3D 曲面
1 | import math |
0.3.5 输出图片
除了使用 OpenCV 库的 imshow
,Matplotlib 也支持输出图片:
1 | import numpy as np |