Math for AI course · Lesson 38 of 180

Linear Algebra in a Tiny Neural Network

Trace every shape, bias, activation, and rank change through a two-layer network with a worked NumPy example and an interactive explorer.

Share this article

A neural network can look like a string of matrix operations, yet each step changes what the numbers mean. Following one small network from input to output reveals the whole route: matrix shapes determine which maps can connect, biases translate those maps, rank limits independent directions, and an activation changes what the composition can express.

By the end, you will be able to trace each shape and calculate a two-layer network by hand or in NumPy, then explain where its bias, rank, and activation affect the result.

This lesson brings together three earlier tools: solving linear systems without explicitly inverting a matrix, using kernel and image to describe lost and reachable directions, and using bias to translate an affine map. Chapter 2 of Mathematics for Machine Learning supplies the linear algebra behind the synthesis. The network, numbers, code, visual, and assessments below are original.

Follow the dimensions through two affine layers

Here, “two-layer network” means two learned affine maps with a hidden activation between them. We will keep the output as two scores; no probability or class decision is applied. Let the input be a column vector x∈R2x\in\mathbb {R}^2. Each affine map combines a matrix with a bias vector:

The shared dimension between adjacent maps must match: W1W_1 has two columns for the two input features, and two rows for its hidden outputs. Then W2W_2 accepts those two hidden values and returns two output scores. The bias b1b_1 is added once to each hidden coordinate, and b2b_2 once to each output. A matrix with incompatible dimensions cannot perform the stated mapping; this is the same shape check that NumPy’s matrix product applies.

Trace a two-input, two-hidden-unit networkMove either input coordinate or switch the hidden activation. The initial state uses x = (1, 1) and ReLU; all values below are rendered before JavaScript runs.
  1. Input · 2 features
  2. Affine map · 2 hidden values
  3. Hidden activation
  4. Affine map · 2 scores
  5. Output · 2 scores

z = W₁x + b₁

h = σ(z)

y = W₂h + b₂

Change the input and hidden rule

Use the arrow keys or slider to change each input from −2 to 2 in steps of 0.25.

Hidden rule
Preactivation z
(-0.50, 0.50)
Hidden output h
(0.00, 0.50)
Scores y
(1.50, -0.50)
Active hidden units
1 / 2
Local Jacobian rank
1

Initial input is 1, 1; hidden output is 0, 0.5; scores are 1.5, −0.5.

The starting values in the figure use these fixed, hand-chosen parameters:

W1=[1−111],b1=[−0.5−1.5],W2=[12−11],b2=[0.5−1].W_1=\begin{bmatrix}1&-1\\1&1\end{bmatrix},\quad b_1=\begin{bmatrix}-0.5\\-1.5\end{bmatrix},\quad W_2=\begin{bmatrix}1&2\\-1&1\end{bmatrix},\quad b_2=\begin{bmatrix}0.5\\-1\end{bmatrix}.

The example is small enough to follow entry by entry. Its values are chosen to show one hidden unit turning off under ReLU; they are not trained parameters or a claim about a typical model.

Compute one prediction, keeping every intermediate

Take x=(1,1)Tx=(1,1)^\mathsf{T}. The first matrix combines the two input coordinates in different ways. Adding the bias shifts each result:

NumPy’s @ operator performs matrix multiplication. The + b operations below use NumPy’s elementwise broadcasting to add a length-two bias to a length-two result. This small implementation prints the actual intermediate vectors:

import numpy as np

W1 = np.array([[1.0, -1.0], [1.0, 1.0]])  # (2, 2)
b1 = np.array([-0.5, -1.5])               # (2,)
W2 = np.array([[1.0, 2.0], [-1.0, 1.0]])  # (2, 2)
b2 = np.array([0.5, -1.0])                # (2,)
x = np.array([1.0, 1.0])                  # (2,)

z = W1 @ x + b1                           # (2,) = (2, 2) @ (2,) + (2,)
h = np.maximum(z, 0.0)                    # (2,), elementwise ReLU
y = W2 @ h + b2                           # (2,) = (2, 2) @ (2,) + (2,)

print('z:', z)  # [-0.5  0.5]
print('h:', h)  # [0.  0.5]
print('y:', y)  # [ 1.5 -0.5]

The same width logic applies to a batch. If BB examples are stored as rows, their array has shape (B,2)(B,2); multiply on the right by W1TW_1^\mathsf{T} to produce (B,2)(B,2) hidden values, and then by W2TW_2^\mathsf{T} to produce (B,2)(B,2) scores. PyTorch’s Linear layer uses that row-oriented convention: its weight shape is (out features,in features)(\text{out features},\text{in features}) and it applies y=xAT+by=xA^\mathsf{T}+b. A Linear(2, 2) therefore stores a (2,2)(2,2) weight and a length-two bias. PyTorch’s ReLU is applied elementwise and preserves the input shape, matching the step used above (Linear docs, ReLU docs, NumPy matmul docs).

Without an activation, two layers are still one affine map

The bias in an affine layer is a translation. If the hidden step is the identity, the two layers compose by substitution:

y=W2(W1x+b1)+b2=(W2W1)x+(W2b1+b2).y=W_2(W_1x+b_1)+b_2 =(W_2W_1)x+(W_2b_1+b_2).

So stacking more affine maps without a nonlinear activation cannot turn the overall input-output rule into a nonlinear one; the matrices multiply into a single matrix, and the biases combine into one offset. For our chosen values, W2W1=[3102]W_2W_1=\begin{bmatrix}3&1\\0&2\end{bmatrix} and W2b1+b2=(−3,−2)TW_2b_1+b_2=(-3,-2)^\mathsf{T}. The identity version therefore maps (1,1)T(1,1)^\mathsf{T} to (1,0)T(1,0)^\mathsf{T}.

The rank of a matrix counts its independent output directions. In this identity example both W1W_1 and W2W_2 have rank two, and their product also has rank two. In a network whose hidden width is rr, the linear part of an affine composition cannot have rank greater than rr, since it factors through only rr coordinates. If either matrix has lower rank, the composition’s rank can be lower still. These are upper bounds: a narrow layer is a possible bottleneck, but width alone does not guarantee a particular rank.

ReLU makes the map piecewise affine

ReLU changes a hidden coordinate to zero when its preactivation is negative. For any region where the signs of all preactivations stay fixed, the set of active units is fixed too. Let DD be a diagonal matrix with a 11 for each active ReLU unit and a 00 for each inactive one. Within that region, the local linear part of the two-layer network is

J=W2DW1.J=W_2DW_1.

For the worked input, z=(−0.5,0.5)Tz=(-0.5,0.5)^\mathsf{T}, so D=[0001]D=\begin{bmatrix}0&0\\0&1\end{bmatrix}. Then

J=[12−11][0001][1−111]=[2211],rank⁡(J)=1.J= \begin{bmatrix}1&2\\-1&1\end{bmatrix} \begin{bmatrix}0&0\\0&1\end{bmatrix} \begin{bmatrix}1&-1\\1&1\end{bmatrix} =\begin{bmatrix}2&2\\1&1\end{bmatrix}, \qquad \operatorname{rank}(J)=1.

Near this input, both output scores change with the same input combination x1+x2x_1+x_2; the difference direction (1,−1)(1,-1) has no effect while the first unit remains inactive. The matrix JJ describes local changes inside this activation region, not one global matrix for the entire network. On a ReLU boundary where a preactivation equals zero, the function has a kink and no single region’s Jacobian describes both sides.

The hidden width and weights also constrain what the network can express. In this particular example, the two preactivation-zero lines partition the input plane into regions with different active-unit patterns; each region has an affine rule, and crossing a line can change that rule. More generally, adding a nonlinear activation makes nonlinear functions possible, but does not make every choice of weights expressive. A hidden unit may remain off for the inputs of interest, or the matrices may collapse directions. The matrix shapes, their ranks, the bias offsets, and the activation pattern all contribute to the behavior.

That is why the output values alone are a thin debugging signal. Trace shapes to catch incompatible connections, compare zz and hh to see which features were gated, and inspect the layer ranks or local Jacobian to identify lost directions. When a separate fitting step asks you to solve Ax≈bAx\approx b, Lesson 025 explains why a numerical solver avoids the hazards of forming A−1A^{-1}. A network forward pass itself uses matrix products; it does not require inverting its learned weights.

Once the maps and their directions are clear, the next question is how large a vector is. Lesson 039 introduces norms to measure vector magnitude, compare embedding scales, and describe parameter penalties.

Check your understanding

Question 1

For the network in this lesson, state the shapes of x, W₁, b₁, z, h, W₂, b₂, and y. Why can W₂ accept h?

Show the step-by-step solution

xx has shape (2,)(2,), W1W_1 is (2,2)(2,2), b1b_1 is (2,)(2,), and zz and hh are each (2,)(2,). The second matrix W2W_2 is (2,2)(2,2) with bias b2b_2 of shape (2,)(2,), producing yy of shape (2,)(2,). The inner dimension of W2W_2 is two, which matches the two entries in hh; the matrix therefore maps the hidden vector to two output scores.

Question 2

With x = (1, 1)ᵀ, calculate z, h, and y using the lesson’s matrices and ReLU. Show the bias and activation steps.

Show the step-by-step solution

First W1x=(0,2)TW_1x=(0,2)^\mathsf{T}, so adding b1=(−0.5,−1.5)Tb_1=(-0.5,-1.5)^\mathsf{T} gives z=(−0.5,0.5)Tz=(-0.5,0.5)^\mathsf{T}. Applying ReLU coordinate by coordinate gives h=(0,0.5)Th=(0,0.5)^\mathsf{T}. Next W2h=(1,0.5)TW_2h=(1,0.5)^\mathsf{T}; adding b2=(0.5,−1)Tb_2=(0.5,-1)^\mathsf{T} gives y=(1.5,−0.5)Ty=(1.5,-0.5)^\mathsf{T}.

Question 3

Why does removing the hidden activation leave an affine map? Find its combined matrix and bias for this example.

Show the step-by-step solution

Substitute the first affine rule into the second: W2(W1x+b1)+b2=(W2W1)x+(W2b1+b2)W_2(W_1x+b_1)+b_2=(W_2W_1)x+(W_2b_1+b_2). Here multiplication gives W2W1=[3102]W_2W_1=\begin{bmatrix}3&1\\0&2\end{bmatrix}. Also W2b1=(−3.5,−1)TW_2b_1=(-3.5,-1)^\mathsf{T}, so adding b2=(0.5,−1)Tb_2=(0.5,-1)^\mathsf{T} yields the combined bias (−3,−2)T(-3,-2)^\mathsf{T}. The result is one affine map because it still has the form Ax+cAx+c.

Question 4

For x = (1, 1)ᵀ, what output does the identity-hidden-step version produce? How does it compare with the ReLU result?

Show the step-by-step solution

With the identity step, use the combined map from the previous solution: [3102](1,1)T+(−3,−2)T=(1,0)T\begin{bmatrix}3&1\\0&2\end{bmatrix}(1,1)^\mathsf{T}+(-3,-2)^\mathsf{T} =(1,0)^\mathsf{T}. With ReLU the output is (1.5,−0.5)T(1.5,-0.5)^\mathsf{T} because the first hidden preactivation is negative and becomes zero, while the second stays 0.50.5. The difference comes from that coordinatewise activation.

Question 5

A network maps 5 inputs through a hidden layer of width 3 to 4 outputs, with no activation. What is the largest possible rank of its linear part? What if the first weight matrix has rank 2 and the second has rank 3?

Show the step-by-step solution

The composed matrix factors through three hidden coordinates, so its rank is at most three. It cannot exceed the input dimension five or output dimension four either. If the first matrix has rank two and the second rank three, the product rank is at most the smallest of those bounds, which is two. These are upper limits; the actual product could have still lower rank if the directions are misaligned.

Question 6

At the worked input, the first ReLU unit is off and the second is on. Calculate the local Jacobian rank and explain its meaning.

Show the step-by-step solution

The activation mask is D=diag⁡(0,1)D=\operatorname{diag}(0,1). Multiplying gives J=W2DW1=[2211]J=W_2DW_1=\begin{bmatrix}2&2\\1&1\end{bmatrix}. Its second column is equal to its first, so it has one independent column and rank one. Within this activation region, small input changes along one direction do not change the output; both scores depend on the same input combination. This is a local statement because a ReLU switch changes the matrix DD.

Question 7

What changes when a ReLU preactivation is exactly zero? Can one use either neighboring region’s Jacobian as the single derivative there?

Show the step-by-step solution

ReLU has a corner at zero: its slope is zero just to the left and one just to the right. At an exactly zero preactivation, the function is not differentiable in that coordinate, so there is no single ordinary Jacobian that describes both sides. The network output is still defined; it is the one local linear approximation across the boundary that fails.

Question 8

A batch has 8 examples with 4 features each. What are the input and output shapes for PyTorch Linear(4, 3), and what is the weight shape?

Show the step-by-step solution

The batch input is (8,4)(8,4), with eight rows and four input features. PyTorch stores the layer weight with shape (out features,in features)(\text{out features},\text{in features}) so its weight is (3,4)(3,4) and its bias has shape (3,)(3,). The layer returns (8,3)(8,3), preserving the batch dimension and replacing the final feature dimension with three outputs.

Sources

  1. Mathematics for Machine Learning book PDF, Chapter 2
  2. NumPy documentation: numpy.matmul
  3. PyTorch documentation: torch.nn.Linear
  4. PyTorch documentation: torch.nn.ReLU