Matrix multiplication is one of the most fundamental operations in linear algebra and appears everywhere in data science, machine learning, computer graphics, physics, and engineering. Whether you are building neural networks, solving systems of linear equations, or transforming coordinates in a graphics pipeline — matrix multiplication sits at the center.
In this long-form guide you'll get:
A clear mathematical definition of matrix multiplication.
Intuition about what multiplication means geometrically (as linear transformations).
Practical, ready-to-run Python implementations using NumPy, PyTorch, and TensorFlow.
Step-by-step, line-by-line explanations of each code snippet so beginners can follow exactly what every statement does.
Common pitfalls, performance notes, and helpful debugging tips.
.jpg)
Table of Contents
-
Introduction — Why matrix multiplication matters (SEO friendly)
-
Quick definition and math rule
-
Geometric/intuitive view (transformations)
-
Important properties & common mistakes
-
Implementation overview: NumPy vs PyTorch vs TensorFlow
-
Detailed code examples (line-by-line explanations)
-
NumPy: matrix × vector and matrix × matrix
-
PyTorch: matrix × vector and matrix × matrix
-
TensorFlow: matrix × vector and matrix × matrix
-
Shape / dtype gotchas and how to fix them
-
-
Performance notes (complexity, BLAS, GPU)
-
Debugging tips and best practices
-
Conclusion
2. Quick definition and the mathematical rule
If A is an matrix and B is an matrix, then the matrix product is defined and will be an matrix. The element in row i and column j of C is:
This is the dot product of the i-th row of A and the j-th column of B.
Two important special cases we will use often:
-
Matrix × Vector: If
vis a vector of lengthn(shapenor(n,1)), andAism × n, thenA @ vyields a vector of lengthm. -
Matrix × Matrix: General case above, yielding shape
m × p.
3. Geometric / intuitive view
One helpful way to think about a matrix is as a linear transformation. Multiplying a vector x by a matrix A produces a new vector y = A x — the input x transformed by the linear rule A. When you multiply two matrices A B, you are composing two linear transformations: first apply B, then apply A. This composition is why matrix multiplication is associative (but not commutative): the order of transformations matters.
4. Important properties & common mistakes
Properties:
-
Associative: .
-
Distributive: .
-
Not commutative in general: usually.
-
Identity: There exists an identity matrix so that (when sizes match).
-
Transpose: .
Common mistakes:
-
Trying to multiply matrices with incompatible shapes.
-
Confusing row vs column vectors (1D arrays vs 2D column arrays).
-
Mixing dtypes (integers vs floats) that lead to unexpected behavior in libraries like PyTorch/TensorFlow.
-
Expecting element-wise multiplication when you write
*instead of@or.dot().
5. Implementation overview: NumPy vs PyTorch vs TensorFlow
-
NumPy: The go-to for CPU-based numerical work and quick prototyping. Use
np.dot()or@for matrix multiplication. Great for learning and small-to-medium data. -
PyTorch: Designed for deep learning with GPU acceleration, auto-differentiation, and dynamic computation graphs. Use
torch.matmul()or@. Minddtypeand.to(device). -
TensorFlow: Also designed for deep learning with static/dynamic graph options, GPU support, and
tf.matmul()andtf.linalg.matvec()for matrix-vector. TensorFlow often needs.numpy()to fetch values if using eager execution.
We’ll now walk through code examples for each library and explain every line.
6. Detailed code examples (line-by-line explanations)
All examples use the same mathematical matrices for clarity:
These choices make the shapes compatible: × → vector of length 2; × → .
We’ll show the outputs and explain the arithmetic step-by-step.
6.1 NumPy — matrix × vector and matrix × matrix
# NumPy example
import numpy as np
# Define A, v, B
A = np.array([[1, 2, 3],
[4, 5, 6]]) # shape: (2, 3)
v = np.array([7, 8, 9]) # shape: (3,)
B = np.array([[1, 2],
[3, 4],
[5, 6]]) # shape: (3, 2)
# Matrix × Vector
result_vector = A @ v # or np.dot(A, v)
print("NumPy - Matrix × Vector:\n", result_vector)
# Matrix × Matrix
result_matrix = A @ B # or np.dot(A, B)
print("NumPy - Matrix × Matrix:\n", result_matrix)
Line-by-line explanation (NumPy)
-
import numpy as np-
Imports NumPy and aliases it as
np. Standard convention.
-
-
A = np.array([[1, 2, 3], [4, 5, 6]])-
Creates a 2×3 array.
A.shapeis(2, 3).
-
-
v = np.array([7, 8, 9])-
Defines a 1D NumPy array (vector) with shape
(3,). NumPy will treat this as a column vector when used with matrix multiplication from the left (A @ v).
-
-
B = np.array([[1, 2], [3, 4], [5, 6]])-
Defines a 3×2 array.
B.shapeis(3, 2).
-
-
result_vector = A @ v-
Uses the
@operator to compute matrix × vector. Equivalent tonp.dot(A, v). Result shape is(2,). -
Let's compute manually (digit-by-digit) to verify correctness:
-
Row 1 of
Ais[1, 2, 3]. Dot withv = [7, 8, 9]:-
-
-
-
Sum:
-
-
Row 2 of
Ais[4, 5, 6]. Dot withv:-
-
-
-
Sum:
-
-
So
result_vector = [50, 122].
-
-
-
print("NumPy - Matrix × Vector:\n", result_vector)-
Displays the computed vector
[50 122].
-
-
result_matrix = A @ B-
Matrix × matrix multiplication producing shape
(2, 2). -
Manual calculation (digit-by-digit):
-
C[0,0]= row1(A)·col1(B) = -
C[0,1]= row1(A)·col2(B) = -
C[1,0]= row2(A)·col1(B) = -
C[1,1]= row2(A)·col2(B) = -
So
result_matrix = [[22, 28], [49, 64]].
-
-
-
print("NumPy - Matrix × Matrix:\n", result_matrix)-
Prints the
2×2result matrix.
-
Notes: In NumPy, 1D arrays (shape (n,)) often behave conveniently in dot products, but sometimes you need explicit shapes. If you want a column vector with shape (3,1), use v = v.reshape(3,1).
6.2 PyTorch — matrix × vector and matrix × matrix
# PyTorch example
import torch
# Define A, v, B
A = torch.tensor([[1, 2, 3],
[4, 5, 6]], dtype=torch.float32) # shape: (2,3)
v = torch.tensor([7, 8, 9], dtype=torch.float32) # shape: (3,)
B = torch.tensor([[1, 2],
[3, 4],
[5, 6]], dtype=torch.float32) # shape: (3,2)
# Matrix × Vector
result_vector = torch.matmul(A, v) # or A @ v
print("PyTorch - Matrix × Vector:\n", result_vector)
# Matrix × Matrix
result_matrix = torch.matmul(A, B) # or A @ B
print("PyTorch - Matrix × Matrix:\n", result_matrix)
Line-by-line explanation (PyTorch)
-
import torch-
Imports the PyTorch library.
-
-
A = torch.tensor([...], dtype=torch.float32)-
Creates a 2×3 tensor with float32 dtype. Using floating types is common for GPU computation and gradients.
-
-
v = torch.tensor([7, 8, 9], dtype=torch.float32)-
Creates a 1D tensor of length 3.
v.shapeis(3,).
-
-
B = torch.tensor([...], dtype=torch.float32)-
Creates a 3×2 tensor.
-
-
result_vector = torch.matmul(A, v)-
Computes
A @ v. The result will be a 1D tensor of shape(2,). The arithmetic is identical to the NumPy manual computation:-
Row1·v = 17 + 28 + 3*9 = 50
-
Row2·v = 47 + 58 + 6*9 = 122
-
-
-
print("PyTorch - Matrix × Vector:\n", result_vector)-
Prints the tensor (e.g.,
tensor([ 50., 122.])).
-
-
result_matrix = torch.matmul(A, B)-
Matrix × matrix, yields a
(2,2)tensor. Same numeric results as NumPy:[[22,28],[49,64]].
-
-
print("PyTorch - Matrix × Matrix:\n", result_matrix)-
Displays the result.
-
PyTorch specifics & tips
-
If you want GPU acceleration, move tensors with
.to('cuda')(if a CUDA GPU is available):A = A.to('cuda')andv = v.to('cuda'). After computation, use.cpu().numpy()to move results to host and convert to NumPy. -
For autograd (gradients), set
requires_grad=Trueon tensors you want to differentiate.
6.3 TensorFlow — matrix × vector and matrix × matrix
# TensorFlow example
import tensorflow as tf
# Define A, v, B
A = tf.constant([[1, 2, 3],
[4, 5, 6]], dtype=tf.float32) # shape: (2,3)
v = tf.constant([7, 8, 9], dtype=tf.float32) # shape: (3,)
B = tf.constant([[1, 2],
[3, 4],
[5, 6]], dtype=tf.float32) # shape: (3,2)
# Matrix × Vector
result_vector = tf.linalg.matvec(A, v) # efficient for matrix-vector
print("TensorFlow - Matrix × Vector:\n", result_vector.numpy())
# Matrix × Matrix
result_matrix = tf.matmul(A, B)
print("TensorFlow - Matrix × Matrix:\n", result_matrix.numpy())
Line-by-line explanation (TensorFlow)
-
import tensorflow as tf-
Imports TensorFlow.
-
-
A = tf.constant([...], dtype=tf.float32)-
Creates a constant tensor with shape
(2,3).
-
-
v = tf.constant([7, 8, 9], dtype=tf.float32)-
Creates a 1D tensor.
-
-
B = tf.constant([...], dtype=tf.float32)-
Creates a
(3,2)tensor.
-
-
result_vector = tf.linalg.matvec(A, v)-
Uses
tf.linalg.matvec(optimized for matrix-vector multiplication). Returns a tensor with shape(2,). Manually:-
Row1·v = 50
-
Row2·v = 122
-
-
-
print("TensorFlow - Matrix × Vector:\n", result_vector.numpy())-
result_vector.numpy()converts the tensor to a NumPy array and fetches it (works in eager mode which is the default).
-
-
result_matrix = tf.matmul(A, B)-
Standard matrix multiplication. Numeric result:
[[22,28],[49,64]].
-
-
print("TensorFlow - Matrix × Matrix:\n", result_matrix.numpy())-
Prints the
2×2matrix.
-
TensorFlow specifics & tips
-
If running in graph mode (older TF versions), wrap in a
tf.functionor run inside a session; eager mode prints with.numpy(). -
To run on GPU, TensorFlow will automatically use available GPUs (no
.to('cuda')like PyTorch), but ensure GPU drivers and CUDA are set up.
6.4 Shapes, broadcasting, and column/row vectors — common gotchas with code
Often you’ll see shape errors. Here are examples and fixes.
Example: shape mismatch
# Wrong: trying to multiply (2,3) with (2,) accidentally
A = np.array([[1,2,3],[4,5,6]]) # (2,3)
w = np.array([1,2]) # (2,) -> wrong length
# A @ w # ShapeError: shapes (2,3) and (2,) not aligned: 3 (dim 1) != 2 (dim 0)
Fix: ensure the inner dimensions match. To multiply A (2×3) you need a vector of length 3: w = np.array([1,2,3]).
Example: explicit column vector vs 1D array
v1 = np.array([7,8,9]) # shape (3,)
v2 = v1.reshape(3,1) # shape (3,1) a column vector
# A @ v1 -> shape (2,) # returns 1D array
# A @ v2 -> shape (2,1) # returns column vector
Use the form you need for downstream code, but be mindful that (2,) and (2,1) behave differently in broadcasting.
7. Performance notes (complexity, BLAS, GPU)
-
Complexity: Naive matrix multiplication for an matrix is . Practical libraries do better via optimized BLAS/LAPACK and blocked algorithms.
-
BLAS: NumPy uses BLAS (like OpenBLAS, MKL) under the hood for
dot/@. This gives highly optimized CPU performance. -
GPU: PyTorch and TensorFlow can offload operations to GPUs — huge speedups for large matrices (provided data transfer overhead is small compared to computation).
-
Batching: Both PyTorch and TensorFlow support batched matrix multiplication (e.g.,
torch.bmmortf.matmulwith 3D tensors), useful for neural networks. -
Precision: Use
float32for speed on GPUs;float64for higher precision on CPU if needed.
8. Debugging tips and best practices
-
Check shapes early: Print
.shapeon arrays/tensors before multiplication. -
Check dtype: In PyTorch/TensorFlow, dtypes must match (e.g.,
float32vsfloat64). Convert with.float()/.double()in PyTorch ortf.cast(..., tf.float32)in TensorFlow. -
Avoid Python lists for heavy math: Convert lists to arrays/tensors for speed.
-
Use
@ormatmulfor linear algebra:*is element-wise multiplication;@is matrix multiplication. -
Profile large ops: Use
timeitor profiler tools to find bottlenecks. -
Minimize CPU↔GPU transfers: Move tensors to GPU once, compute many operations, then bring results back.
9. Conclusion
Matrix multiplication is more than a formula — it is a powerful algebraic tool for composing linear transformations. In practical Python work you’ll usually rely on libraries like NumPy for quick CPU work and PyTorch/TensorFlow when you want GPU acceleration and autograd. This article walked through the mathematical rule, geometric intuition, and full, line-by-line implementations in NumPy, PyTorch, and TensorFlow for both matrix×vector and matrix×matrix cases.
If you’re learning linear algebra or building machine learning models, practice by:
-
Implementing matrix multiplication by hand for small matrices.
-
Inspecting shapes and dtypes in your code regularly.
-
Trying the PyTorch examples on GPU to see speed differences for large matrices.
Comments
Post a Comment