Watch: shape, broadcasting, gradient
Nothing to write here. Here you watch what PyTorch does — with small numbers, so you can check it with a pencil. Every printed value below is real, captured by running the code.
A tensor, its shape and its type
a = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
shape: torch.Size([2, 3]) dtype: torch.float32 device: cpu
Three properties you will see in every error message:
shape—[2, 3]: two rows, three columns;dtype— the number type.float32is the currency of learning: gradients live only on floating-point numbers;device— where the tensor lives.cpuis the processor. On your machine it will stay that way.
One operation, every element
a * 2 + 1 = tensor([[ 3., 5., 7.],
[ 9., 11., 13.]])
Every element multiplied by 2, then 1 added. Not a single for.
When shapes differ: broadcasting
a [2, 3] [[ 1., 2., 3.],
[ 4., 5., 6.]]
row [3] [10., 20., 30.]
a + row = [[11., 22., 33.],
[14., 25., 36.]]
The [3] row was "poured" over both rows of a. The rule: shapes are compared from the right, and an axis that has only 1 (or is missing altogether) stretches to the other tensor's size. Here [2, 3] and [3] agree in the last axis, and row simply has no first one.
Matrix product
a @ w [2, 3] @ [3, 1] -> [2, 1]
w = [[ 0.5],
[ 0.0],
[-0.5]]
a @ w = [[-1.], 1*0.5 + 2*0.0 + 3*(-0.5) = -1
[-1.]] 4*0.5 + 5*0.0 + 6*(-0.5) = -1
Every row of the result is a weighted sum of a row of a: each input multiplied by its weight, everything added up. That is exactly the definition of lesson one's neuron. Keep the shape rule in mind: the inner numbers (3 and 3) must match and "disappear", the outer ones (2 and 1) remain.
Gradient: what autograd says
w = torch.tensor(3.0, requires_grad=True)
x = torch.tensor(2.0)
b = torch.tensor(1.0, requires_grad=True)
loss = (w * x + b - 10.0) ** 2
loss.backward()
loss: 9.0
w.grad: -12.0 b.grad: -6.0
The prediction w*x + b = 3*2 + 1 = 7, but we wanted 10. The error is 7 − 10 = −3, the loss is its square, 9.
A gradient answers "how would the loss change if I increased this number?". Here w.grad = −12: increasing w lowers the loss, and quite steeply. That makes sense: the prediction is too small and w makes it bigger. By hand: 2 · (−3) · x = 2 · (−3) · 2 = −12, and for b — without the factor x: 2 · (−3) = −6.
Autograd does not know "formulas". It follows every operation that produced loss, and when you ask for .backward() it walks back and builds the gradients. That is why requires_grad=True means "keep an eye on this number".
A picture is a tensor too
The FashionMNIST file holds a [60000, 28, 28] tensor of type uint8 — whole numbers from 0 to 255. Part of one row of the first picture (an ankle boot, label 9), columns 10–17:
in the file (uint8): [0, 0, 237, 226, 217, 223, 222, 219]
after dividing by 255: [0.0000, 0.0000, 0.9294, 0.8863, 0.8510, 0.8745, 0.8706, 0.8588]
A neuron takes not a table but a row: [28, 28] becomes [784], row after row. The answers to the first questions: 784 numbers, and a stack of 64 pictures has shape [64, 28, 28] (or [64, 784] when each is flattened).
Three observations
1. Before doing anything, look at the shape. Matrix product, broadcasting, flattening — all of them are just shape arithmetic. If the shape equation does not balance, PyTorch tells you exactly so (you will see the messages in step 6).
2. A gradient has a sign and a size. −12 says "increase w, and increase it hard"; −6 — "increase b, but half as much". In lesson 4 a number appears that turns all these "directions and sizes" into one correction. For now it is enough to understand that a gradient is advice, not an order.
3. Everything here is numbers and shapes. Picture, weights, loss, gradient — one and the same kind of thing. That is why the rest of the course fits into a few dozen lines.
What next
In the next step you write two of these pieces as Python: the gradient and the flattening of a picture. You have already seen both at work.