TAI BUI
Back to curriculum
BuildPython, Julia

Vectors, Matrices & Operations

A neural network layer is a matrix multiply, a bias add, and a nonlinearity, stacked until it's deep enough to call a model.

TIME: ~60 minutesPREREQUISITES: Phase 1, Lesson 01 (Linear Algebra Intuition)

Learning Objectives

  • Build a Matrix class with element-wise operations, matrix multiplication, transpose, determinant, and inverse
  • Distinguish element-wise multiplication from matrix multiplication and explain when each applies
  • Implement a single dense neural network layer (`relu(W @ x + b)`) using only the from-scratch Matrix class
  • Explain broadcasting rules and how bias addition works in neural network frameworks

The Problem

You want to build a neural network. You read the code and see this:

output = activation(weights @ input + bias)

That @ is matrix multiplication. The weights are a matrix. The input is a vector. If you do not know what those operations do, this line is magic. If you do know, it is the entire forward pass of a layer in three operations.

Every image your model processes is a matrix of pixel values. Every word embedding is a vector. Every layer of every neural network is a matrix transformation. You cannot build AI systems without being fluent in matrix operations the same way you cannot write code without understanding variables.

This lesson builds that fluency from scratch.

Warm-up0/3
1. In ordinary arithmetic, is `3 * 5` the same as `5 * 3`?Warm-up
Two boxes, 3 times 5 equals question mark and 5 times 3 equals question mark, with a note asking whether swapping the order changes the answer.
2. Which number can you multiply anything by and get back exactly what you started with?Warm-up
Four boxes: 7 times 10, 7 times 0, 7 times -1, and 7 times 1, each equal to a question mark, asking which gives back exactly 7.
3. What is the dot product of the vectors [1, 2, 3] and [4, 5, 6]?Warm-up
Vectors a = [1, 2, 3] and b = [4, 5, 6] shown as number rows, with a question mark and the label dot product equals question mark.

The Concept

Vectors: a quick callback

Lesson 01 covered what a vector means: a list of numbers that doubles as a point or direction in space. Nothing about that changes here. The notation carries forward unchanged:

v = [3, 4]        -- a 2D vector
w = [1, 0, -2]    -- a 3D vector

This lesson picks up where that left off: what happens when you group vectors into a grid, and the rules for combining those grids.

Matrices: what they look like in code

A matrix is a 2D grid of numbers, arranged in rows and columns. An m x n matrix has m rows and n columns.

A = | 1  2  3 |     -- 2x3 matrix (2 rows, 3 columns)
    | 4  5  6 |

In code, a matrix is just a list of rows, the same way lesson 01 showed a vector as a plain list:

python
A = [[1, 2, 3], [4, 5, 6]]             # plain Python: a list of rows
A = np.array([[1, 2, 3], [4, 5, 6]])   # NumPy -- what you'll actually use
print(A.shape)                         # (2, 3)

Analogy: think of a matrix as a spreadsheet. Each row is one record (one image, one customer, one sentence), and each column is one attribute of that record (one pixel, one feature, one word position). A batch of 32 images, each flattened to 784 pixel values, is a 32x784 matrix: 32 rows of records, 784 columns of attributes.

In AI, matrices are how you store and move whole batches of numbers at once:

  • A batch of embeddings, one row per item, one column per embedding dimension
  • A weight layer, a grid of numbers that reshapes an input vector into an output vector
  • An image, a matrix of pixel intensities, or three matrices stacked, one per color channel

Why shapes matter for matrix multiplication

Matrix multiplication has a strict rule: (m x n) @ (n x p) = (m x p). The inner dimensions have to match.

The reason traces straight back to the dot product from lesson 01. Every entry in the result of A @ B is a dot product between one row of A and one column of B, and a dot product only works if both lists it sums over are the same length. That shared length is the n in the rule above: the number of columns in A has to equal the number of rows in B, because that count is exactly how many terms get multiplied and added for every output cell.

(128 x 784) @ (784 x 1) = (128 x 1)
  weights       input       output

Inner dimensions: 784 = 784  -- valid

Analogy: it's a pair of gears. Two gears only mesh if their teeth line up; a matrix multiply only lines up if the inner dimensions match. Get it wrong and instead of a smooth connection you get a shape error.

python
A = np.zeros((128, 784))
B = np.zeros((10, 1))
A @ B   # ValueError: matmul, shapes (128,784) and (10,1) not aligned

Order matters too, unlike scalar multiplication. A @ B and B @ A don't even need to have compatible shapes, and when they do, they usually give different answers.

If you ever hit a shape mismatch error in PyTorch, this is why. The same rule governs every batched matrix multiply inside attention, and every weight-loading bug where a saved checkpoint doesn't match the model it's being loaded into.

Element-wise vs. matrix multiplication

Element-wise multiplication multiplies matching positions; both matrices must already be the same shape.

| 1  2 |   | 5  6 |   | 5  12 |
| 3  4 | * | 7  8 | = | 21 32 |

Matrix multiplication takes dot products of rows and columns instead. Only the inner dimensions need to match, and the result usually ends up a different shape than either input.

| 1  2 |   | 5  6 |   | 1*5+2*7  1*6+2*8 |   | 19  22 |
| 3  4 | @ | 7  8 | = | 3*5+4*7  3*6+4*8 | = | 43  50 |
python
A * B    # element-wise: same shape in, same shape out
A @ B    # matrix multiply: dot products, shape can change

Both operations take two matrices and hand back one matrix, and in NumPy the two are one character apart, * versus @. That's exactly why this pair trips up beginners more than anything else in this lesson: the syntax barely moves, but the math underneath is completely different.

Analogy: element-wise multiplication is two spreadsheets laid side by side, multiplying cell by cell straight down the line; no cell ever talks to a cell outside its own position. Matrix multiplication is more like a round of interviews: every row of the first matrix sits down with every column of the second, and each interview produces one combined number.

Where each one turns up in production code:

  • Element-wise: gating in LSTMs, dropout masks, applying an activation function to every value in a layer's output
  • Matrix multiply: every dense layer's forward pass, attention score computation, any linear transformation from one space into another

Determinant and inverse

The determinant is a single number computed from a square matrix. It tells you how much the matrix scales area (in 2D) or volume (in 3D), and whether it flips things inside out along the way.

python
np.linalg.det(np.array([[1, 2], [3, 4]]))   # -2.0

A determinant of zero means the transformation crushes at least one dimension flat: a 2D square gets squashed onto a line, or a 3D cube onto a plane. This is the same collapse lesson 01 covered under rank: a rank-deficient matrix loses a dimension, and a zero determinant is the 2D/3D symptom of exactly that.

Two matrices applied to the same unit square: a full-rank matrix keeps a 2D parallelogram, a rank-deficient matrix collapses the square onto a single line

The inverse undoes a matrix's transformation. A @ A_inverse gets you back to the identity matrix, the same way 5 * (1/5) gets you back to 1.

python
np.linalg.inv(np.array([[1, 2], [3, 4]]))

Analogy: think of the inverse as a decoder ring. Whatever scrambling the matrix did to a vector, multiplying by the inverse unscrambles it exactly, but only if the scrambling didn't throw information away in the first place. A zero determinant means it did: two different starting points landed on the same output, so there's no way to look at that output and work out which one you started from. No decoder ring can undo that, which is why inverse_2x2 in the code below checks the determinant first and raises an error if it's zero.

You'll run into this any time you check whether a linear system has a unique solution before solving it, in closed-form linear regression (the normal equations need an invertible matrix), and in ridge regression, which adds a small multiple of the identity matrix specifically to keep a near-singular matrix invertible.

The identity matrix

The identity matrix is the matrix version of the number 1: square, with 1s on the diagonal and 0s everywhere else.

I = | 1  0 |
    | 0  1 |

Multiply any matrix by the identity matrix, in either order, and you get the original matrix back unchanged, the same way multiplying any number by 1 changes nothing.

python
I = np.eye(2)
A = np.array([[3, 1], [0, 2]])
A @ I   # [[3, 1], [0, 2]] -- unchanged

Analogy: it's a matrix that does nothing, on purpose. Useful the same way a do-nothing step is useful anywhere else in a pipeline: a safe default, or a starting point you then modify.

It shows up in weight initialization schemes that start a layer close to identity so it begins as a near-no-op, and in residual connections in ResNets and transformers, which add the input back onto a layer's output (output = layer(x) + x) so information keeps flowing even when the layer hasn't learned anything useful yet.

Broadcasting

Broadcasting is NumPy's rule for combining two arrays of different shapes without raising an error: it stretches the smaller one to match the larger one, never copying any data to do it. You need the rule the moment you add a bias vector to a matrix of outputs, because those two shapes usually don't match: one row of numbers against several rows of outputs.

| 1  2  3 |   +   [10, 20, 30]
| 4  5  6 |

Broadcasting stretches the vector across rows:

| 1  2  3 |   | 10  20  30 |   | 11  22  33 |
| 4  5  6 | + | 10  20  30 | = | 14  25  36 |
python
matrix = np.array([[1, 2, 3], [4, 5, 6]])
bias = np.array([10, 20, 30])
matrix + bias   # bias is stretched across both rows

Analogy: it's like handing every table in a restaurant the same menu insert. You only printed one insert, not one per table, but every table still gets to read it. The rule for when this works: line the shapes up from the right, and each pair of dimensions must either match exactly or one of them must be 1, which is the one that gets stretched.

You'll see it everywhere: adding a bias vector to every row of a layer's output, applying one set of normalization statistics across a whole batch, and combining tensors of different ranks anywhere in a deep learning framework without writing a manual loop.

Every operation, at a glance

OperationWhat it doesNeural network use
AdditionElement-wise combineAdding bias to output
Element-wise multiplyMultiply matching positionsGating, masking, activations
Scalar multiplyScale every elementLearning rate * gradients
Matrix multiplyDot products of rows and columnsLayer forward pass
TransposeFlip rows and columnsBackpropagation
DeterminantSingle number summaryChecking invertibility
InverseUndo a transformationSolving linear systems
IdentityDo-nothing matrixInitialization, residual connections
BroadcastingStretch a smaller shape to fitBias addition, batch operations
Check: The Concept0/31
1. In this lesson's AI usage, what is a vector?Define
The list [4, 2] written as a bracketed column of two numbers, next to the same values plotted as an arrow from the origin to the point (4, 2).
2. In an "m x n matrix", what do m and n count?Define
A matrix A with two rows of three numbers each, labelled shape equals question mark by question mark, with no rows or columns counted out loud.
3. What is a matrix, beyond being a table of numbers?Define
A 3 by 3 grid M next to a question mark and a 3-row column x, with a note asking whether M can do something with x beyond holding numbers.
4. What is matrix multiplication?Define
Two 2 by 2 matrices A and B with an at sign between them and a question mark for the result, with no rows or columns highlighted.
5. What does element-wise multiplication do with two matrices?Define
Two 2 by 2 matrices A and B with an asterisk between them and a question mark for the result, with no positions marked.
6. What does transposing a matrix actually do to it?Define
A 2 by 3 matrix A with the label A transpose equals question mark, nothing swapped yet.
7. What does a determinant of zero tell you about a square matrix?Define
A 2 by 2 matrix with its determinant given as 0, followed by a question mark, and a note asking what a zero determinant tells you.
8. What does the inverse of a matrix do?Define
A 2 by 2 matrix A with the label A to the power of -1 equals question mark.
9. What is the identity matrix?Define
A flow of three boxes: A, then a mystery matrix, then A unchanged, with a note that multiplying by the mystery matrix in either order changes nothing.
10. What does broadcasting do?Define
A 2 by 3 matrix plus a single row of three numbers, with a question mark for the result; the shapes do not match.
11. If `A @ B` is defined, is `B @ A` guaranteed to be defined and equal to `A @ B`?Explain
A flow showing A shaped 2 by 3 and B shaped 3 by 4 combining into A @ B shaped 2 by 4, with a separate question asking about B @ A and a question mark, not resolved.
12. Why must the inner dimensions match in `(m x n) @ (n x p)`?Explain
A flow of a 3 by 2 shape and a 2 by 4 shape combining into a 3 by 4 shape, with a note asking why the middle number on each side must match.
13. Why does a dense layer's forward pass use matrix multiplication instead of element-wise multiplication?Explain
A flow from inputs to a box marked asterisk or at sign to outputs, with a note asking which operation a dense layer should use.
14. Why does a weight matrix of shape (128, 784) turn a 784-element input into a 128-element output?Explain
A flow from a weight matrix shaped 128 by 784 and an input vector of 784 entries to an output marked with an unknown number of entries.
15. Why is broadcasting needed when you add a bias to a layer's output?Explain
A flow from a single bias row and a multi-row output to a sum marked with a question mark.
16. What makes two shapes broadcast-compatible?Explain
Two shapes, (8, 1, 3) and (1, 4, 3), with a note asking whether each aligned position matches, stretches, or fails.
17. A matrix has determinant 0. Which idea from the prerequisite lesson describes what is happening geometrically?Explain
A 2 by 2 matrix with determinant given as 0, followed by a question mark, and a note asking what this says geometrically.
18. Ridge regression adds a small multiple of the identity matrix to a near-singular matrix before inverting it. Why?Explain
A near-singular matrix with determinant 0.00010, and a note about adding a small multiple of the identity matrix to it, with the effect left as a question mark.
19. Why do residual connections add the input back onto a layer's output (`output = layer(x) + x`)?Explain
The formula output equals layer of x plus x, with layer(x) labelled as barely trained yet, x labelled as the input, and a question mark for the result.
20. You add a NumPy array of shape (4, 5) to one of shape (5,). What happens?Apply
Two shapes, (4, 5) and (5,), combined with addition into a sum marked with a question mark.
21. You have a dataset of 1000 samples, each with 20 features. Following the spreadsheet picture from this lesson, which shape would you store it as?Apply
A table sketch of 1000 samples each with 20 feature values, noted as 1000 samples and 20 features each, with a question mark for how to write the shape.
22. For A = [[1, 2], [3, 4]] and B = [[5, 6], [7, 8]], what is entry (row 2, column 1) of `A @ B`?Apply
A = [[1, 2], [3, 4]] with row 2 highlighted, B = [[5, 6], [7, 8]] with column 1 highlighted, and a question mark for the resulting entry.
23. What is the shape of the transpose of [[1, 2, 3], [4, 5, 6]], and what is its first row?Apply
A 2 by 3 matrix with the label A transpose equals question mark by question mark.
24. A 128x784 weight matrix is multiplied by an input vector, and a shape-mismatch error is raised. What is the most likely cause?Analyze
A weight matrix shaped 128 by 784 multiplied by an input of unstated shape raises a shape-mismatch error, with a note asking for the most likely cause.
25. You multiply a matrix A by the identity matrix I and the result differs from A. What does that tell you?Analyze
A multiplied by the identity matrix I gives a result that does not equal A, with a note asking what that implies.
26. Two square 2x2 matrices are combined with `A * B`, and the result is [[5, 12], [21, 32]] instead of the expected [[19, 22], [43, 50]]. What is the bug?Analyze
A times B using the asterisk operator gave [[5, 12], [21, 32]], but [[19, 22], [43, 50]] was expected, with a question mark for the cause.
27. A layer output of shape (1, 10) plus a bias of shape (10, 1) raises no error, yet the model gives wrong numbers. Why?Analyze
A shape (1, 10) added to a shape (10, 1), raising no error, but the note says the resulting numbers come out wrong.
28. Two different input vectors produce exactly the same output when passed through a matrix M. What does this imply?Analyze
A matrix M with two different vectors v1 and v2, both producing the exact same output through M, which is marked as a question mark.
29. A teammate computes a dense layer as `weights * x` with weights (128, 784) and x (784,), hoping for 128 outputs. What goes wrong?Analyze
A weight matrix shaped 128 by 784 combined with an input of 784 entries using the asterisk operator, hoping for 128 outputs, with the result marked as a question mark.
30. You want a dense layer with 128 units to process a batch of 32 inputs of 784 features each, stored one row per sample. Which setup gives a (32, 128) output?Design
A batch X shaped 32 by 784 feeding a layer with weights and bias of unknown shape, ending in an output shaped 32 by 128.
31. You want a new layer to start as a near no-op by initializing its weights close to the identity matrix. What does this require?Design
A flow from an input of unstated size through weights close to the identity matrix to an output of unstated size, with a note asking what must be true of the two sizes.

Build It

Step 1: Vector class

Start with a minimal Vector class. It needs exactly the operations the concept section leaned on: addition, subtraction, scalar multiplication, the dot product, and magnitude.

python
class Vector:
    def __init__(self, data):
        self.data = list(data)
        self.size = len(self.data)
 
    def __repr__(self):
        return f"Vector({self.data})"
 
    def __add__(self, other):
        return Vector([a + b for a, b in zip(self.data, other.data)])
 
    def __sub__(self, other):
        return Vector([a - b for a, b in zip(self.data, other.data)])
 
    def __mul__(self, scalar):
        return Vector([x * scalar for x in self.data])
 
    def dot(self, other):
        return sum(a * b for a, b in zip(self.data, other.data))
 
    def magnitude(self):
        return sum(x ** 2 for x in self.data) ** 0.5

Step 2: Matrix class with core operations

A Matrix wraps a list of rows and implements every operation from the table above: element-wise ops, matrix multiply, transpose, determinant, and inverse.

python
class Matrix:
    def __init__(self, data):
        self.data = [list(row) for row in data]
        self.rows = len(self.data)
        self.cols = len(self.data[0])
        self.shape = (self.rows, self.cols)
 
    def __repr__(self):
        rows_str = "\n  ".join(str(row) for row in self.data)
        return f"Matrix({self.shape}):\n  {rows_str}"
 
    def __add__(self, other):
        return Matrix([
            [self.data[i][j] + other.data[i][j] for j in range(self.cols)]
            for i in range(self.rows)
        ])
 
    def __sub__(self, other):
        return Matrix([
            [self.data[i][j] - other.data[i][j] for j in range(self.cols)]
            for i in range(self.rows)
        ])
 
    def scalar_multiply(self, scalar):
        return Matrix([
            [self.data[i][j] * scalar for j in range(self.cols)]
            for i in range(self.rows)
        ])
 
    def element_wise_multiply(self, other):
        return Matrix([
            [self.data[i][j] * other.data[i][j] for j in range(self.cols)]
            for i in range(self.rows)
        ])
 
    def matmul(self, other):
        return Matrix([
            [
                sum(self.data[i][k] * other.data[k][j] for k in range(self.cols))
                for j in range(other.cols)
            ]
            for i in range(self.rows)
        ])
 
    def transpose(self):
        return Matrix([
            [self.data[j][i] for j in range(self.rows)]
            for i in range(self.cols)
        ])
 
    def determinant(self):
        if self.shape == (1, 1):
            return self.data[0][0]
        if self.shape == (2, 2):
            return self.data[0][0] * self.data[1][1] - self.data[0][1] * self.data[1][0]
        det = 0
        for j in range(self.cols):
            minor = Matrix([
                [self.data[i][k] for k in range(self.cols) if k != j]
                for i in range(1, self.rows)
            ])
            det += ((-1) ** j) * self.data[0][j] * minor.determinant()
        return det
 
    def inverse_2x2(self):
        det = self.determinant()
        if det == 0:
            raise ValueError("Matrix is singular, no inverse exists")
        return Matrix([
            [self.data[1][1] / det, -self.data[0][1] / det],
            [-self.data[1][0] / det, self.data[0][0] / det]
        ])
 
    @staticmethod
    def identity(n):
        return Matrix([
            [1 if i == j else 0 for j in range(n)]
            for i in range(n)
        ])

Step 3: See it work

Run every operation once and check it against the hand-worked examples above.

python
A = Matrix([[1, 2], [3, 4]])
B = Matrix([[5, 6], [7, 8]])
 
print("A + B =", (A + B).data)
print("A @ B =", A.matmul(B).data)
print("A^T =", A.transpose().data)
print("det(A) =", A.determinant())
print("A^-1 =", A.inverse_2x2().data)
 
I = Matrix.identity(2)
print("A @ A^-1 =", A.matmul(A.inverse_2x2()).data)

Step 4: Connect to neural networks

A dense neural network layer is nothing more than relu(W @ x + b): one matrix multiply, one broadcasted addition, one element-wise nonlinearity. Build it with only the class above, no NumPy, so none of it is hidden magic:

python
import random
 
inputs = Matrix([[0.5], [0.8], [0.2]])
weights = Matrix([
    [random.uniform(-1, 1) for _ in range(3)]
    for _ in range(2)
])
bias = Matrix([[0.1], [0.1]])
 
def relu_matrix(m):
    return Matrix([[max(0, val) for val in row] for row in m.data])
 
pre_activation = weights.matmul(inputs) + bias
output = relu_matrix(pre_activation)
 
print(f"Input shape: {inputs.shape}")
print(f"Weight shape: {weights.shape}")
print(f"Output shape: {output.shape}")
print(f"Output: {output.data}")

This is a single dense layer: output = relu(W @ x + b). Every dense layer in every neural network does exactly this.

Check: Build It0/9
1. Why does `inverse_2x2` check the determinant before building the result?Explain
The start of the lesson's own inverse_2x2 method: it computes the determinant, then checks if it is close to zero before building anything.
2. You are building `output = relu(W @ x + b)` where W has shape (128, 784) and x has shape (784,). What shape must b have?Apply
The formula output equals relu of W at x plus b, with x shaped 784 and W shaped 128 by 784, and b's shape marked with a question mark.
3. After computing `A_inv = A.inverse_2x2()` for a matrix A, what should `A.matmul(A_inv)` produce?Apply
Matrix A and its already-computed inverse A_inv shown side by side, with A.matmul(A_inv) marked as a question mark.
4. What does `Matrix([[1, 2], [3, 4]]).determinant()` return?Apply
The matrix [[1, 2], [3, 4]] with the label det(A) equals question mark.
5. With `weights` of shape (2, 3) and `inputs` of shape (3, 1), what is the shape of `weights.matmul(inputs) + bias` when `bias` is (2, 1)?Apply
A flow of weights shaped 2 by 3, inputs shaped 3 by 1 and a bias shaped 2 by 1, ending in a question mark for the final shape.
6. If `weights.matmul(inputs) + bias` gives [[-0.4], [0.5]], what is `relu_matrix(pre_activation).data`?Apply
The column [[-0.4], [0.5]] labelled pre_activation, with relu_matrix of it marked as a question mark.
7. What does `Matrix([[2, 1], [1, 1]]).inverse_2x2().data` return?Apply
The matrix [[2, 1], [1, 1]] with the label inverse equals question mark.
8. What happens when you call `Matrix([[1, 2], [2, 4]]).inverse_2x2()`?Apply
The matrix [[1, 2], [2, 4]] with the label dot inverse_2x2 open parenthesis close parenthesis equals question mark.
9. You need a two-layer network with 3 inputs, 4 hidden units and 2 outputs, using `W @ x` for each layer. Which weight shapes fit?Design
A flow of x with 3 entries, W1 of unknown shape, h with 4 entries, W2 of unknown shape, and y with 2 entries.

Use It

NumPy does everything above in fewer lines and orders of magnitude faster.

python
import numpy as np
 
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
 
print("A + B =\n", A + B)
print("A * B (element-wise) =\n", A * B)
print("A @ B (matrix multiply) =\n", A @ B)
print("A^T =\n", A.T)
print("det(A) =", np.linalg.det(A))
print("A^-1 =\n", np.linalg.inv(A))
print("I =\n", np.eye(2))
 
inputs = np.random.randn(3, 1)
weights = np.random.randn(2, 3)
bias = np.array([[0.1], [0.1]])
output = np.maximum(0, weights @ inputs + bias)
 
print(f"\nNeural network layer: {weights.shape} @ {inputs.shape} = {output.shape}")
print(f"Output:\n{output}")

The @ operator in Python calls __matmul__. NumPy implements it with optimized BLAS (Basic Linear Algebra Subprograms) routines written in C and Fortran. Same math, 100x faster.

Broadcasting in NumPy:

python
matrix = np.array([[1, 2, 3], [4, 5, 6]])
bias = np.array([10, 20, 30])
print(matrix + bias)

NumPy automatically broadcasts the 1D bias across both rows. This is how bias addition works in every neural network framework.

Check: Use It0/1
1. What is the second row of `np.array([[1, 2], [3, 4]]) + np.array([10, 20])`?Apply
The matrix [[1, 2], [3, 4]] with its second row highlighted, plus the row [10, 20], with the second row of the result marked as a question mark.

Ship It

This lesson produces a prompt for teaching matrix operations through geometric intuition. See outputs/prompt-matrix-operations.md.

The Matrix class you built here becomes the foundation for the mini neural network framework in Phase 3, Lesson 10.

Author's Note

The bug that actually costs you hours here is rarely the math. It's almost always the shapes. I've burned more debugging time on a silently wrong array shape, (10, 1) instead of (1, 10), or a weight matrix transposed one call too early, than on anything conceptually hard in this lesson. NumPy and PyTorch will often broadcast a mismatch into something that runs and returns a wrong number instead of raising an error you'd actually notice. When a model's output looks off for no obvious reason, print every shape in the pipeline before you touch the math. That habit alone has saved me more debugging time than any amount of cleverness.

Exercises

  1. Verify the inverse. Multiply A @ A.inverse_2x2() and confirm you get the identity matrix. Try it with three different 2x2 matrices. What happens when the determinant is zero?

  2. Implement 3x3 inverse. Extend the Matrix class to compute inverses for 3x3 matrices using the adjugate method. Test it against NumPy's np.linalg.inv.

  3. Build a two-layer network. Using only your Matrix class (no NumPy), create a two-layer neural network: input (3) -> hidden (4) -> output (2). Initialize random weights, run a forward pass, and verify all shapes are correct.

Key Terms

TermWhat people sayWhat it actually means
Vector"An arrow"An ordered list of numbers. In AI: a point in high-dimensional space.
Matrix"A table of numbers"A linear transformation. It maps vectors from one space to another.
Matrix multiply"Just multiply the numbers"Dot products between every row of the first matrix and every column of the second. Order matters.
Transpose"Flip it"Swap rows and columns. Turns an m x n matrix into n x m. Critical in backpropagation.
Determinant"Some number from the matrix"Measures how much the matrix scales area (2D) or volume (3D). Zero means the transformation crushes a dimension.
Inverse"Undo the matrix"The matrix that reverses the transformation. Only exists when the determinant is not zero.
Identity matrix"The boring matrix"The matrix equivalent of multiplying by 1. Used in residual connections (ResNets).
Broadcasting"Magic shape fixing"Stretching a smaller array to match a larger one by repeating along missing dimensions.
Element-wise"Regular multiplication"Multiply matching positions. Both arrays must have the same shape (or be broadcastable).

Further Reading

Code

Ships With