np.dot product between two 3D matrices along specified axis

强颜欢笑 提交于 2020-01-25 01:04:20

问题


I have two 3D matrices:

a = np.random.normal(size=[3,2,5])
b = np.random.normal(size=[5,2,3])

I want the dot product of each slice along 2 and 0 axes respectively:

c = np.zeros([3,3,5]) # c.size is 45
c[:,:,0] = a[:,:,0].dot(b[0,:,:])
c[:,:,1] = a[:,:,1].dot(b[1,:,:])
...

I would like to do that using np.tensordot (for efficiency and speed) I have tried:

c = np.tensordot(a, b, axes=[2,0])

but I get a 4D array with 36 elements (instead of 45). c.shape, c.size = ((3L, 2L, 2L, 3L), 36). I have found a similar question here (Numpy tensor: Tensordot over frontal slices of tensor) but it's not exactly what I want, and I was unable to extrapolate that solution to my problem. To summarise, can I use np.tensordot to compute c array show above?

Update #1

The answer by @hpaulj is what I wanted, however in my system (python 2.7 and np 1.13.3) those aproaches are pretty slow:

n = 3000
a = np.random.normal(size=[n, 20, 5])
b = np.random.normal(size=[5, 20, n])


t = time.clock()
c_slice = a[:,:,0].dot(b[0,:,:])
print('one slice_x_5: {:.3f} seconds'.format( (time.clock()-t)*5 ))

t = time.clock()
c = np.zeros([n, n, 5])
for i in range(5):
     c[:,:,i] = a[:,:,i].dot(b[i,:,:])
print('for loop: {:.3f} seconds'.format(time.clock()-t))

t = time.clock()
d = np.einsum('abi,ibd->adi', a, b)
print('einsum: {:.3f} seconds'.format(time.clock()-t))

t = time.clock()
e = np.tensordot(a,b,[1,1])
e1 = e.transpose(0,3,1,2)[:,:,np.arange(5),np.arange(5)]
print('tensordot: {:.3f} seconds'.format(time.clock()-t))

a = a.transpose(2,0,1)
t = time.clock()
f = np.matmul(a,b)
print('matmul: {:.3f} seconds'.format(time.clock()-t))

回答1:


It's easier to work with einsum than tensordot. So let's start there:

In [469]: a = np.random.normal(size=[3,2,5])
     ...: b = np.random.normal(size=[5,2,3])
     ...: 
In [470]: c = np.zeros([3,3,5]) # c.size is 45
In [471]: for i in range(5):
     ...:     c[:,:,i] = a[:,:,i].dot(b[i,:,:])
     ...:     

In [472]: d = np.einsum('abi,ibd->iad', a, b)
In [473]: d.shape
Out[473]: (5, 3, 3)
In [474]: d = np.einsum('abi,ibd->adi', a, b)
In [475]: d.shape
Out[475]: (3, 3, 5)
In [476]: np.allclose(c,d)
Out[476]: True

I had to think a bit about to match up the dimensions. It helped to focus on a[:,:,i] as 2d, and similarly for b[i,:,:]. So the dot sum is over the middle dimension of both arrays (size 2).

In testing ideas it might help if the first 2 dimensions of c were different. There'd be less chance of mixing them up.

It's easy to specify the dot summation axis (axes) in tensordot, but harder to constrain the handling of the other dimensions. That's why you get a 4d array.

I can get it to work with a transpose, followed by taking the diagonal:

In [477]: e = np.tensordot(a,b,[1,1])
In [478]: e.shape
Out[478]: (3, 5, 5, 3)
In [479]: e1 = e.transpose(0,3,1,2)[:,:,np.arange(5),np.arange(5)]
In [480]: e1.shape
Out[480]: (3, 3, 5)
In [481]: np.allclose(c,e1)
Out[481]: True

I've calculated a lot more values than needed, and thrown most of them away.

matmul with some transposing might work better.

In [482]: f = a.transpose(2,0,1)@b
In [483]: f.shape
Out[483]: (5, 3, 3)
In [484]: np.allclose(c, f.transpose(1,2,0))
Out[484]: True

I think of the 5 dimension as 'going-along-for-ride'. That's what your loop does. In einsum the i is the same in all parts.



来源:https://stackoverflow.com/questions/49281459/np-dot-product-between-two-3d-matrices-along-specified-axis

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!