matrix
类在NumPy中的状态是什么?
我一直被告知应该使用ndarray
类。在我编写新代码时,使用matrix
类是否值得/安全?我不理解为什么应该使用ndarray
而不是matrix
。
matrix
类在NumPy中的状态是什么?
我一直被告知应该使用ndarray
类。在我编写新代码时,使用matrix
类是否值得/安全?我不理解为什么应该使用ndarray
而不是matrix
。
简述: 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]
和arr[0,:]
都会给出一个1d ndarray
,而mat[:,0]
的形状为(N,1)
,mat[0,:]
的形状为(1,M)
,如果是matrix
。mat1.shape[1] == mat2.shape[0]
,则mat1 * mat2
有效,但是如果arr1.shape == arr2.shape
,则arr1 * arr2
有效(当然结果完全不同)。另外,令人惊讶的是,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
的子类,但是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)
。这两个对象被广播成了形状为(3,3)
。@
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
*
使表达式更简洁易读。但是,在使用现代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 grammar都允许将@=
作为matmul的增强赋值,但在numpy 1.15中尚未实现这种方式来处理ndarrays。
Matrix类是一个例外:它被编写为自然地表达线性代数的方法。然而,当你混合矩阵和数组时,事情会变得有点棘手,即使坚持使用矩阵,也存在困惑和限制——如何表示行向量和列向量?当你遍历一个矩阵时会得到什么?等等。
已经有很多关于这些问题的讨论,提出了很多好的想法,也有一些关于如何改进的共识,但没有人有足够的动力去做这件事。
这些反映了矩阵类带来的好处和困难。我能找到的最早的废弃建议是来自2008年,尽管部分动机是由于已经改变的不直观行为(特别是对矩阵进行切片和迭代将产生(行)矩阵,正如大多数人可能期望的那样)。该建议表明这是一个极具争议性的主题,并且中缀运算符用于矩阵乘法至关重要。
我找到的下一个提及是在2014年,这是一个非常富有成果的讨论。随后的讨论引出了一个问题,即如何处理numpy子类,这个问题仍然非常重要。同时也有强烈的批评:这场关于Github的讨论的起因是:无法编写适用于以下情况的鸭子类型代码:
- ndarrays
- matrices
- scipy.sparse稀疏矩阵
所有三种的语义都不同;scipy.sparse介于矩阵和ndarrays之间,某些操作像矩阵一样随机工作,而其他操作则不行。
可以夸张地说,从开发者的角度来看,np.matrix的存在已经并将会继续通过破坏Python中ndarray语义的未声明规则来做恶。
接下来就是对矩阵可能的未来进行了很多有价值的讨论。即使当时还没有@
运算符,也有很多思考花费在了废弃矩阵类以及它如何影响下游用户上。据我所知,这次讨论直接导致了PEP 465的产生,引入了matmul。
我认为,"固定"版本的np.matrix应该(1)不是np.ndarray子类,(2)存在于第三方库中而不是numpy本身。
我认为在其当前状态下作为ndarray子类修复np.matrix并不可行,但即使修复后的矩阵类也不真正属于numpy本身,因为numpy具有太长的发布周期和兼容性保证,无法进行实验——更不用说numpy中矩阵类的存在会误导新用户。
一旦@
运算符有了一段时间的使用,关于弃用矩阵的讨论再次浮出水面, 重新提出了矩阵弃用与scipy.sparse
之间的关系。
最终,在2017年11月底首次采取了弃用numpy.matrix
的行动。关于该类的依赖项:
社区将如何处理scipy.sparse矩阵子类?这些仍然广泛使用。
它们在相当长一段时间内都不会消失(至少直到稀疏ndarrays出现)。因此,需要移动np.matrix,而不是删除它。
(来源)
虽然我和任何人一样希望尽快摆脱np.matrix,但这样做会非常具有破坏性。有很多小脚本是由不懂得更好的人编写的;我们确实希望他们学会不使用np.matrix,但打破所有这些脚本是一种痛苦的方式。有一些主要项目(如scikit-learn)由于scipy.sparse的原因根本没有替代方案,只能使用np.matrix。因此,我认为前进的方式是这样的:现在或者在有人提出PR时,在np.matrix._init_中发布一个PendingDeprecationWarning(除非它影响scikit-learn等项目的性能),并在文档顶部放置一个大警告框。这里的想法是不真正打破任何人的代码,但开始传达我们绝对不认为任何人应该使用这个,如果他们有任何替代方案。在有了scipy.sparse的替代方案之后:加强警告,可能一直到FutureWarning,以便现有脚本不会崩溃,但会收到嘈杂的警告。最终,如果我们认为这将减少维护成本:将其拆分为子包。(source)即使是用于线性代数,也不再建议使用此类。请改用常规数组。该类可能会在未来被删除。
标准数组子类的文档页面上说:
强烈建议不要使用矩阵子类。如下所述,它使编写能够一致处理矩阵和普通数组的函数非常困难。目前,它们主要用于与
scipy.sparse
交互。我们希望为此提供替代方案,并最终删除matrix
子类。
同时,在matrix.__new__
中添加了PendingDeprecationWarning
。不幸的是,废弃警告(几乎总是)默认被静音处理, 因此大多数numpy的最终用户将看不到这个强烈提示。
最后,numpy路线图截至2018年11月提到了多个相关主题作为“[numpy社区]将投入资源的任务和特性”之一:
NumPy中的一些内容实际上与NumPy的范围不匹配。
- 为numpy.fft编写后端系统(这样fft-mkl就不需要monkeypatch numpy)
- 重写掩码数组以不是ndarray子类的方式 - 可能在单独的项目中?
- MaskedArray作为鸭类型数组,和/或
- 支持缺失值的数据类型
- 编写有关如何处理linalg和fft的numpy和scipy之间重叠的策略(并实施它)。
- 弃用np.matrix
由于更大的库/许多用户(特别是scipy.sparse
)依赖于矩阵类,因此这种状态可能会保持不变。然而,正在进行讨论将scipy.sparse
移动到依赖于其他东西,例如pydata/sparse
。
np.matrix
的遗留问题。这个API复制了SciPy稀疏容器,并使用与NumPy数组(而不是矩阵)相匹配的接口。下游库的维护者,如NetworkX和scikit-learn非常渴望尽快切换到新的API。ndarray
类,并尽可能移植旧代码。最终,矩阵类可能会以单独的包形式出现,以消除其目前形式存在的一些负担。
scipy.sparse
并不依赖于np.matrix
。但事实上,它的实现受限于二维,并且操作符的使用方式是基于np
版本的。但是,没有一个稀疏格式是np.matrix
的子类。而将其转换为np.matrix
的转换器sparse.todense
实际上是采用np.asmatrix(M.toarray())
的方式实现的。 - hpauljnumpy
应用程序的人 - 真是太好了。在解析代码和追踪基于混淆ndarray
和matrix
的错误之间,以及尝试使用语言进行更高维度的张量代数运算,这个分叉自从我开始使用numpy
以来一直是一个巨大的头痛。非常感谢那些在后台进行困难编码的人,我知道他们正在努力完成这项工作。 - Daniel F(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)
的二维数组。使用矩阵而不是向量点可能会有一些性能损失,但语义应该与您所期望的相同。 - Andras Deak -- Слава Україні