晓查 编译整理
量子位 报道 | 公众号 QbitAI
NumPy是Python的最重要的扩展程序库之一,也是入门机器学习编程的必备工具。然而对初学者来说,NumPy的大量运算方法非常难记。


教程内容分为向量 (一维数组)、矩阵 (二维数组)、三维与更高维数组3个部分。
Numpy数组与Python列表
在介绍正式内容之前,先让我们先来了解一下Numpy数组与Python列表的区别。
乍一看,NumPy数组类似于Python列表。它们都可以用作容器,具有获取(getting)和设置(setting)元素以及插入和移除元素的功能。
两者有很多相似之处,以下是二者在运算时的一个示例:

更紧凑,尤其是在一维以上的维度;向量化操作时比Python列表快,但在末尾添加元素比Python列表慢。

向量运算
向量初始化
创建NumPy数组的一种方法是从Python列表直接转换,数组元素的类型与列表元素类型相同。

因此,常见的做法是定义一个Python列表,对它进行操作,然后再转换为NumPy数组,或者用np.zeros和np.empty初始化数组,预分配必要的空间:




但是有更好的方法:arange函数对数据类型敏感,如果将整数作为参数,生成整数数组;如果输入浮点数(例如arange(3.)),则生成浮点数组。
但是arange在处理浮点数方面并不是特别擅长:

这就是为什么将小数部分加到步骤arange通常是一个不太好的方法:我们可能会遇到一个bug,导致数组的元素个数不是我们想要的数,这会降低代码的可读性和可维护性。
这时候,linspace会派上用场。它不受舍入错误的影响,并始终生成要求的元素数。
出于测试目的,通常需要生成随机数组,NumPy提供随机整数、均匀分布、正态分布等几种随机数形式:

一旦将数据存储在数组中,NumPy便会提供简单的方法将其取出:

但它们都是所谓的view,也就是不存储原始数据。并且如果原始数组在被索引后进行更改,则不会反映原始数组的改变。
这些索引方法允许分配修改原始数组的内容,因此需要特别注意:只有下面最后一种方法才是复制数组,如果用其他方法都可能破坏原始数据:


如上所述,布尔索引也会改写数组。它有两个常见的函数,分别是np.where和np.clip:

算术运算是NumPy速度最引入注目的地方之一。NumPy的向量运算符已达到C++级别,避免了Python的慢循环。
NumPy允许像普通数字一样操作整个数组(加减乘除、整除、幂):

向量还可以与标量进行类似的运算,方法相同:





NumPy还可以执行以下基本的统计运算(最大最小值、平均值、方差、标准差):


与Python列表相反,NumPy数组没有index方法。

更快的方式是通过Numba中的next((i[0] for i, v in np.ndenumerate(a) if v==x), -1)来加速。
一旦对数组进行排序,情况就会变得更好:v = np.searchsorted(a, x); return v if a[v]==x else -1的复杂度为O(log N),确实非常快,但是首先需要O(N log N)的排序时间。
比较浮点数
函数np.allclose(a, b)用于比较具有给定公差的浮点数组:

math.isclose进行比较没有假设前提,而是基于用户给出的一个合理abs_tol值:math.isclose(0.1+0.2–0.3, abs_tol=1e-8) == True。
除此之外np.allclose在绝对和相对公差公式中还存在一些小问题,例如,对某些数存在allclose(a, b) != allclose(b, a)。这些问题已在math.isclose函数中得到解决。
矩阵运算
NumPy中曾经有一个专用的类matrix,但现在已弃用,因此下面将交替使用矩阵和2D数组两个词。
矩阵初始化语法与向量相似:

随机矩阵的生成也类似于向量的生成:


axis参数
在许多操作(例如求和)中,我们需要告诉NumPy是否要跨行或跨列进行操作。为了使用任意维数的通用表示法,NumPy引入了axis的概念:axis参数实际上是所讨论索引的数量:第一个索引是axis=0,第二个索引是axis=1,等等。
因此在二维数组中,如果axis=0是按列,那么axis=1就是按行。

除了普通的运算符(如+,-,*,/,//和**)以元素方式计算外,还有一个@运算符可计算矩阵乘积:


从上面的示例可以看出,在二维数组中,行向量和列向量被不同地对待。
默认情况下,一维数组在二维操作中被视为行向量。因此,将矩阵乘以行向量时,可以使用(n,)或(1,n),结果将相同。
如果需要列向量,则有转置方法对其进行操作:


因此,NumPy中总共有三种类型的向量:一维数组,二维行向量和二维列向量。这是两者之间显式转换的示意图:

矩阵操作
连接矩阵有两个主要函数:

因为如上所述,一维数组被解释为行向量,而不是列向量。解决方法是将其转换为列向量,或者使用column_stack自动执行:







如果我们要创建以下矩阵:


但是实际上,在NumPy中有一种更好的方法。无需在整个矩阵上耗费存储空间。仅存储大小正确的矢量就足够了,运算规则将处理其余的内容:

除了在二维或三维数组上初始化外,meshgrid还可以用于索引数组:

就像之前提到的统计函数一样,二维数组接受到axis参数后,会采取相应的统计运算:



尽管axis参数对上面列出的函数很有用,但对二维排序却没有帮助:

1、按第一列对数组排序:a[a[:,0].argsort()]

此技巧可以重复,但是必须小心,以免下一个排序混淆前一个排序的结果:
a = a[a[:,2].argsort()]a = a[a[:,1].argsort(kind=’stable’)]a = a[a[:,0].argsort(kind=’stable’)]

a[np.lexsort(np.flipud(a[2,5].T))]:先通过第2列排序,再通过第5列排序;
a[np.lexsort(np.flipud(a.T))]:按从左到右所有列依次进行排序。

4、因为这个特殊的操作方式更具可读性和它可能是一个更好的选择,这样做的pandas不易出错:
pd.DataFrame(a).sort_values(by=[2,5]).to_numpy():通过第2列再通过第5列进行排序。
pd.DataFrame(a).sort_values().to_numpy():通过从左向右所有列进行排序
高维数组运算
通过重排一维向量或转换嵌套的Python列表来创建3D数组时,索引的含义为(z,y,x)。
第一个索引是平面的编号,然后才是在该平面上的移动:

但是此索引顺序不是通用的。处理RGB图像时,通常使用(y,x,z)顺序:前两个是像素坐标,最后一个是颜色坐标(Matplotlib中是RGB ,OpenCV中是BGR ):

因此,创建特定几何形状的实际命令取决于正在处理的域的约定:


如果数据的布局不同,则使用concatenate命令堆叠图像,并在axis参数中提供显式索引数会更方便:


混合索引顺序的另一个操作是数组转置。检查它可能会让我们对三维数组更加熟悉。
根据我们决定的axis顺序,转置数组所有平面的实际命令将有所不同:对于通用数组,它交换索引1和2,对于RGB图像,它交换0和1:

最后,还有一个函数,可以在处理多维数组时节省很多Python循环,并使代码更简洁,这就是爱因斯坦求和函数einsum:

最后,若要掌握NumPy,可以前去GitHub上的项目——100道NumPy练习题,验证自己的学习成果。
原文链接:
https://medium.com/better-programming/numpy-illustrated-the-visual-guide-to-numpy-3b1d4976de1d
100道NumPy练习题:
https://github.com/rougier/numpy-100
— 完 —
本文系网易新闻•网易号特色内容激励计划签约账号【量子位】原创内容,未经账号授权,禁止随意转载。
原标题:《看图学NumPy:掌握n维数组基础知识点,看这一篇就够了》