Use of `np.einsum()`
np.einsum()
is a powerful function in NumPy that performs Einstein summation, which allows for flexible manipulation of multi-dimensional arrays (tensors) using summation notation. It can handle various operations like matrix multiplication, element-wise operations, and tensor contractions in a very efficient way.
Syntax:
np.einsum(subscripts, *operands, **kwargs)
-
subscripts
: A string representing the Einstein summation convention. -
*operands
: The arrays (tensors) on which the operation is performed. -
**kwargs
: Optional arguments likeoptimize
, which can be used to improve performance.
Einstein Summation Convention
The Einstein summation convention is a notational shorthand where repeated indices are implicitly summed over. For example:
-
ij,jk->ik
denotes a matrix multiplication between two 2D arrays. -
ii->i
sums the diagonal elements of a matrix.
Common Examples
-
Matrix Multiplication (
np.dot
ornp.matmul
)import numpy as np A = np.array([[1, 2], [3, 4]]) B = np.array([[5, 6], [7, 8]]) # Matrix multiplication result = np.einsum('ij,jk->ik', A, B) print(result)
Explanation:
-
ij
: Refers to the indices of the matrixA
. -
jk
: Refers to the indices of the matrixB
. -
ik
: The result is the matrix multiplication ofA
andB
.
-
-
Sum over an axis (similar to
np.sum
)input_array = np.array([[1, 2], [3, 4]]) # Sum over all elements (similar to np.sum) total_sum = np.einsum('ij->', input_array) print(total_sum)
Explanation:
-
ij->
: This notation sums over all indices of the matrixinput_array
, returning the total sum.
-
-
Trace of a Matrix (sum of diagonal elements)
input_array = np.array([[1, 2], [3, 4]]) # Trace (sum of diagonal elements) trace = np.einsum('ii->', D) print(trace)
Explanation:
-
ii->
: This notation picks the diagonal elements of the matrixinput_array
and sums them.
-
-
Element-wise multiplication
first_array = np.array([[1, 2], [3, 4]]) second_array = np.array([[5, 6], [7, 8]]) # Element-wise multiplication element_wise = np.einsum('ij,ij->ij', first_array, second_array) print(element_wise)
Explanation:
-
ij,ij->ij
: The indicesij
are the same for both arraysfirst_array
andsecond_array
, resulting in element-wise multiplication.
-
- Dot product of vectors (
np.dot
)x = np.array([1, 2, 3]) y = np.array([4, 5, 6]) # Dot product dot_product = np.einsum('i,i->', x, y) print(dot_product)
-
Tensor contraction (generalized summation over axes)
first_array = np.random.rand(3, 3, 3) second_array = np.random.rand(3, 3) # Contract tensor G and matrix H tensor_contraction = np.einsum('ijk,jl->ikl', first_array, second_array) print(tensor_contraction.shape)
Explanation:
-
ijk,jl->ikl
: Summation is performed over the common axisj
, resulting in a contraction of the tensor.Advantages of
np.einsum
-
- Flexibility: You can perform many types of operations in a single function call.
- Efficiency: It can be faster than separate functions like
np.dot
,np.sum
, etc., especially when you have complex operations to perform.