6  Matrix Multiplication as Composition

The itch

A single matrix moves space exactly one time. We ended the last chapter by noting that a neural network does not just move space once. It moves the data repeatedly, using matrix after matrix, handing the output of one step directly to the next. The natural question is what happens when we chain two transformations together. We apply one matrix, and then we apply a second matrix to the result. This creates two back to back moves.

We could always do this the slow way. We could take a vector, apply the first matrix, take that answer, apply the second matrix, and finally read where it ends up. That works perfectly, but it forces us to walk through the entire two-step process every single time. It would be far more efficient if the pair of transformations acted as a single transformation. We want one matrix that achieves in a single move what previously took two. This would allow an entire stack of layers to collapse into a single operation we can easily understand.

The combination of two matrices is always just another matrix. Finding this combined matrix is the exact definition of matrix multiplication. It is not a strange rule you need to memorize. It is simply the answer to a very practical question. If you perform one transformation and then follow it with another, what single transformation did you actually complete? That final combined transformation is the product of the two matrices. This chapter explains why this process works the way it does.

The picture

We already have everything we need to find the combined transformation. We know the single fact that completely defines any transformation, which is where the basis vectors land. A combined transformation is still just a transformation. This means it is entirely fixed by where it sends the two basis vectors. We do not need to worry about the infinite number of vectors in the flat plane. We only need to track two of them.

Let us take the first basis vector \([1, 0]\) and push it through both moves. First, we apply the initial transformation and watch where the vector goes. Then, we take that new result and apply the second transformation to it. Wherever the vector finally ends up is where the combined transformation sends \([1, 0]\). We then do the exact same thing for \([0, 1]\). We push it through the first move, follow with the second move, and record where it lands. We now have both final landing spots for the combined transformation. Based on the rule from the previous chapter, this completely defines the new transformation. When we write those two landing spots as vertical columns, we create the product matrix.

Figure 6.1: To compose two transformations, send each basis vector through the first, then the second. The two final landing spots, as columns, are the combined transformation.

This is the complete definition of matrix multiplication. Multiplying two matrices simply asks where the basis vectors end up after both transformations happen in sequence. You then record those final landing spots as the columns of a brand new matrix. Every mechanical rule people memorize for this operation is just the basic arithmetic of tracking the basis vectors through both steps.

The order of the operations is strictly locked in and cannot be swapped. We warned about this in the last chapter. Doing one transformation first and the other second creates a completely different journey than reversing the order. The two different journeys will naturally end in different locations. This means the two orders create completely different combined transformations and different product matrices. When we write the product of two matrices, the written order records which transformation happens first. Getting this order backward will compute the wrong journey entirely.

The math, built up

We can now translate this physical tracking process into arithmetic and watch the standard rule build itself. We will call the first transformation \(B\) and the second transformation \(A\). This means we perform \(B\) first, and then we perform \(A\). We want to find the combined matrix, and we will build it column by column. The first column is where \([1, 0]\) ends up after both moves. The second column is where \([0, 1]\) ends up.

Let us track where \([1, 0]\) goes. First, transformation \(B\) acts on it. Applying any matrix to \([1, 0]\) simply extracts the first column of that matrix. This happens because \([1, 0]\) asks for exactly one copy of the first column and zero copies of the second. So after applying \(B\), our basis vector lands perfectly on the first column of \(B\). Next, transformation \(A\) acts on that new location. Applying \(A\) to a vector is the exact operation we built in the last chapter. We just combine the columns of \(A\) using the numbers inside the vector. This means the first column of our final combined matrix is simply \(A\) applied directly to the first column of \(B\). The story is exactly the same for \([0, 1]\). It moves to the second column of \(B\), and then \(A\) acts on that result.

The combined matrix is simply \(A\) applied to each individual column of \(B\), one column at a time. This provides the entire rule written in a meaningful way:

\[AB = \big[\; A(\text{first column of } B) \;\;\; A(\text{second column of } B) \;\big]\]

The complex formula people usually memorize is just the arithmetic of these simple applications spelled out. We can make this concrete with an example. We will use the following matrices:

\[A = \begin{bmatrix} 0 & -1 \\ 1 & 0 \end{bmatrix}, \qquad B = \begin{bmatrix} 1 & 1 \\ 0 & 1 \end{bmatrix}\]

In this setup, \(A\) represents a quarter-turn rotation and \(B\) represents the shearing motion from earlier. We will perform \(B\) first, followed by \(A\). We take the first column of \(B\), which is \([1, 0]\). We apply \(A\) to it by combining the columns of \(A\). This calculates as \(1\cdot[0,1] + 0\cdot[-1,0] = [0, 1]\). Next, we take the second column of \(B\), which is \([1, 1]\). We apply \(A\) to this column, giving \(1\cdot[0,1] + 1\cdot[-1,0] = [-1, 1]\). These two final results become the columns of our new product matrix:

\[AB = \begin{bmatrix} 0 & -1 \\ 1 & 1 \end{bmatrix}\]

We can compare this to the traditional rule taught in textbooks. The standard rule states that each entry of the product is a row of \(A\) dotted with a column of \(B\). We can check the top-left entry to verify this. The first row of \(A\) dotted with the first column of \(B\) gives \([0, -1]\cdot[1, 0] = 0\). The answers match perfectly. This row-by-column recipe is not a completely separate concept you need to learn. It is exactly what happens when you apply \(A\) to each column of \(B\) step by step. As we saw in the last chapter, applying a matrix naturally produces each output entry through a dot product. The dreaded textbook formula is merely our geometric column tracking written out one specific number at a time.

Tracing the columns makes one important bookkeeping rule very obvious. For the combination to work at all, matrix \(A\) must be fully capable of acting on the columns of matrix \(B\). A column of \(B\) exists in the specific space that \(B\) outputs into. Matrix \(A\) must be designed to accept vectors from that exact same space. If these spaces do not match, the combined journey is physically impossible, and the product simply does not exist. This explains why two matrices can only be multiplied when the width of the first matches the height of the second. It is not a random rule about grid shapes. It is the strict geometric requirement that the output of one transformation must be a valid input for the next.

Build it yourself

We calculate the product in Python using the @ symbol. This is the exact same operator we used to apply a matrix to a vector, but now we place it between two matrices. Since the core idea of this chapter is tracking columns, we will build the process manually first to ensure the built-in operator behaves as expected.

Here are the quarter-turn and shear matrices from the previous section:

import numpy as np

A = np.array([[0, -1],
              [1,  0]])     # quarter turn

B = np.array([[1, 1],
              [0, 1]])      # shear

We want to perform \(B\) first and then \(A\). This means we need to apply \(A\) to each individual column of \(B\). We extract the columns of \(B\) and send them through \(A\) one by one:

col1 = A @ B[:, 0]     # A applied to B's first column
col2 = A @ B[:, 1]     # A applied to B's second column

combined = np.column_stack([col1, col2])
print(combined)
[[ 0 -1]
 [ 1  1]]

These two final landing spots, stacked together as vertical columns, create the combined matrix. Now we can test the built-in product operator:

print(A @ B)
[[ 0 -1]
 [ 1  1]]

The program outputs the exact same matrix. The command A @ B simply performs our manual steps automatically. It applies \(A\) to each column of \(B\) and collects the final results into columns. We can compare them directly to prove they are identical:

print(np.array_equal(combined, A @ B))
True

The system returns True. The built-in operator and our manual geometric tracking are doing the exact same mathematical work.

We can now observe the strict order dependence directly. We will perform the two transformations in the reverse order. We do \(A\) first and then \(B\), which is written as the product \(B @ A\). Then we compare the results:

print(A @ B)
print(B @ A)
[[ 0 -1]
 [ 1  1]]
[[ 1 -1]
 [ 1  0]]

The outputs reveal two entirely different matrices. Performing the shear before the quarter-turn creates a completely different transformation than doing the quarter-turn before the shear. The arithmetic proves this clearly. Swapping the order produces a different grid of numbers because you requested a completely different geometric journey. This is the socks and shoes analogy we discussed in the last chapter, which NumPy easily confirms in just two lines of code.

Just like before, none of this logic breaks when the data gets larger. If you multiply a matrix three hundred columns wide by a matrix three hundred rows tall, the @ symbol handles it perfectly. It traces every single basis vector through both transformations. It seamlessly compresses two massive moves into a single matrix, executing the math on one line of code even in spaces we cannot physically visualize.

Where it lives in ML

This entire concept explains how a neural network can be deep. Structurally, a deep network is just a long sequence of geometric transformations. The input data enters the system as a vector. The first matrix moves it to a new location. That result is handed directly to the second matrix, which moves it again. This process continues all the way down through the layers. The word deep simply means combining many transformations back to back. That combination process is exactly what we just learned. Every time a model passes data forward, it is executing this precise chaining operation.

Viewing a network as a series of composed transformations explains why depth is so important. A single matrix can only reshape a space in one basic linear way. However, a long sequence of transformations can carry a data point on a highly complex journey. Each step acts on the output of the previous step, allowing the network to reshape the space in careful stages. The early layers handle the large, coarse adjustments. The deeper layers handle the fine refinements. The combination of all these layers creates one massive transformation that turns raw input into a highly useful output. A deep network is simply a long composition of matrices, and this chapter explains how those puzzle pieces lock together.

There is an incredible practical benefit hiding in this math. When a system needs to apply a fixed sequence of linear transformations to millions of data points, we do not force every point through every individual step. We can combine the entire sequence of steps into one single master matrix ahead of time. We then apply that one master matrix to every data point. The multi-step journey and the compressed one-step journey always produce the exact same final coordinates. The compressed version simply does a fraction of the computing work. Collapsing a chain of matrices into a single block is a standard trick for efficiency. This is only possible because the mathematical product of matrices is always another matrix.

However, this exact ability to collapse is the major limitation we have been circling for several chapters. We can finally state the problem clearly. If every layer in a network was purely a matrix, the entire massive network would simply compress down into one single matrix. All that impressive depth would collapse into one basic linear transformation. A deep network built this way could not solve any problems a single layer could not solve alone. The composition process that allows depth would simultaneously render that depth completely pointless. A thousand linear moves chained together still equal just one linear move.

This geometric limitation is the exact reason a real neural network inserts a small non-linear step between its matrices. That specific step breaks the ability to compress. By introducing a bend between every matrix, the mathematical layers can no longer collapse into a single block. The depth finally becomes meaningful. Each layer adds a unique distortion that the other layers cannot simply undo. The matrices provide the heavy geometric movement. The composition process links them together. The non-linear steps prevent the entire chain from snapping shut. We have promised to explain this bend several times now. When we finally build it in Part 4, you will understand that it exists entirely to prevent this collapse.

Common misunderstandings

The concept of composition is elegant and clear, but the mathematical notation is highly misleading. This creates several common traps you should carefully avoid.

The right-hand matrix acts first. The notation \(AB\) tricks almost everyone at first. Because we read text from left to right, \(AB\) naturally looks like it means matrix \(A\) applies first and \(B\) applies second. It actually means the exact opposite. When you apply the product \(AB\) to a vector, the math looks like \(A(B\mathbf{v})\). The vector physically sits on the far right. This means the matrix closest to it, which is \(B\), interacts with the data first. Matrix \(A\) then acts on the new result. When you read a chain of matrices, you must read the order of action from right to left. If you process them backward, you will compute a completely different journey and output the wrong answer, and the computer will never warn you.

Matrix multiplication has shape limits. You can multiply ordinary numbers in absolutely any combination. Matrices are much stricter because they carry physical shapes. Those internal shapes must fit together. The product \(AB\) is only possible if the output space of \(B\) perfectly matches the input space that \(A\) expects. This is represented by a shared dimension size between the two matrices. If the shapes do not match, there is no combined journey. The mathematical operation simply does not exist. You do not need to memorize complex rules for this. It is a logical consequence of how composition works. The second transformation must be physically capable of receiving what the first transformation produced.

A product matrix deletes its own history. Once you calculate \(AB\), the final result is just one matrix representing one single transformation. It retains absolutely no memory that it was originally built from two separate moves. The two-step history is completely absorbed into the new grid of numbers. This total absorption is why the efficiency trick we discussed earlier actually works. However, this has a secondary effect. If you are only given the final product matrix, you generally cannot figure out which two matrices were combined to create it. Knowing a final sum is twelve does not prove it came from seven plus five. The mathematical composition flows strictly in one direction and cannot be reversed.

Grouping matrices is perfectly safe. We have heavily emphasized that the strict order matters. We know that \(AB\) is usually completely different from \(BA\). That rule remains perfectly true. However, changing how you group the steps is completely safe. When you chain three or more transformations together, the specific groupings do not alter the final result. Doing \(A\) after combining \(B\) and \(C\) produces the exact same outcome as combining \(A\) and \(B\) before applying them to \(C\). You can group them however you like as long as the overall left-to-right reading order stays exactly the same. The sequence is locked, but the grouping is free. This is why mathematicians write long chains of matrices without any brackets. You can collapse the pieces in whatever sequence is most efficient for the computer.

Check your intuition

Try to answer these questions before looking at the solutions. These questions require you to picture the geometric concepts rather than performing heavy arithmetic.

1. \(A\) is a quarter-turn counterclockwise and \(B\) is a stretch that doubles the horizontal axis. In the product \(AB\), which transformation acts on a vector first?

2. You compose a transformation with the identity matrix, the one that does nothing. What is \(AI\), and what is \(IA\)? Does order matter here?

3. A transformation \(R\) rotates the plane by ninety degrees. What is \(R\) composed with itself four times, \(RRRR\), as a transformation? You should be able to answer without multiplying anything.

4. Someone computes \(AB\) and \(BA\) for two matrices and gets the same result both times. Have they necessarily made a mistake?

5. You have a fixed chain of three matrices that you must apply to a million data points. Describe two different orders of operations that give the same answers, and say which does less total work.

1. Matrix \(B\) acts first. In the product \(AB\) applied to a vector, the vector sits on the right side as \(AB\mathbf{v}\). The nearest matrix to the data is \(B\), so it reaches the data first. Matrix \(A\) then acts on the stretched result. Reading the action right to left is the correct habit to build.

2. Both \(AI\) and \(IA\) equal exactly \(A\). Composing any movement with a transformation that does nothing simply leaves the movement unchanged. Doing nothing before a move or doing nothing after a move both leave you with just the basic move. This is a special case where order does not matter. The identity matrix commutes with absolutely everything because it alters nothing.

3. It is the identity matrix, the transformation that leaves everything safely where it started. A quarter-turn repeated four times is one full turn of three hundred and sixty degrees. This brings every vector back to its exact starting location. No math is required for this answer. Four quarter-turns equal one full rotation, and a full rotation is physically identical to doing nothing at all.

4. No, they have not necessarily made a mistake. Order usually matters, but there are exceptions where pairs genuinely commute. For example, two rotations sharing the same center will commute because the total angle is identical regardless of the order. A stretching motion and the identity matrix commute easily. Calculating \(AB = BA\) is unusual but mathematically possible. It simply means those two specific transformations do not interfere with one another.

5. For the first method, you could push each of the million data points through all three matrices sequentially. This requires three matrix applications per point, totaling three million operations. For the second method, you could first compose the three matrices into a single master matrix. This is a small calculation done only once. You then apply that master matrix to the million points. This requires exactly one application per point, totaling one million operations. Both methods provide identical answers because composition preserves the exact journey. However, the second method requires roughly one third of the computing work. This is the efficiency collapse we discussed, which relies entirely on the grouping rule.