NumPy 中文文档

version: 1.14.0

Built with Teadocs

NumPy 内部

# NumPy 内部结构

# 目录

# Numpy数组的内部组织结构

它有助于理解一下如何处理numpy数组,以帮助更好地理解numpy。本节不会详细介绍。那些希望了解完整细节的人可以参考Travis Oliphant的书 “NumPy指南”。

NumPy数组由两个主要组件组成,即原始数组数据(从现在开始,称为数据缓冲区),以及有关原始数组数据的信息。 数据缓冲区通常是人们认为是C或Fortran中的数组,这是一个包含固定大小数据项的连续(和固定)内存块。 NumPy还包含一组重要的数据,用于描述如何解释数据缓冲区中的数据。 这些额外信息包含(除其他外):

  1. 基本数据元素的大小(以字节为单位)
  2. 数据缓冲区内数据的开始(相对于数据缓冲区开头的偏移量)。
  3. 尺寸的数量和每个尺寸的大小
  4. 每个维度的元素之间的分离('步幅')。这不必是元素大小的倍数
  5. 数据的字节顺序(可能不是本机字节顺序)
  6. 缓冲区是否为只读
  7. 关于基本数据元素的解释的信息(通过dtype对象)。基本数据元素可以像int或float一样简单,或者它可以是复合对象(例如,类似struct),固定字符字段或Python对象指针。
  8. 数组是否被解释为C顺序或Fortran顺序。

这种安排允许非常灵活地使用数组阵列。它允许的一件事是简单地更改元数据以更改数组缓冲区的解释。更改数组的字节顺序是一个简单的更改,不涉及重新排列数据。可以非常轻松地更改阵列的形状,而无需更改数据缓冲区中的任何内容或任何数据复制。

除了可以做到的其他事情之外,可以创建一个新的数组元数据对象,该对象使用相同的数据缓冲区来创建该数据缓冲区的新视图,该视图具有不同的缓冲区解释(例如,不同的形状,偏移,字节顺序,大步等)但共享相同的数据字节。numpy中的许多操作都是这样做的,比如切片。其他操作(如转置)不会在数组中移动数据元素,而是更改有关形状和步幅的信息,以便更改数组的索引,但不会移动数据。

通常,这些新版本的数组元数据但相同的数据缓冲区是数据缓冲区中的新“视图”。有一个不同的ndarray对象,但它使用相同的数据缓冲区。这就是为什么有必要通过使用.copy() 方法强制复制的原因,如果真的想要创建一个新的独立的数据缓冲区副本。

对数组的新视图意味着数据缓冲区的对象引用计数增加。如果仍然存在其他视图,那么简单地取消原始数组对象将不会删除数据缓冲区。

# 多维数组索引顺序问题

索引多维数组的正确方法是什么? 在你得出关于索引多维数组的唯一和真实方法的结论之前,理解为什么这是一个令人困惑的问题是值得的。 本节将尝试详细解释numpy索引的工作原理以及为什么我们采用我们为图像做的约定,以及何时采用其他约定。

首先要理解的是,索引二维数组有两种相互矛盾的约定。矩阵表示法使用第一个索引来指示选择哪个行,使用第二个索引来指示选择哪个列。 这与图像的几何定向惯例相反,其中人们通常认为第一索引表示x位置(即列),第二索引表示y位置(即行)。 仅这一点就是混乱的根源; 面向矩阵的用户和面向图像的用户在索引方面需要两种不同的东西。

要理解的第二个问题是索引如何与数组存储在内存中的顺序相对应。 在Fortran中,当存储在存储器中时,第一个索引是通过二维数组的元素移动时变化最快的索引。 如果采用矩阵约定进行索引,则表示矩阵一次存储一列(因为第一个索引在更改时移动到下一行)。 因此,Fortran被认为是专栏主要语言。 C恰恰相反。 在C中,最后一个索引在存储在内存中的数组中移动时变化最快。 因此C是Row主要语言。 矩阵按行存储。 注意,在两种情况下,它都假定正在使用索引的矩阵约定,即,对于Fortran和C,第一个索引是行。 请注意,此约定意味着索引约定是不变的,并且数据顺序会更改以保持这一点。

但这不是看待它的唯一方法。 假设有一个存储在数据文件中的大型二维数组(图像或矩阵)。 假设数据是按行而不是按列存储的。 如果我们要保留我们的索引约定(无论是矩阵还是图像),这意味着取决于我们使用的语言,如果将数据读入内存以保留我们的索引约定,我们可能会被迫重新排序数据。 例如,如果我们将行排序数据读入内存而不重新排序,它将匹配C的矩阵索引约定,但不匹配Fortran。 相反,它将匹配Fortran的图像索引约定,但不匹配C.对于C,如果使用按行顺序存储的数据,并且想要保留图像索引约定,则在读入内存时必须重新排序数据。

最后,你为Fortran或C所做的取决于哪个更重要,而不是重新排序数据或保留索引约定。 对于大图像,重新排序数据可能很昂贵,并且通常会反转索引约定以避免这种情况。

numpy的情况使这个问题变得更加复杂。 numpy数组的内部机制足够灵活,可以接受任何索引排序。 人们可以通过操纵数组的内部步幅信息来简单地重新排序索引,而根本不重新排序数据。 NumPy将知道如何在不移动数据的情况下将新索引顺序映射到数据。

所以如果这是真的,为什么不选择符合你最期望的索引顺序呢?特别是,为什么不定义行排序图像以使用图像约定? (这有时被称为Fortran约定与C约定,因此'C'和'FORTRAN'顺序选项用于numpy中的数组排序。)这样做的缺点是潜在的性能损失。通常在数组操作中隐式地访问数据,或者通过循环遍历图像的行来显式地访问数据。完成后,将以非最佳顺序访问数据。随着第一个索引递增,实际发生的是在内存中间隔很远的元素被顺序访问,通常存储器访问速度很差。例如,对于定义的二维图像'im',使得im [0,10]表示x = 0处的值,y = 10。为了与通常的Python行为保持一致,im [0]将表示x = 0处的列。然而,由于数据按行顺序存储,因此数据将分布在整个阵列上。

尽管numpy的索引具有灵活性,但由于数据顺序或者连续的子数组仍然难以处理(例如,第一行的im [:, 0],vs im [0]),因此基本操作无效,因此无法使用像im中的行这样的习惯用法。 对于col中的col确实有效,但不会产生连续的列数据。

事实证明,numpy在处理ufuncs时足够聪明,以确定哪个索引是内存中变化最快的索引,并将其用于最内层循环。 因此,对于ufuncs,在大多数情况下,这两种方法都没有很大的内在优势。 另一方面,将.flat与FORTRAN有序数组一起使用将导致非最佳内存访问,因为展平数组中的相邻元素(实际上是迭代器)在内存中不是连续的。

实际上,事实是Python对列表和其他序列的索引自然会导致从外到内的排序(第一个索引获得最大的分组,下一个索引获得最大的分组,最后一个索引获得最小的元素)。 由于图像数据通常按行存储,因此这对应于行中最后一项索引的位置。

如果你确实想使用Fortran排序,请认识到有两种方法需要考虑:1)接受第一个索引不是内存中变化最快的,并且当从内存到磁盘时,所有I / O例程都会重新排序数据 反之亦然,或使用numpy的机制将第一个索引映射到最快速变化的数据。 如果可能,我们建议使用前者。 后者的缺点是,除非你小心使用'order'关键字,否则许多numpy的函数将生成没有Fortran排序的数组。 这样做非常不方便。

否则,我们建议在访问数组元素时学习反转索引的常规顺序。 当然,它与谷物相悖,但它更符合Python语义和数据的自然顺序。