NumPy矩阵类的弃用状态

NumPy矩阵类的弃用状态,第1张

NumPy矩阵类的弃用状态

tl;
DR:

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)
    这并不具有任何实际意义)。
  • 索引:索引数组可以为您提供任何大小的数组,具体取决于您对其进行索引的方式。在矩阵上建立索引表达式将始终为您提供矩阵。这意味着2d数组
    arr[:,0]
    arr[0,:]
    2d数组都为1d
    ndarray
    ,同时
    mat[:,0]
    具有形状
    (N,1)
    mat[0,:]
    形状
    (1,M)
    (如果为)
    matrix
  • 算术运算:过去使用矩阵的主要原因是矩阵的算术运算(尤其是乘法和幂)执行矩阵运算(矩阵乘法和矩阵幂)。数组的相同结果会导致元素乘法和幂运算。因此
    mat1 * mat2
    ,if是有效的
    mat1.shape[1] == mat2.shape[0]
    ,但是if
    arr1 * arr2
    是有效的
    arr1.shape == arr2.shape
    (当然,结果意味着完全不同的东西)。而且,令人惊讶地,
    mat1 / mat2
    执行两个矩阵的 元素 除法。这种行为可能是继承自
    ndarray
    矩阵,但对矩阵毫无意义,尤其是考虑到的含义
    *
  • 特殊属性:矩阵有一些方便的属性,除了什么阵列具有:
    mat.A
    mat.A1
    具有相同的值作为阵列的意见
    np.array(mat)
    np.array(mat).ravel()
    分别。
    mat.T
    并且
    mat.H
    是矩阵的转置和共轭转置(伴随);
    arr.T
    是该类存在的唯一此类属性
    ndarray
    。最后
    mat.I
    是的逆矩阵
    mat

编写适用于ndarray或矩阵的代码非常容易。但是,当这两个类有可能必须在代码中进行交互时,事情就会变得困难起来。特别是,许多代码对于的子类自然 可以
正常工作

ndarray
,但它们
matrix
是行为不端的子类,很容易破坏试图依赖鸭子类型的代码。考虑以下使用数组和形状矩阵的示例
(3,4)

import numpy as npshape = (3, 4)arr = np.arange(np.prod(shape)).reshape(shape) # ndarraymat = np.matrix(arr) # same data in a matrixprint((arr + mat).shape)# (3, 4), makes senseprint((arr[0,:] + mat[0,:]).shape) # (1, 4), makes senseprint((arr[:,0] + mat[:,0]).shape) # (3, 3), surprising

根据我们沿切片的尺寸,将两个对象的切片相加会发生灾难性的变化。当形状相同时,对矩阵和数组的加法都会逐元素进行。上面的前两种情况很直观:我们添加两个数组(矩阵),然后分别添加两个行。最后一种情况确实令人惊讶:我们可能打算添加两列,最后得到一个矩阵。原因当然是

arr[:,0]
具有
(3,)
与形状兼容
(1,3)
mat[:.0]
具有形状的形状
(3,1)
。两者一起广播以成形
(3,3)

最后,当在python
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 varyprint(v_row * mat * v_row.T) # pre-matmul matrix style# [[0.71344704]]print(v @ arr @ v) # matmul style# 0.713447037658556

综上所述,很明显为什么矩阵类在处理线性代数方面广受青睐:infix

*
运算符使表达式的冗长程度降低,并且易于阅读。但是,
@
使用现代python和numpy的运算符具有相同的可读性。此外,请注意,矩阵案例为我们提供了形状
(1,1)
上应为标量的形状矩阵。这也意味着我们不能将列向量与这个“标量”相乘:
(v_row* mat * v_row.T) * v_row.T
在上面的示例中,由于矩阵的形状不同
(1,1)
(3,1)
无法按此顺序相乘,因此会产生错误。

为了完整起见,应该注意的是,尽管matmul运算符修复了ndarrays与矩阵相比不是最理想的最常见方案,但是使用ndarrays优雅地处理线性代数仍然存在一些缺点(尽管人们仍然倾向于认为总体而言最好坚持使用后者)。一个这样的例子是矩阵幂:是矩阵

mat**3
的适当的第三矩阵幂(而它是ndarray的元素级立方)。不幸的
numpy.linalg.matrix_power
是更加冗长。此外,就地矩阵乘法仅适用于矩阵类。相比之下,虽然PEP
465和python语法都允许
@=
使用matmul进行增强分配,但从numpy 1.15开始,对于ndarrays尚未实现此功能。

弃用历史

考虑到上述有关

matrix
该类的复杂性,很长一段时间以来一直在反复讨论其可能被弃用。引入
@
infix运算符是此过程的巨大先决条件,发生于2015年9月。不幸的是,矩阵类在早期的优点意味着它的使用范围很广。有些库依赖于矩阵类(最重要的依赖项之一是
scipy.sparse
使用
numpy.matrix
语义,并且在进行密化时经常返回矩阵),因此完全弃用它们总是有问题的。

从2009年开始出现在一个小小的邮件列表线程中,我发现诸如

numpy是为满足通用计算需求而设计的,而不是数学的任何一个分支。nd数组对很多事情都非常有用。相比之下,例如Matlab最初被设计为线性代数封装的简单前端。就我个人而言,当我使用Matlab时,我发现这很尴尬-
我通常在写几行与线性代数无关的代码时,实际上每行几行都进行矩阵数学运算。因此,我更喜欢numpy的方式-代码的线性代数行越长越尴尬,但其余的则好得多。

Matrix类是这种情况的例外:编写该类是为了提供一种表达线性代数的自然方法。但是,当您将矩阵和数组混合使用时,事情变得有些棘手,即使坚持使用矩阵也存在困惑和局限性-
如何表达行向量与列向量?在矩阵上迭代时会得到什么?等等

关于这些问题的讨论很多,很多好主意,关于如何改进它的一些共识,但是没有一个熟练的人有足够的动力去做。

这些反映了矩阵类带来的好处和困难。我可以找到的最早的弃用建议是从2008年开始的,尽管部分原因是由于非直觉的行为,此行为自此发生了变化(特别是,对矩阵进行切片和迭代将产生(行)矩阵,这很可能是人们期望的)。该建议表明,这是一个极具争议的主题,并且矩阵乘法的中缀运算符至关重要。

我可以找到的下一个提及来自2014年,事实证明这是一个 非常
富有成果的话题。随后的讨论提出了一般情况下处理numpy子类的问题,该主题仍然很受关注。也有强烈的批评:

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

  • ndarrays
  • 矩阵
  • 稀疏稀疏矩阵

这三个词的语义是不同的。scipy.sparse在矩阵和ndarray之间,某些东西像矩阵一样随机工作,而另一些则不然。

加上一些夸张的说法,可以说,从开发人员的角度来看,np.matrix正在做,并且已经通过捣乱Python中ndarray语义的未声明规则而已经作恶了。

其次是关于矩阵可能的未来的许多有价值的讨论。即使

@
当时没有运算符,对于矩阵类的弃用及其对下游用户的影响也有很多想法。据我所知,这种讨论直接导致了引入matmul的PEP
465的诞生。

在2015年初:

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

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

一旦

@
*** 作员可以使用一段时间,关于折旧的讨论再次浮出水面,引发了关于矩阵折旧与矩阵关系的话题
scipy.sparse

最终,于2017年11月下旬采取了第一个弃用

numpy.matrix
措施。关于班级的家属:

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

他们已经很长时间没有去任何地方了(直到稀疏的ndarray至少实现了)。因此,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,相关的pull
request和commit),矩阵类docstring包含以下注释:

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

并且同时

PendingDeprecationWarning
已将添加到
matrix.__new__
。不幸的是,默认情况下,弃用警告(几乎总是)是静音的,因此大多数numpy的最终用户都不会看到此强烈提示。

最后,截至2018年11月的numpy路线图提到了多个相关主题,因为“ numpy社区将在以下方面投入资源和任务和功能之一

NumPy内部的某些内容实际上与NumPy的范围不匹配。

  • numpy.fft的后端系统(因此,例如fft-mkl不需要猴子补丁numpy)
  • 将掩码数组重写为不是ndarray子类-也许在单独的项目中?
  • MaskedArray为鸭子数组类型,和/或
  • 支持缺失值的dtypes
  • 编写有关如何处理linalg和fft的numpy和scipy之间的重叠的策略(并实现它)。
  • 弃用np.matrix

只要较大的库/许多用户(尤其是

scipy.sparse
)依赖于矩阵类,这种状态就可能保持不变。但是,正在进行讨论以转移
scipy.sparse
到其他方面,例如
pydata/sparse
。无论弃用过程的发展如何,用户都应
ndarray
在新代码中使用该类,并且如果可能的话,最好移植旧代码。最终,矩阵类可能最终会放在单独的程序包中,以消除因其以当前形式存在而造成的一些负担。



欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5646637.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存