Deep learning
PyTorch autograd
Autograd records the operations you run and computes gradients with backward(). It is the backprop algorithm, done for you.
To train a network you need the gradient of the loss with respect to every weight. Autograd is PyTorch's engine for that. You write the forward computation, and PyTorch records each operation. Calling backward() then applies the chain rule back through the record. It is the backpropagation algorithm, run for you.
Concept
A tensor with requires_grad=True is tracked. After backward(), its .grad field holds the gradient of the final scalar with respect to that tensor. Model parameters have this set by default.
Walking through a tiny graph
Let w hold two weights and x one input row. The output is a scalar:
import torch
w = torch.tensor([[1.0], [2.0]], requires_grad=True)
x = torch.tensor([[3.0, 4.0]])
y = (x @ w).sum() # 3 * 1 + 4 * 2 = 11
y.backward()
print(y.item())
print(w.grad) # dy/dw = x transposedOutput:
11.0
tensor([[3.],
[4.]])Trace it by hand. y = 3·w₁ + 4·w₂, so dy/dw₁ = 3 and dy/dw₂ = 4. Those are the numbers in w.grad. Autograd did the same work for a network with 200,000 weights.
Turning tracking off
Tracking costs memory, so turn it off when you do not need gradients:
torch.no_grad()is a block where nothing is tracked. Use it for evaluation and manual weight updates.tensor.detach()returns a tensor with the same values and no history. Use it when you only want to log a value.torch.inference_mode()is a stricter, faster version ofno_gradfor pure inference.
with torch.no_grad():
z = w * 2
print(z.requires_grad) # False
d = (w * 2).detach()
print(d.requires_grad) # FalseTwo rules that catch people
backward() on a non-scalar needs an extra gradient= argument. Reduce to a scalar loss first, which is what training does anyway.
Gradients accumulate. Each backward() adds to .grad rather than replacing it. Two passes without zeroing give double the gradient:
w = torch.ones(2, requires_grad=True)
(w * 3).sum().backward()
(w * 3).sum().backward()
print(w.grad)Output:
tensor([6., 6.])Each pass contributed 3. This is why training loops call zero_grad() before every step.
Warning
In-place changes to tensors that autograd still needs, such as x += 1 in the middle of a forward pass, can break the backward pass. Prefer out-of-place operations inside the model.
Common mistakes
- Forgetting to zero gradients. Each step adds to the last one, so updates grow wrong.
- Detaching by accident. A
.detach()in the middle of the model cuts the path, and no gradient reaches the earlier layers. - Leaving tracking on in evaluation. It wastes memory. Wrap validation in
inference_mode().
Check yourself
- After two backward passes without zeroing, what is
w.gradcompared with one pass? Answer: the sum of both gradients, so it is doubled when both passes are identical. - Why call
detach()before logging a loss? Answer: it removes the autograd history, so logging does not keep the graph alive in memory.
Key takeaways
- Autograd records operations on tensors with
requires_grad=Trueand computes gradients withbackward(). .gradis the gradient of the scalar loss with respect to each tracked tensor.- Use
no_grad,detachorinference_modewhen you do not need gradients. - Gradients accumulate, so zero them every step.
Further learning
After the micrograd video, the same ideas appear in real PyTorch:
Next, see how weights and data are organized in modules and data.
Related
Backpropagation from scratch
How training computes the gradient of the loss for every weight in one backward pass. Covers computational graphs, reverse-mode autodiff and the bugs that break training.
PyTorch tensors
Tensors carry a shape, a dtype and a device. Views, broadcasting and strides explain most of the surprises you will hit.
The PyTorch training loop
The canonical loop repeats zero_grad, forward, loss, backward and step. The order matters, and so does switching between train and eval mode.