Math for AI course · Lesson 20 of 180
Matrix-Matrix Products for AI: Compose Transformations and Batches
Check matrix shapes, compute a small matrix product, and read the same contraction as a composed map or a batched dense-layer calculation.
A dense layer does the same kind of calculation for one input or for many inputs at once. For one input, a matrix-vector product combines a row of weights with a feature vector. For a batch, the rows of an input matrix are combined with every output rule. Matrix-matrix multiplication is the compact notation that makes this reuse possible.
By the end of this lesson, you will be able to check compatible shapes, compute a small matrix product, explain its output dimensions, and interpret the same product as either composed transformations or a batch of matrix-vector products.
Learning outcome: Check compatible shapes, compute a small matrix product, explain output dimensions, and interpret the same product as composed transformations or a batch of matrix-vector products.
Lesson 19: Matrix-vector products as weighted sums treated one input at a time. The official Mathematics for Machine Learning companion develops matrix products in Section 2.2 of the book. The examples, code, visual, and assessments here are original applications of that definition.
The inner dimensions decide whether a product exists
Let
The product is defined because the number of columns in equals the number of rows in . Its shape keeps the two outside dimensions:
The shared dimension tells us how many aligned pairs are multiplied and added for each output entry. The outer dimensions and tell us how many rows and columns the result has. In other words, the output shape is , not and not .
This rule is a contraction over the shared axis. It is not an instruction to
multiply entries in the same positions. That distinction matters in code:
NumPy documents numpy.matmul as the matrix product and uses @ as its
operator shorthand in the ordinary two-dimensional case
(documentation).
A shape-flow simulator keeps the axes visible
The following visual starts with a complete, compatible product. Choose a different preset to see other valid outer shapes, switch between the direct row-by-column reading and the batch reading, or use the native controls to trace one output entry. Every state also has a semantic table below it, so the calculation remains readable without JavaScript, color, or animation.
Left matrix × Right matrix → Output matrix
Row-by-column trace
C11 = 1 × 2 + (-1) × 3 + 2 × 0 = -1
Running total: -1 · complete dot product
| Output entry | Left row | Right column | Dot product |
|---|---|---|---|
| C11 (row 1, column 1) | [1, -1, 2] | [2, 3, 0] | -1 |
| C12 (row 1, column 2) | [1, -1, 2] | [1, -1, 4] | 10 |
| C21 (row 2, column 1) | [2, 0, 1] | [2, 3, 0] | 4 |
| C22 (row 2, column 2) | [2, 0, 1] | [1, -1, 4] | 6 |
| Batch row | Input row (a right-matrix column) | Output row |
|---|---|---|
| 1 | [2, 3, 0] | [-1, 4] |
| 2 | [1, -1, 4] | [10, 6] |
The tables are rendered with the lesson and stay visible without JavaScript. Focused controls, text labels, and the highlighted star provide non-color cues for the interactive state.
The server-rendered first state shows the full product before the optional client-side interaction becomes visible. The slider reveals partial sums, not new mathematics: it only decides how many terms of the same dot product are currently displayed. In batch mode, the rows shown are complete examples, and the table is the permanent fallback for all of them.
A small product reveals four dot products
Consider the original matrices
The shape check comes first:
The two inner dimensions are both , so the product exists. The two outer dimensions predict four output entries, each formed from three scalar multiplications.
The location of each answer is part of the result. For example, is not “the second multiplication” in a flattened list; it is the entry at row , column , and it always pairs row of with column of .
The same product composes transformations in a fixed order
The matrix shapes also describe domains and codomains. Here maps to , while maps to :
Therefore is the single matrix for “apply , then apply .” For
the intermediate vector is
Applying next gives
The matrix product gives the same endpoint without materializing the middle vector:
The order is not decorative. The reverse product is valid here, but it has a different shape and meaning:
maps to , so it cannot be substituted for on the two-coordinate vector . The book’s Section 2.2 also emphasizes that matrix multiplication is generally non-commutative; the domain-and-codomain picture explains why swapping the order changes the calculation (book PDF).
The contraction is a batch of matrix-vector products
Suppose three examples each have three features. Put one example per row:
Use the earlier as a dense-layer weight matrix and call it . It has shape : three input features and two output scores. A row-oriented batch calculation needs the transpose :
The shape calculation is
The first dimension, , is the number of examples. The second dimension, , is the number of output scores per example. Adding a bias vector
to every row gives
NumPy’s broadcasting rules allow a shape bias to align with the last axis of each row (broadcasting documentation). The matrix product itself is still the contraction; the bias addition is a separate elementwise operation.
This row-batch convention is also the convention exposed by
torch.nn.Linear:
with in_features=3 and out_features=2, PyTorch stores a weight of shape
and computes . An input with shape is
therefore returned with shape . The transpose bridges the mathematical
column-vector convention and the implementation convention in which each
sample is a row. For tensors with more batch dimensions,
torch.matmul
keeps the final two axes for the matrix product and broadcasts compatible
leading batch axes.
There is another way to see the same result. The columns of are two three-coordinate inputs for the map . Put those inputs into rows by transposing :
Each row of is one column of , and each row of is the corresponding output of on that input. The identity is therefore the bridge between “columns as separate inputs” and “rows as a batch.”
NumPy makes the contraction testable
The following small program writes the three nested loops explicitly and checks them against NumPy. It also checks the batch identity and keeps the elementwise operator visibly separate. NumPy’s quickstart introduces the same distinction between elementwise operations and matrix products.
import numpy as np
A = np.array([[1, -1, 2], [2, 0, 1]])
B = np.array([[2, 1], [3, -1], [0, 4]])
def matrix_product_by_loops(left, right):
if left.shape[1] != right.shape[0]:
raise ValueError("inner dimensions do not match")
result = np.zeros((left.shape[0], right.shape[1]), dtype=int)
for i in range(left.shape[0]):
for j in range(right.shape[1]):
for ell in range(left.shape[1]):
result[i, j] += left[i, ell] * right[ell, j]
return result
C_by_loops = matrix_product_by_loops(A, B)
C = A @ B
np.testing.assert_array_equal(C_by_loops, C)
np.testing.assert_array_equal(B.T @ A.T, C.T)
print(C)
print("elementwise A * B.T has shape", (A * B.T).shape)
try:
A @ np.ones((2, 2), dtype=int)
except ValueError as error:
print(type(error).__name__, "for incompatible inner dimensions")
The program prints
[[-1 10]
[ 4 6]]
elementwise A * B.T has shape (2, 3)
ValueError for incompatible inner dimensions
The invalid call fails because has three columns while the second
operand has two rows. The elementwise expression A * B.T does not fail here
because both arrays have shape ; it produces
which has no row-by-column summation. NumPy documents multiply as
element-by-element multiplication (reference).
The fact that two operators accept the same operands is not evidence that
they represent the same mathematical operation.
Identity and zero results are useful boundary cases
Two edge cases sharpen the shape rule. First, a compatible identity matrix changes no values:
The identity on the left has to have two rows and columns here because is . In general, for and when the dimensions fit. Identity matrices preserve a valid product; they do not repair an invalid one.
Second, a valid product can be all zeros because terms cancel. Let
Then
The inner dimensions match, so the zero result is a calculation, not a shape error. When debugging a dense layer, check both kinds of evidence: the shape contract tells you whether the operation is legal, while the actual values tell you whether cancellation, a zero weight, or a data issue explains the output.
The reusable habit is short: name the left shape, name the right shape, match
the inner dimensions, keep the outer dimensions, then compute each output
entry from a left row and a right column. That habit scales from the small
worked example to X @ W.T + b, to a composition of learned maps, and to
larger batched tensor operations.
Check your understanding
Question 1
If L has shape 4 × 3 and R has shape 3 × 5, what shape does LR have, and how many scalar multiplications are needed for the full product?
Show the step-by-step solution
Step 1: check the shared axis. The left matrix has 3 columns and the right matrix has 3 rows, so the inner dimensions match.
Step 2: keep the outer axes. The product has shape .
Step 3: count the work. There are output entries. Each one uses a length-3 dot product, so the direct calculation uses scalar multiplications.
Question 2
Compute P Q for P = [[1, 0, −2], [2, 1, 1]] and Q = [[3, 1], [0, −2], [4, 2]].
Show the step-by-step solution
Step 1: predict the shape. The shapes are and , so the result must be .
Step 2: compute row 1. Its entries are and .
Step 3: compute row 2. Its entries are and .
Therefore
Question 3
A dense layer stores W with shape (2, 3), receives X with shape (4, 3), and has bias b with shape (2,). What product and output shape should you use?
Show the step-by-step solution
Step 1: bridge the conventions. Since examples are rows, transpose the weight matrix: has shape .
Step 2: multiply the batch. has shape .
Step 3: add the bias row-wise. The bias has two entries, matching the two output features, so remains . The leading 4 counts examples; it is not part of the learned weight shape.
Question 4
For the lesson's A and B, why does Bᵀ Aᵀ equal (A B)ᵀ, and what does each row of Bᵀ represent?
Show the step-by-step solution
Step 1: transpose the factors in reverse order. Transposing gives .
Step 2: identify the rows. Each row of is one column of , so it is a three-coordinate input for the map .
Step 3: read the outputs. Each corresponding row of is the output for that input under the row-batch convention. Numerically,
Question 5
Using the lesson's A, B, and x = [1, 2]ᵀ, which map acts first in (A B)x, and what is the final vector?
Show the step-by-step solution
Step 1: read the order. The rightmost factor acts first, so compute before applying .
Step 2: compute the intermediate vector.
Step 3: apply the second map.
Thus represents “ first, then ,” and the final vector is .
Question 6
Why is A * Bᵀ not the same operation as A B, even though both expressions are accepted for the lesson's arrays?
Show the step-by-step solution
Step 1: compare shapes. and both have shape .
Step 2: identify the operator. * is elementwise multiplication, so it
multiplies the six aligned pairs and keeps shape . It does not pair a
row with a column and sum over three terms.
Step 3: contrast the matrix product. A @ B uses shapes and
, contracts the shared dimension, and returns . For these
values, ,
whereas .
Question 7
A has shape (2, 3) and D has shape (2, 2). Why does A @ D fail, and is D @ A valid?
Show the step-by-step solution
Step 1: test . The inner dimensions would be 3 and 2. They differ,
so no row of can be paired with a complete column of using the
matrix-product rule; A @ D is invalid.
Step 2: test . The inner dimensions are now 2 and 2, so the product is valid and has outer shape .
Step 3: preserve the order. A reversal can change legality and always changes the map being represented. It is not a general repair to swap factors until a shape happens to fit.
Question 8
For P = [[1, −1], [0, 0]] and Q = [[3, 3], [3, 3]], why can P Q be the zero matrix even though the product is valid? What does I₂ C do?
Show the step-by-step solution
Step 1: check the product. Both matrices are , so the inner dimensions match.
Step 2: explain the zero. Each entry in the first output row is , and the second row contributes zero directly. Thus is zero because of cancellation and zero entries, not because the shapes were incompatible.
Step 3: apply the identity. With and shape , . The identity preserves a compatible product; it does not change the values or fix a mismatch.
Question 9
For torch.nn.Linear(3, 2), a batch has shape (5, 3). State the weight shape, bias shape, output shape, and row-batch formula.
Show the step-by-step solution
Step 1: read the layer parameters. in_features=3 and out_features=2
mean that the learned weight has shape and the bias has shape .
Step 2: transpose for row inputs. A batch of five rows has shape , so multiplying by uses .
Step 3: state the result. The output has shape and the formula is . The five remains the batch dimension; each row receives two output features and the two-entry bias is added to every row.
Sources
- Mathematics for Machine Learning companion website
- Mathematics for Machine Learning book PDF
- NumPy documentation: numpy.matmul
- NumPy documentation: numpy.multiply
- NumPy quickstart: elementwise and matrix products
- NumPy documentation: broadcasting
- PyTorch documentation: torch.nn.Linear
- PyTorch documentation: torch.matmul