NumPy矩阵类的弃用状态

And*_*eak 44 python numpy matrix deprecated numpy-ndarray

matrixNumPy中班级的状态是什么?

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

And*_*eak 59

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

为什么(不)矩阵类?

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

  • 形状:数组可以具有0到无穷大(或32)的任意数量的维度.矩阵总是二维的.奇怪的是,虽然无法使用更多维度创建矩阵,但可以将单个维度注入矩阵,从而在技术上最终得到一个多维矩阵:( np.matrix(np.random.rand(2,3))[None,...,None].shape == (1,2,3,1)并不是说这具有任何实际意义).
  • 索引:索引数组可以为您提供任何大小的数组,具体取决于您对其进行索引的方式.矩阵上的索引表达式总是会给你一个矩阵.这意味着,arr[:,0]arr[0,:]用于2D阵列给你一个1D 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但对矩阵毫无意义,特别是考虑到它的含义*.
  • 特殊属性:矩阵有一些方便的属性,除了什么阵列具有:mat.Amat.A1具有相同的值作为阵列的意见np.array(mat)np.array(mat).ravel()分别.mat.T并且mat.H是矩阵的转置和共轭转置(伴随); arr.T是该类唯一存在的此类属性ndarray.最后,mat.I是逆矩阵mat.

编写适用于ndarrays或矩阵的代码非常容易.但是当两个类有可能在代码中进行交互时,事情开始变得困难.特别是,很多代码可以自然地用于子类ndarray,但是matrix是一个不良行为的子类,可以轻易地破坏试图依赖于duck类型的代码.考虑使用形状的数组和矩阵的以下示例(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
Run Code Online (Sandbox Code Playgroud)

根据我们切片的尺寸,添加两个对象的切片是灾难性的不同.当形状相同时,矩阵和数组的加法在元素上发生.上面的前两种情况很直观:我们添加两个数组(矩阵),然后我们从每个数组中添加两行.最后一种情况真的很令人惊讶:我们可能想要添加两列并最终得到一个矩阵.其原因当然是arr[:,0]具有(3,)与形状相容(1,3)mat[:.0]具有形状的形状(3,1).这两个人一起播出来塑造(3,3).

最后,当@dbthon 3.5中引入matmul运算符时,矩阵类的最大优点(即,简明地表达涉及大量矩阵乘积的复杂矩阵表达式的可能性)被删除,首先在numpy 1.10中实现.比较简单二次形式的计算:

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
Run Code Online (Sandbox Code Playgroud)

看看上面的内容,很明显为什么矩阵类广泛用于处理线性代数:中缀*运算符使表达式更简洁,更易于阅读.但是,我们@使用现代python和numpy 与操作员获得相同的可读性.此外,请注意矩阵情况为我们提供了一个形状矩阵,在(1,1)技术上应该是一个标量.这也意味着,我们不能繁殖的列矢量与该"标量":(v_row * mat * v_row.T) * v_row.T在上面的例子中引发错误,因为与形状基质(1,1)(3,1)不能以该顺序相乘.

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

弃用历史

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

已经在2009年的一个numpy邮件列表线程中我发现了诸如此类的评论

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

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

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

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

我能找到的下一个提及是从2014年开始,结果证明这是一个非常富有成果的线索.随后的讨论提出了一般处理numpy子类的问题,这个一般主题仍然在桌面上.还有强烈的批评:

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

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

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

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

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

2015年初:

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

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

一旦@运营商已经有一段是可利用弃用的讨论再次浮出水面,reraising话题关于矩阵折旧和的关系scipy.sparse.

最终,在2017年11月下旬采取了首次弃用行动numpy.matrix.关于班上的家属:

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

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

(来源)和

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

  • 那些不太了解的人写了很多小脚本; 我们确实希望他们学习不使用np.matrix,但打破他们所有的脚本是一种痛苦的方法

  • 由于scipy.sparse,像scikit-learn这样的主要项目除了使用np.matrix之外别无选择.

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

  • 现在或每当有人聚在一起PR:在np.matrix .__ init__中发出PendingDeprecationWarning(除非它杀死scikit-learn和朋友的表现),并在文档的顶部放置一个大警告框.这里的想法是实际上不打破任何人的代码,但是开始告诉我们,如果他们有任何替代方案,我们绝对不认为任何人应该使用它.

  • 之后有scipy.sparse的替代方案:提升警告,可能一直到FutureWarning,这样现有的脚本不会中断,但它们会收到嘈杂的警告

  • 最终,如果我们认为它会降低维护成本:将其拆分为子包

(来源).

现状

截至2018年5月(numpy 1.15,相关拉取请求提交)矩阵类docstring包含以下注释:

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

并且同时PendingDeprecationWarning添加了一个matrix.__new__.不幸的是,默认情况下,弃用警告(几乎总是)会被静音,因此numpy的大多数最终用户都不会看到这种强烈暗示.

最后,截至2018年11月的numpy路线图提到了多个相关主题,作为" 任务和功能[numpy社区]将投入资源 "之一:

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

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

只要较大的库/许多用户(特别是scipy.sparse)依赖于矩阵类,这种状态就可能保持不变.但是,正在进行讨论,scipy.sparse以便依赖其他东西,例如pydata/sparse.无论弃用过程的发展如何,用户都应该ndarray在新代码中使用该类,并且如果可能的话,最好移植旧代码.最终,矩阵类可能最终会出现在一个单独的包中,以消除由于其当前形式存在而造成的一些负担.

  • 我特别喜欢无限= 32 (3认同)
  • 我不认为`scipy.sparse`取决于`np.matrix`.是的,因为实现限制为2d,并且它的运算符的使用是`np`版本的模型.但是稀疏格式都不是`np.matrix`的子类.转换为`np.matrix`,`sparse.todense`实际上实现为`np.asmatrix(M.toarray())`. (2认同)
  • @A.Donda 已经考虑过:您可以使用形状为 `(1,n)` 和 `(n,1)` 的数组来限制操作,就像您希望 `matrix` 类工作的方式相同。考虑 `vrow = np.random.rand(3)[None,:]; vcol = np.random.rand(3)[:,None]; M = np.random.rand(3,3)`。结果数组将只服从线性代数,并且单例维度将被保留,因此 `vrow @ vcol` 是一个形状为 `(1,1)` 的二维数组,而 `vcol @ vrow` 是一个形状为 `( 3,3)`。使用矩阵而不是矢量点可能会影响一些性能,但语义应该按照您的方式保留。 (2认同)