Learn ML

Chapter 9 of 25

Linear Algebra for Neural Networks

Vectors, matrices, and why networks are just matrix math

22 min read

Motivation

Here's a fact that surprises a lot of newcomers: a neural network with a million parameters isn't running a million separate little calculations when it makes a prediction. It's running a small handful of matrix multiplications. Every weighted sum inside a neuron, every layer's worth of neurons computed at once, every gradient flowing backward during training — all of it is linear algebra, dressed up in neural network vocabulary.

This matters for a very practical reason, not just an aesthetic one: linear algebra is why deep learning is fast. GPUs are extraordinarily good at one thing — multiplying large matrices — and the entire deep learning software stack is built around exploiting that fact. If you understand the operations in this chapter, every equation in the rest of this course will read as arithmetic you already know, instead of unfamiliar notation.

A Real-Life Scenario

Imagine a streaming service scoring how likely you are to enjoy a movie, based on a profile of features: how much you liked comedies, how much you liked action films, your average rating for movies from the last five years, and so on — say, 50 features in total. Now imagine it needs to do this not just for you, but for 10 million users, against a catalog of 20,000 movies, updated every night.

Doing this one multiplication at a time, in a Python for loop, would take unreasonably long. Doing it as one enormous matrix multiplication — every user's feature vector against every movie's feature vector, all at once — is something specialized hardware can chew through in seconds. The operations in this chapter are exactly what make that possible, and they are exactly what happens, at a smaller scale, inside every layer of an MLP.

Building the Intuition

Before the notation, here's the mental model to hold onto:

  • A scalar is just a single number: your age, a temperature, a loss value.
  • A vector is an ordered list of numbers — think of it as one row of a spreadsheet, or one data point's full set of features.
  • A matrix is a grid of numbers — think of it as an entire spreadsheet, or a whole batch of data points stacked together.
  • Inside an MLP, a layer's weights are a matrix, its bias is a vector, and a single input example is a vector. Multiplying the weight matrix by the input vector computes the pre-activation of every neuron in that layer, simultaneously — that single fact is the reason this chapter exists.
Scalar, Vector, Matrix, Tensor

A scalar is a single number, e.g. a=3.5a = 3.5. A vector is an ordered list of numbers, written as a column: v=[v1v2vn]Rn\mathbf{v} = \begin{bmatrix} v_1 \\ v_2 \\ \vdots \\ v_n \end{bmatrix} \in \mathbb{R}^n. A matrix is a rectangular array with mm rows and nn columns, written ARm×n\mathbf{A} \in \mathbb{R}^{m \times n}. A tensor generalizes these to any number of dimensions — a scalar is a 0-dimensional tensor, a vector is 1-dimensional, a matrix is 2-dimensional.

Notation used throughout this course: lowercase italics (xx) are scalars, lowercase bold (x\mathbf{x}) are vectors, uppercase bold (W\mathbf{W}) are matrices, A\mathbf{A}^\top is a transpose, and AijA_{ij} is the entry in row ii, column jj.

Vectors

Addition and Scalar Multiplication

Vector Addition and Scalar Multiplication
u+v=[u1+v1u2+v2un+vn],cv=[cv1cv2cvn]\mathbf{u} + \mathbf{v} = \begin{bmatrix} u_1+v_1 \\ u_2+v_2 \\ \vdots \\ u_n+v_n \end{bmatrix}, \qquad c\mathbf{v} = \begin{bmatrix} cv_1 \\ cv_2 \\ \vdots \\ cv_n \end{bmatrix}

Where: u,vRn\mathbf{u}, \mathbf{v} \in \mathbb{R}^n are same-size vectors and cRc \in \mathbb{R} is a scalar.

Intuition: Addition combines vectors component by component — geometrically, place one vector's tail at the other's head and see where you land. Scalar multiplication stretches (c>1|c|>1), shrinks (c<1|c|<1), or flips (c<0c<0) a vector without changing its underlying direction, aside from that sign flip.

The Dot Product

Dot Product
uv=uv=i=1nuivi=u1v1+u2v2++unvn\mathbf{u}\cdot\mathbf{v} = \mathbf{u}^\top\mathbf{v} = \sum_{i=1}^n u_iv_i = u_1v_1 + u_2v_2 + \cdots + u_nv_n

Where: u,vRn\mathbf{u}, \mathbf{v} \in \mathbb{R}^n.

Why it matters: This is the single most important operation in an entire MLP. The pre-activation of every neuron, z=wx+bz = \mathbf{w}^\top\mathbf{x} + b, is nothing but a dot product between that neuron's weight vector and the input, plus a bias.

Geometric intuition: The dot product also equals uvcosθ\|\mathbf{u}\|\|\mathbf{v}\|\cos\theta, where θ\theta is the angle between the vectors. It's large and positive when two vectors point in similar directions, zero when they're perpendicular, and negative when they point roughly opposite ways — it measures alignment.

Worked example: let w=(0.5,1.2,2.0)\mathbf{w} = (0.5, -1.2, 2.0) and x=(1.0,3.0,0.5)\mathbf{x} = (1.0, 3.0, 0.5).

wx=(0.5)(1.0)+(1.2)(3.0)+(2.0)(0.5)=0.53.6+1.0=2.1\mathbf{w}^\top\mathbf{x} = (0.5)(1.0) + (-1.2)(3.0) + (2.0)(0.5) = 0.5 - 3.6 + 1.0 = -2.1

With bias b=0.3b = 0.3, the neuron's pre-activation is z=2.1+0.3=1.8z = -2.1 + 0.3 = -1.8 — exactly the same arithmetic as a perceptron's weighted sum from two chapters ago, just written in vector notation.

Vector Norms

L1, L2, and L∞ Norms
v1=i=1nvi,v2=i=1nvi2,v=maxivi\|\mathbf{v}\|_1 = \sum_{i=1}^n |v_i|, \qquad \|\mathbf{v}\|_2 = \sqrt{\sum_{i=1}^n v_i^2}, \qquad \|\mathbf{v}\|_\infty = \max_i |v_i|

Where: v1\|\mathbf{v}\|_1 (the Manhattan norm) sums absolute values; v2\|\mathbf{v}\|_2 (the Euclidean norm) is the familiar straight-line length; v\|\mathbf{v}\|_\infty is the single largest absolute component.

Why this matters: L1 and L2 norms of a weight vector underlie L1/L2 regularization, a technique you'll meet later in this course for discouraging overly large weights. The L2 norm of a gradient vector is also used for gradient clipping, a training-stability technique.

For v=(3,4)\mathbf{v} = (3, -4): v1=3+4=7\|\mathbf{v}\|_1 = |3| + |-4| = 7, and v2=32+(4)2=25=5\|\mathbf{v}\|_2 = \sqrt{3^2+(-4)^2} = \sqrt{25} = 5.

Matrices

Matrix Multiplication

Matrix multiplication is what lets you compute an entire layer of neurons in one shot, instead of looping over dot products one neuron at a time.

Matrix Multiplication

For ARm×n\mathbf{A} \in \mathbb{R}^{m\times n} and BRn×p\mathbf{B} \in \mathbb{R}^{n\times p}, the product C=ABRm×p\mathbf{C} = \mathbf{AB} \in \mathbb{R}^{m\times p} is defined entry-by-entry as:

Cij=k=1nAikBkjC_{ij} = \sum_{k=1}^n A_{ik}B_{kj}

Where: CijC_{ij} (row ii, column jj of the result) is the dot product of row ii of A\mathbf{A} with column jj of B\mathbf{B}.

Critical requirement: the number of columns of A\mathbf{A} must equal the number of rows of B\mathbf{B} — this is the single most common source of shape-mismatch bugs when implementing networks from scratch.

How Matrix Multiplication Combines Rows and ColumnsIllustration coming soon
Entry C_ij is the dot product of row i of A with column j of B. For the multiplication to be valid, A's number of columns must match B's number of rows.

Worked example: let A=[1201]\mathbf{A} = \begin{bmatrix} 1 & 2 \\ 0 & -1 \end{bmatrix} and B=[34]\mathbf{B} = \begin{bmatrix} 3 \\ 4 \end{bmatrix}.

AB=[(1)(3)+(2)(4)(0)(3)+(1)(4)]=[114]\mathbf{AB} = \begin{bmatrix} (1)(3)+(2)(4) \\ (0)(3)+(-1)(4) \end{bmatrix} = \begin{bmatrix} 11 \\ -4 \end{bmatrix}
A Full Layer Is One Matrix Multiplication

If WRnout×nin\mathbf{W} \in \mathbb{R}^{n_{out}\times n_{in}} is a layer's weight matrix and xRnin\mathbf{x} \in \mathbb{R}^{n_{in}} is the input, then every neuron's pre-activation in that layer is computed in a single step:

z=Wx+bRnout\mathbf{z} = \mathbf{W}\mathbf{x} + \mathbf{b} \in \mathbb{R}^{n_{out}}

Intuition: each row of W\mathbf{W} is one neuron's weight vector. Multiplying W\mathbf{W} by x\mathbf{x} simultaneously takes the dot product of every row with x\mathbf{x} — that's every neuron's weighted sum, computed at once. This is exactly why deep learning frameworks are built around fast matrix multiplication, and why GPUs (which excel at exactly this operation) are central to modern deep learning.

Matrix multiplication is not commutative

ABBA\mathbf{AB} \neq \mathbf{BA} in general — and often one of the two products isn't even defined, due to shape mismatches. Always check dimensions before multiplying; this is one of the most common bugs when implementing neural networks from scratch.

The Transpose

Matrix Transpose
(A)ij=Aji(\mathbf{A}^\top)_{ij} = A_{ji}

Intuition: transposing flips a matrix over its diagonal, turning rows into columns and vice versa. If ARm×n\mathbf{A}\in\mathbb{R}^{m\times n}, then ARn×m\mathbf{A}^\top \in \mathbb{R}^{n\times m}. Transposes show up constantly in backpropagation, because gradients flow backward through the same weight matrices used in the forward pass, but with their roles — and dimensions — reversed.

Element-wise (Hadamard) Product

Hadamard Product
(AB)ij=AijBij(\mathbf{A}\odot\mathbf{B})_{ij} = A_{ij}B_{ij}

Where: A\mathbf{A} and B\mathbf{B} must have identical shape. Unlike matrix multiplication, this multiplies corresponding entries directly — no dot products, no summing.

Why this matters: the Hadamard product appears in backpropagation when combining a layer's upstream gradient with the local derivative of its activation function, since activation functions are always applied element-wise, one neuron at a time. You'll see this exact operation again in the backpropagation chapter, written with the \odot symbol.

Don't mix these two products up

Matrix multiplication (AB\mathbf{AB}) combines rows with columns via dot products and can change the shape entirely. The Hadamard product (AB\mathbf{A}\odot\mathbf{B}) requires identical shapes and multiplies entry-by-entry, preserving shape. They solve different problems and are not interchangeable.

Special Matrices Worth Recognizing

Special matrices frequently encountered in deep learning
MatrixDescription
Identity (I)1s on the diagonal, 0s elsewhere; satisfies AI = IA = A
Zero matrixEvery entry is zero
Diagonal matrixAll off-diagonal entries are zero
Symmetric matrixSatisfies A = Aᵀ
Orthogonal matrixSatisfies AᵀA = I; preserves vector lengths and angles — relevant to some weight initialization schemes covered later in this course

Implementing It

import numpy as np
 
# Vectors
u = np.array([1.0, 2.0, 3.0])
v = np.array([4.0, -1.0, 0.5])
 
print("u + v      =", u + v)
print("dot(u, v)  =", np.dot(u, v))          # equivalently u @ v
print("L2 norm(u) =", np.linalg.norm(u))
print("L1 norm(u) =", np.linalg.norm(u, 1))
 
# Matrices
W = np.array([[0.2, -0.5, 1.0],
              [0.8,  0.1, -0.3]])   # shape (2, 3): 2 output units, 3 inputs
x = np.array([1.0, 0.5, -2.0])      # shape (3,)
b = np.array([0.1, -0.2])           # shape (2,)
 
z = W @ x + b                       # shape (2,): pre-activations of a layer
print("z =", z)
 
# Batch of examples: X has shape (batch_size, n_features)
X = np.array([[1.0, 0.5, -2.0],
              [0.0, 1.0,  1.0]])    # 2 examples, 3 features each
Z = X @ W.T + b                     # shape (2, 2): pre-activations for both examples
print("Z (batched) =\n", Z)

A few things worth noticing: the @ operator is NumPy's matrix multiplication, distinct from * (which does element-wise/Hadamard multiplication). Notice the batched line, X @ W.T + b: rather than looping over each of the two examples in Python, one matrix multiplication computes both examples' pre-activations at once, with b automatically broadcast across every row. This vectorized batch formulation, not manual loops, is what lets deep learning frameworks exploit fast, GPU-accelerated computation.

Remember the batched shape convention

Always compute a batch's pre-activations as X @ W.T + b, where X has shape (batch_size, n_features) and W has shape (n_outputs, n_features). Getting this transpose backwards is one of the most common shape-mismatch errors when implementing a layer from scratch.

Key Takeaways

Key Takeaways
  • Vectors and matrices are the basic data structures of an MLP: inputs, weights, and biases are all represented this way.
  • The dot product wx\mathbf{w}^\top\mathbf{x} computes one neuron's weighted sum; matrix multiplication Wx\mathbf{Wx} computes an entire layer's pre-activations at once.
  • Norms (1\|\cdot\|_1, 2\|\cdot\|_2) measure vector size and underlie regularization and gradient clipping.
  • Matrix multiplication requires matching inner dimensions and is not commutative — check shapes carefully.
  • The Hadamard (element-wise) product is a distinct operation from matrix multiplication, and it's exactly what backpropagation uses to combine gradients with activation derivatives.
  • NumPy's @ operator and broadcasting make all of these operations efficient and concise in code.

Common Mistakes

Confusing matrix multiplication with the Hadamard product

W @ x and W * x do very different things in NumPy, and mixing them up silently produces wrong (but sometimes shape-compatible!) results. Matrix multiplication combines rows and columns via dot products; the Hadamard product multiplies entry-by-entry and requires identical shapes.

Forgetting to check dimension compatibility before multiplying

AB\mathbf{AB} is only defined when A\mathbf{A}'s column count matches B\mathbf{B}'s row count — and even when both AB\mathbf{AB} and BA\mathbf{BA} happen to be defined, they are almost never equal. Always sanity-check shapes, especially when reshaping between single-example and batched code.

Interview Corner

Quick Check
1. For weight matrix W with shape (n_out, n_in) and input vector x with shape (n_in,), what is the shape of z = Wx?
2. What is the key difference between matrix multiplication (AB) and the Hadamard product (A ⊙ B)?

Where This Shows Up in Practice

Every forward pass through every layer of an MLP is a matrix multiplication plus a bias vector; every backward pass reuses transposed weight matrices and Hadamard products with activation derivatives. The next chapter builds the other half of the toolkit — calculus — so that when we derive backpropagation, both the "what" (linear algebra, from this chapter) and the "why it works" (calculus, next) will already feel familiar.