Vocabulary for this course

The terms the chapters assume, in plain words. You can open this from any chapter.

activation
A rule applied to each number in a layer that lets the network bend, rather than only scale and add.
autograd
Software that records operations during a forward pass and later computes their gradients automatically.
backpropagation
Working backwards through a network to find how each weight affects the loss, by multiplying local slopes.
batch
A group of examples processed together in one step, averaged into a single update.
epoch
One full pass over the training examples.
forward pass
Running inputs through a network to produce outputs, before any error is measured.
gradient
The direction and steepness in which a value rises fastest; training moves weights the opposite way.
learning rate
How far each update moves the weights; too large overshoots, too small crawls.
loss
One number saying how wrong the current predictions are, which training tries to reduce.
optimiser
The procedure that uses gradients to decide how each weight changes.
overfitting
When a network memorises accidental details of its training examples and does worse on new ones.
regularisation
Any penalty or limit that discourages a network from chasing noise in its training examples. The aim is better performance on new data.
tensor
A nested list of numbers with a shape that says what each axis means.

Deep learning from the ground up

Tensors as nested lists

A tensor is a nested list plus a shape. Every layer is a shape transformation, and most deep learning bugs are shape bugs wearing a numeric costume.

Part 1 of 10

Intermediate14 min readBack to Courses

A list with a contract

A vector is a list of numbers. A matrix is a list of lists. A batch of colour images is a list of lists of lists of lists. At that depth, the nesting stops being a convenient picture and becomes the data structure. A tensor is the general name for that structure. The numbers are the easy part. The shape is the contract, and it says which axis means what.

The rank is the number of axes. The shape is the length along each one. A batch of 32 RGB images at 224 by 224 pixels is (32, 3, 224, 224) in PyTorch. The same batch is (32, 224, 224, 3) in TensorFlow. The pixels are identical; the promise about axis order is not. Code that ignores the promise still runs. That silence is what makes the bugs expensive.

A scalar has rank zero, a vector rank one, and a matrix rank two. The names matter less than the count. What matters is which axis holds the batch, which axis holds the channels, and whether the last axis is a feature or a class. Write those three facts down before you write a layer.

Axis order is a decision

PyTorch puts channels before height and width. A convolution reads a whole channel at a time, and contiguous channels make that read cheap. TensorFlow and most image libraries put channels last. That is how a camera and a PNG store them. Neither order is wrong. Mixing them is.

The batch axis comes first almost everywhere. One example and a hundred examples then travel through the same code. That convenience has a trap. Indexing tensor[0] removes the batch axis, so a layer that expected a batch now sees a different rank. Keep the axis with tensor[0:1] when you mean a batch of one.

Memory layout is the other half of the story. A contiguous tensor stores its values in row-major order, so the last axis varies fastest. Its strides say how many elements to skip when you move one step along each axis. A transpose usually returns a view with different strides, not a copy. A reshape after a transpose may have to copy, because the old strides cannot describe the new shape. The order of the two operations can then change both the speed and the result.

Shape, strides and a batched product

import numpy as np

x = np.arange(24).reshape(2, 3, 4)      # batch, rows, columns
print(x.shape, x.strides)

y = x.transpose(0, 2, 1)                # batch, columns, rows
print(y.shape)                          # (2, 4, 3)

z = np.einsum("bij,bjk->bik", x, y)
print(z.shape)                          # (2, 3, 3)

Walk the shape

Run the walker, then reshape the flat list to (2, 6) and confirm the last element is still 12. Change the nesting and watch the shape change.

Output
Click "Run" to execute your code

Reshape, transpose, squeeze

A reshape reinterprets the same flat buffer under a new shape. It reads the buffer in row-major order, so it does not move numbers and costs nothing. It will also produce a nonsensical arrangement if the order you had in mind was different. A transpose does move numbers, or at least changes how they are addressed. It is the operation that turns channels-first into channels-last.

Squeeze and unsqueeze are reshapes that add or remove one axis. They are the usual fix for a missing batch axis. They are also the usual cause of one. A model trained on (N, C) receives (C,) and treats the channels as a batch. Print the shape at every boundary, and this class of bug disappears.

Indexing follows the same logic. A slice keeps the axis, an integer index removes it, and a boolean mask flattens the result. tensor[:, 0] selects the first column of every row and drops the column axis. tensor[:, 0:1] keeps it. The difference is one character and one rank. The layer downstream will notice.

Write the expected shape in a comment above every layer and assert it at runtime. A shape assertion costs one line and turns a silent broadcast into a stack trace at the line that caused it.

Broadcasting is a shortcut with rules

When two shapes meet, NumPy and every framework compare their axes from the right. Two axes are compatible if they are equal, or if one of them is 1. A size-1 axis is stretched to match the other side. A (2, 3) matrix plus a (3,) vector adds the vector to every row. A (2, 1) column plus a (1, 3) row produces a (2, 3) outer sum.

Broadcasting is why a bias vector needs no loop. It is also why a mistake can pass unnoticed. A (32, 3, 224, 224) batch minus a (3,) mean subtracts the mean from the last axis, which is width, not channels. The operation succeeds and the image turns into noise. Check which axis you meant, then reshape the statistic to (1, 3, 1, 1) so the intent is explicit.

A useful habit is to write the intended shapes beside the expression before running it. If the comment and the error disagree, either the comment is wrong or the code is. Either way, the bug is localised. Broadcasting is a convenience. It is not a substitute for knowing which axis you are on.

Broadcasting by hand

Add the bias to every row with an explicit loop, then with the broadcast rule. Ask for an incompatible shape and read the error the rule produces.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What does the shape (32, 3, 224, 224) describe?