Home > Net >  numpy complicated manipulation of matrix multiplication
numpy complicated manipulation of matrix multiplication

Time:07-28

I want to multiply a 3D tensor with a 2D tensor and get a 2D tensor as result. The way I envision this to be done is rather strange, so I ask for your help.

The 3D tensor, for when we fix the value of the first dimension to some integer within range(3D_tensor.shape[0]), is a 2D matrix. We picture this 2D matrix as:

``[[a0, b0, c0], [d0, e0, f0], [g0, h0, p0]]``

I want this to be multiplied by the 2D tensor (see above) which can be pictured as:

 ``[[10, 20, 30], [40, 50, 60], [70, 80, 90]]``

The multiplication shall not be a matrix-matrix multiplication, but each row from the (left ) 2D matrix to be multiplied, element wise, with the corresponding row from the 2D tensor. The result shall be a 3 element array:

``[a0 * 10   b0 * 20   c0 * 30,  d0 * 40, e0 * 50, f0 * 60, g0 * 70   h0 * 80   p0 * 90]``.

This multiplication procedure repeats for all the other "slices" of the 3D tensor. That is, if the 2D matrix picture above was for 3D_tensor[0, :, :], then we would have

``[[a1, b1, c1], [d1, e1, f1] ,[g1, h1, p1]]`` 

for 3D_tensor[1, :, :], and the result shall be

``[a1 * 10   b1 * 20   c1 * 30, d1 * 40   e1 * 50   f1 * 60, g1 * 70   h1 * 80   p1 * 90]``.

These 3-element 1D arrays shall be glued together into a bigger 2D array (the result I want to obtain), where each 3-element 1D array to be one of the 2D array's rows (so place them horizontally). That would be, my final result in the pictures above, is a 2D array:

``[ [a0 * 10   b0 * 20   c0 * 30,  d0 * 40, e0 * 50, f0 * 60, g0 * 70   h0 * 80   p0 * 90], [a1 * 10   b1 * 20   c1 * 30, d1 * 40   e1 * 50   f1 * 60, g1 * 70   h1 * 80   p1 * 90] ] ``

Ideally, I want this to be done with the BLAS routines, but using numpy. Is what I want to obtain possible?

I know about np.dot, multiply, @, ... , and tried to use them, but I cannot figure how I can do the above, EFFICIENTLY, myself.

Thank you!

CodePudding user response:

Use:

import numpy as np

tensor3d = np.arange(27).reshape((3, 3, 3))
tensor2d = np.array([[10, 20, 30], [40, 50, 60], [70, 80, 90]])

res = (tensor3d * tensor2d).sum(2)
print(res)

Output

[[  80  620 1700]
 [ 620 1970 3860]
 [1160 3320 6020]]

The expression:

tensor3d * tensor2d

returns:

[[[   0   20   60]
  [ 120  200  300]
  [ 420  560  720]]

 [[  90  200  330]
  [ 480  650  840]
  [1050 1280 1530]]

 [[ 180  380  600]
  [ 840 1100 1380]
  [1680 2000 2340]]]

basically element-wise multiplication, as requested. Then:

.sum(2)

sums the results across the third axis (2 for 0 based indexing). For example, for the first row of the final result:

80    = 0   20   60 
620   = 120   200   300
1700  = 420   560   720

UPDATE

To do the above operation for multiple arrays do:

import numpy as np

tensor3d = np.array([np.arange(27).reshape((3, 3, 3)), np.arange(27).reshape((3, 3, 3))])
tensor2d = np.array([np.array([[10, 20, 30], [40, 50, 60], [70, 80, 90]]),
                     np.array([[10, 20, 30], [40, 50, 60], [70, 80, 90]])])

tensor2d = tensor2d[:, np.newaxis, ...]

res = (tensor3d * tensor2d).sum(3)
print(res)

Output

[[[  80  620 1700]
  [ 620 1970 3860]
  [1160 3320 6020]]

 [[  80  620 1700]
  [ 620 1970 3860]
  [1160 3320 6020]]]

In this example:

tensor3d.shape = (2, 3, 3, 3)  # two 3d tensors
tensor2d.shape = (2, 1, 3, 3)  # two 2d matrices
res.shape = (2, 3, 3) # two 2d matrices

note that the original shape of tensor2d is (2, 3, 3) but in order for broadcasting to work you need to add an extra dimension, hence the expression:

tensor2d = tensor2d[:, np.newaxis, ...]

CodePudding user response:

I suspect this is largely a repeat of Dani's answer, but I just want to confirm my understanding of your question.

Making a more concrete version of your 3d array:

In [340]: A = np.arange(2*3*3).reshape(2,3,3); B = np.arange(10,100,10).reshape(3,3)
In [341]: A
Out[341]: 
array([[[ 0,  1,  2],
        [ 3,  4,  5],
        [ 6,  7,  8]],

       [[ 9, 10, 11],
        [12, 13, 14],
        [15, 16, 17]]])
In [342]: B
Out[342]: 
array([[10, 20, 30],
       [40, 50, 60],
       [70, 80, 90]])
In [343]: (A*B).shape
Out[343]: (2, 3, 3)

It looks like you just want to multiply the 2 arrays, taking advantage of broadcasting, and sum along the last dimension (across columns):

In [344]: (A*B).sum(axis=2)    # axis=-1 for last
Out[344]: 
array([[  80,  620, 1700],
       [ 620, 1970, 3860]])

So for one plane of A and one row:

In [345]: (A[0,0,:]*B[0]).sum()
Out[345]: 80

In general if A is (N,3,3) and B is (3,3), then A*B is also (N,3,3), and the sum reduces it on the last dimension, producing a (N,3) result.

This can also be expressed as a matrix product with einsum:

In [352]: np.einsum('ijk,jk->ij',A,B)
Out[352]: 
array([[  80,  620, 1700],
       [ 620, 1970, 3860]])

matmul/@ can also do it, but getting mix of dimensions right is trickier.

If A is (N,M,3,3) and B is (M,3,3), A*B.sum(axis=-1) produces (N,M,3).

In broadcasting the B is expanded to (1,M,3,3) and then to (N,M,3,3) to match A.

  • Related