从图学NumPy:阅读本文足以掌握n维数组的基础知识NumPy是Python最重要的扩展库之一,是机器学习编程入门的必备工具。然而,对于初学者来说,NumPy大量的计算方式是很难记住的。近日,一位国外程序员将NumPy的基本操作以图形化的方式写了下来,让学习过程轻松有趣。它在Reddit机器学习社区不到半天的时间就获得了500多个点赞。让我们跟着他的教程一起学习吧!教程内容分为向量(一维数组)、矩阵(二维数组)、三维及更高维数组三个部分。Numpy数组和Python列表在介绍正式内容之前,我们先来了解一下Numpy数组和Python列表的区别。乍一看,NumPy数组类似于Python列表。它们都可以用作容器,具有获取和设置元素以及插入和删除元素的功能。两者之间有很多相似之处。下面是两者的操作示例:与Python列表相比,Numpy数组具有以下特点:更紧凑,尤其是在一维以上的维度;矢量化操作比Python列表快,但在末尾添加元素比Python列表慢。△在末尾添加元素时,Pythonlist的复杂度是O(1),NumPy的复杂度是O(N)Vectoroperationvectorinitialization创建NumPy数组的一种方法是直接从Python列表转换,数组元素的类型与列表元素的类型相同。NumPy数组不能像Python列表一样加长,因为在数组末尾没有保留空间。因此,通常的做法是定义一个Python列表,对其进行操作,然后转换为NumPy数组,或者使用np.zeros和np.empty初始化数组,预先分配必要的空间:有时我们需要创建一个大小和元素类型的空数组与现有数组相同:几乎所有用常量填充创建的数组的函数都有一个_like对应物来创建相同类型的常量数组:在NumPy中,单调序列可以用arange或linspace数组初始化:如果你需要一个像[0.,1.,2.]这样的浮点数组,你可以改变arange输出的类型:arange(3).astype(float)。但是有更好的方法:arange函数对数据类型敏感。如果使用整数作为参数,则生成一个整数数组;如果输入一个浮点数(例如arange(3.)),则会生成一个浮点数组。但是arange并不是特别擅长处理浮点数:那是因为0.1对我们来说是一个有限的十进制数,但对计算机来说不是。在二进制中,0.1是无限小数,必须在某处截断。这就是为什么将小数部分添加到steparange通常不是一个好主意:我们可能会遇到一个错误,即数组没有我们想要的那么多元素,这会降低代码的可读性和可读性。可维护性。这时候linspace就派上用场了。它不受舍入错误的影响,并且始终生成请求数量的元素。出于测试目的,通常需要生成随机数组。NumPy提供了随机整数、均匀分布、正态分布等几种形式的随机数:VectorIndex数据存入数组后,NumPy提供了一种简单的取出方式:各种索引如上所示,如取出一个特定的范围,从右到左索引,只取出奇数位,等等。但它们都是所谓的视图,即它们不存储原始数据。而如果原数组被索引后发生变化,则不会反映原数组的变化。这些索引方法允许赋值修改原始数组的内容,所以需要特别注意:只有下面的最后一个方法是数组的副本,任何其他方法都可能破坏原始数据:AnothersuperusefulwaytogetdatafromNumPy数组方法是布尔索引,它允许使用各种逻辑运算符来检索符合条件的元素:注意:Python中的三元比较3<=a<=5在NumPy数组中不起作用。如上所述,布尔索引也会覆盖数组。它有两个常用函数,np.where和np.clip:向量运算算术运算是NumPy速度最引人注目的方面之一。NumPy的向量运算符已经达到了C++的水平,避免了Python的慢循环。NumPy允许对整个数组像普通数一样进行操作(加减乘除除次幂):△和Python中一样,a//b表示divb(可整除),x**n表示x?向量也可以执行与标量运算类似的操作,方法相同:大多数数学函数都有NumPy对应的处理向量的函数:向量点积、叉积也有运算符:我们还可以执行三角函数、反三角函数和斜边运算:arrayscanberounded是一个整数:△floor取下界;ceil取上限;round是四舍五入甚至NumPy也可以进行以下基本的统计操作(最大值和最小值,平均值,方差,标准差):但是排序函数的功能比Python列表对应的功能更少:在一个列表中搜索元素vector与Python列表相反,NumPy数组没有索引方法。一种查找元素的方法是np.where(a==x)[0][0],这种方法既不优雅也不快速,因为要查找的项需要从头开始遍历数组的所有元素。一种更快的方法是在Numba中通过next((i[0]fori,vinnp.ndenumerate(a)ifv==x),-1)加速。一旦数组被排序,事情就会变得更好:v=np.searchsorted(a,x);returnvifa[v]==xelse-1isO(logN),非常快,但首先需要O(NlogN)排序时间。比较浮点数函数np.allclose(a,b)用于比较具有给定公差的浮点数组:np.allclose假定所有比较的数字都是秩为1的单位。比如上图中,它认为1e-9和2e-9是一样的。如果想做更详细的比较,需要通过atol指定比较级别1:np.allclose(1e-9,2e-9,atol=1e-17)==False。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中曾经有一个专门的类矩阵,但现在已弃用,因此下面将交替使用术语矩阵和二维数组。矩阵初始化语法类似于向量:这里需要双括号,因为第二个位置参数是为dtype保留的。随机矩阵的生成也类似于向量的生成:二维索引语法比嵌套列表更方便:与一维数组一样,上面的视图显示切片数组实际上不进行任何复制。修改数组后,更改也会反映在切片中。axis参数在很多操作(比如求和)中,我们需要告诉NumPy我们是想跨行操作还是跨列操作。为了对任意维度使用通用符号,NumPy引入了轴的概念:轴参数实际上是相关索引的数量:第一个索引是轴=0,第二个是轴=1,等等。因此,在二维数组中,如果axis=0是按列,那么axis=1就是按行。矩阵运算除了按元素计算的+、-、*、/、//和**等常规运算符之外,还有一个计算矩阵乘积的@运算符:在第一部分中,我们已经看到向量Forproduct的运算,NumPy允许元素在向量和矩阵之间,甚至是两个向量:行向量和列向量之间的混合运算从上面的例子可以看出,在一个二维数组中,行向量和列向量是不同对待。默认情况下,一维数组在二维运算中被视为行向量。所以当一个矩阵乘以一个行向量时,你可以使用(n,)或(1,n)并且结果是一样的。如果需要列向量,有转置的方法可以对其进行操作:可以从一维数组生成二维数组列向量的两个操作是使用命令reshaperearrangement和newaxis创建新索引:这里的-1参数表示reshape自动计算数组沿第二个维度的长度,方括号中的None作为np.newaxis的快捷方式,在指定位置添加一个空轴。因此,NumPy中存在三种类型的向量:一维数组、二维行向量和二维列向量。下面是两者之间显式转换的示意图:1D数组按照规则被隐式解释为2D行向量,所以两个数组之间一般不需要转换,对应的区域用灰色标出。矩阵运算连接矩阵有两个主要函数:当仅堆叠矩阵或仅堆叠向量时,这两个函数都可以正常工作。但是当谈到一维数组和矩阵之间的混合堆叠时,vstack工作正常:hstack给出维度不匹配错误。因为,如上所述,一维数组被解释为行向量,而不是列向量。解决方案是将其转换为列向量,或者使用column_stack自动完成:堆叠的逆运算是拆分:矩阵可以通过两种方式完成:平铺类似于复制粘贴,重复类似于逐页页面打印。具体的列和行可以用delete删除:逆向操作是insert:append和hstack一样,这个函数不能自动转置一维数组,所以需要对vector重新转置或者加length,或者改用column_stack:实际上面,如果我们需要做的只是将常量值添加到数组的边界,那么pad函数就足够了:Meshgrid如果我们要创建以下矩阵:这两种方法都很慢,因为它们使用Python循环。MATLAB中处理这类问题的方法是创建一个meshgrid:meshgrid函数接受任意一组索引,mgrid只是一个slice,indices只能生成一个完整的索引范围。fromfunction如上所述,仅使用I和J参数调用提供的函数一次。但实际上,在NumPy中有更好的方法。不需要在整个矩阵上花费存储空间。只需存储一个正确大小的向量就足够了,算法会处理剩下的事情:在没有indexing='ij'参数的情况下,meshgrid将更改参数的顺序:J,I=np.meshgrid(j,i)—这是一种“xy”模式,用于可视化3D图。meshgrid除了可以在二维或三维数组上初始化外,还可以用来对数组进行索引:矩阵统计就像上面提到的统计函数。二维数组在接收到axis参数后,会进行相应的统计操作:二维和更高维度,argmin和argmax函数返回最大值和最小值的索引:all和any这两个函数也可以使用axis参数:矩阵排序虽然axis参数对上面列出的函数有用,但对二维排序没有帮助:axis绝不是Pythonlist关键参数的替代品。不过NumPy有多个函数可以按列排序:1.按第一列对数组进行排序:a[a[:,0].argsort()]argsort排序后,这里返回原数组的索引数组.这个技巧可以重复,但必须注意下一个排序不会混淆前一个排序:a=a[a[:,2].argsort()]a=a[a[:,1].argsort(kind='stable')]a=a[a[:,0].argsort(kind='stable')]2.有一个辅助函数lexsort对所有可用的列进行排序,但总是按行排序,因为例子:a[np.lexsort(np.flipud(a[2,5].T))]:先按第2列排序,再按第5列排序;a[np.lexsort(np.flipud(a.T))]:按所有列从左到右顺序排序。3.还有一个参数顺序,但如果从普通(非结构化)数组开始,它既不快也不好用。4.由于这个特定的操作更具可读性,它可能是一个更好的选择,pandas不太容易出错:pd.DataFrame(a).sort_values(by=[2,5]).to_numpy():Sortby第2列,然后是第5列。pd.DataFrame(a).sort_values().to_numpy():将所有列从左到右排序高维数组操作通过重新排列一维向量或转换嵌套的Python列表来创建3维数组,索引的含义为(z,y,x).第一个索引是平面的编号,然后是该平面上的移动:这种索引顺序很方便,例如用于保存一堆灰度图像:这个a[i]是引用第i个图像的快捷方式。但是这个索引顺序并不通用。在处理RGB图像时,通常使用(y,x,z)顺序:前两个是像素坐标,最后一个是颜色坐标(Matplotlib中的RGB,OpenCV中的BGR):这样方便参考对于特定像素:a[i,j]给出像素的RGB元组(i,j)。因此,创建特定几何图形的实际命令取决于正在处理的域的约定:显然,像hstack、vstack或dstack这样的NumPy函数不知道这些约定。硬编码索引顺序为(y,x,z),RGB图像顺序为:△RGB图像数组(为简单起见,上图只有2种颜色)。如果数据布局不同,使用concatenate命令堆叠图像,并且在axis参数中提供一个明确的索引号更方便:如果使用axis不方便,数组转换可以hard-编码为hstack的形式:此转换不会发生实际复制。它只是混合了索引的顺序。另一个混合索引顺序的操作是数组转置。检查它可能会让我们更熟悉3D数组。根据我们决定的轴顺序,转置数组所有平面的实际命令将有所不同:对于通用数组,它交换索引1和2,对于RGB图像,它交换0和1:有趣的是,(也是唯一的operationmode)默认的axes参数颠倒索引顺序,不符合以上两种索引顺序约定。最后,还有一个函数可以在处理多维数组时省去很多Python循环,让代码更简洁,那就是爱因斯坦求和函数einsum:它会沿着索引重复的数组求和。最后,如果你想精通NumPy,可以去GitHub上的项目——100个NumPy练习来验证你的学习成果。
