Contents
Deep Breakdown
What it tells me (Situation Awareness): This formula is the tensor contraction (generalized dot product) along a shared index . You have tensor with indices and tensor with indices ; summing over collapses that shared axis and produces output tensor with indices . When this reduces to the ordinary matrix product . When and it reduces to the scalar dot product. In DA/DS the tensor dot product appears in multi-linear regression (contracting a coefficient tensor against input feature tensors), in convolutional neural networks (weight tensors contracted against input activation tensors), and in factor models where a 3D data cube (samples × features × time) is contracted with a factor loading tensor to produce compressed representations.
Flowchart
Real-world use
Where it is used
Used in deep learning when contracting weight tensors against batched input tensors (e.g., a linear layer applied to a batch of sequence embeddings), in multi-dimensional factor analysis (Tucker decomposition), and in multi-task regression where a coefficient 3D tensor is contracted against a feature matrix to produce predictions across multiple targets simultaneously.
Problem it solves
Standard matrix multiplication only handles 2D inputs. Real DS pipelines routinely work with batched 3D or 4D tensors (batch × sequence × features, or channels × height × width × depth). The tensor dot product generalizes the contraction operation to arbitrary tensor ranks, so the same algebraic structure (sum over a shared axis) works regardless of how many other axes exist.
How it solves the problem
By specifying which axes to contract over, np.tensordot(A, B, axes=([ax_A], [ax_B])) sums out only those axes and leaves all remaining axes free. The output shape is exactly the remaining axes of concatenated with the remaining axes of . This is equivalent to reshaping both tensors to 2D matrices (grouping contracted vs free axes), performing a standard matrix product, then reshaping back.
Example in a business
A data scientist builds a multi-output regression for a retail chain: inputs are a batch of weekly snapshots, each described by features, organized as a 3D tensor of shape (four lags included). A coefficient tensor of shape maps features to product categories. Contracting over the feature axis: np.tensordot(X, W, axes=([2], [0])) produces predictions of shape , one prediction per batch item, per lag, per product category, in a single operation.
The formula, piece by piece
- : the output tensor; its free indices are all indices of except , followed by all indices of except
- : summation over all values of the shared (contracted) index ; this collapses one axis from both and
- : tensor evaluated at free indices and contraction index ; has rank
- : tensor evaluated at contraction index and free indices ; has rank
- The contracted index must have the same dimension in both and ; the result has rank Full formula:
The formula in words
"Pick one axis in tensor and one axis in tensor that have the same size. Multiply corresponding slices along those axes element-wise and sum them up. The result is a new tensor whose shape is everything that was left in combined with everything that was left in ." Tiny numeric example (matrix multiply as a special case): (shape ), (shape ), contract over (columns of and rows of ). , . , . Result: .
Critical Concepts
- Rank of the result: contracting two tensors of ranks and over one axis each produces a tensor of rank . Two vectors (rank 1 each) contract to a scalar (rank 0). Two matrices (rank 2 each) contract to a matrix (rank 2). A matrix and a 3D tensor contract to a 3D tensor.
- Axis alignment: the contracted axes must have the same dimension.
np.tensordot(A, B, axes=([ax_a], [ax_b]))specifies which axis of and which axis of to sum over. - Einstein summation notation:
np.einsum('imk,kjn->imjn', A, B)is the most readable way to express arbitrary contractions. The repeated index is summed over; all other indices appear in the output. - Special cases: - Dot product of two vectors:
np.tensordot(a, b, axes=1)ora @ b- Matrix multiply:np.tensordot(A, B, axes=1)orA @ B- Outer product (no contraction):np.tensordot(a, b, axes=0) - Memory cost: the output has shape equal to the Cartesian product of all free axes. Contracting a tensor with an matrix gives a output, which is 15 million elements if using float64. Plan accordingly.
- NumPy implementation:
np.tensordot(A, B, axes)andnp.einsumboth implement tensor contractions.
For batched matrix multiplications specifically, np.matmul (or @) broadcasts over leading batch axes.
Step-by-step calculation (tiny example)
Step 1: Define a rank-3 tensor A and a matrix B, identify the contraction axis has shape : two "batch" items, each a matrix. Let , . has shape : . Contract over axis 2 of (size 2) and axis 0 of (size 2). Output shape: .
Step 2: Compute output for batch item 0 . Each row of is dot-producted with each column of .
Step 3: Compute output for batch item 1 and verify with NumPy
.
Full output has shape and is equivalent to np.tensordot(A, B, axes=([2], [0])).
Code examples
Beginner example (Python)
import numpy as np # -- Tensor dot product: contract a 3D tensor with a 2D matrix ----------
# A shape: (batch=2, rows=3, features=2)
# B shape: (features=2, outputs=2)
# Contract over the 'features' axis -> output shape: (2, 3, 2)
A = np.array([[[1.0, 0.0], [0.0, 1.0], [1.0, 1.0]], [[2.0, 1.0], [0.0, 0.0], [1.0, 2.0]]])
B = np.array([[1.0, 0.0], [0.0, 2.0]]) # np.tensordot: axes=([2], [0]) means axis-2 of A contracts with axis-0 of B
C_tensordot = np.tensordot(A, B, axes=([2], [0])) # Equivalent einsum notation: 'brf,fo->bro'
C_einsum = np.einsum('brf,fo->bro', A, B) # Equivalent using matmul (broadcasts over leading batch axis)
C_matmul = A @ B
print(f"A shape: {A.shape}")
print(f"B shape: {B.shape}")
print(f"C shape: {C_tensordot.shape} (expected (2, 3, 2))")
print(f"\nC (batch item 0):\n{C_tensordot[0]}")
print(f"\nC (batch item 1):\n{C_tensordot[1]}")
print(f"\ntensordot == einsum: {np.allclose(C_tensordot, C_einsum)}")
print(f"tensordot == matmul: {np.allclose(C_tensordot, C_matmul)}")Beginner example (R)
# -- Tensor dot product: contract a 3D array with a 2D matrix -----------
# In R, use the 'tensor' or manually reshape to 2D and back.
A <- array(c(1, 0, 1, 0, 1, 1, # batch item 0 (col-major, so specify carefully) 2, 0, 1, 1, 0, 2),
dim = c(3, 2, 2)) # shape: (rows=3,
features=2, batch=2)
# Reorder to (batch, rows, features) for clarity
# R arrays are column-major; reshape and aperm as needed.
B <- matrix(c(1, 0, 0, 2), nrow = 2, byrow = TRUE) # (2 x 2) # Contract axis 2 (features,
dim=2) of A with axis 1 of B
# Strategy: reshape A to (batch*rows, features), multiply by B, reshape back
batch <- 2; rows_a <- 3; feats <- 2; out_cols <- 2
A_mat <- matrix(A, nrow = batch * rows_a, ncol = feats) # (6, 2)
C_mat <- A_mat %*% B # (6, 2)
C_arr <- array(C_mat, dim = c(rows_a, batch, out_cols))
cat(sprintf("A shape: %s\n", paste(dim(A), collapse = " x ")))
cat(sprintf("B shape: %s\n", paste(dim(B), collapse = " x ")))
cat(sprintf("C shape (rows x batch x outputs): %s\n", paste(dim(C_arr), collapse = " x "))) # Verify: batch item 0 (first 3 rows of C_mat) should be A[,,1] %*% B
expected_batch0 <- A[, , 1] %*% B
actual_batch0 <- C_mat[1:rows_a, ]
cat(sprintf("Batch 0 correct: %s\n", isTRUE(all.equal(expected_batch0, actual_batch0))))Professional example (Python)
import numpy as np
import pandas as pd
np.random.seed(42) # -- Business scenario: multi-output, multi-lag regression via tensor contraction
# Weekly retail data for N stores, each with F features, over T lag periods.
# Coefficient tensor W maps features to P product categories.
# Prediction: Y[n, t, p] = sum_f X[n, t, f] * W[f, p]
N_STORES = 120
N_LAGS = 4
N_FEATURES = 8
N_PRODUCTS = 5
FEATURE_NAMES = [f"feat_{f}" for f in range(N_FEATURES)]
PRODUCT_NAMES = [f"prod_{p}" for p in range(N_PRODUCTS)] # Simulate input tensor X: (stores, lags, features)
X = np.random.normal(0, 1, size=(N_STORES, N_LAGS, N_FEATURES)) # True coefficient matrix W: (features, products)
W_true = np.random.normal(0, 0.5, size=(N_FEATURES, N_PRODUCTS)) # Compute predictions via tensor dot product: contract over feature axis
# Equivalent to applying the same linear map to every (store, lag) slice
Y_pred = np.tensordot(X, W_true, axes=([2], [0])) # shape: (N_STORES, N_LAGS, N_PRODUCTS) # Verify with einsum (more readable for multi-index notation)
Y_einsum = np.einsum('ntf,fp->ntp', X, W_true)
print(f"tensordot == einsum: {np.allclose(Y_pred, Y_einsum)}") # Verify output shape
print(f"\nInput tensor X shape : {X.shape} (stores x lags x features)")
print(f"Coefficient matrix W : {W_true.shape} (features x products)")
print(f"Output tensor Y shape : {Y_pred.shape} (stores x lags x products)") # Example: extract predictions for store 0 across all lags and products
store_0_df = pd.DataFrame( Y_pred[0], index=[f"lag_{t}" for t in range(N_LAGS)], columns=PRODUCT_NAMES,
)
print(f"\nPredictions for store 0 (across lags and products):")
print(store_0_df.round(3).to_string()) # Demonstrate that marginalizing over lags via mean recovers a (N_STORES, N_PRODUCTS) summary
Y_mean_over_lags = Y_pred.mean(axis=1) # (N_STORES, N_PRODUCTS)
print(f"\nMean prediction over lags shape: {Y_mean_over_lags.shape}")
print(f"Mean prediction for first 3 stores:\n{pd.DataFrame(Y_mean_over_lags[:3], columns=PRODUCT_NAMES).round(3).to_string()}")
print("\nInterpretation: tensor contraction applies the same feature-to-product linear map across all store/lag combinations in one vectorized operation, avoiding explicit loops.")Source: domain knowledge