繁体   English   中英

numpy - einsum 表示法:矩阵堆栈与向量堆栈的点积

[英]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 会发生什么?)

提前致谢!

阅读爱因斯坦求和符号

基本上,规则是:

没有->

  • 输入中重复的任何字母表示要乘以和求和的轴
  • 输入中未重复的任何字母都包含在输出中

带有->

  • 输入中重复的任何字母表示要乘以的轴
  • 任何不在输出中的字母都代表要求和的轴

因此,例如,对于具有相同形状的矩阵AB

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旁边)配对。 kmat outvecoutvec

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]])

jl维度没有出现在右侧,因此它们被求和。 它们可以在相乘之前求和,因为它们每个只出现在一项中。 我添加了None以启用广播(将iki相乘)。

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矩阵的三重堆栈和一个数组的三重堆栈

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM