NumPy 矩阵类的弃用状态 [英] Deprecation status of the NumPy matrix class

查看:37
本文介绍了NumPy 矩阵类的弃用状态的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

NumPy 中 matrix 类的状态是什么?

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

解决方案

tl;博士: numpy.matrix 类正在被弃用.有一些引人注目的库将类作为依赖项(最大的一个是 scipy.sparse),这阻碍了类的适当短期弃用,但强烈建议用户使用ndarray 类(通常使用 创建numpy.array 便利函数)代替.随着用于矩阵乘法的 @ 运算符的引入,矩阵的许多相对优势已被消除.

为什么(不是)矩阵类?

numpy.matrixnumpy.ndarray 的子类.它最初是为了方便在涉及线性代数的计算中使用,但与更一般的数组类的实例相比,它们的行为方式既有局限性,也有惊人的差异.行为根本差异的示例:

  • 形状:数组可以有任意数量的维度,范围从 0 到无穷大(或 32).矩阵总是二维的.奇怪的是,虽然不能创建更多维度的矩阵,但可以将单个维度注入矩阵中,最终得到一个多维矩阵:np.matrix(np.random.rand(2,3))[None,...,None].shape == (1,2,3,1)(并不是说这具有任何实际意义).
  • 索引:索引数组可以为您提供任意大小的数组,具体取决于您的情况索引它.矩阵上的索引表达式总是会给你一个矩阵.这意味着对于二维数组,arr[:,0]arr[0,:] 都会为您提供一维 ndarray,而 mat[:,0] 有形状 (N,1)mat[0,:] 有形状 (1,M)matrix 的情况下.
  • 算术运算:过去使用矩阵的主要原因是对矩阵的算术运算(特别是乘法和幂)执行矩阵运算(矩阵乘法和矩阵幂).数组的结果是元素乘法和幂.因此 mat1 * mat2mat1.shape[1] == mat2.shape[0] 时有效,但 arr1 * arr2 在以下情况下有效arr1.shape == arr2.shape(当然结果意味着完全不同的东西).此外,令人惊讶的是,mat1/mat2 执行两个矩阵的 elementwise 除法.这种行为可能是从 ndarray 继承的,但对矩阵没有意义,特别是考虑到 * 的含义.
  • 特殊属性:矩阵有一些方便的属性除了数组所具有的:mat.Amat.A1 是与 np.array(mat)np.array(mat) 具有相同值的数组视图code>np.array(mat).ravel(),分别.mat.Tmat.H 是矩阵的转置和共轭转置(伴随);arr.Tndarray 类中唯一存在的此类属性.最后,mat.Imat的逆矩阵.

编写适用于 ndarray 或矩阵的代码很容易.但是当两个类有可能在代码中进行交互时,事情就开始变得困难了.特别是,很多代码可以自然地用于 ndarray 的子类,但是 matrix 是一个行为不良的子类,可以很容易地破坏代码试图依靠鸭子打字.考虑以下使用形状为 (3,4) 的数组和矩阵的示例:

将 numpy 导入为 np形状 = (3, 4)arr = np.arange(np.prod(shape)).reshape(shape) # ndarraymat = np.matrix(arr) # 矩阵中的相同数据print((arr + mat).shape) # (3, 4), 有道理print((arr[0,:] + mat[0,:]).shape) # (1, 4), 有道理print((arr[:,0] + mat[:,0]).shape) # (3, 3), 令人惊讶

根据我们切片的维度,添加两个对象的切片是灾难性的.当形状相同时,矩阵和数组的加法都是按元素进行的.上面的前两种情况很直观:我们添加两个数组(矩阵),然后从每个数组(矩阵)添加两行.最后一种情况确实令人惊讶:我们可能打算添加两列并最终得到一个矩阵.原因当然是 arr[:,0] 有形状 (3,) 与形状 (1,3) 兼容,但是 mat[:.0] 有形状 (3,1).两者是broadcast一起形成(3,3).

最后,当@ matmul 运算符是在 python 3.5 中引入的,首先实现了 在 numpy 1.10 中.比较一个简单的二次型的计算:

v = np.random.rand(3);v_row = np.matrix(v)arr = np.random.rand(3,3);垫 = np.matrix(arr)print(v.dot(arr.dot(v))) # pre-matmul 样式# 0.713447037658556,你的会有所不同print(v_row * mat * v_row.T) # pre-matmul 矩阵样式# [[0.71344704]]print(v @arr @ v) # matmul 样式# 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 465python 语法 允许 @= 作为增强赋值使用 matmul,从 numpy 1.15 开始,这不是为 ndarrays 实现的.

弃用历史

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

已经在 一个 2009 年的 numpy 邮件列表线程 我发现诸如

之类的评论<块引用>

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

Matrix 类是一个例外:被编写以提供一个表达线性代数的自然方式.然而,事情变得有点棘手当你混合矩阵和数组时,甚至当你坚持使用矩阵时有混淆和限制——你如何表达一行和一个列向量?迭代矩阵时会得到什么?等

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

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

我能找到的下一个提及 来自 2014 年 结果证明这是一个非常富有成效的话题.随后的讨论提出了一般处理 numpy 子类的问题,哪个总主题仍然很重要.还有强烈批评:

<块引用>

引发这场讨论(在 Github 上)的是,不可能编写适合以下情况的鸭式代码:

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

三者的语义不同;scipy.sparse 在某处在矩阵和 ndarrays 之间,有些东西是随机工作的,比如矩阵和其他没有.

添加了一些夸张的东西,从开发人员的角度可以说看来, np.matrix 正在做并且已经通过存在做恶了,通过搞乱 Python 中 ndarray 语义的未说明规则.

接着是关于矩阵可能的未来的许多有价值的讨论.即使当时没有 @ 运算符,也有很多关于矩阵类的弃用以及它可能如何影响下游用户的想法.据我所知,这个讨论直接导致了 PEP 465 引入 matmul.

2015 年初:

<块引用>

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

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

一旦 @ 操作符可用一段时间 关于弃用的讨论再次浮出水面重提矩阵弃用与scipy.sparse的关系.

最终,第一个弃用 numpy.matrix 的行动 于 2017 年 11 月下旬拍摄.关于班级的家属:

<块引用><块引用>

社区将如何处理 scipy.sparse 矩阵子类?这些仍然普遍使用.

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

(来源)和

<块引用>

虽然我想尽可能地摆脱 np.matrix任何人,很快就会这样做真的具有破坏性.

  • 有大量的小脚本是由那些不知道更好;我们确实希望他们学会不使用 np.matrix 但打破他们所有的脚本是一种痛苦的方式

  • 像 scikit-learn 这样的重大项目根本没有使用 np.matrix 的替代方法,因为 scipy.sparse.

所以我认为前进的道路是这样的:

  • 现在或每当有人聚在一起 PR:发出np.matrix.__init__ 中的 PendingDeprecationWarning(除非它杀死scikit-learn 和朋友的表现),并放了一个大警告框在文档的顶部.这里的想法是不要真正打破任何人的代码,但开始发出信息,我们肯定如果他们有其他选择,不要认为任何人都应该使用它.

  • 在有了 scipy.sparse 的替代方案之后:增加警告,可能一直到 FutureWarning,这样现有的脚本就不会中断,但他们确实收到了嘈杂的警告

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

(来源).

现状

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

<块引用>

不再推荐使用这个类,即使是线性代数.而是使用常规数组.将来可能会删除该类.

同时在 matrix.__new__ 中添加了 PendingDeprecationWarning.不幸的是,弃用警告(几乎总是)默认情况下是静音的,所以大多数 numpy 的最终用户不会看到这个强烈的提示.

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

<块引用>

NumPy 中的某些内容实际上与 NumPy 的 Scope 不匹配.

  • numpy.fft 的后端系统(这样 fft-mkl 就不需要对 numpy 进行猴子补丁)
  • 将掩码数组重写为不是 ndarray 子类——也许在一个单独的项目中?
  • MaskedArray 作为duck-array 类型,和/或
  • 支持缺失值的数据类型
  • 编写关于如何处理 linalg 和 fft 的 numpy 和 scipy 之间重叠的策略(并实施它).
  • 弃用 np.matrix

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

What is the status of the matrix class in NumPy?

I keep being told that I should use the ndarray class instead. Is it worth/safe using the matrix class in new code I write? I don't understand why I should use ndarrays instead.

解决方案

tl; dr: the numpy.matrix class is getting deprecated. There are some high-profile libraries that depend on the class as a dependency (the largest one being scipy.sparse) which hinders proper short-term deprecation of the class, but users are strongly encouraged to use the ndarray class (usually created using the numpy.array convenience function) instead. With the introduction of the @ operator for matrix multiplication a lot of the relative advantages of matrices have been removed.

Why (not) the matrix class?

numpy.matrix is a subclass of numpy.ndarray. It was originally meant for convenient use in computations involving linear algebra, but there are both limitations and surprising differences in how they behave compared to instances of the more general array class. Examples for fundamental differences in behaviour:

  • Shapes: arrays can have an arbitrary number of dimensions ranging from 0 to infinity (or 32). Matrices are always two-dimensional. Oddly enough, while a matrix can't be created with more dimensions, it's possible to inject singleton dimensions into a matrix to end up with technically a multidimensional matrix: np.matrix(np.random.rand(2,3))[None,...,None].shape == (1,2,3,1) (not that this is of any practical importance).
  • Indexing: indexing arrays can give you arrays of any size depending on how you are indexing it. Indexing expressions on matrices will always give you a matrix. This means that both arr[:,0] and arr[0,:] for a 2d array gives you a 1d ndarray, while mat[:,0] has shape (N,1) and mat[0,:] has shape (1,M) in case of a matrix.
  • Arithmetic operations: the main reason for using matrices in the old days was that arithmetic operations (in particular, multiplication and power) on matrices performs matrix operations (matrix multiplication and matrix power). The same for arrays results in elementwise multiplication and power. Consequently mat1 * mat2 is valid if mat1.shape[1] == mat2.shape[0], but arr1 * arr2 is valid if arr1.shape == arr2.shape (and of course the result means something completely different). Also, surprisingly, mat1 / mat2 performs elementwise division of two matrices. This behaviour is probably inherited from ndarray but makes no sense for matrices, especially in light of the meaning of *.
  • Special attributes: matrices have a few handy attributes in addition to what arrays have: mat.A and mat.A1 are array views with the same value as np.array(mat) and np.array(mat).ravel(), respectively. mat.T and mat.H are the transpose and conjugate transpose (adjoint) of the matrix; arr.T is the only such attribute that exists for the ndarray class. Finally, mat.I is the inverse matrix of mat.

It's easy enough writing code that works either for ndarrays or for matrices. But when there's a chance that the two classes have to interact in code, things start to become difficult. In particular, a lot of code could work naturally for subclasses of ndarray, but matrix is an ill-behaved subclass that can easily break code that tries to rely on duck typing. Consider the following example using arrays and matrices of shape (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

Adding slices of the two objects is catastrophically different depending on the dimension along which we slice. Addition on both matrices and arrays happens elementwise when the shapes are the same. The first two cases in the above are intuitive: we add two arrays (matrices), then we add two rows from each. The last case is really surprising: we probably meant to add two columns and ended up with a matrix. The reason of course is that arr[:,0] has shape (3,) which is compatible with shape (1,3), but mat[:.0] has shape (3,1). The two are broadcast together to shape (3,3).

Finally, the largest advantage of the matrix class (i.e. the possibility to concisely formulate complicated matrix expressions involving a lot of matrix products) was removed when the @ matmul operator was introduced in python 3.5, first implemented in numpy 1.10. Compare the computation of a simple quadratic form:

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

Looking at the above it's clear why the matrix class was widely preferred for working with linear algebra: the infix * operator made the expressions much less verbose and much easier to read. However, we get the same readability with the @ operator using modern python and numpy. Furthermore, note that the matrix case gives us a matrix of shape (1,1) which should technically be a scalar. This also implies that we can't multiply a column vector with this "scalar": (v_row * mat * v_row.T) * v_row.T in the above example raises an error because matrices with shape (1,1) and (3,1) can't be multiplied in this order.

For completeness' sake it should be noted that while the matmul operator fixes the most common scenario in which ndarrays are suboptimal compared to matrices, there are still a few shortcomings in handling linear algebra elegantly using ndarrays (although people still tend to believe that overall it's preferable to stick to the latter). One such example is matrix power: mat ** 3 is the proper third matrix power of a matrix (whereas it's the elementwise cube of an ndarray). Unfortunately numpy.linalg.matrix_power is quite more verbose. Furthermore, in-place matrix multiplication only works fine for the matrix class. In contrast, while both PEP 465 and the python grammar allow @= as an augmented assignment with matmul, this is not implemented for ndarrays as of numpy 1.15.

Deprecation history

Considering the above complications concerning the matrix class there have been recurring discussions of its possible deprecation for a long time. The introduction of the @ infix operator which was a huge prerequisite for this process happened in September 2015. Unfortunately the advantages of the matrix class in earlier days meant that its use spread wide. There are libraries that depend on the matrix class (one of the most important dependent is scipy.sparse which uses both numpy.matrix semantics and often returns matrices when densifying), so fully deprecating them has always been problematic.

Already in a numpy mailing list thread from 2009 I found remarks such as

numpy was designed for general purpose computational needs, not any one branch of math. nd-arrays are very useful for lots of things. In contrast, Matlab, for instance, was originally designed to be an easy front-end to linear algebra package. Personally, when I used Matlab, I found that very awkward -- I was usually writing 100s of lines of code that had nothing to do with linear algebra, for every few lines that actually did matrix math. So I much prefer numpy's way -- the linear algebra lines of code are longer an more awkward, but the rest is much better.

The Matrix class is the exception to this: is was written to provide a natural way to express linear algebra. However, things get a bit tricky when you mix matrices and arrays, and even when sticking with matrices there are confusions and limitations -- how do you express a row vs a column vector? what do you get when you iterate over a matrix? etc.

There has been a bunch of discussion about these issues, a lot of good ideas, a little bit of consensus about how to improve it, but no one with the skill to do it has enough motivation to do it.

These reflect the benefits and difficulties arising from the matrix class. The earliest suggestion for deprecation I could find is from 2008, although partly motivated by unintuitive behaviour that has changed since (in particular, slicing and iterating over a matrix will result in (row) matrices as one would most likely expect). The suggestion showed both that this is a highly controversial subject and that infix operators for matrix multiplication are crucial.

The next mention I could find is from 2014 which turned out to be a very fruitful thread. The ensuing discussion raises the question of handling numpy subclasses in general, which general theme is still very much on the table. There is also strong criticism:

What sparked this discussion (on Github) is that it is not possible to write duck-typed code that works correctly for:

  • ndarrays
  • matrices
  • scipy.sparse sparse matrixes

The semantics of all three are different; scipy.sparse is somewhere between matrices and ndarrays with some things working randomly like matrices and others not.

With some hyberbole added, one could say that from the developer point of view, np.matrix is doing and has already done evil just by existing, by messing up the unstated rules of ndarray semantics in Python.

followed by a lot of valuable discussion of the possible futures for matrices. Even with no @ operator at the time there is a lot of thought given to the deprecation of the matrix class and how it might affect users downstream. As far as I can tell this discussion has directly led to the inception of PEP 465 introducing matmul.

In early 2015:

In my opinion, a "fixed" version of np.matrix should (1) not be a np.ndarray subclass and (2) exist in a third party library not numpy itself.

I don't think it's really feasible to fix np.matrix in its current state as an ndarray subclass, but even a fixed matrix class doesn't really belong in numpy itself, which has too long release cycles and compatibility guarantees for experimentation -- not to mention that the mere existence of the matrix class in numpy leads new users astray.

Once the @ operator had been available for a while the discussion of deprecation surfaced again, reraising the topic about the relationship of matrix deprecation and scipy.sparse.

Eventually, first action to deprecate numpy.matrix was taken in late November 2017. Regarding dependents of the class:

How would the community handle the scipy.sparse matrix subclasses? These are still in common use.

They're not going anywhere for quite a while (until the sparse ndarrays materialize at least). Hence np.matrix needs to be moved, not deleted.

(source) and

while I want to get rid of np.matrix as much as anyone, doing that anytime soon would be really disruptive.

  • There are tons of little scripts out there written by people who didn't know better; we do want them to learn not to use np.matrix but breaking all their scripts is a painful way to do that

  • There are major projects like scikit-learn that simply have no alternative to using np.matrix, because of scipy.sparse.

So I think the way forward is something like:

  • Now or whenever someone gets together a PR: issue a PendingDeprecationWarning in np.matrix.__init__ (unless it kills performance for scikit-learn and friends), and put a big warning box at the top of the docs. The idea here is to not actually break anyone's code, but start to get out the message that we definitely don't think anyone should use this if they have any alternative.

  • After there's an alternative to scipy.sparse: ramp up the warnings, possibly all the way to FutureWarning so that existing scripts don't break but they do get noisy warnings

  • Eventually, if we think it will reduce maintenance costs: split it into a subpackage

(source).

Status quo

As of May 2018 (numpy 1.15, relevant pull request and commit) the matrix class docstring contains the following note:

It is no longer recommended to use this class, even for linear algebra. Instead use regular arrays. The class may be removed in the future.

And at the same time a PendingDeprecationWarning has been added to matrix.__new__. Unfortunately, deprecation warnings are (almost always) silenced by default, so most end-users of numpy will not see this strong hint.

Finally, the numpy roadmap as of November 2018 mentions multiple related topics as one of the "tasks and features [the numpy community] will be investing resources in":

Some things inside NumPy do not actually match the Scope of NumPy.

  • A backend system for numpy.fft (so that e.g. fft-mkl doesn’t need to monkeypatch numpy)
  • Rewrite masked arrays to not be a ndarray subclass – maybe in a separate project?
  • MaskedArray as a duck-array type, and/or
  • dtypes that support missing values
  • Write a strategy on how to deal with overlap between numpy and scipy for linalg and fft (and implement it).
  • Deprecate np.matrix

It's likely that this state will stay as long as larger libraries/many users (and in particular scipy.sparse) rely on the matrix class. However, there's ongoing discussion to move scipy.sparse to depend on something else, such as pydata/sparse. Irrespective of the developments of the deprecation process users should use the ndarray class in new code and preferably port older code if possible. Eventually the matrix class will probably end up in a separate package to remove some of the burdens caused by its existence in its current form.

这篇关于NumPy 矩阵类的弃用状态的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆