numpy矩阵代数最佳实践

时间:2018-01-28 23:18:30

标签: python python-3.x numpy

我的问题是关于下面的最后一行:mu@sigma@mu。它为什么有效?将一维ndarray视为行向量还是列向量?无论哪种方式,都不应该是mu.T@sigma@mumu@sigma@mu.T?我知道mu.T仍然会返回mu,因为mu只有一个维度,但是,解释器似乎太聪明了。

>> import numpy as np
>> mu = np.array([1, 1])
>> print(mu)

[1 1]

>> sigma = np.eye(2) * 3
>> print(sigma)

[[ 3.  0.]
 [ 0.  3.]]

>> mu@sigma@mu

6.0

更一般地说,这是python中矩阵代数的一种更好的做法:使用ndarray和@进行上面的矩阵乘法(更清晰的代码),或者使用np.matrix和重载的{{1}如下(数学上不那么混乱)

*

2 个答案:

答案 0 :(得分:2)

Python从左到右执行链式操作:

In [32]: mu=np.array([1,1])
In [33]: sigma= np.array([[3,0],[0,3]])
In [34]: mu@sigma@mu
Out[34]: 6

与做两个表达式相同:

In [35]: temp=mu@sigma
In [36]: temp.shape
Out[36]: (2,)
In [37]: temp@mu
Out[37]: 6

在我的评论(已删除)中,我声称@正在执行np.dot。那不太对劲。该文档描述了不同的1d数组的处理。但结果形状是相同的:

In [38]: mu.dot(sigma).dot(mu)
Out[38]: 6
In [39]: mu.dot(sigma).shape
Out[39]: (2,)

对于1d和2d数组,np.dot@应该产生相同的结果。它们在处理高维数组时有所不同。

历史上numpy使用的数组可以是0d,1d和on。 np.dot是原始的矩阵乘法方法/函数。

添加了

np.matrix,主要是为了方便任性的MATLAB程序员。它只允许2d数组(就像旧的,20世纪90年代的MATLAB一样)。它使用

重载__mat__(*)
def __mul__(self, other):
    if isinstance(other, (N.ndarray, list, tuple)) :
        # This promotes 1-D vectors to row vectors
        return N.dot(self, asmatrix(other))
    if isscalar(other) or not hasattr(other, '__rmul__') :
        return N.dot(self, other)
    return NotImplemented

Mu*sigmaMu@sigma表现相同,但调用树不同

In [48]: Mu@sigma@Mu
...
ValueError: shapes (1,2) and (1,2) not aligned: 2 (dim 1) != 1 (dim 0)

Mu*sigma产生一个(1,2)矩阵,它不能矩阵乘以a(1,2),因此需要转置:

In [49]: Mu@sigma@Mu.T
Out[49]: matrix([[6]])

请注意,这是一个(1,1)矩阵。如果你想要一个标量,你必须使用item。 (在MATLAB中,没有标量这样的东西。一切都有形状/大小。)

@是Python和numpy的最新成员。它作为未实现的运算符添加到Python中。 numpy(可能还有其他包)实现了它。

它使链式表达式成为可能,但我对dot中的链式[38]没有任何问题。在处理更高维度的情况时,它更有用。

此添加意味着使用旧np.matrix类的理由减少了一个。 (矩阵式行为在scipy.sparse矩阵类中根深蒂固。)

如果你想要数学纯度'我建议采用数学物理方法,并使用爱因斯坦符号 - 在np.einsum中实现。

对于这么小的数组,时序反映的是调用结构,而不是实际的计算次数:

In [57]: timeit mu.dot(sigma).dot(mu)
2.79 µs ± 7.75 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [58]: timeit mu@sigma@mu
6.29 µs ± 31.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [59]: timeit Mu@sigma@Mu.T
17.1 µs ± 134 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [60]: timeit Mu*sigma*Mu.T
17.7 µs ± 517 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

请注意,老式的' dot是最快的,而两个矩阵版本都比较慢。

关于转置

我可能会在mu@sigma@mu.T这样的表达式中添加.T,因此@matmul)不知道有尝试转置mu。它只是使用转置的结果。请记住它是解析表达式而不是numpy函数的Python解释器。因此,如果mu.Tmu没有任何作用,就像1d数组的情况一样,.T也不会对@产生影响。

transpose已为2d数组定义良好。 numpy转置的编写方式使得它也适用于任何维度的数组。它改变了轴的顺序,但从不添加尺寸。还有其他工具可以执行此操作,例如reshapenp.newaxis索引。

在Octave中,只为二维物体定义转置

>> ones(2,3,4)'
error: transpose not defined for N-d objects

MATLAB中不存在1-d对象。

In [270]: np.ones((2,3,4),int).T.shape
Out[270]: (4, 3, 2)

内外产品

np.dot非常清楚如何处理1d数组,'向量':

  
      
  • 如果ab都是1-D数组,则它是向量的内积   (没有复杂的共轭)。
  •   

matmul中,描述更复杂,但结果是相同的。

关于你的评论:

  

A = [1,2]且B = [3,5]为(2,)ndarray,A @ B可能意味着[1,2] * [3,5]' = 13,或者它可能意味着[1,2]' * [3,5] = [[3,5],[6,10]]

numpy中获取产品的各种方式是:

In [273]: A = np.array([1,2]); B = np.array([3,5])

元素乘法(MATLAB中的.*

In [274]: A*B
Out[274]: array([ 3, 10])

内在产品

In [275]: A@B       # same as np.dot(A,B)
Out[275]: 13

外部产品

In [276]: np.outer(A,B)
Out[276]: 
array([[ 3,  5],
       [ 6, 10]])

获得内在产品的另一种方法:

In [278]: np.sum(A*B)
Out[278]: 13

用爱因斯坦符号(来自数学物理学),内在和外在:

In [280]: np.einsum('i,i',A,B)
Out[280]: array(13)
In [281]: np.einsum('i,j',A,B)
Out[281]: 
array([[ 3,  5],
       [ 6, 10]])

使用广播获取外部

In [282]: A[:,None]*B[None,:]
Out[282]: 
array([[ 3,  5],
       [ 6, 10]])

[1, 2]'所要求的内容在numpy中实现为A[:,None],将1d数组转换为列向量(2d)。

Octave在numpy之后很久就加入了广播。我不知道MATLAB是否已经发展到那么远。 :)

@不进行广播,但可以使用列或行向量:

In [283]: A@B[:,None]       # your imagined A*B'
Out[283]: array([13])

要使用@获取外部,我需要添加维度以使A成为列向量,并B添加行向量:

In [284]: A[:,None]@B
ValueError: shapes (2,1) and (2,) not aligned: 1 (dim 1) != 2 (dim 0)
In [285]: A[:,None]@B[None,:]
Out[285]: 
array([[ 3,  5],
       [ 6, 10]])

当数组确实是2d,并且包括一维为1的情况时,@ / dot行为与常见的矩阵约定不同。当尺寸为1或大于2时,MATLAB的直觉失败,部分原因是MATLAB无法处理这些直觉。

这个Octave错误表明n-d矩阵只能捎带2d。它们在numpy意义上并不是真正的n-d。

>> ones(2,3,4) * ones(2,3,4)
error: operator *: nonconformant arguments (op1 is 2x12, op2 is 2x12)

答案 1 :(得分:1)

基本上,如果您有1d数组one_d和2d数组two_d,则将它们与@相乘会以这样的方式处理1d数组,即获得常规矩阵 - 向量乘法:

one_d @ two_d # gives a row vector (that is returned as 1d array)
two_d @ one_d # gives a column vector (also returned as 1d array)

在内部,1d数组扩展为shape(n,1)或(1,n)数组,执行乘法,然后将其转换回一维数组。

关于哪个选项更好,scipy documentation推荐数组。