机器之心报道
编辑:魔王、杜伟、小舟
NumPy团队撰写了一篇综述文章,介绍NumPy的发展过程、主要特性和数组编程等。这篇文章现已发表在Nature上。
NumPy是什么?它是大名鼎鼎的使用Python进行科学计算的基础软件包,是Python生态系统中数据分析、机器学习、科学计算的主力军,极大简化了向量与矩阵的操作处理。除了计算外,它还包括了:
功能强大的N维数组对象。
精密广播功能函数。
集成C/C++和Fortran代码的工具。
强大的线性代数、傅立叶变换和随机数功能
今日,NumPy核心开发团队的论文终于在Nature上发表,详细介绍了使用NumPy的数组编程(Arrayprogramming)。这篇综述论文的发表距离NumPy诞生已经过去了15年。
NumPy官方团队在Twitter上简要概括了这篇论文的核心内容:
NumPy为数组编程提供了简明易懂、表达力强的高级API,同时还考虑了维持快速运算的底层机制。
NumPy提供的数组编程基础和生态系统中的大量工具结合,形成了适合探索性数据分析的完美交互环境。NumPy还包括增强与PyTorch、Dask和JAX等外部库互操作性的协议。
基于这些特性,NumPy为张量计算提供了标准的API,成为Python中不同数组技术之间的核心协调机制。
接下来,我们来看这篇NumPy综述论文的详细内容。
论文摘要
数组编程为访问、操纵和计算向量、矩阵和高维数组中的数据提供了功能强大、紧凑且表达力强的语法。NumPy是Python语言的主要数组编程库,它在物理、化学、天文学、地球科学、生物学、心理学、材料科学、工程学、金融和经济学等领域的研究分析中都起着至关重要的作用。例如,在天文学中,NumPy是发现引力波和黑洞首次成像的软件栈中的重要部分。
这篇论文回顾了一些基本的数组概念,以及它们如何形成一种简单而强大的编程范式,使其能够用于组织、探索和分析科学数据。NumPy是构建科学Python生态系统的基础。它的应用十分普遍,一些面向特殊需求受众的项目已经开发出自己的类NumPy接口和数组对象。
由于其在Python生态系统中的核心地位,NumPy越来越多地充当数组计算库之间的互操作层,并且和其API一起提供了灵活的框架,以支持未来十年的科学和工业分析。
NumPy的演变史
在NumPy之前,已经出现了两个Python数组包。Numeric包开发于20世纪90年代中期,它提供了Python中的数组对象和array-aware函数。Numeric是用C语言写的,并链接到线性代数的标准快速实现。其最早的应用之一是美国劳伦斯利弗莫尔国家实验室的惯性约束核聚变研究。
为了处理来自哈勃太空望远镜的大型天文图像,Numeric被重实现为Numarray,它添加了对结构化数组、灵活indexing、内存映射、字节序变体、更高效的内存使用以及更好的类型转换规则的支持。
尽管Numarray与Numeric高度兼容,但这两个包之间的差异足以将社区开发者分为两类。而年,NumPy的出现完美地统一了这两个包,它将Numarray的功能和Numeric的small-array性能及其丰富的CAPI结合起来。
如今,15年过去了,NumPy几乎支持所有进行科学和数值计算的Python库(包括SciPy、Matplotlib、pandas、scikit-learn和scikit-image)。NumPy是一个社区开发的开源库,它提供了多维Python数组对象以及对其进行操作的array-aware函数。由于其固有的简洁性,事实上NumPy数组已经成为Python中数组数据的交换格式。
NumPy使用CPU对内存内(in-memory)数组进行操作。为了利用现代的专用存储和硬件,最近已经扩展出一系列Python数组包。与Numarray–Numeric之间存在较大差异的情况不同,现在的这些新库很难在社区开发者中引起分歧,因为它们都是建立在NumPy之上的。但是,为了使社区能够使用新的探索性技术,NumPy正在过渡为核心协调机制,该机制规划了良好定义的数组编程API,并在合适的时候将其分发给专门的数组实现。
NumPy数组
NumPy数组是一种能够高效存储和访问多维数组的数据结构,支持广泛类型的科学计算。NumPy数组包括指针和用于解释存储数据的元数据,即datatype(数据类型)、shape(形状)和strides(步幅),参见下图1a。
图1:NumPy数组包括多种基础数组概念。
数据类型描述了数组中存储元素的本质。一个数组只有一个数据类型,数组中的每个元素在内存中占用的字节数是一样的。数据类型包括实数、复数、字符串、timestamp和指针等。
数组的形状决定了每个轴上的元素数量,轴的数量即为数组的维数。例如,数字向量可存储为形状为N的一维数组,而彩色视频是形状为(T,M,N,3)的四维数组。
步幅是解释计算机内存的必要组件,它可以线性地存储元素。步幅描述了在内存中逐行逐列移动时所需的字节数。例如,形状为(4,3)的二维浮点数数组,它其中的每个元素均在内存中占用8个字节数。要想在连续列之间移动,我们需要在内存中前进8个字节数,要想到达下一行,则需要前进3×8=24个字节数。因此该数组的步幅为(24,8)。NumPy可以用C或Fortran的内存顺序存储数组,沿着行或列遍历。这使得使用这些语言写的外部库可以直接访问内存中的NumPy数组数据。
用户使用「indexing」(访问子数组或单个元素)、「operators」(各种运算符)和「array-awarefunction」与NumPy数组进行交互。它们为NumPy数组编程提供了简明易懂、表达力强的高级API,同时还考虑了维持快速运算的底层机制。
对数组执行indexing将返回单个元素、子数组或满足特定条件的元素(参见上图1b)。数组甚至还可以用其他数组进行indexing(参加图1c)。返回子数组的indexing还可以返回原始数组的「view」,以便在两个数组之间共享数据。这就为内存有限的情况下基于数组数据子集进行运算提供了一种强大的方式。
为了补充数组语法,NumPy还包括对数组执行向量化计算的函数,包括arithmetic、statistics和trigonometry(参见图1d)。向量化计算基于整个数组运行而不是其中的单个元素,这对于数组编程而言是必要的。这意味着,在C等语言中需要几十行才能表达的运算在这里只需一个清晰的Python表达式即可实现。这就带来了简洁的代码,并使得用户不必