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数组都为1dndarray
,同时mat[:,0]
具有形状(N,1)
和mat[0,:]
形状(1,M)
(如果为)matrix
。 - 算术运算:过去使用矩阵的主要原因是矩阵的算术运算(尤其是乘法和幂)执行矩阵运算(矩阵乘法和矩阵幂)。数组的相同结果会导致元素乘法和幂运算。因此
mat1 * mat2
,if是有效的mat1.shape[1] == mat2.shape[0]
,但是ifarr1 * 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在新代码中使用该类,并且如果可能的话,最好移植旧代码。最终,矩阵类可能最终会放在单独的程序包中,以消除因其以当前形式存在而造成的一些负担。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)