[英]numpy - einsum notation: dot product of a stack of matrices with stack of vectors
我想将 m* m 个矩阵的 n-dim 堆栈乘以向量的 n-dim 堆栈(长度为 m),以便得到的 m*n 数组包含矩阵和第 n 个向量的点积的结果入口:
vec1=np.array([0,0.5,1,0.5]); vec2=np.array([2,0.5,1,0.5])
vec=np.transpose(n.stack((vec1,vec2)))
mat = np.moveaxis(n.array([[[0,1,2,3],[0,1,2,3],[0,1,2,3],[0,1,2,3]],[[-1,2.,0,1.],[0,0,-1,2.],[0,1,-1,2.],[1,0.1,1,1]]]),0,2)
outvec=np.zeros((4,2))
for i in range(2):
outvec[:,i]=np.dot(mat[:,:,i],vec[:,i])
受这篇文章Element wise dot product of matrix and vectors 的启发,我在 einsum 中尝试了所有不同的索引组合扰动,并发现
np.einsum('ijk,jk->ik',mat,vec)
给出正确的结果。
不幸的是,我真的不明白这一点 - 我假设我在 'ijk,jk' 部分重复条目 k 的事实意味着我对 k 进行乘法和求和。 我试图阅读文档https://docs.scipy.org/doc/numpy-1.15.1/reference/generated/numpy.einsum.html ,但我仍然不明白。
(我之前的尝试包括,
np.einsum('ijk,il->ik', mat, vec)
我什至不确定这意味着什么。 当我删除它时索引 l 会发生什么?)
提前致谢!
阅读爱因斯坦求和符号。
基本上,规则是:
->
->
因此,例如,对于具有相同形状的矩阵A
和B
:
np.einsum('ij, ij', A, B) # is A ddot B, returns 0d scalar
np.einsum('ij, jk', A, B) # is A dot B, returns 2d tensor
np.einsum('ij, kl', A, B) # is outer(A, B), returns 4d tensor
np.einsum('ji, jk, kl', A, B) # is A.T @ B @ A, returns 2d tensor
np.einsum('ij, ij -> ij', A, B) # is A * B, returns 2d tensor
np.einsum('ij, ij -> i' , A, A) # is norm(A, axis = 1), returns 1d tensor
np.einsum('ii' , A) # is tr(A), returns 0d scalar
In [321]: vec1=np.array([0,0.5,1,0.5]); vec2=np.array([2,0.5,1,0.5])
...: vec=np.transpose(np.stack((vec1,vec2)))
In [322]: vec1.shape
Out[322]: (4,)
In [323]: vec.shape
Out[323]: (4, 2)
stack
函数的一个好处是我们可以指定一个轴,跳过转置:
In [324]: np.stack((vec1,vec2), axis=1).shape
Out[324]: (4, 2)
为什么混合np.
和n.
? NameError: name 'n' is not defined
。 这种事情差点把我赶走。
In [326]: mat = np.moveaxis(np.array([[[0,1,2,3],[0,1,2,3],[0,1,2,3],[0,1,2,3]],[[-1,2.,0
...: ,1.],[0,0,-1,2.],[0,1,-1,2.],[1,0.1,1,1]]]),0,2)
In [327]: mat.shape
Out[327]: (4, 4, 2)
In [328]: outvec=np.zeros((4,2))
...: for i in range(2):
...: outvec[:,i]=np.dot(mat[:,:,i],vec[:,i])
...:
In [329]: outvec
Out[329]:
array([[ 4. , -0.5 ],
[ 4. , 0. ],
[ 4. , 0.5 ],
[ 4. , 3.55]])
In [330]: # (4,4,2) (4,2) 'kji,ji->ki'
从您的循环中, i
轴(大小 2)的位置很清楚 - 在所有 3 个数组中排在最后。 这为vec
留下了一个轴,让我们称之为j
。 它与最后一个(在mat
i
旁边)配对。 k
从mat
outvec
到outvec
。
In [331]: np.einsum('kji,ji->ki', mat, vec)
Out[331]:
array([[ 4. , -0.5 ],
[ 4. , 0. ],
[ 4. , 0.5 ],
[ 4. , 3.55]])
通常einsum
字符串会写入自己。 例如,如果mat
被描述为 (m,n,k) 并且vec
被描述为 (n,k),结果是 (m,k)
在这种情况下,只对j
维求和 - 它出现在左侧,但在右侧。 最后一个层面, i
在我的符号,不总结,因为如果双方出现,只是因为它在你的迭代。 我认为那是“随心所欲”。 它不是dot
积的积极组成部分。
您实际上是在最后一个维度上堆叠,尺寸为 2。 通常我们堆叠在第一个上,但是您将两个都调换为最后一个。
您的“失败”尝试运行,并且可以复制为:
In [332]: np.einsum('ijk,il->ik', mat, vec)
Out[332]:
array([[12. , 4. ],
[ 6. , 1. ],
[12. , 4. ],
[ 6. , 3.1]])
In [333]: mat.sum(axis=1)*vec.sum(axis=1)[:,None]
Out[333]:
array([[12. , 4. ],
[ 6. , 1. ],
[12. , 4. ],
[ 6. , 3.1]])
j
和l
维度没有出现在右侧,因此它们被求和。 它们可以在相乘之前求和,因为它们每个只出现在一项中。 我添加了None
以启用广播(将ik
与i
相乘)。
np.einsum('ik,i->ik', mat.sum(axis=1), vec.sum(axis=1))
如果您堆叠在第一个上,并为vec
(2,4,1) 添加一个维度,则它会使用 (2,4,4) 垫进行matmul
。 mat @ vec[...,None]
。
In [337]: m1 = mat.transpose(2,0,1)
In [338]: m1@v1[...,None]
Out[338]:
array([[[ 4. ],
[ 4. ],
[ 4. ],
[ 4. ]],
[[-0.5 ],
[ 0. ],
[ 0.5 ],
[ 3.55]]])
In [339]: _.shape
Out[339]: (2, 4, 1)
einsum
很容易(当你玩过一段时间的索引排列时,那就是......)。
让我们处理一些简单的事情,一个2×2矩阵的三重堆栈和一个2×数组的三重堆栈
import numpy as np
a = np.arange(3*2*2).reshape((3,2,2))
b = np.arange(3*2).reshape((3,2))
我们需要知道我们将使用einsum
计算einsum
In [101]: for i in range(3):
...: print(a[i]@b[i])
[1 3]
[23 33]
[77 95]
我们所做的? 当我们在堆叠矩阵之一和堆叠向量之一(均由i
索引)之间执行点积时,我们有一个固定的索引i
,并且单个输出行意味着对堆叠矩阵的最后一个索引的求和和堆叠向量的唯一索引。
这很容易在einsum
指令中编码
i
索引来指定矩阵、向量和输出,k
j
因此
In [102]: np.einsum('ijk,ik->ij', a, b)
Out[102]:
array([[ 1, 3],
[23, 33],
[77, 95]])
我希望我对如何获得正确指令的讨论是清晰、正确和有用的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.