打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
NumPy视觉指南

从头开始学习NumPy

> Image credit: Author

NumPy是一个基本库,受(PyTorch)的启发,大多数广泛使用的Python数据处理库都是基于(pandas)构建的,或者可以与(TensorFlow,Keras等)有效地共享数据。了解NumPy的工作方式也可以提高您在这些库中的技能。也可以在GPU¹上不做任何改动或进行最少改动即可运行NumPy代码。

NumPy的中心概念是n维数组。这样做的好处是,不管数组有多少维,大多数操作看起来都是一样的。但是一维和二维情况有些特殊。本文包括三个部分:

· 向量,一维数组

· 矩阵,二维数组

· 3D以上

我从JayAlammar²撰写了一篇很棒的文章' NumPy的视觉介绍'作为起点,大大扩展了其覆盖范围,并修改了一些细微差别。

Numpy数组与Python列表

乍一看,NumPy数组类似于Python列表。它们都可以用作容器,具有快速获取和设置项目以及较慢地插入和移除元素的功能。

NumPy数组拍子列表的简化示例是算术运算:

除此之外,NumPy数组是:

· 更紧凑,尤其是在一个以上的维度上

· 可以向量化操作时比列表快

· 将元素追加到末尾比列表慢

· 通常是同质的:只能快速处理一种类型的元素

这里的O(N)表示完成操作所需的时间与数组的大小成正比(请参见Big-O备忘单³网站),以及O *(1)(即所谓的'摊销'的O(1)))表示时间通常不取决于数组的大小(请参见Python时间复杂度wiki页面)

1.向量,一维数组

向量初始化

创建NumPy数组的一种方法是转换Python列表。该类型将从列表元素类型中自动推导:

确保输入同质的列表,否则您将得到dtype ='object',它会消除速度,只保留NumPy中包含的语法糖。

NumPy数组无法像Python列表那样增长:在数组末尾没有保留空间以方便快速追加。因此,通常的做法是增长一个Python列表,并在准备就绪时将其转换为NumPy数组,或者使用np.zeros或np.empty预先分配必要的空间:

通常需要创建一个空数组,以形状和元素类型匹配现有数组:

实际上,所有创建用常量值填充的数组的函数都具有_like对应项:

NumPy中有多达两个函数用于具有单调序列的数组初始化:

如果您需要类似[0.,1.,2.]的浮点数组,则可以更改arange输出的类型:arange(3).astype(float),但是有更好的方法。arange函数对类型敏感:如果将int作为参数输入,它将生成int,并且如果输入浮点数(例如arange(3。)),则将生成浮点数。

但是arange在处理浮点数方面并不是特别擅长:

这个0.1对我们来说似乎是一个有限的十进制数,但对计算机而言却不是:二进制,它是一个无穷小数,必须四舍五入到一个错误的地方。这就是为什么将小数部分加到范围的步通常不是一个好主意:您可能会遇到一个错误的错误。您可以使间隔的末尾落入非整数步数(解决方案1),但这会降低可读性和可维护性。这是linspace派上用场的地方。它不受舍入错误的影响,并始终生成您要求的元素数量。但是,linspace有一个常见的陷阱。它计算点,而不是间隔,因此最后一个参数始终是您通常会想到的加一个。因此它是11,而不是上面示例中的10。

为了进行测试,通常需要生成随机数组:

向量索引

一旦将数据存储在数组中,NumPy便会出色地提供简单的方法来将其取回:

除花式索引外,以上介绍的所有索引方法实际上都是所谓的'视图':它们不存储数据,并且如果原始数组在被索引后发生更改,则不会反映原始数组中的更改。

所有这些方法,包括花式索引,都是可变的:如上所述,它们允许通过分配来修改原始数组的内容。此功能通过切片来打破复制数组的习惯:

从NumPy数组中获取数据的另一种超级有用的方法是布尔索引,它允许使用各种逻辑运算符:

> any and all act just like their python peers, but don't short-circuit

小心点;3 <= a <= 5之类的Python'三元'比较在这里不起作用。

如上所述,布尔索引也是可写的。它有两个常见的用例,它们是专用功能:过度重载的np.where函数(请参阅下面的两种含义)和np.clip。

向量运算

算术是NumPy速度最耀眼的地方之一。向量运算符已转移到c ++级别,使我们避免了慢Python循环的代价。NumPy允许像普通数字一样操作整个数组:

As usual in Python, a//b means a div b (quotient from division), x**n means xⁿ

将加法或减法将int提升为浮点数的方式相同,将标量提升(也称为广播)至数组:

大多数数学函数都有NumPy对应项,可以处理矢量:

标量产品具有自己的运算符:

您也不需要三角函数的循环:

数组可以四舍五入为一个整体:

> floor rounds to -∞, ceil to +∞ and around — to the nearest integer (.5 to even)

名称np.around只是引入的np.round的别名,以避免从numpy import *写入时遮盖Python的回合(而不是更常见的导入numpy作为np)。您也可以使用a.round()。

NumPy还可以执行以下基本统计信息:

> Each of these functions has a nan-resistant variant: eg nansum, nanmax, etc

排序功能比Python对应功能具有更少的功能:

在一维情况下,可以通过反转结果来轻松地补偿反向关键字的缺失。在2D中,它有些棘手(功能要求⁵)。

搜索向量中的元素

与Python列表相反,NumPy数组没有索引方法。相应的功能请求⁶已经悬挂了一段时间。

> The square brackets in the definition of index() mean that either j or both i and j can be omitted

· 查找元素的一种方法是np.where(a == x)[0] [0],它既不优雅也不快速,因为它需要遍历数组的所有元素,即使要查找的项位于开始。

· 一种更快的方法是使用Numba⁷加速next((i的i [0],np中的v.ndenumerate(a),如果v == x),-1)(否则,在最坏的情况下,它的速度比)。

· 一旦对数组进行排序,情况就会变得更好:v = np.searchsorted(a,x);如果a [v] == x,则返回v,否则-1的复杂度为O(log N)确实非常快,但首先需要O(N log N)的时间才能排序。

实际上,通过在C中实现搜索来加速搜索不是问题。问题是浮点比较。这是一项对于任意数据开箱即用的工作。

比较浮点数

函数np.allclose(a,b)比较具有给定公差的浮点数组

> There is no silver bullet!

· np.allclose假定所有比较数字的典型比例为1。例如,如果使用纳秒,则需要将默认atol参数值除以1e9:np.allclose(1e-9,2e-9,atol = 1e-17)== False。

· math.isclose不对要比较的数字做任何假设,而是依靠用户提供一个合理的abs_tol值(采用默认的np.allclose atol值1e-8足以满足典型小数位数为1的数字):math.isclose(0.1 + 0.2–0.3,abs_tol = 1e-8)==真。

除此之外,np.allclose在绝对和相对公差的公式中还有一些小问题,例如,对于某些a,b allclose(a,b)!= allclose(b,a)。这些问题已在(标量)函数math.isclose中解决(稍后介绍)。要了解更多信息,请查看GitHub上出色的浮点指南⁸和相应的NumPy问题⁹。

2.矩阵,二维数组

NumPy中曾经有一个专用的矩阵类,但现在已弃用,因此我将交替使用矩阵和2D数组一词。

矩阵初始化语法与向量相似:

这里需要双括号,因为第二个位置参数是为(可选)dtype(它也接受整数)保留的。

随机矩阵的生成也类似于矢量的生成:

二维索引语法比嵌套列表更方便:

'视图'符号表示切片数组时实际上并未进行任何复制。修改数组后,更改也将反映在切片中。

轴参数

在许多操作(例如,求和)中,您需要告诉NumPy是否要跨行或跨列进行操作。为了具有适用于任意数量维的通用表示法,NumPy引入了axis的概念:axis参数的值实际上是所讨论的索引的数量:第一个索引是axis = 0,第二个是axis = 1,依此类推。因此在2D中,轴= 0是列方向,轴= 1是指行方向。

矩阵算术

除了普通的运算符(例如+,-,*,/,//和**)可以逐个元素地工作之外,还有一个@运算符可计算矩阵乘积:

作为在第一部分中已经看到的从标量广播的概括,NumPy允许向量和矩阵之间,甚至两个向量之间的混合运算:

> Broadcasting in 2D

行向量和列向量

从上面的示例可以看出,在2D上下文中,行向量和列向量被不同地对待。这与通常的NumPy做法相反,后者通常会尽可能使用一种类型的1D数组(例如a [:,j]-2D数组a的第j列是1D数组)。默认情况下,一维数组在2D操作中被视为行向量,因此,将矩阵乘以行向量时,可以使用形状(n,)或(1,n)-结果将相同。如果您需要列向量,则有几种方法可以从一维数组中进行处理,但是令人惊讶的是,转置不是其中一种:

能够从1D数组中生成2D列向量的两个操作是使用newaxis重新塑形和索引:

这里的-1参数告诉reshape自动计算尺寸尺寸之一,方括号中的None用作np.newaxis的快捷方式,它将在指定位置添加一个空轴。

因此,NumPy中共有三种类型的向量:1D数组,2D行向量和2D列向量。这是两者之间显式转换的示意图:

根据广播规则,一维数组被隐式解释为二维行向量,因此通常不必在这两个数组之间进行转换-因此,相应区域被阴影化。

矩阵操作

连接数组有两个主要功能:

这两个仅适用于堆叠矩阵或仅适用于向量,但在将一维数组和矩阵进行混合堆叠时,只有vstack可以按预期工作:hstack生成维度不匹配错误,因为如上所述,一维数组被解释作为行向量,而不是列向量。解决方法是将其转换为行向量,或使用专门的column_stack函数自动执行此操作:

堆叠的逆向分裂:

可以通过两种方式完成矩阵复制:拼贴的行为类似于复制粘贴,重复的行为类似于分页打印:

特定的列和行可以像这样删除:

相反的操作是insert:

像hstack一样,append函数无法自动转置1D数组,因此,再次需要重新调整向量的形状或添加维,或者需要使用column_stack来代替:

实际上,如果您需要做的就是向数组的边界添加常量值,那么(稍微有点复杂)的pad函数就足够了:

网状网格

广播规则使使用网格网格的工作更加简单。假设您需要以下矩阵(但尺寸很大):

两种明显的方法很慢,因为它们使用Python循环。MATLAB处理此类问题的方法是创建一个网格:

meshgrid函数接受任意一组索引mgrid-仅切片和索引只能生成完整的索引范围。如上所述,fromfunction仅使用I和J参数调用提供的函数一次。

但是实际上,在NumPy中有一种更好的方法。无需在整个I和J矩阵上花费内存(即使meshgrid足够聪明,仅在可能的情况下仅存储对原始向量的引用)。仅存储正确形状的矢量就足够了,广播规则将处理其余的内容:

如果没有indexing ='ij'参数,则meshgrid将更改参数的顺序:J,I = np.meshgrid(j,i)-这是一种' xy'模式,可用于可视化3D绘图(请参见docs)。

除了在二维或三维网格上初始化函数外,网格还可以用于索引数组:

> Works with sparse meshgrids, too

矩阵统计

就像sum一样,所有其他统计函数(min / max,argmin / argmax,mean / median / percentile,std / var)都接受axis参数并相应地起作用:

np.amin is an alias of np.min to avoid shadowing the Python min when you write 'from numpy import *'

2D及更高版本中的argmin和argmax函数令人讨厌返回平坦索引(最小和最大值的第一个实例)。要将其转换为两个坐标,需要一个unravel_index函数:

量词all和any也都知道axis参数:

矩阵排序

尽管axis参数对于上面列出的函数很有用,但对2D排序却没有帮助:

排序矩阵或电子表格通常不是您所希望的:轴绝不能替代key参数。但是幸运的是,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列排序,然后(按第2列的值相等)按第5列排序。np.lexsort(np.flipud(aT))]按从左到右的顺序对所有列进行排序。

在这里,flipud沿上下方向翻转矩阵(准确地说,沿axis = 0方向,与a [::-1,…]相同,其中三个点表示'所有其他维度'),因此突然翻转(而不是fliplr)翻转1D数组)。

3.还有一个order参数可以排序,但是如果从普通(非结构化)数组开始,则既不快速也不容易使用。

4.在熊猫中执行此操作可能是一个更好的选择,因为该特定操作在此处更具可读性且不易出错:– pd.DataFrame(a).sort_values(by = [2,5])。to_numpy()按第2列,然后按第5列排序。– pd.DataFrame(a).sort_values()。to_numpy()按从左到右的顺序按所有列排序。

3. 3D及以上

通过重塑1D向量或转换嵌套的Python列表来创建3D数组时,索引的含义为(z,y,x)。第一个索引是平面的编号,然后坐标在该平面上移动:

此索引顺序很方便,例如用于保留一堆灰度图像:a [i]是引用第i个图像的快捷方式。

但是此索引顺序不是通用的。使用RGB图像时,通常使用(y,x,z)顺序:第一个是两个像素坐标,最后一个是颜色坐标(Matplotlib中的RGB,OpenCV中的BGR):

这样可以方便地引用特定像素:a [i,j]给出(i,j)像素的RGB元组。

因此,创建某种几何形状的实际命令取决于您正在使用的域的约定:

显然,诸如hstack,vstack或dstack之类的NumPy函数并不了解这些约定。其中硬编码的索引顺序是(y,x,z),RGB图像顺序:

> Stacking RGB images (only two colors here)

如果您的数据布局不同,则使用concatenate命令堆叠图像,并在axis参数中为其提供显式索引号,将更加方便:

> Stacking generic 3D arrays

如果您不方便考虑轴数,可以将数组转换为硬编码为hstack和co的形式:

这种转换是便宜的:没有实际的复制发生。它只是动态混合索引的顺序。

混合索引顺序的另一个操作是数组转置。检查它可能会让您对3D阵列更加熟悉。根据您决定的轴顺序,转置数组所有平面的实际命令会有所不同:对于通用数组,它交换索引1和2,对于RGB图像,它交换0和1:

有趣的是,用于转置的默认轴参数(以及唯一的a.T操作模式)会颠倒索引顺序,这与上述两个索引顺序约定都不相符。

最后,这是一个函数,可以在处理多维数组时为您节省很多Python循环,并使您的代码更简洁— einsum(爱因斯坦求和):

它将沿重复索引的数组求和。在此特定示例中,np.tensordot(a,b,axis = 1)在两种情况下都足够,但是在更复杂的情况下,einsum可能工作得更快,并且通常更容易读写,只要您了解其背后的逻辑即可。

如果您想测试自己的NumPy技能,可以在GitHub上进行一组棘手的众筹,其中包括100个NumPy练习。

如果我错过了您最喜欢的NumPy功能,请告诉我,我将尽力配合使用!

(本文由闻数起舞翻译自Lev Maximov的文章《NumPy Illustrated: The Visual Guide to NumPy》,转载请注明出处,原文链接:https://medium.com/better-programming/numpy-illustrated-the-visual-guide-to-numpy-3b1d4976de1d)

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
图解NumPy:常用函数的内在机制(上)
看图学NumPy:掌握n维数组基础知识点,看这一篇就够了
Numpy统计计算、数组比较,看这篇就够了
1.3NumPy:创建和操作数值数据
入门numpy(25﹪-50﹪)【解读numpy官方文档】
从零开始学Python【4】--numpy
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服