Numpy elementwise product of 3d array

2019-01-24 08:04发布

问题:

I have two 3d arrays A and B with shape (N, 2, 2) that I would like to multiply element-wise according to the N-axis with a matrix product on each of the 2x2 matrix. With a loop implementation, it looks like

C[i] = dot(A[i], B[i])

Is there a way I could do this without using a loop? I've looked into tensordot, but haven't been able to get it to work. I think I might want something like tensordot(a, b, axes=([1,2], [2,1])) but that's giving me an NxN matrix.

回答1:

It seems you are doing matrix-multiplications for each slice along the first axis. For the same, you can use np.einsum like so -

np.einsum('ijk,ikl->ijl',A,B)

We can also use np.matmul -

np.matmul(A,B)

On Python 3.x, this matmul operation simplifies with @ operator -

A @ B

Benchmarking

Approaches -

def einsum_based(A,B):
    return np.einsum('ijk,ikl->ijl',A,B)

def matmul_based(A,B):
    return np.matmul(A,B)

def forloop(A,B):
    N = A.shape[0]
    C = np.zeros((N,2,2))
    for i in range(N):
        C[i] = np.dot(A[i], B[i])
    return C

Timings -

In [44]: N = 10000
    ...: A = np.random.rand(N,2,2)
    ...: B = np.random.rand(N,2,2)

In [45]: %timeit einsum_based(A,B)
    ...: %timeit matmul_based(A,B)
    ...: %timeit forloop(A,B)
100 loops, best of 3: 3.08 ms per loop
100 loops, best of 3: 3.04 ms per loop
100 loops, best of 3: 10.9 ms per loop


回答2:

You just need to perform the operation on the first dimension of your tensors, which is labeled by 0:

c = tensordot(a, b, axes=(0,0))

This will work as you wish. Also you don't need a list of axes, because it's just along one dimension you're performing the operation. With axes([1,2],[2,1]) you're cross multiplying the 2nd and 3rd dimensions. If you write it in index notation (Einstein summing convention) this corresponds to c[i,j] = a[i,k,l]*b[j,k,l], thus you're contracting the indices you want to keep.

EDIT: Ok, the problem is that the tensor product of a two 3d object is a 6d object. Since contractions involve pairs of indices, there's no way you'll get a 3d object by a tensordot operation. The trick is to split your calculation in two: first you do the tensordot on the index to do the matrix operation and then you take a tensor diagonal in order to reduce your 4d object to 3d. In one command:

d = np.diagonal(np.tensordot(a,b,axes=()), axis1=0, axis2=2)

In tensor notation d[i,j,k] = c[i,j,i,k] = a[i,j,l]*b[i,l,k].