Beginner

Matrix Transformations: Visualizing Linear Algebra for Machine Learning

Stop viewing matrices as data containers. Learn how neural networks use linear algebra as an active engine to geometrically fold, shear, and crush high-dimensional space.

There’s a particular wall that people hit when studying deep learning β€” somewhere around the point where the tutorials run out and the papers begin. The PyTorch calls still work, the loss still goes down, but nothing quite makes sense anymore. Gradients vanish. Models don’t converge. Architectures that β€œshould” work don’t.

Nine times out of ten, the root cause is that matrices were never understood as anything other than data containers β€” glorified spreadsheets. That framing is useful for storage, but it actively obscures what a neural network is doing to your data.

In linear algebra, a matrix is an active transformation of space. When a feed-forward layer processes an input, it isn’t querying a table. It’s warping the geometric coordinate system the data lives in β€” rotating it, scaling it, shearing it β€” until the structure of the data becomes legible. Everything else in deep learning is built on top of that operation.

The Coordinate System Is Not Fixed

Before we can talk about transforming space, we need to be precise about what defines it.

Any point in a 2D plane is specified by two numbers: how far it travels horizontally and how far it travels vertically. But that description depends on what β€œhorizontal” and β€œvertical” mean β€” and those directions are defined by two foundational vectors we call basis vectors:

  • i^\hat{i}: one unit to the right, [10]\begin{bmatrix} 1 \\ 0 \end{bmatrix}
  • j^\hat{j}: one unit upward, [01]\begin{bmatrix} 0 \\ 1 \end{bmatrix}

Every vector in 2D space is a linear combination of these two. When you write vβƒ—=[32]\vec{v} = \begin{bmatrix} 3 \\ 2 \end{bmatrix}, you’re saying: take i^\hat{i}, scale it by 3, take j^\hat{j}, scale it by 2, add them:

v⃗=3[10]+2[01]=[32]\vec{v} = 3\begin{bmatrix} 1 \\ 0 \end{bmatrix} + 2\begin{bmatrix} 0 \\ 1 \end{bmatrix} = \begin{bmatrix} 3 \\ 2 \end{bmatrix}

The key shift: the grid itself is downstream of i^\hat{i} and j^\hat{j}. Move the basis vectors, and every point in the space moves with them β€” because every point was defined in terms of those vectors. A matrix is precisely a description of where the basis vectors land after a transformation.

Matrices as Spatial Instructions

A 2Γ—22 \times 2 matrix encodes exactly two pieces of information: the new position of i^\hat{i} (first column) and the new position of j^\hat{j} (second column):

W=[abcd]W = \begin{bmatrix} a & b \\ c & d \end{bmatrix}

The first column [ac]\begin{bmatrix} a \\ c \end{bmatrix} is where i^\hat{i} lands. The second column [bd]\begin{bmatrix} b \\ d \end{bmatrix} is where j^\hat{j} lands. Matrix-vector multiplication then follows from that definition directly: to find where input x⃗\vec{x} ends up, scale the new i^\hat{i} by xx and the new j^\hat{j} by yy, then add:

Wx⃗=[abcd][xy]=x[ac]+y[bd]W\vec{x} = \begin{bmatrix} a & b \\ c & d \end{bmatrix}\begin{bmatrix} x \\ y \end{bmatrix} = x\begin{bmatrix} a \\ c \end{bmatrix} + y\begin{bmatrix} b \\ d \end{bmatrix}

The Shear Transformation

A shear transformation pushes one axis while leaving the other fixed. To shear horizontally β€” sliding the top of the space right while keeping the bottom anchored β€” we leave i^\hat{i} in place and push j^\hat{j} one unit to the right:

Wshear=[1101]W_{shear} = \begin{bmatrix} 1 & 1 \\ 0 & 1 \end{bmatrix}

Apply this to an arbitrary point:

Wshear[xy]=[x+yy]W_{shear}\begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} x + y \\ y \end{bmatrix}

A square input becomes a parallelogram. This is, geometrically, what happens to your data in the first dense layer of a network.

Determinant: How Much Does Space Scale?

The determinant of a 2Γ—22 \times 2 matrix measures how much the transformation scales area. If the original input occupies a region of area 1, after applying WW, that region has area ∣det⁑(W)∣|\det(W)|:

det⁑[abcd]=adβˆ’bc\det\begin{bmatrix} a & b \\ c & d \end{bmatrix} = ad - bc

For WshearW_{shear} above: det⁑=(1)(1)βˆ’(1)(0)=1\det = (1)(1) - (1)(0) = 1. Area is preserved β€” the parallelogram has the same area as the original square.

A negative determinant means the transformation flips orientation (a reflection is involved). And a determinant of exactly zero means the transformation is singular: the entire 2D plane collapses into a line (or a point).

Dimensional Collapse

This is the degenerate case worth understanding carefully. Consider:

Wcollapse=[2βˆ’14βˆ’2]W_{collapse} = \begin{bmatrix} 2 & -1 \\ 4 & -2 \end{bmatrix}

The new i^\hat{i} is [24]\begin{bmatrix} 2 \\ 4 \end{bmatrix} and the new j^\hat{j} is [βˆ’1βˆ’2]\begin{bmatrix} -1 \\ -2 \end{bmatrix}. Notice that j^=βˆ’12i^\hat{j} = -\frac{1}{2}\hat{i} β€” they’re collinear. When the two basis vectors land on the same line, the entire 2D plane gets crushed onto that line. Every point that was anywhere in the plane is mapped to some point on that one diagonal.

Verify: det⁑(Wcollapse)=(2)(βˆ’2)βˆ’(βˆ’1)(4)=βˆ’4+4=0\det(W_{collapse}) = (2)(-2) - (-1)(4) = -4 + 4 = 0. Zero determinant, collapsed space.

This is irreversible. Once you’ve crushed a 2D plane to a 1D line, you can’t reconstruct where any particular point came from β€” infinitely many input points map to the same output. There’s no inverse matrix because information has been permanently destroyed.

In ML, this collapse is used intentionally. Embedding layers and autoencoders use non-square weight matrices to project high-dimensional data into lower-dimensional spaces, discarding redundant dimensions and keeping the principal structure. The non-invertibility isn’t a bug; it’s the compression step.

Linear Layers in Neural Networks

A dense (fully connected) layer implements:

y⃗=Wx⃗+b⃗\vec{y} = W\vec{x} + \vec{b}

Each term has a geometric role:

Wx⃗W\vec{x} applies the transformation — rotating, scaling, shearing the space. This part is a linear map: lines stay lines, the origin stays fixed.

+ bβƒ—+\,\vec{b} is a translation, shifting the entire transformed space by a constant offset. This breaks the origin-fixed constraint of pure linear maps and produces an affine transformation (linear transformation + translation). Without the bias, a neuron with zero input would always output zero regardless of weights, which severely limits representational capacity.

The goal during training is to find a WW and bβƒ—\vec{b} that reshapes the input space so that whatever you’re trying to predict β€” spam vs. not spam, cat vs. dog β€” becomes geometrically separable. Backpropagation is the algorithm that calculates how to adjust the entries of WW to move the loss in the right direction.

Why Depth Alone Doesn’t Help: The Linearity Collapse Problem

Here’s the architectural trap. Suppose we stack three dense layers with no activation functions between them:

y1⃗=W1x⃗+b1⃗\vec{y_1} = W_1\vec{x} + \vec{b_1} y2⃗=W2y1⃗+b2⃗=W2(W1x⃗+b1⃗)+b2⃗=W2W1x⃗+(W2b1⃗+b2⃗)\vec{y_2} = W_2\vec{y_1} + \vec{b_2} = W_2(W_1\vec{x} + \vec{b_1}) + \vec{b_2} = W_2W_1\vec{x} + (W_2\vec{b_1} + \vec{b_2}) y3⃗=W3y2⃗+b3⃗=W3W2W1x⃗+(W3W2b1⃗+W3b2⃗+b3⃗)\vec{y_3} = W_3\vec{y_2} + \vec{b_3} = W_3W_2W_1\vec{x} + (W_3W_2\vec{b_1} + W_3\vec{b_2} + \vec{b_3})

The final expression has the form Weffx⃗+b⃗effW_{eff}\vec{x} + \vec{b}_{eff}, where both Weff=W3W2W1W_{eff} = W_3W_2W_1 and b⃗eff\vec{b}_{eff} are just constants. Three layers of linear transformations collapse into one single affine map. A network with 1,000 such layers is mathematically equivalent to one with exactly one.

The consequence: a purely linear network can only learn linear decision boundaries. No matter how many layers you add, it can only draw hyperplanes through the data. Spirals, concentric rings, XOR β€” none of these are linearly separable, and a linear model cannot learn them at any depth.

Nonlinearity: Breaking the Collapse

The solution is to introduce a nonlinear function between layers β€” an activation function β€” that breaks the composability of linear maps.

The most common choice is ReLU (Rectified Linear Unit):

f(x)=max⁑(0,x)f(x) = \max(0, x)

ReLU sets all negative values to zero and leaves positives unchanged. Applied element-wise after each linear layer, it folds the transformed space: any coordinates pushed into negative territory get snapped to the axis. This folding is genuinely nonlinear β€” it cannot be represented by any matrix β€” so the composability argument no longer applies. Three ReLU layers are not equivalent to one.

Why ReLU specifically, rather than Sigmoid or tanh? The short answer is that Sigmoid and tanh both saturate: for large inputs (positive or negative), their gradients approach zero. During backpropagation through deep networks, this causes gradients to vanish β€” updates to early layers become negligible, and the network stops learning. ReLU doesn’t saturate on the positive side, so gradients flow through cleanly. The cost is β€œdead neurons” (units stuck outputting zero when their input is persistently negative), which is why variants like Leaky ReLU or GELU are used in practice, but the core motivation is gradient preservation.

Implementation: Tracing a Vector Through a Layer

The following demonstrates a single dense layer with ReLU, tracking what happens to a specific input vector at each step. One thing to notice: no explicit loops over data points. The @ operator performs matrix multiplication on the entire batch simultaneously, which on modern hardware maps directly to SIMD vector instructions.

spatial_transform.py
import numpy as np
def relu(x: np.ndarray) -> np.ndarray:
return np.maximum(0, x)
# Weight matrix and bias for a single dense layer
W = np.array([
[ 1.5, -0.5],
[ 0.8, 2.0]
])
b = np.array([[-1.0],
[-0.5]])
# A single input vector in the bottom-right quadrant
x = np.array([[1.0],
[-1.0]])
print("Input vector:")
print(x)
linear_out = (W @ x) + b
print("\nAfter linear transform (W @ x + b):")
print(linear_out)
final_out = relu(linear_out)
print("\nAfter ReLU:")
print(final_out)
Input vector:
[[ 1.]
[-1.]]
After linear transform (W @ x + b):
[[ 1. ]
[-1.7]]
After ReLU:
[[1.]
[0.]]

The input started at (1,βˆ’1)(1, -1). The weight matrix and bias moved it to (1,βˆ’1.7)(1, -1.7). ReLU then clamped the negative yy-component to zero, landing it at (1,0)(1, 0) on the horizontal axis.

That sequence — linear map, then nonlinear clamp — is the atomic operation that every layer in a feed-forward network repeats. Stack enough of them, and the network can carve out arbitrarily complex decision regions in the input space. The training process (gradient descent + backpropagation) is entirely about finding the WW and b⃗\vec{b} values that make those regions align with the labels in your data.

Understanding that the weights are spatial instructions, not lookup values, is what makes the rest of the math fall into place: why initialization matters (you’re setting the initial geometry), why batch normalization helps (it reconditions the transformed space between layers), and why very deep networks without residual connections struggle (the effective transformation accumulates numerical instability across many matrix products).

V

Author

Varkin Academy

Tags

#artificial intelligence #machine learning #deep learning #python