数组编程为访问、操作和操作矢量、矩阵和高维数组中的数据提供了强大、紧凑和富有表现力的语法。NumPy是Python语言的主要数组编程库。它在物理、化学、天文学、地学、生物学、心理学、材料科学、工程、金融和经济等不同领域的研究分析管道中发挥着至关重要的作用。例如,在天文学中,NumPy是用于发现引力波1和首次对黑洞成像2的软件堆栈的重要组成部分。在这里,我们回顾几个基本的阵列概念如何导致一个简单而强大的编程范例,用于组织、探索和分析科学数据。NumPy是构建科学Python生态系统的基础。它是如此普及,以至于有几个针对有特殊需求的受众的项目已经开发了它们自己的类似NumPy的接口和数组对象。由于NumPy在生态系统中的中心地位,NumPy越来越多地充当此类阵列计算库之间的互操作性层,并与其应用程序编程接口(API)一起提供灵活的框架,以支持下一个十年的科学和工业分析。
在NumPy之前存在两个Python数组包。Numerical包是在20世纪90年代中期开发的,它用Python提供数组对象和数组感知函数。它是用C语言编写的,并链接到线性代数3,4的标准快速实现。它最早的用途之一是引导C++应用程序用于劳伦斯·利弗莫尔国家实验室5号的惯性约束聚变研究。为了处理来自哈勃太空望远镜的大型天文图像,Numarray的重新实现Numarray增加了对结构化数组、灵活索引、内存映射、字节顺序变体、更高效的内存使用、灵活的IEEE 754标准错误处理能力和更好的类型转换的支持。然而,在2005年,NumPy成为了“两个世界统一的最佳7”--将Numarray的特性与Numeric的小数组性能及其丰富的CAPI相结合。
15年后的今天,NumPy几乎支撑了所有进行科学或数值计算的Python库8、9、10、11,包括SciPy 12、Matplotlib 13、Pandas 14、SCRICKIT-LEARN 15和SCRICKIT-IMAGE 16。NumPy是一个社区开发的开放源代码库,它提供了一个多维Python数组对象,以及在其上操作的数组感知函数。由于其固有的简单性,NumPy数组实际上是Python中数组数据的交换格式。
NumPy使用中央处理单元(CPU)对内存中的阵列进行操作。为了利用现代的、专门的存储和硬件,最近出现了大量的Python数组包。与Numarray和Numerical的划分不同的是,现在这些新的库很难分裂用户社区--因为已经在NumPy之上构建了大量的工作。但是,为了向社区提供对新技术和探索性技术的访问,NumPy正在过渡到一个中央协调机制,该机制指定一个定义良好的数组编程API,并根据需要将其分派给专门的数组实现。
NumPy数组是有效地存储和访问多维数组17(也称为张量)的数据结构,并且能够进行各种科学计算。它包括一个指向内存的指针,以及用于解释存储在那里的数据的元数据,特别是“数据类型”、“形状”和“跨度”(图2)。(1A)。
A、NumPy数组数据结构及其关联元数据字段。B.使用切片和步骤对数组进行索引。这些操作返回原始数据的“视图”。C,用掩码、标量坐标或其他数组索引数组,以便它返回原始数据的“副本”。在最下面的示例中,一个数组与其他数组一起编制索引;这会在执行查找之前广播索引参数。D,向量化有效地将运算应用于元素组。E、二维阵列乘法中的广播。F,约简操作沿一个或多个轴进行。在此示例中,数组沿选择的轴求和以产生向量,或沿两个轴连续求和以产生标量。G,示例NumPy代码,说明其中一些概念。
数据类型描述数组中存储的元素的性质。数组只有一种数据类型,并且数组的每个元素在内存中占用相同数量的字节。数据类型的示例包括实数和复数(精度较低和较高)、字符串、时间戳和指向Python对象的指针。
数组的形状确定沿每个轴的元素数量,以及
要将线性存储元素的计算机内存解释为多维数组,跨度是必需的。它们描述了在内存中向前移动以从一行跳到行、从列跳到列的字节数,依此类推。例如,考虑形状为(4,3)的浮点数的二维数组,其中每个元素占用内存中的8个字节。要在连续的列之间移动,我们需要在内存中向前跳转8兆字节,并访问下一行,3兆字节×8兆字节=224兆字节。因此,该数组的跨度为(24,08)。NumPy可以按C或Fortran内存顺序存储数组,首先对行或列进行迭代。这允许用这些语言编写的外部库直接访问内存中的NumPy数组数据。
用户使用“索引”(访问子数组或单个元素)、“运算符”(例如,+、−和×表示向量化操作,@表示矩阵乘法)以及“数组感知函数”与NumPy数组交互;这些函数共同为数组编程提供了一个易于阅读、可表达的高级API,而NumPy则处理快速操作的基本机制。
索引数组将返回满足特定条件的单个元素、子数组或元素(图2)。(1B)。数组甚至可以使用其他数组进行索引(图2。1C)。只要有可能,检索子数组的索引就会返回原始数组的“视图”,以便在两个数组之间共享数据。这提供了一种在限制内存使用的同时对阵列数据子集进行操作的强大方式。
为了补充数组语法,NumPy包括对数组执行矢量化计算的函数,包括算术、统计和三角(图2)。1d)。矢量化-对整个数组而不是对其单个元素进行操作-对于数组编程至关重要。这意味着在C等语言中需要数十行代码才能表达的操作通常可以实现为一个清晰的Python表达式。这会产生简洁的代码,使用户能够专注于分析的细节,而NumPy则以近乎最佳的方式处理数组元素的循环-例如,考虑跨度以最大限度地利用计算机的高速缓存内存。
在对两个形状相同的数组执行向量化操作(如加法)时,应该发生什么是很清楚的。通过“广播”,NumPy允许维度不同,并产生吸引直觉的结果。一个微不足道的例子是向数组添加标量值,但是广播也可以推广到更复杂的例子,比如缩放数组的每一列或生成坐标网格。在广播中,一个或两个数组被虚拟复制(即,不复制存储器中的任何数据),使得操作数的形状匹配(图2)。1d)。当使用索引数组对数组进行索引时,也可以应用广播(图2)。1C)。
其他数组感知函数,如SUM、Mean和Maximum,执行逐个元素的“缩减”,跨单个数组的一个、多个或所有轴聚合结果。例如,对d个轴上的n维数组求和得到维数为n的数组−和d(图2)。1F)。
NumPy还包括数组感知函数,用于创建、整形、连接和填充数组;搜索、排序和计数数据;以及读取和写入文件。它为生成伪随机数提供广泛支持,包括各种概率分布,并使用几个后端之一(如OpenBLAS 18、19或针对手头CPU优化的英特尔MKL)执行加速线性代数(有关更多详细信息,请参阅补充方法)。
总之,简单的内存中数组表示、紧密模仿数学的语法和各种支持数组的实用函数的组合形成了一种高效且具有强大表现力的数组编程语言。
Python是一种开源的通用解释型编程语言,非常适合标准编程任务,如清理数据、与Web资源交互和解析文本。添加快速数组运算和线性代数使科学家能够在一种编程语言中完成所有工作-这种编程语言的优势是非常容易学习和教授,许多大学采用这种编程语言作为主要学习语言就证明了这一点。
尽管NumPy不是Python标准库的一部分,但它得益于与Python开发人员的良好关系。多年来,Python语言添加了新特性和特殊语法,以便NumPy拥有更简洁、更易于阅读的数组表示法。但是,因为NumPy不是标准库的一部分,所以它能够规定自己的发布策略和开发模式。
SciPy和Matplotlib与NumPy在历史、发展和使用上是紧密耦合的。SciPy为科学计算提供基本算法,包括数学、科学和工程例程。Matplotlib生成可供发布的图形和可视化效果。NumPy、SciPy和Matplotlib的组合,再加上IPython20或Jupyter 21等高级交互环境,为Python中的数组编程提供了坚实的基础。科学蟒蛇生态系统(图2)。2)在此基础上构建以提供几个广泛使用的特定于技术的库15、16、22,这些库又构成许多特定于领域的项目23、24、25、26、27、28的基础。NumPy是数组感知库生态系统的基础,它设置了文档标准,提供了数组测试基础设施,并增加了对Fortran和其他编译器的构建支持。
依赖于NumPy的API的必需库和项目可以访问支持NumPy的数组协议的新数组实现(图2)。3)。
许多研究小组设计了大型、复杂的科学库,为生态系统添加了特定于应用程序的功能。例如,由Event Horizon Telescope Collaboration开发的用于无线电干涉成像、分析和模拟的EHT成像库29依赖于Python科学生态系统的许多较低级别的组件。特别值得一提的是,EHT合作小组利用这个库首次对黑洞进行成像。在EHT成像中,NumPy阵列用于存储和操作处理链中的每一步的数字数据:从原始数据到校准和图像重建。SciPy提供了用于一般图像处理任务(如过滤和图像对齐)的工具,而SCRICIT-IMAGE(扩展了SciPy的图像处理库)提供了更高级别的功能,如边缘过滤器和霍夫变换。“scipy.Optimize”模块执行数学优化。用于复杂网络分析的软件包NetworkX 22用于验证图像比较一致性。Astropy 23、24处理标准天文文件格式并计算时间坐标转换。Matplotlib用于可视化数据并生成黑洞的最终图像。
由编程基础阵列和周围的工具生态系统创建的交互环境-在IPython或Jupyter内部-非常适合探索性数据分析。用户可以流畅地检查、操作和可视化他们的数据,并快速迭代以改进编程语句。然后将这些语句缝合成命令式或函数式程序,或者包含计算和叙述的笔记本。除了探索性工作之外,科学计算通常是在文本编辑器或集成开发环境(IDE)(如Spyder)中完成的。这种丰富而富有成效的环境让Python在科学研究中大行其道。
为了补充这一用于探索性工作和快速原型制作的设施,NumPy发展了一种使用经过时间考验的软件工程实践来改善协作和减少错误30的文化。这种文化不仅被项目领导采纳,而且还热情地传授给新人。NumPy团队很早就采用了分布式修订控制和代码审查来改进代码协作,并对NumPy的每个提议更改运行大量自动化测试的持续测试。该项目还拥有全面、高质量的文档,并集成了源代码31、32、33。
这种使用最佳实践来生产可靠的科学软件的文化已经被建立在NumPy之上的图书馆生态系统所采用。例如,在皇家天文学会最近授予Astropy的一项奖励中,他们指出:“Astropy项目为数百名初级科学家提供了专业标准软件开发实践方面的经验,包括使用版本控制、单元测试、代码审查和问题跟踪程序。对于现代研究人员来说,这是一项至关重要的技能,
最近数据科学、机器学习和人工智能的快速增长进一步戏剧性地推动了Python的科学使用。其重要应用的例子,例如EHT成像库,现在几乎存在于自然科学和社会科学的每一门学科中。这些工具已经成为许多领域的主要软件环境。NumPy及其生态系统通常在大学课程、新兵训练营和暑期学校授课,是世界各地社区会议和研讨会的焦点。NumPy及其API已经变得无处不在。
NumPy在CPU上提供内存中的多维同构类型(即单指针和跨距)数组。它运行在从嵌入式设备到世界上最大的超级计算机的各种机器上,性能接近编译语言。在其存在的大部分时间里,NumPy解决了绝大多数数组计算用例。
然而,科学数据集现在通常会超过一台机器的内存容量,可能会存储在多台机器上,也可能存储在云中。此外,最近加速深度学习和人工智能应用的需要导致了专用加速器硬件的出现,包括图形处理单元(GPU)、张量处理单元(TPU)和现场可编程门阵列(FPGA)。由于其内存数据模型,NumPy目前无法直接利用这种存储和专用硬件。然而,分布式数据以及GPU、TPU和FPGA的并行执行都很好地映射到阵列编程的范例:因此,导致可用的现代硬件架构与利用其计算能力所需的工具之间存在差距。
社区为填补这一空白所做的努力导致了新阵列实现的激增。例如,每个深度学习框架都创建了自己的阵列;PyTorch 38、TensorFlow 39、Apache MXNet 40和JAX阵列都能够以分布式方式在CPU和GPU上运行,并使用惰性评估来实现额外的性能优化。SciPy和PyData/Sparse都提供稀疏数组,这些稀疏数组通常包含很少的非零值,并且为了提高效率,只将这些值存储在内存中。此外,还有一些项目将NumPy数组构建为数据容器,并扩展其功能。分布式数组是通过Dask实现的,并通过xarray 41标记数组-为了清楚起见,按名称而不是按索引引用数组的维度,将x[:,1t1]与x.loc[:,939;time';]进行比较。
这类库通常模仿NumPy API,因为这降低了新手进入的门槛,并为更广泛的社区提供了稳定的编程接口阵列。这反过来又防止了破坏性的分裂,如数字和数字阵列之间的分歧。但是探索使用数组的新方法本质上是实验性的,事实上,几个很有前途的库(如Theano和Caffe)已经停止了开发。每次用户决定尝试一项新技术时,他们都必须更改import语句,并确保新的库实现了他们当前使用的NumPy API的所有部分。
理想情况下,使用NumPy函数或语义对专用数组进行操作会很简单,这样用户只需编写一次代码,然后就可以根据需要在NumPy数组、GPU数组、分布式数组等之间进行切换。为了支持外部数组对象之间的数组操作,NumPy因此增加了使用明确指定的API(图2)充当中央协调机制的功能。2)。
为了促进这种互操作性,NumPy提供了“协议”(或操作合同),允许将专门的数组传递给NumPy函数(图2)。3)。NumPy则根据需要将操作分派到原始库。支持400多个最流行的NumPy函数。这些协议由广泛使用的库实现,如Dask、CuPy、xarray和PyData/Sparse。例如,多亏了这些发展,用户现在可以使用Dask将他们的计算从单机扩展到分布式系统。这些协议也很好地组合在一起,允许用户在分布式的多GPU系统上大规模地重新部署NumPy代码,例如,通过嵌入到Dask数组中的CuPy数组。使用NumPy的高级API,用户可以在具有数百万核的多个系统上利用高度并行的代码执行,所有这些都只需最少的代码更改42。
在本例中,对Dask数组调用NumPy的“Mean”函数。调用通过调度到适当的库实现(在本例中为Dask)而成功,并产生一个新的Dask数组。将此代码与图中的示例代码进行比较。1g.
这些阵列协议现在是NumPy的一个关键功能,预计其重要性只会增加。NumPy开发人员-其中许多人是这篇评论的作者-迭代地改进和添加协议设计,以提高实用性和简化采用。
NumPy将数组编程的表现力、C的性能以及Python的可读性、可用性和多功能性结合在一个成熟的、经过良好测试、有良好文档记录和社区开发的库中。科学Python生态系统中的库提供最重要算法的快速实现。在需要极端优化的地方,可以使用编译语言,比如Cython43、Numba44和Pythran 45;这些语言扩展了Python并透明地加速了瓶颈。由于NumPy的简单内存模型,很容易编写低级的、手工优化的代码,通常是用Co。
.