Vectorizing Numpy 3D and 2D array operation - python

I'm trying to create K MxN matrices in Python, stored in a (M,N,K) numpy array, C, from two matrices, A and B, with shapes (K, M) and (K,N) respectively. The first matrix is computed as C0 = a0.T x b0, where a0 is the first row of A and b1 is the first row of B, the second matrix as C1 = a1.T x b0 and so on.
Right now I'm using a for loop to compute the matrices.
import numpy as np
A = np.random.random((10,800))
B = np.random.random((10,500))
C = np.zeros((800,500,10))
for k in range(10):
C[:,:,k] = A[k,:][:,None] # B[k,:][None,:]
Since the operations are independent, I was wondering if there was some pythonic way to avoid the for loop. Perhaps I can vectorize the code, but I fail to see how it could be done.

In [235]: A = np.random.random((10,800))
...: B = np.random.random((10,500))
...: C = np.zeros((800,500,10))
...: for k in range(10):
...: C[:,:,k] = A[k,:][:,None] # B[k,:][None,:]
...:
In [236]: C.shape
Out[236]: (800, 500, 10)
Batched matrix product, followed by transpose
In [237]: np.allclose((A[:,:,None]#B[:,None,:]).transpose(1,2,0), C)
Out[237]: True
But since the matrix product axis is size 1, and there's no other summation, broadcasted multiply is just as good:
In [238]: np.allclose((A[:,:,None]*B[:,None,:]).transpose(1,2,0), C)
Out[238]: True
Execution time is about the same

Related

Numpy: Iterate multiplication of 3D array by 1D array

I have a 3D array (4,3,3) in which I would like to iteratively multiply with a 1D array (t variable) and sum to end up with an array (A) that is a summation of the four 3,3 arrays
I'm unsure on how I should be assigning indexes or how and if I should be using np.ndenumerate
Thanks
import numpy as np
import math
#Enter material constants for calculation of stiffness matrix
E1 = 20
E2 = 1.2
G12 = 0.8
v12=0.25
v21=(v12/E1)*E2
theta = np.array([30,-30,-30,30])
deg = ((math.pi*theta/180))
k = len(theta) #number of layers
t = np.array([0.005,0.005,0.005,0.005])
#Calculation of Q Values
Q11 = 1
Q12 = 2
Q21 = 3
Q22 = 4
Q66 = 5
Qbar = np.zeros((len(theta),3,3),order='F')
#CALCULATING THE VALUES OF THE QBAR MATRIX
for i, x in np.ndenumerate(deg):
m= np.cos(x) #sin of rotated lamina
n= np.sin(x) #cos of rotated lamina
Qbar11=Q11*3
Qbar12=Q22*4
Qbar16=Q16*4
Qbar21 = Qbar12
Qbar22=Q22*1
Qbar26=Q66*2
Qbar66=Q12*3
Qbar[i] = np.array([[Qbar11, Qbar12, Qbar16], [Qbar21, Qbar22, Qbar26], [Qbar16, Qbar26, Qbar66]], order = 'F')
print(Qbar)
A = np.zeros((3,3))
for i in np.nditer(t):
A[i]=Qbar[i]*t[i]
A=sum(A[i])
If I understand correctly, you want to multiply Qbar and t over the first axis, and then summing the result over the first axis (which results in an array of shape (3, 3)).
I created random arrays to make the code minimal:
import numpy as np
Qbar = np.random.randint(2, size=(4, 3, 3))
t = np.arange(4)
A = (Qbar * t[:, None, None]).sum(axis=0)
t[:, None, None] will create two new dimensions so that the shape becomes (4, 1, 1), which can be multiplied to Qbar element-wise. Then we just have to sum over the first axis.
NB: A = np.tensordot(t, Qbar, axes=([0],[0])) also works and can be faster for larger dimensions, but for the dimensions you provided I prefer the first solution.

For-loop Alternative to do 2D & 3D Matrix Multiplication in Numpy

For discussions' sake, I have a 2D matrix (A) of shape 2x2 and a 3D matrix (B) of shape 2x2x10. I am currently looping over the last axis of matrix B, and constructing the full matrix one sub-matrix at a time.
A = np.random.random((2,2))
B = np.random.random((2,2,10))
C = np.zeros_like(B)
for i in range(B.shape[-1]):
C[:,:,i] = A # B[:, :, i]
In reality, my matrices are much larger than this and I know there must be something more efficient than a for loop. I have looked at a couple of prior questions where the solution involves using np.tensordot or np.einsum, but frankly, I don't think I am using it right.
# Basic
C_basic = A # B
print(f'Basic {np.allclose(C, C_basic)}') # False
# Einsum
C_einsum = np.einsum('ij, jik-> ijk', A, B)
print(f'np.einsum {np.allclose(C, C_einsum)}') # False
# Newaxis
C_newaxis = A[np.newaxis, ...] # B
print(f'np.newaxis {np.allclose(C, C_newaxis)}') # False
# Swapaxes
C_swapaxes = A # np.swapaxes(B, 0, 2)
C_swapaxes = np.swapaxes(C_swapaxes, 0, 2)
print(f'np.swapaxes {np.allclose(C, C_swapaxes)}') # False
Here are a few possibilities:
import numpy as np
A = np.random.random((2,2))
B = np.random.random((2,2,10))
C = np.zeros_like(B)
for i in range(B.shape[-1]):
C[:,:,i] = A # B[:, :, i]
Cs = [np.einsum('ij,jkl',A,B),
np.tensordot(A,B,((-1,),(0,))),
(A#B.reshape(len(B),-1)).reshape(-1,*B.shape[1:]),
np.moveaxis(A#np.moveaxis(B,-1,0),0,-1),
(A#B.transpose(2,0,1)).transpose(1,2,0),
np.inner(B.T,A).T,
(B.T#A.T).T]
print([np.allclose(C,Ci) for Ci in Cs])
prints:
[True, True, True, True, True, True, True]
These are, however, not 100% equivalent: For example. the first three are C-contiguous, the last two Fortran and the middle two neither.
You can inspect using:
for Ci in Cs:
print(Ci.flags)

numpy apply function over two 3d matrixes

So I would like to apply a function over two 3d matrixes with numpy and I can't figure out how. I read about numpy.apply_over_axes() but can't make it work.
This is my code now:
c = np.random.beta(2,3,size=(2,80))
def my_func(a,b):
xi = np.matmul(b, c)
spe = np.power(a - xi, 2)
return spe.sum()
a = np.zeros(shape=(5,1000,80))
b = np.random.beta(2,3,size=(5,1000,2))
np.apply_over_axes(func=my_func,a=[a,b],axes=[0,0,0])
Which doesnt work and returns
could not broadcast input array from shape (5,1000,80) into shape (5,1000)
I will like to iterate though a and b and apply my_func to every vector of the 3rd dimension.
This would do the job but with normal for loops:
results = []
for i in range(len(a)): #5 Iterations
for j in range(len(a[i])): #1000 Iterations
results.append(my_func(a[i][j], b[i][j]))
I would like to obtain this results this but using numpy functions.
The contraction operation hidden in np.matmul(b, c) can be achieved through np.tensordot(b, c, axes=[2, 0]), where the [2, 0] indicates that the third axis in b is contracted with the first axis in c. That is, np.tensordot(b, c, axes=[2, 0]).shape is (5, 1000, 80). From there on, ordinary broadcasting applies, and your code boils down to
a = np.zeros(shape=(5, 1000, 80))
b = np.random.beta(2, 3, size=(5, 1000, 2))
c = np.random.beta(2, 3, size=(2, 80))
xi = np.tensordot(b, c, axes=[2, 0])
spe = np.power(a - xi, 2)
results2 = spe.sum(axis=2)
Let's check that this indeed matches what you get by simply using loops:
In [55]: results = np.array(results).reshape(5, 1000)
In [56]: np.allclose(results, results2)
Out[56]: True
Running your code in an ipython session:
In [88]: c = np.random.beta(2,3,size=(2,80))
...:
...: def my_func(a,b):
...: xi = np.matmul(b, c)
...:
...: spe = np.power(a - xi, 2)
...: return spe.sum()
...:
...: a = np.zeros(shape=(5,1000,80))
...: b = np.random.beta(2,3,size=(5,1000,2))
...:
...: np.apply_over_axes(func=my_func,a=[a,b],axes=[0,0,0])
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-88-c5e5a66c9d0a> in <module>
10 b = np.random.beta(2,3,size=(5,1000,2))
11
---> 12 np.apply_over_axes(func=my_func,a=[a,b],axes=[0,0,0])
<__array_function__ internals> in apply_over_axes(*args, **kwargs)
/usr/local/lib/python3.6/dist-packages/numpy/lib/shape_base.py in apply_over_axes(func, a, axes)
485
486 """
--> 487 val = asarray(a)
488 N = a.ndim
489 if array(axes).ndim == 0:
/usr/local/lib/python3.6/dist-packages/numpy/core/_asarray.py in asarray(a, dtype, order)
83
84 """
---> 85 return array(a, dtype, copy=False, order=order)
86
87
ValueError: could not broadcast input array from shape (5,1000,80) into shape (5,1000)
You should have shown us the full error with traceback.
That traceback shows us that it is trying make one array from your list of two. Since the shapes don't match, it raises an error. With a different mismatch it would have created a (2,) object array, which would just move raise problems later:
In [89]: np.array([a,b])
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-89-964832cdcfcd> in <module>
----> 1 np.array([a,b])
ValueError: could not broadcast input array from shape (5,1000,80) into shape (5,1000)
But the problem is you did not read the docs, or take them seriously:
func : function
This function must take two arguments, `func(a, axis)`.
a : array_like
Input array.
axes : array_like
Axes over which `func` is applied; the elements must be integers.
a is supposed to be an array, not a list of two arrays. func is supposed to take an axis parameter, not another array. And I don't know what you are trying to do with [0,0,0]. For 3d arrays [0,1] might apply, but not a repeated 0.
your loop
With a bit better numpy style:
In [91]: results = []
...: for i in range(a.shape[0]): #5 Iterations
...: for j in range(a.shape[1]): #1000 Iterations
...: results.append(my_func(a[i,j], b[i,j]))
...:
In [92]: np.array(results).shape
Out[92]: (5000,)
rework my_func
To do this without the loops, we need to use whole-array functions within my_func. There isn't a numpy apply that compiles python code - you have to look at numba or cython for that.
xi=np.matmul(b,c). b is (5,1000,2), c is (2,80). matmul is happy doing a dot combining the last axis of b with the 2nd to the last of c.
In [93]: xi = np.matmul(b,c)
In [94]: xi.shape
Out[94]: (5, 1000, 80)
That matches a, so
In [97]: spe = np.power(a-xi,2)
In [98]: spe.shape
Out[98]: (5, 1000, 80)
then sum on that last axis:
In [99]: res = spe.sum(axis=2)
In [100]: res.shape
Out[100]: (5, 1000)
which matches your loop:
In [101]: np.allclose(res.ravel(), np.array(results))
Out[101]: True
Except for the last sum, your myfunc runs with the whole arrays.
In [103]: my_func(a,b)
Out[103]: 46883.49325596101

Most efficient way to perform large dot/tensor dot products while only keeping diagonal entries [duplicate]

This question already has answers here:
Matrix multiplication for multidimensional matrix (/array) - how to avoid loop?
(3 answers)
Closed 3 years ago.
I'm trying to figure out a way to use numpy to perform the following algebra in the most time-efficient way possible:
Given a 3D matrix/tensor, A, with shape (n, m, p) and a 2D matrix/tensor, B, with shape (n, p), calculate C_ij = sum_over_k (A_ijk * B_ik), where the resulting matrix C would have dimension (n, m).
I've tried two ways to do this. One is to loop through the first dimension, and calculate a regular dot product each time.
The other method is to use np.tensordot(A, B.T) to calculate a result with shape (n, m, n), and then take the diagonal elements along 1st and 3rd dimension. Both methods are shown below.
First method:
C = np.zeros((n,m))
for i in range(n):
C[i] = np.dot(A[i], B[i])
Second method:
C = np.diagonal(np.tensordot(A, B.T, axes = 1), axis1=0, axis2=2).T
However, because n is a very large number, the loop over n in the first method is costing a lot of time. The second method calculates too many unnecessary entries to obtain that huge (n, m, n)matrix, and is also costing too much time, I'm wondering if there's any efficient way to do this?
Define 2 arrays:
In [168]: A = np.arange(2*3*4).reshape(2,3,4); B = np.arange(2*4).reshape(2,4)
Your iterative approach:
In [169]: [np.dot(a,b) for a,b in zip(A,B)]
Out[169]: [array([14, 38, 62]), array([302, 390, 478])]
The einsum practically writes itself from your C_ij = sum_over_k (A_ijk * B_ik):
In [170]: np.einsum('ijk,ik->ij', A, B)
Out[170]:
array([[ 14, 38, 62],
[302, 390, 478]])
#, matmul, was added to perform batch dot products; here the i dimension is the batch one. Since it uses the last of A and 2nd to the last of B for the dot summation, we have to temporarily expand B to (2,4,1):
In [171]: A#B[...,None]
Out[171]:
array([[[ 14],
[ 38],
[ 62]],
[[302],
[390],
[478]]])
In [172]: (A#B[...,None])[...,0]
Out[172]:
array([[ 14, 38, 62],
[302, 390, 478]])
Typically matmul is fastest, since it passes the task to BLAS like code.
here is my implementation:
B = np.expand_dims(B, axis=1)
E = A * B
E = np.sum(E, axis=-1)
Check :
import numpy as np
n, m, p = 2, 2, 2
np.random.seed(0)
A = np.random.randint(1, 10, (n, m, p))
B = np.random.randint(1, 10, (n, p))
C = np.diagonal(np.tensordot(A, B.T, axes = 1), axis1=0, axis2=2).T
# from here is my implementation
B = np.expand_dims(B, axis=1)
E = A * B
E = np.sum(E, axis=-1)
print(np.array_equal(C, E))
True
use the np.expand_dims() to add a new dimension.
And use the broadcast multiply. Finally, sum along the third dimension.
Thanks check code from user3483203

Compute cosine similarity between 3D numpy array and 2D numpy array

I have a 3D numpy array A of shape (m, n, 300) and a 2D numpy array B of shape (p, 300).
For each of the m (n, 300) matrices in the 3D array, I want to compute its cosine similarity matrix with the 2D numpy array. Currently, I am doing the following:
result = []
for sub_matrix in A:
result.append(sklearn.metrics.pairwise.cosine_similarity(sub_matrix, B)
The sklearn cosine_similarity function does not support operations with 3D arrays, so is there a more efficient way of computing this that does not involve using the for-loop?
You can reshape to 2D and use the same function -
from sklearn.metrics.pairwise import cosine_similarity
m,n = A.shape[:2]
out = cosine_similarity(A.reshape(m*n,-1), B).reshape(m,n,-1)
The output would be 3D after the reshape at the end, which is what you would get after array conversion of result.
Sample run -
In [336]: np.random.seed(0)
...: A = np.random.rand(5,4,3)
...: B = np.random.rand(2,3)
...:
...: result = []
...: for sub_matrix in A:
...: result.append(cosine_similarity(sub_matrix, B))
...: out_org = np.array(result)
...:
...: from sklearn.metrics.pairwise import cosine_similarity
...:
...: m,n = A.shape[:2]
...: out = cosine_similarity(A.reshape(m*n,-1), B).reshape(m,n,-1)
...:
...: print np.allclose(out_org, out)
True

Categories