NumPy矩阵类的弃用状态

问题描述 投票:43回答:1

NumPy中matrix课程的状态如何?

我一直被告知我应该使用ndarray课程。在我编写的新代码中使用matrix类是否值得/安全?我不明白为什么我应该使用ndarrays代替。

python numpy matrix deprecated numpy-ndarray
1个回答
53
投票

TL;博士:numpy.matrix课程已被弃用。有一些高调的库依赖于类作为依赖(最大的一个是scipy.sparse)阻碍了类的适当的短期弃用,但强烈建议用户使用ndarray类(通常使用numpy.array方便创建)功能)而不是。随着用于矩阵乘法的@算子的引入,已经消除了矩阵的许多相对优势。

Why (not) the matrix class?

numpy.matrixnumpy.ndarray的子类。它最初是为了方便地用于涉及线性代数的计算,但与更通用的数组类的实例相比,它们的行为方式存在局限性和惊人的差异。行为基本差异的例子:

  • 形状:数组可以具有从0到无穷大(或32)的任意数量的维度。矩阵总是二维的。奇怪的是,虽然无法创建具有更多维度的矩阵,但可以将单个维度注入矩阵,从而在技术上最终得到一个多维矩阵:np.matrix(np.random.rand(2,3))[None,...,None].shape == (1,2,3,1)(并非这具有任何实际意义)。
  • 索引:索引数组可以为您提供任何大小的数组,具体取决于how you are indexing it。矩阵上的索引表达式总是会给你一个矩阵。这意味着qdxswpoi和arr[:,0]为2d阵列给你1d arr[0,:],而ndarray有形状mat[:,0](N,1)有形状mat[0,:](1,M)的情况下。
  • 算术运算:过去使用矩阵的主要原因是矩阵上的算术运算(特别是乘法和幂)执行矩阵运算(矩阵乘法和矩阵幂)。对于数组而言相同会导致元素乘法和幂。因此matrix有效,如果mat1 * mat2,但mat1.shape[1] == mat2.shape[0]有效,如果arr1 * arr2(当然结果意味着完全不同的东西)。另外,令人惊讶的是,arr1.shape == arr2.shape执行两个矩阵的元素划分。这种行为可能是继承自mat1 / mat2但对矩阵毫无意义,特别是考虑到ndarray的含义。
  • 特殊属性:除了数组之外,矩阵还有*a few handy attributesmat.A分别是与mat.A1np.array(mat)具有相同值的数组视图。 np.array(mat).ravel()mat.T是矩阵的转置和共轭转置(伴随); mat.Harr.T类唯一存在的属性。最后,ndarraymat.I的逆矩阵。

编写适用于ndarrays或矩阵的代码非常容易。但是当两个类有可能在代码中进行交互时,事情开始变得困难。特别是,很多代码可以自然地用于mat的子类,但ndarray是一个不良行为的子类,可以轻松破坏试图依赖鸭子类型的代码。考虑使用形状matrix的数组和矩阵的以下示例:

(3,4)

根据我们切片的尺寸,添加两个对象的切片是灾难性的不同。当形状相同时,矩阵和数组的加法在元素上发生。上面的前两种情况很直观:我们添加了两个数组(矩阵),然后我们从每个数组中添加两行。最后一种情况真的很令人惊讶:我们可能想要添加两列并最终得到一个矩阵。当然的原因是import numpy as np shape = (3, 4) arr = np.arange(np.prod(shape)).reshape(shape) # ndarray mat = np.matrix(arr) # same data in a matrix print((arr + mat).shape) # (3, 4), makes sense print((arr[0,:] + mat[0,:]).shape) # (1, 4), makes sense print((arr[:,0] + mat[:,0]).shape) # (3, 3), surprising 有形状arr[:,0]与形状(3,)兼容,但(1,3)有形状mat[:.0]。这两个是(3,1)一起塑造broadcast

最后,当(3,3)首次实施the @ matmul operator was introduced in python 3.5时,矩阵类的最大优势(即,简明地表达涉及大量矩阵产品的复杂矩阵表达式的可能性)被删除。比较简单二次形式的计算:

in numpy 1.10

看看上面的内容,很明显为什么矩阵类广泛用于线性代数:infix v = np.random.rand(3); v_row = np.matrix(v) arr = np.random.rand(3,3); mat = np.matrix(arr) print(v.dot(arr.dot(v))) # pre-matmul style # 0.713447037658556, yours will vary print(v_row * mat * v_row.T) # pre-matmul matrix style # [[0.71344704]] print(v @ arr @ v) # matmul style # 0.713447037658556 算子使表达式更简洁,更易于阅读。但是,我们使用*运算符使用现代python和numpy获得相同的可读性。此外,请注意矩阵的情况给我们一个形状@的矩阵,技术上应该是一个标量。这也意味着我们不能将列向量与这个“标量”相乘:上例中的(1,1)会引发错误,因为形状为(v_row * mat * v_row.T) * v_row.T(1,1)的矩阵不能按此顺序相乘。

为了完整起见,应该注意的是,尽管matmul算子修复了与矩阵相比ndarray不是最理想的最常见场景,但是使用ndarrays处理线性代数仍然存在一些缺点(尽管人们仍然倾向于认为整体上它是最好坚持后者)。一个这样的例子是矩阵幂:(3,1)是矩阵的适当的第三矩阵幂(而它是ndarray的元素立方体)。不幸的是mat ** 3更加冗长。此外,就地矩阵乘法仅适用于矩阵类。相比之下,虽然numpy.linalg.matrix_powerPEP 465都允许python grammar作为matmul的增强赋值,但是numpar 1.15中没有实现ndarrays。

Deprecation history

考虑到上述关于@=类的复杂性,长期以来一直在讨论其可能的弃用问题。引入matrix中缀运算符,这是这个过程@的一个巨大先决条件。不幸的是,早期矩阵类的优点意味着它的使用范围很广。有些库依赖于矩阵类(其中一个最重要的依赖是happened in September 2015,它使用scipy.sparse语义并且通常在致密化时返回矩阵),因此完全弃用它们一直存在问题。

已经在numpy.matrix我发现了诸如此类的评论

numpy是为通用计算需求而设计的,而不是任何一个数学分支。 nd-arrays对很多东西非常有用。相比之下,Matlab最初设计为线性代数包的简单前端。就我个人而言,当我使用Matlab时,我发现非常尴尬 - 我通常会编写100行与线性代数无关的代码行,因为每几行实际上都进行了矩阵数学运算。所以我更喜欢numpy的方式 - 代码的线性代数行更长,更尴尬,但其余的要好得多。

Matrix类是例外:is是为了提供表达线性代数的自然方式而编写的。然而,当你混合矩阵和数组时,事情变得有点棘手,即使坚持使用矩阵也存在混淆和限制 - 你如何表达行与列向量?迭代矩阵时你会得到什么?等等

有很多关于这些问题的讨论,很多好的想法,关于如何改进它的一点共识,但没有一个有技能的人有足够的动力去做。

这些反映了矩阵类带来的好处和困难。我能找到的最早的弃用建议是a numpy mailing list thread from 2009,虽然部分原因是由于非直观的行为已经改变(特别是切片和迭代矩阵将导致(行)矩阵,因为人们很可能会期望)。该建议表明这是一个备受争议的主题,并且用于矩阵乘法的中缀运算符至关重要。

下一次提到我可以找到from 2008,结果证明是一个非常富有成效的线程。随后的讨论提出了一般处理numpy子类的问题,is from 2014。还有which general theme is still very much on the table

引发此讨论(在Github上)的原因是,不可能编写适用于以下情况的鸭类代码:

  • ndarrays
  • 矩阵
  • scipy.sparse稀疏矩阵

这三者的语义不同; scipy.sparse介于矩阵和ndarray之间,有些东西像矩阵一样随机工作,而其他东西则没有。

添加了一些hyberbole,可以说从开发人员的角度来看,np.matrix正在做,并且已经通过现有的方式做了恶,通过搞乱Python中ndarray语义的未说明的规则。

接下来是对矩阵可能的未来进行了大量有价值的讨论。即使当时没有strong criticism运算符,也有很多人考虑过矩阵类的弃用以及它如何影响下游用户。据我所知,这个讨论直接导致了PEP 465的推出,引入了matmul。

@

在我看来,np.matrix的“固定”版本应该(1)不是np.ndarray子类,(2)存在于第三方库中,而不是numpy本身。

我认为将np.matrix作为ndarray子类固定在当前状态是不可行的,但即使是固定矩阵类也不属于numpy本身,它具有太长的发布周期和实验的兼容性保证 - 更不用说在numpy中仅仅存在矩阵类会导致新用户误入歧途。

一旦In early 2015算子有一段时间可用@the discussion of deprecation surfaced again关于矩阵弃用和reraising the topic的关系。

最终,scipy.sparse。关于班上的家属:

社区如何处理scipy.sparse矩阵子类?这些仍然是常用的。

他们不会去任何地方很长一段时间(直到稀疏的ndarrays至少实现)。因此,需要移动np.matrix,而不是删除。

first action to deprecate numpy.matrix was taken in late November 2017)和

虽然我想像任何人一样摆脱np.matrix,但很快就会这样做会非常具有破坏性。

  • 那些不太了解的人写了很多小脚本;我们确实希望他们学习不使用np.matrix,但打破他们所有的脚本是一种痛苦的方法
  • 由于scipy.sparse,像scikit-learn这样的主要项目除了使用np.matrix之外别无选择。

所以我认为前进的方向是:

  • 现在或每当有人聚在一起PR:在np.matrix .__ init__中发出PendingDeprecationWarning(除非它杀死scikit-learn和朋友的表现),并在文档的顶部放置一个大警告框。这里的想法是实际上不打破任何人的代码,但是开始告诉我们,如果他们有任何替代方案,我们绝对不认为任何人应该使用它。
  • 之后有scipy.sparse的替代方案:提升警告,可能一直到FutureWarning,这样现有的脚本不会中断,但它们确实会收到嘈杂的警告
  • 最终,如果我们认为它会降低维护成本:将其拆分为子包

(Qazxswpoi)。

现状

截至2018年5月(numpy 1.15,相关的sourcesourcepull request包含以下注释:

不再建议使用此类,即使对于线性代数也是如此。而是使用常规数组。该课程将来可能会被删除。

与此同时,commit被添加到matrix class docstring。不幸的是,PendingDeprecationWarning,所以numpy的大多数最终用户都不会看到这种强烈暗示。

最后,matrix.__new__截至2018年11月提到了多个相关主题,作为“任务和功能[numpy社区]将投入资源”之一:

NumPy中的一些内容实际上与NumPy的范围不匹配。

  • numpy.fft的后端系统(例如fft-mkl不需要monkeypatch numpy)
  • 重写掩码数组不是一个ndarray子类 - 可能在一个单独的项目中?
  • MaskedArray作为鸭子阵列类型,和/或
  • 支持缺失值的dtypes
  • 写一个关于如何处理linalg和fft(和实现它)的numpy和scipy之间重叠的策略。
  • 弃用np.matrix

只要较大的库/许多用户(特别是deprecation warnings are (almost always) silenced by default)依赖于矩阵类,这种状态很可能会持续下去。然而,有the numpy roadmap移动scipy.sparse依赖其他东西,如ongoing discussion。无论弃用过程的发展如何,用户都应该在新代码中使用scipy.sparse类,并且如果可能的话,最好移植旧代码。最终,矩阵类可能最终会出现在一个单独的包中,以消除由于其当前形式存在而造成的一些负担。

© www.soinside.com 2019 - 2024. All rights reserved.