什么是字符串数组,什么是字符叠加器

  

  机器心脏报告   

  

  编辑:魔王、杜伟、小舟   

  

  NumPy团队写了一篇评测文章,介绍了NumPy的开发过程、主要特性和数组编程。这篇文章现已发表在《自然》杂志上。   

  

  什么是NumPy?它是知名的使用Python进行科学计算的基础软件包,是Python生态系统中数据分析、机器学习和科学计算的主力军,大大简化了向量和矩阵的运算和处理。除计算外,还包括:   

  

  强大的N维数组对象。   

  

  精准播报功能。   

  

  一个集成C/C和Fortran代码的工具。   

  

  线性代数,傅立叶变换,随机数的强大功能。   

  

  今天,NumPy核心开发团队的论文终于发表在《自然》杂志上,详细介绍了使用NumPy的数组编程。这篇综述论文在NumPy诞生至今,已经过去了15年。   

  

     

  

  NumPy官方团队在推特上简要总结了这篇论文的核心内容:   

  

  NumPy为数组编程提供了一个易于理解和表达的高级API,同时也考虑了底层机制来维持快速运行。   

  

  NumPy提供的数组编程基础,结合生态系统中的大量工具,形成一个适合探索性数据分析的完美交互环境。NumPy还包括增强与外部库互操作性的协议,如PyTorch、Dask和JAX。   

  

  基于这些特点,NumPy提供了张量计算的标准API,成为Python中不同数组技术间的核心协调机制。   

  

  接下来,我们来看看这篇NumPy评论论文的细节。   

  

  论文摘要   

  

  数组编程为访问、操作和计算向量、矩阵和高维数组中的数据提供了强大、紧凑和富有表现力的语法。NumPy是Python语言的主要数组编程库。它在物理、化学、天文学、地球科学、生物学、心理学、材料科学、工程学、金融和经济学等领域的研究和分析中发挥着至关重要的作用。例如,在天文学中,NumPy是发现引力波和首次对黑洞成像的软件栈的重要组成部分。   

  

  本文回顾了一些基本的数组概念,以及它们如何形成一个简单而强大的编程范式,可用于组织、探索和分析科学数据。NumPy是构建科学Python生态系统的基础。它的应用非常普遍,一些有特殊需求的项目开发了自己的类似NumPy的接口和数组对象。   

  

  由于其在Python生态系统中的核心地位,NumPy越来越多地充当数组计算库之间的互操作层,并与其API一起提供了一个灵活的框架,以支持未来十年的科学和工业分析。   

  

  NumPy 的演变史   

  

  在NumPy之前,已经出现了两个Python数组包。数字软件包是在20世纪90年代中期开发的。它在Python中提供了数组对象和数组感知函数。Numeric是用C语言编写的,并链接到线性代数的标准快速实现。其最早的应用之一是劳伦斯利弗莫尔国家实验室的惯性约束聚变研究。   

  

  为了处理来自哈勃太空望远镜的大型天文图像,Numeric被重新实现为Numarray,它增加了对结构化数组、灵活索引、内存映射、字节顺序变体、更有效的内存使用和更好的类型转换规则的支持。   

  

  虽然Numarray与Numeric高度兼容,但这两个包的区别足以将社区开发者分为两类。2005年,NumPy的出现完美地统一了两个包,将Numarray的功能与Numeric的小数组性能及其丰富的C API结合在一起。   

  

  15年后的今天,NumPy支持几乎所有执行科学和数值计算的Python库(包括SciPy、Matplotlib、pandas、scikit-learn和scikit-image)。NumPy是一个由社区开发的开源库。它提供了多维Python数组对象和数组感知函数来操作它们。NumPy数组由于其固有的简单性,实际上已经成为Python中数组数据的交换格式。   

  

  NumPy使用CPU对内存中的数组进行操作。为了利用现代专用存储和硬件,最近扩展了一系列Python数组包。不像Numarray-Numeric之间差别很大的情况,这些新库很难引起社区开发者的分歧,因为它们都是基于NumPy的。然而,为了使社区能够使用新的探索性技术,NumPy正在过渡到核心协调机制,该机制计划一个定义良好的阵列编程API,并在适当的时候将其分发给专门的阵列实现。   

  

  NumPy 数组   

  

  NumPy数组是一种可以高效存储和访问多维数组的数据结构,支持广泛的科学计算。NumPy数组包括用于解释存储数据(即dat)的指针和元数据。   

a type(数据类型)、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-aware function」与 NumPy 数组进行交互。它们为 NumPy 数组编程提供了简明易懂、表达力强的高级 API,同时还考虑了维持快速运算的底层机制。

  

对数组执行 indexing 将返回单个元素、子数组或满足特定条件的元素(参见上图 1b)。数组甚至还可以用其他数组进行 indexing(参加图 1c)。返回子数组的 indexing 还可以返回原始数组的「view」,以便在两个数组之间共享数据。这就为内存有限的情况下基于数组数据子集进行运算提供了一种强大的方式。

  

为了补充数组语法,NumPy 还包括对数组执行向量化计算的函数,包括 arithmetic、statistics 和 trigonometry(参见图 1d)。向量化计算基于整个数组运行而不是其中的单个元素,这对于数组编程而言是必要的。这意味着,在 C 等语言中需要几十行才能表达的运算在这里只需一个清晰的 Python 表达式即可实现。这就带来了简洁的代码,并使得用户不必关注分析细节,同时 NumPy 以接近最优的方式循环遍历数组元素。

  

对两个形状相同的数组执行向量化计算(如加法)时,接下来会发生什么是很明确的。而「broadcasting」机制允许 NumPy 处理维度不同的数组之间的运算,例如向数组添加一个标量值。broadcasting 还能泛化至更复杂的示例,如缩放数组的每一列或生成坐标网格。在 broadcasting 中,单个或两个数组可以重叠(没有从内存中复制任何数据),使得 operands 的形状匹配(参见图 1d)。

  

其他 array-aware function(如加、求平均值、求最大值)都是执行逐元素的「reduction」,累积单个数组的一个、多个或所有轴上的结果。例如,将一个 n 维数组与 d 个轴进行累加,得到维度为 n d 的数组(参见图 1f)。

  

NumPy 还包含可以创建、reshaping、concatenating 和 padding 数组,执行数据排序和计数,读取和写入文件的 array-aware function。这为生成伪随机数提供了大量支持,它还可以使用 OpenBLAS 或 Intel MKL 等后端执行加速线性代数。

  

总之,内存内的数组表示、紧密贴近数学的语法和多种 array-aware function 共同构成了生产力强、表达力强的数组编程语言。

  

科学 Python 生态系统

  

Python 是一个开源、通用的解释型编程语言,非常适合数据清洗、与 web 资源交互和解析文本之类的标准编程任务。添加快速数组操作和线性代数能够让科学家在一种编程语言中完成所有的工作。

  

尽管 NumPy 不是 Python 标准库的一部分,但它也从与 Python 开发者的良好关系中受益。在过去这些年中,Python 语言已经加入了一些新的功能和特殊的语法,以便 NumPy 具备更加简洁和易于阅读的数组表示法。但是,由于 NumPy 不是 Python 标准库的一部分,所以它能够规定自己的发布策略和开发模式。

  

从发展史、开发和应用的角度来看,SciPy 和 Matplotlib 与 NumPy 联系紧密。SciPy 为科学计算提供了基础算法,包括数学、科学和工程程序。Matplotlib 生成可发表品质的图表和可视化文件。NumPy、SciPy 和 Matplotlib 的结合,再加上 IPython、Jupyter 这类高级交互环境,为 Python 中的数组编程提供了坚实的基础。

  

如图 2 所示,科学 Python 生态系统建立在上述基础之上,它提供了多种广泛应用的专有技术库,而这又是众多领域特定项目的基础。NumPy 是这一 array-aware 库生态系统的基础,它设置了文档标准、提供了数组测试基础结构,并增加了对 Fortran 等编译器的构建支持。

  

  

图 2:NumPy 是科学 Python 生态系统的基础。

  

很多研究团队设计出大型、复杂的科学库,这些库为 Python 生态系统增添了特定于具体应用的功能。例如,由事件视界望远镜(Event Horizon Telescope, EHT)合作项目开发的 eht-imaging 库依赖科学 Python 生态系统的很多低级组件。而 EHT 合作项目利用该库捕获了黑洞的首张图像。

  

在 eht-imaging 库中,NumPy 数组在流程链的每一步存储和操纵数值数据。

  

基于数组编程创建的交互式环境及其周边的工具生态系统(IPython 或 Jupyter 内部)完美适用于探索性数据分析。用户可以流畅地检查、操纵和可视化他们的数据,并快速迭代以改善编程语句。然后,将这些语句拼接入命令式或函数式程序,或包含计算和叙述的 notebook。

  

超出探索性研究的科学计算通常在文本编辑器或 Spyder 等集成开发环境(IDE)中完成。这一丰富和高产的环境使 Python 在科学研究界流行开来。

  

为了给探索性研究和快速原型提供补充支持,NumPy 形成了使用经过时间检验的软件工程实践来提升协作、减少误差的文化。这种文化不仅获得了项目领导者的采纳,而且还被传授给初学者。NumPy 团队很早就采用分布式版本控制和代码审查机制来改善代码协同,并使用持续测试对 NumPy 的每个提议更改运行大量自动化测试。

  

这种使用最佳实践来制作可信赖科学软件的文化已经被基于 NumPy 构建的生态系统所采用。例如,在近期英国皇家天文学会授予 Astropy 的一项奖项中表示:「Astropy 项目为数百名初级科学家提供了专业水平的软件开发实践,包括版本控制使用、单元测试、代码审查和问题追踪程序等。这对于现代研究人员而言是一项重要的技能组合,但物理或天文学专业的正规大学教育却常常忽略这一点。」社区成员通过课程和研讨会来弥补正规教育中的这一缺失。

  

近来数据科学、机器学习和人工智能的快速发展进一步大幅提升了 Python 的科学使用。Python 的重要应用,如 eht-imaging 库,现已存在于自然和社会科学的几乎每个学科之中。这些工具已经成为很多领域主要的软件环境。大学课程、新手培训营和暑期班通常教授 NumPy 及其生态系统,它们也成为世界各地社区会议和研讨会的焦点。NumPy 和它的 API 已经无处不在了。

  

数组激增和互操作性

  

NumPy 在 CPU 上提供了内存内、多维和均匀键入(即单一指向和跨步的)的数组。NumPy 可以在嵌入式设备和世界上最大的超级计算机等机器上运行,其性能接近编译语言。在大多数情况下,NumPy 解决了绝大部分的数组计算用例。

  

但是现在,科学数据集通常超出单个机器的存储容量,并且可以在多个机器或云上存储。此外,近来深度学习和人工智能应用的加速需求已经促生了专用加速器硬件,包括 GPU、TPU 和 FPGA。目前,由于 NumPy 具有的内存内数据模型,它无法直接使用这类存储和专用硬件。

  

然而,GPU、TPU 和 FPGA 的分布式数据和并行执行能够很好地映射到数组编程范式,所以可用的现代硬件架构与利用它们的计算能力所必需的工具之间存在着差距。

  

社区为弥补这一差距做出的努力使得新的数组实现激增。例如,每个深度学习框架都创建了自己的数组。PyTorch、TensorFlow、Apache MXNet 和 JAX 数组都有能力以分布式方式在 CPU 和 GPU 上运行,其中使用惰性计算(lazy evaluation)实现额外性能优化。SciPy 和 PyData/Sparse 都提供有稀疏数组,这些数组通常包含很少的非零值,并只在内存中存储以提升效率。

  

此外,还有一些项目在 NumPy 数组上构建作为数据容器,并扩展相应功能。Dask 通过这种方式使分布式数组成为可能,而标记数组是通过 xarray 实现的。

  

这类库常常模仿 NumPy API,以降低初学者准入门槛,并为更广泛的社区提供稳定的数组编程接口。这反过来也会阻止一些破坏性分立(disruptive schism),如 Numeric 和 Numarray 之间的差异。

  

但是探索使用数组的新方法从本质上讲是试验性的,事实上,Theano 和 Caffe 等一些有前途的库已经停止了开发。每当用户决定尝试一项新技术时,他们必须更改 import 语句,并确保新库能够实现他们当前使用的所有 NumPy API 部件。

  

在理想状态下,用户可以通过 NumPy 函数或语义在专用数组上进行操作,这样他们可以编写一次代码,然后从 NumPy 数组、GPU 数组、分布式数组以及其他数组之间的切换中获益。为了支持外部数组对象之间的数组操作,NumPy 增加了一项充当核心协调机制的功能,并提供指定的 API,具体如上图 2 所示。

  

为了促进这种互操作性,NumPy 提供了允许专用数组传递给 NumPy 函数的「协议」,具体如下图 3 所示。反过来,NumPy 根据需要将操作分派给原始库。超过 400 个最流行的 NumPy 函数得到了支持。该协议通过 Dask、CuPy、xarray 和 PyData/Sparse 等广泛使用的库来实现。

  

得益于这些进展,用户现在可以使用 Dask 将自己的计算从单个机器扩展至多个系统。该协议允许用户通过 Dask 数组中嵌入的 CuPy 数组等,在分布式多 GPU 系统上大规模地重新部署 NumPy 代码。

  

使用 NumPy 的高级 API,用户可以在具有数百万个核的多系统上利用高度并行化的代码执行,并且需要的代码更改最少。

  

如下图 3 所示,NumPy 的 API 和数组协议向生态系统提供了新的数组:

  

  

现在,这些数组协议是 NumPy 的主要特征,它们的重要性预计也会越来越大。NumPy 开发者(很多也是这篇文章的作者)迭代地改善和增加协议设计,以改进实用性和简化应用方式。

  

论文最后对 NumPy 的现状和未来进行了总结和展望:

  

在未来十年中,NumPy 开发者将面临多项挑战。新设备将出现,现有的专用硬件将面临摩尔定律的收益递减,数据科学从业者将越来越多,类型也更加广泛。而他们中的大部分将使用 NumPy。

  

随着光片显微镜和大型综合巡天望远镜(LSST)等设备和仪器的采用,科学数据的规模将持续扩大。新一代语言、解释器和编译器,如 Rust、Julia 和 LLVM,将创造出新的概念和数据结构。

相关文章