Vectors, Matrices, Tensors & Matrix Operations for ML
Master vectors, matrices, and tensors for machine learning: shapes, axes, reshaping, broadcasting, matrix multiplication, einsum, and common mistakes, with NumPy code.
BitCodeMatrix | AI-ML Fundamentals
Vectors, Matrices, Tensors, and Matrix Operations
A hands-on guide to the data structures of machine learning: shapes, axes, indexing, reshaping, broadcasting, matrix multiplication, and tensor operations, with tested NumPy code.
Introduction
Every machine learning model, from a simple linear regression to a large language model, receives numbers, transforms them, and outputs numbers. Those numbers are always organised in the same few structures: scalars, vectors, matrices, and tensors. The operations that transform them, such as addition, matrix multiplication, reshaping, and reductions, are the building blocks of every model.
Understanding these structures is the most practical mathematical skill in machine learning. Most errors in model code are shape errors, and most performance gains come from replacing loops with tensor operations. Reading a research paper, a PyTorch model, or a TensorFlow tutorial becomes far easier once you can track how shapes change from one operation to the next.
This article is a hands-on companion to our earlier article, "Linear Algebra for Machine Learning", which covered the theory, including eigenvalues, PCA, and the SVD. Here the focus is practical: how data is stored, how to manipulate it, and how matrix operations work in code. Every example uses NumPy and has been run to confirm its output.
Scalars, Vectors, Matrices, and Tensors
These four structures differ in one way: the number of axes, also called dimensions or the order (sometimes rank) of the tensor. In NumPy this is the ndim attribute.
Figure 1: Shapes and axes of a vector, a matrix, and a 3-D tensor
Three properties describe any tensor:
Shape: the size of each axis, written as a tuple. A matrix with 2 rows and 3 columns has shape (2, 3).
Size: the total number of elements, which is the product of the shape. A (2, 3) matrix has 6 elements.
Data type (dtype): the numeric type of the elements, such as float32, float64, or int64. All elements of a tensor share one dtype.
Axes are numbered from zero. In a matrix, axis 0 runs down the rows and axis 1 runs across the columns. Strictly speaking, scalars, vectors, and matrices are all tensors of order 0, 1, and 2, but in everyday ML language "tensor" usually means three or more axes.
Output: the scalar has shape (), the vector (3,), the matrix (2, 3) with dtype int64, and the tensor (2, 3, 4) with 24 elements.
How Real ML Data Maps to Tensor Shapes
Knowing the conventional shape of each data type is essential, because models expect their inputs in a specific layout. By convention the first axis is almost always the batch axis (N), the number of samples processed together.
Common Types of Vectors and Matrices
Indexing and Slicing
Indexing selects individual elements; slicing selects ranges. NumPy uses zero-based indexing and the syntax start:stop, where stop is excluded. A colon on its own means "everything along this axis".
Here X[0] returns [0, 1, 2, 3], X[:, 2] returns the column [2, 6, 10], X[1, 3] returns 7, and the boolean mask keeps the last two rows, whose first feature is greater than 3. Boolean masks are widely used to filter samples, for example to select all rows with a particular label.
Views versus copies: basic slicing returns a view that shares memory with the original array, so modifying the slice also modifies the original. Boolean and integer-array indexing return copies. Call .copy() when you need an independent array.
Reshaping and Rearranging Tensors
Reshaping changes how elements are grouped into axes without changing the elements themselves. The total number of elements must stay the same: 24 elements can become (4, 6), (2, 3, 4), or (3, 8), but never (5, 5).
NumPy stores arrays in row-major (C) order by default, and reshape fills the new shape in that order: the last axis changes fastest. This is why reshape and transpose are not interchangeable. Reshaping a (2, 3) matrix to (3, 2) regroups the same sequence of numbers, while transposing it actually swaps rows and columns.
Element-wise Operations
Element-wise operations apply to each pair of matching elements independently. They include addition, subtraction, multiplication, division, powers, and functions such as exp, log, and the activation functions used in neural networks. The inputs must have the same shape, or shapes that are compatible under broadcasting.
The difference between * and @ is one of the most important distinctions in NumPy and PyTorch code. The element-wise (Hadamard) product, usually written with a circled-dot symbol in papers, appears in LSTM and GRU gates and in attention masks; the matrix product powers every dense layer.
Broadcasting
Broadcasting lets NumPy, PyTorch, and TensorFlow combine tensors of different shapes without writing loops or copying data. Conceptually, the smaller tensor is stretched to match the larger one.
Figure 2: Broadcasting a (3, 1) column and a (4,) row into a (3, 4) result
Two shapes are compatible when, comparing them from the rightmost axis to the left, each pair of sizes is either equal or one of them is 1. Missing leading axes are treated as size 1. For example, (3, 1) and (4,) are aligned as (3, 1) and (1, 4), which broadcast to (3, 4). By contrast, (3, 2) and (3,) fail, because the trailing sizes 2 and 3 differ.
The column means are [2, 300], and after standardisation each column has values of about −1.22, 0, and 1.22. The last line raises "operands could not be broadcast together with shapes (3,2) (3,)", which is exactly the incompatibility described above.
Matrix Multiplication
Matrix multiplication is the most important operation in machine learning. For A of shape (m, n) and B of shape (n, p), the product C = AB has shape (m, p), and each element is the dot product of a row of A with a column of B:
cij = Σk aik bkj (m × n) · (n × p) = (m × p)
Figure 3: How one element of a matrix product is computed, and the shape rule
The inner dimensions (n) must match, and they disappear from the result; the outer dimensions (m and p) form the output shape. Computing the product requires m × n × p multiply-add operations, which is why matrix size drives the cost of training and running models.
Special Cases of Matrix Multiplication
Properties to Remember
Not commutative: AB and BA are generally different and may not even have the same shape. For A (2 × 3) and B (3 × 2), AB is 2 × 2 while BA is 3 × 3.
Associative: (AB)C = A(BC). The result is the same, but the cost can differ greatly, so libraries choose the cheaper order.
Distributive: A(B + C) = AB + AC.
Transpose of a product: (AB)T = BTAT. Note the reversed order.
Identity: AI = IA = A for an identity matrix of the right size.
The result is C = [[58, 64], [139, 154]]. For arrays with more than two axes, the @ operator (np.matmul) treats the last two axes as matrices and broadcasts over the leading batch axes. This is how deep learning frameworks multiply many matrices in a single call.
A Neural Network Layer in Matrix Form
A fully connected (dense) layer applied to a batch of inputs is a single matrix multiplication followed by a broadcast addition:
Y = XW + b (N, in) @ (in, out) + (out,) → (N, out)
With a batch of 32 samples, 784 input features, and 128 neurons, X is (32, 784), W is (784, 128), b is (128,), and Y is (32, 128). Note that PyTorch's nn.Linear stores its weight with shape (out, in) and computes Y = XWT + b; the mathematics is identical, only the storage convention differs.
Transpose, Inverse, Determinant, Trace, and Rank
The determinant is 10, the inverse is [[0.6, −0.7], [−0.2, 0.4]], the trace is 10, and solving Ax = b gives x = [−0.8, 0.6]. In practice, prefer np.linalg.solve or np.linalg.lstsq over computing an inverse explicitly.
Reductions Along Axes
A reduction combines many values into fewer values, for example by summing, averaging, or taking the maximum. The axis argument controls which axis is collapsed. The simplest way to remember it: the axis you name is the one that disappears.
Figure 4: Summing a (2, 3) matrix along axis 0 and along axis 1
Summing over axis 0 gives [5, 7, 9] (one value per column); summing over axis 1 gives [6, 15] (one value per row). The softmax example shows why keepdims=True matters: keeping the reduced axis with size 1, shape (2, 1), lets the result broadcast back against the original (2, 3) array. Each row of probabilities sums to 1, and argmax returns the predicted classes [0, 1].
Combining and Splitting Tensors
Concatenate joins tensors along an existing axis. Two (2, 2) matrices concatenated along axis 0 give (4, 2); along axis 1 they give (2, 4).
Stack joins tensors along a new axis. Stacking two (2, 2) matrices gives (2, 2, 2). Stacking individual samples is how a batch is built.
Split divides a tensor into parts, for example into training, validation, and test sets.
The split produces [0 to 5], [6, 7], and [8, 9]. In real projects, shuffle the data before splitting, or split by time for time-dependent data.
Einstein Summation (einsum)
einsum expresses many tensor operations in one compact notation. Each input gets a letter for each of its axes; letters that appear in the inputs but not in the output are summed over. It is available in NumPy, PyTorch, TensorFlow, and JAX, and it is common in research code, especially for attention.
All four checks print True, and the last line produces a (8, 5, 5) tensor: a 5 × 5 matrix of token-to-token scores for each of the 8 sequences.
Performance: Writing Fast Tensor Code
Vectorise Instead of Looping
Vectorised operations hand the work to optimised, compiled libraries such as BLAS and LAPACK on the CPU, or CUDA kernels on the GPU. A Python loop processes one element at a time with interpreter overhead.
Both approaches give the same answer, but the vectorised version is typically hundreds of times faster; the exact speed-up depends on the hardware.
Choose the Right Data Type
Memory adds up quickly. A float32 batch of shape (32, 3, 224, 224) contains 4,816,896 values, which at 4 bytes each is about 19.3 MB. Storing it as float64 doubles that to about 38.5 MB. Converting NumPy's float64 data to float32 before training is a common and worthwhile step.
Other Performance Tips
Prefer views to copies: slicing, reshaping contiguous arrays, and transposing usually create views without copying data.
Batch your work: one large matrix multiplication is much faster than many small ones, especially on GPUs.
Keep data on one device: in PyTorch, moving tensors between CPU and GPU memory is slow, so move data once and keep it there.
Use sparse formats for sparse data: scipy.sparse matrices store only the non-zero values, saving memory and time for text and ratings data.
NumPy and PyTorch Equivalents
PyTorch tensors behave much like NumPy arrays, with added GPU support and automatic differentiation. The table below maps common operations.
Common Mistakes and How to Avoid Them
Confusing * with @: * is element-wise; @ is matrix multiplication. Both may run without error on square matrices but give completely different results.
Silent broadcasting bugs: subtracting predictions of shape (N, 1) from targets of shape (N,) broadcasts to an (N, N) matrix instead of N differences. A loss computed this way runs but is wrong. Check shapes, or flatten both arrays first.
Wrong reduction axis: averaging over features instead of samples, or applying softmax across the batch instead of across classes.
Forgetting the batch axis: models expect (N, ...) input, so a single sample of shape (3, 224, 224) must become (1, 3, 224, 224).
Channels-first versus channels-last: feeding an (N, H, W, C) image batch to a PyTorch model that expects (N, C, H, W).
Reshape when you meant transpose: reshape regroups elements in memory order; it does not swap axes.
Modifying a view by accident: changing a slice also changes the original array. Use .copy() when you need independence.
Integer dtypes: arrays created from integers stay integers, so assigning 2.7 into one silently stores 2. Specify dtype=np.float32 when creating model inputs.
Quick Reference: Shape Rules
Conclusion
Vectors, matrices, and tensors are the containers of machine learning, and matrix operations are the machinery. With a clear understanding of shapes, axes, broadcasting, and the matrix multiplication rule, you can read model code fluently, debug errors quickly, and write efficient, vectorised implementations. These skills transfer directly from NumPy to PyTorch, TensorFlow, and JAX.
In upcoming articles on BitCodeMatrix, we will build on these foundations with calculus and gradients for optimisation, and then implement a neural network from scratch using only the operations covered here.
Frequently Asked Questions
What is the difference between a vector, a matrix, and a tensor? A vector has one axis, a matrix has two, and a tensor generalises to any number of axes. Scalars, vectors, and matrices are tensors of order 0, 1, and 2.
What does the shape of a tensor mean? The shape lists the size of each axis. A shape of (32, 3, 224, 224) means 32 images, each with 3 colour channels and 224 × 224 pixels.
What is the difference between element-wise multiplication and matrix multiplication? Element-wise multiplication multiplies matching elements and keeps the shape. Matrix multiplication computes dot products of rows and columns and requires matching inner dimensions.
What is broadcasting? Broadcasting automatically stretches tensors with compatible shapes so element-wise operations can be applied without loops or copying data. Shapes are compared from the right, and sizes must be equal or 1.
Why is matrix multiplication not commutative? AB combines the rows of A with the columns of B, while BA combines the rows of B with the columns of A. These are different calculations, and the results usually differ in value and often in shape.
Should I learn NumPy or PyTorch first? Start with NumPy to learn the concepts, then move to PyTorch or TensorFlow. The operations and shape rules are almost identical, so the skills transfer directly.