NumPy中matrix
课程的状态如何?
我一直被告知我应该使用ndarray
课程。在我编写的新代码中使用matrix
类是否值得/安全?我不明白为什么我应该使用ndarray
s代替。
TL;博士:numpy.matrix
课程已被弃用。有一些高调的库依赖于类作为依赖(最大的一个是scipy.sparse
)阻碍了类的适当的短期弃用,但强烈建议用户使用ndarray
类(通常使用numpy.array
方便创建)功能)而不是。随着用于矩阵乘法的@
算子的引入,已经消除了矩阵的许多相对优势。
numpy.matrix
是numpy.ndarray
的子类。它最初是为了方便地用于涉及线性代数的计算,但与更通用的数组类的实例相比,它们的行为方式存在局限性和惊人的差异。行为基本差异的例子:
np.matrix(np.random.rand(2,3))[None,...,None].shape == (1,2,3,1)
(并非这具有任何实际意义)。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 attributes和mat.A
分别是与mat.A1
和np.array(mat)
具有相同值的数组视图。 np.array(mat).ravel()
和mat.T
是矩阵的转置和共轭转置(伴随); mat.H
是arr.T
类唯一存在的属性。最后,ndarray
是mat.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_power
和PEP 465都允许python grammar作为matmul的增强赋值,但是numpar 1.15中没有实现ndarrays。
考虑到上述关于@=
类的复杂性,长期以来一直在讨论其可能的弃用问题。引入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,相关的source和source)pull 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
类,并且如果可能的话,最好移植旧代码。最终,矩阵类可能最终会出现在一个单独的包中,以消除由于其当前形式存在而造成的一些负担。