NumPy矩阵和数组类的乘法有何区别?

145

NumPy文档建议使用array而不是matrix处理矩阵。然而,与我最近使用的Octave不同,*符号不执行矩阵乘法,需要使用函数matrixmultiply()。我认为这使得代码非常难以阅读。

是否有人与我持相同意见,并找到了解决方案?


8
你在征求意见,而不是提出问题。我们能帮你处理更具体的事情或者指导你使内容更易读吗? - wheaties
2
实际上,文档建议在进行线性代数运算且不想使用multiply()函数时使用矩阵,那么问题出在哪里呢? - Matti Pastell
1
我还没有详细阅读文档。只是好奇,数组相对于矩阵类有什么优势?我发现数组不区分行和列。这是因为数组应该被视为张量而不是矩阵吗?正如Joe指出的那样,矩阵类是二维的,这相当受限制。这种设计背后的思想是什么,为什么不像Matlab/Octave一样只有一个矩阵类呢? - elexhobby
我猜主要问题在于Python没有.**语法来区分逐元素乘法和矩阵乘法。如果有这个区分的话,那么一切都会更简单,但我很惊讶他们选择使用*表示逐元素乘法而不是矩阵乘法。 - Charlie Parker
8个回答

134
避免使用matrix类的主要原因是它本质上是二维的,并且与“正常”的numpy数组相比存在额外的开销。如果您所做的只是线性代数,那么请随意使用矩阵类...但个人认为这样做更麻烦而不值得。

对于数组(Python 3.5之前),请使用dot而不是matrixmultiply

例如:

import numpy as np
x = np.arange(9).reshape((3,3))
y = np.arange(3)

print np.dot(x,y)

在较新版本的numpy中,可以使用x.dot(y)来进行矩阵乘法。

个人认为,与*操作符暗示的矩阵乘法相比,这种方法更易读...

对于Python 3.5中的数组,请使用x @ y


10
当你有一堆乘法时,例如 x'*A'Ax,它很难读懂。 - elexhobby
15
@elexhobby - 在我看来,x.T.dot(A.T).dot(A).dot(x) 并不难读。当然,每个人都有自己的喜好。如果你主要是进行矩阵乘法运算,那么请务必使用 numpy.matrix - Joe Kington
7
顺便问一下,为什么矩阵乘法叫做“点乘”?它在什么意义上是一个点积? - amcnabb
8
在一些教科书中,矩阵乘法有时被称为“点积”(在这些书中,你所想的点积被称为“标量积”或“内积”)。毕竟,标量积就是两个向量的矩阵乘法,因此使用“点积”来表示一般的矩阵乘法并不算过于牵强。至少在我的经验中,这种特定的符号似乎在工程和科学文本中比在数学文本中更常见。在numpy中广泛使用这种符号主要是因为numpy.matrixmultiply很难键入。 - Joe Kington
7
@amcnabb 的观点是,点乘可以泛化到任意维度而不会产生歧义。正是这一点使得 numpy.dot 等价于矩阵乘法。如果您真的不喜欢这种符号表示,可以使用 matrix 类。 - Henry Gomersall
显示剩余11条评论

83

进行NumPy数组操作和进行NumPy 矩阵操作需要知道的关键信息如下:

  • NumPy矩阵是NumPy数组的子类

  • NumPy数组操作是逐元素的(考虑到广播)

  • NumPy矩阵操作遵循线性代数的普通规则

以下是一些代码片段以说明:

>>> from numpy import linalg as LA
>>> import numpy as NP

>>> a1 = NP.matrix("4 3 5; 6 7 8; 1 3 13; 7 21 9")
>>> a1
matrix([[ 4,  3,  5],
        [ 6,  7,  8],
        [ 1,  3, 13],
        [ 7, 21,  9]])

>>> a2 = NP.matrix("7 8 15; 5 3 11; 7 4 9; 6 15 4")
>>> a2
matrix([[ 7,  8, 15],
        [ 5,  3, 11],
        [ 7,  4,  9],
        [ 6, 15,  4]])

>>> a1.shape
(4, 3)

>>> a2.shape
(4, 3)

>>> a2t = a2.T
>>> a2t.shape
(3, 4)

>>> a1 * a2t         # same as NP.dot(a1, a2t) 
matrix([[127,  84,  85,  89],
        [218, 139, 142, 173],
        [226, 157, 136, 103],
        [352, 197, 214, 393]])

但是,如果这两个NumPy矩阵被转换为数组,则此操作将失败:

>>> a1 = NP.array(a1)
>>> a2t = NP.array(a2t)

>>> a1 * a2t
Traceback (most recent call last):
   File "<pyshell#277>", line 1, in <module>
   a1 * a2t
   ValueError: operands could not be broadcast together with shapes (4,3) (3,4) 

虽然使用 NP.dot 语法可以适用于数组,但此操作的工作原理类似于矩阵乘法:

>> NP.dot(a1, a2t)
array([[127,  84,  85,  89],
       [218, 139, 142, 173],
       [226, 157, 136, 103],
       [352, 197, 214, 393]])

你是否需要一个NumPy矩阵?也就是说,对于线性代数计算,使用NumPy数组是否足够(只要你知道正确的语法,即NP.dot)?

规则似乎是,如果参数(数组)的形状(m x n)与给定的线性代数操作兼容,则可以使用,否则,NumPy会报错。

我遇到的唯一例外(可能还有其他情况)是计算矩阵逆

以下是我调用纯线性代数操作(实际上来自NumPy的线性代数模块)并传入NumPy数组的代码片段。

数组的行列式

>>> m = NP.random.randint(0, 10, 16).reshape(4, 4)
>>> m
array([[6, 2, 5, 2],
       [8, 5, 1, 6],
       [5, 9, 7, 5],
       [0, 5, 6, 7]])

>>> type(m)
<type 'numpy.ndarray'>

>>> md = LA.det(m)
>>> md
1772.9999999999995

特征向量/特征值对:

>>> LA.eig(m)
(array([ 19.703+0.j   ,   0.097+4.198j,   0.097-4.198j,   5.103+0.j   ]), 
array([[-0.374+0.j   , -0.091+0.278j, -0.091-0.278j, -0.574+0.j   ],
       [-0.446+0.j   ,  0.671+0.j   ,  0.671+0.j   , -0.084+0.j   ],
       [-0.654+0.j   , -0.239-0.476j, -0.239+0.476j, -0.181+0.j   ],
       [-0.484+0.j   , -0.387+0.178j, -0.387-0.178j,  0.794+0.j   ]]))

矩阵范数:

>>>> LA.norm(m)
22.0227

QR分解:

>>> LA.qr(a1)
(array([[ 0.5,  0.5,  0.5],
        [ 0.5,  0.5, -0.5],
        [ 0.5, -0.5,  0.5],
        [ 0.5, -0.5, -0.5]]), 
 array([[ 6.,  6.,  6.],
        [ 0.,  0.,  0.],
        [ 0.,  0.,  0.]]))

矩阵

>>> m = NP.random.rand(40).reshape(8, 5)
>>> m
array([[ 0.545,  0.459,  0.601,  0.34 ,  0.778],
       [ 0.799,  0.047,  0.699,  0.907,  0.381],
       [ 0.004,  0.136,  0.819,  0.647,  0.892],
       [ 0.062,  0.389,  0.183,  0.289,  0.809],
       [ 0.539,  0.213,  0.805,  0.61 ,  0.677],
       [ 0.269,  0.071,  0.377,  0.25 ,  0.692],
       [ 0.274,  0.206,  0.655,  0.062,  0.229],
       [ 0.397,  0.115,  0.083,  0.19 ,  0.701]])
>>> LA.matrix_rank(m)
5

矩阵条件:

>>> a1 = NP.random.randint(1, 10, 12).reshape(4, 3)
>>> LA.cond(a1)
5.7093446189400954

反演需要 NumPy 矩阵

>>> a1 = NP.matrix(a1)
>>> type(a1)
<class 'numpy.matrixlib.defmatrix.matrix'>

>>> a1.I
matrix([[ 0.028,  0.028,  0.028,  0.028],
        [ 0.028,  0.028,  0.028,  0.028],
        [ 0.028,  0.028,  0.028,  0.028]])
>>> a1 = NP.array(a1)
>>> a1.I

Traceback (most recent call last):
   File "<pyshell#230>", line 1, in <module>
   a1.I
   AttributeError: 'numpy.ndarray' object has no attribute 'I'

但是摩尔-彭若斯伪逆矩阵似乎能够正常工作

>>> LA.pinv(m)
matrix([[ 0.314,  0.407, -1.008, -0.553,  0.131,  0.373,  0.217,  0.785],
        [ 1.393,  0.084, -0.605,  1.777, -0.054, -1.658,  0.069, -1.203],
        [-0.042, -0.355,  0.494, -0.729,  0.292,  0.252,  1.079, -0.432],
        [-0.18 ,  1.068,  0.396,  0.895, -0.003, -0.896, -1.115, -0.666],
        [-0.224, -0.479,  0.303, -0.079, -0.066,  0.872, -0.175,  0.901]])

>>> m = NP.array(m)

>>> LA.pinv(m)
array([[ 0.314,  0.407, -1.008, -0.553,  0.131,  0.373,  0.217,  0.785],
       [ 1.393,  0.084, -0.605,  1.777, -0.054, -1.658,  0.069, -1.203],
       [-0.042, -0.355,  0.494, -0.729,  0.292,  0.252,  1.079, -0.432],
       [-0.18 ,  1.068,  0.396,  0.895, -0.003, -0.896, -1.115, -0.666],
       [-0.224, -0.479,  0.303, -0.079, -0.066,  0.872, -0.175,  0.901]])

3
mInv = NP.linalg.inv(m) 计算一个数组的逆矩阵。 - db1234
这里需要注意的一个重要点是,* 表示逐元素相乘,而 dot 则表示真正的矩阵乘法。请参见 https://dev59.com/mXXYa4cB1Zd3GeqP9bn-#18255635。 - Minh Triet
1
重要提示:建议使用数组而不是numpy矩阵。文档中指出:「即使在进行线性代数计算时,也不再建议使用此类。请改用常规数组。该类可能会在将来被删除。」另请参见 https://dev59.com/N2855IYBdhLWcg3wxHcq#61156350。 - HopeKing

22

2
谢谢!耶,很高兴看到我不是唯一觉得当前符号难以阅读的人。 - elexhobby

15
在处理数组和矩阵时,点操作符会得到不同的结果。例如,假设有以下情况:
>>> a=numpy.array([1, 2, 3])
>>> b=numpy.array([1, 2, 3])

让我们把它们转换成矩阵:
>>> am=numpy.mat(a)
>>> bm=numpy.mat(b)

现在,我们可以看到两种情况下的不同输出结果:

>>> print numpy.dot(a.T, b)
14
>>> print am.T*bm
[[1.  2.  3.]
 [2.  4.  6.]
 [3.  6.  9.]]

具体来说,* 表示逐元素相乘,dot 表示矩阵乘法。请参见https://dev59.com/mXXYa4cB1Zd3GeqP9bn-#18255635 - Minh Triet
这是因为作为一个numpy数组,a.T == a,转置不会有任何变化。 - patapouf_ai
如果你写成 at = np.array([[1],[2],[3]]),那么 numpy.dot(at,b) 应该会给你同样的结果。矩阵和数组之间的区别不在于点乘,而在于转置。 - patapouf_ai
或者,如果你写 a = numpy.array([[1,2,3]]),那么 a.T 将会真正地进行转置,并且一切都会像矩阵一样正常工作。 - patapouf_ai

8

参考自http://docs.scipy.org/doc/scipy/reference/tutorial/linalg.html

..., 强烈不建议使用numpy.matrix类,因为它没有比2D numpy.ndarray对象更强大的功能,并且可能会导致使用哪个类的混淆。例如:

>>> import numpy as np
>>> from scipy import linalg
>>> A = np.array([[1,2],[3,4]])
>>> A
    array([[1, 2],
           [3, 4]])
>>> linalg.inv(A)
array([[-2. ,  1. ],
      [ 1.5, -0.5]])
>>> b = np.array([[5,6]]) #2D array
>>> b
array([[5, 6]])
>>> b.T
array([[5],
      [6]])
>>> A*b #not matrix multiplication!
array([[ 5, 12],
      [15, 24]])
>>> A.dot(b.T) #matrix multiplication
array([[17],
      [39]])
>>> b = np.array([5,6]) #1D array
>>> b
array([5, 6])
>>> b.T  #not matrix transpose!
array([5, 6])
>>> A.dot(b)  #does not matter for multiplication
array([17, 39])

scipy.linalg 操作可同样应用于 numpy.matrix 或 2D 的 numpy.ndarray 对象。


7

这个技巧也许是你正在寻找的。它是一种简单的运算符重载。

你可以像下面这样使用建议的Infix类:

a = np.random.rand(3,4)
b = np.random.rand(4,3)
x = Infix(lambda x,y: np.dot(x,y))
c = a |x| b

5

PEP 465 - A dedicated infix operator for matrix multiplication 中提到的一句相关的引用,由 @petr-viktorin 提出,澄清了OP所指的问题:

[...] numpy 提供了两种具有不同 __mul__ 方法的类型。对于 numpy.ndarray 对象,* 执行逐元素的乘法,矩阵乘法必须使用函数调用 (numpy.dot)。对于 numpy.matrix 对象,* 执行矩阵乘法,逐元素的乘法需要函数语法。使用 numpy.ndarray 编写代码是可以正常工作的。使用 numpy.matrix 编写代码也可以正常工作。但是当我们尝试将这两个代码片段集成在一起时,问题就开始了。期望得到一个 ndarray 却得到一个 matrix 或反之亦然的代码可能会崩溃或返回错误结果

引入中缀运算符 @ 应该有助于统一和简化 Python 矩阵代码。


1

函数matmul(自NumPy 1.10.1起)对两种类型都有效,并将结果作为NumPy矩阵类返回:

import numpy as np

A = np.mat('1 2 3; 4 5 6; 7 8 9; 10 11 12')
B = np.array(np.mat('1 1 1 1; 1 1 1 1; 1 1 1 1'))
print (A, type(A))
print (B, type(B))

C = np.matmul(A, B)
print (C, type(C))

输出:

(matrix([[ 1,  2,  3],
        [ 4,  5,  6],
        [ 7,  8,  9],
        [10, 11, 12]]), <class 'numpy.matrixlib.defmatrix.matrix'>)
(array([[1, 1, 1, 1],
       [1, 1, 1, 1],
       [1, 1, 1, 1]]), <type 'numpy.ndarray'>)
(matrix([[ 6,  6,  6,  6],
        [15, 15, 15, 15],
        [24, 24, 24, 24],
        [33, 33, 33, 33]]), <class 'numpy.matrixlib.defmatrix.matrix'>)

自从 Python 3.5 早期提到,你也可以使用一个新的矩阵乘法运算符 @ ,例如:

C = A @ B

并获得与上面相同的结果。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接