Deep learning
A tiny autograd class
A small Value class that remembers how it was made and runs the local rules backward. It is enough to compute every gradient for a scalar expression.
The whole idea of backprop fits in one small class. Each value remembers its parents and a rule for passing gradients back to them. A backward function then visits the values in the right order and runs those rules. This is the same design as Karpathy's micrograd, written from scratch.
Concept
Before you start: the computational graph page explains the forward and backward passes this code implements.
Step 1: the forward pass records the graph
This version supports addition and multiplication. Each result stores its parents and a backward_fn closure that knows how to pass gradients back.
class Value:
def __init__(self, data, parents=()):
self.data, self.grad, self.parents = data, 0.0, parents
self.backward_fn = lambda: None
def __add__(self, other):
out = Value(self.data + other.data, (self, other))
def back():
self.grad += out.grad
other.grad += out.grad
out.backward_fn = back
return out
def __mul__(self, other):
out = Value(self.data * other.data, (self, other))
def back():
self.grad += other.data * out.grad
other.grad += self.data * out.grad
out.backward_fn = back
return out
a, b, c, d = Value(2.0), Value(3.0), Value(4.0), Value(5.0)
L = (a * b + c) * d
print("L =", L.data)Output:
L = 50.0Each back function uses +=. That matters when a value is used more than once, as you will see below.
Step 2: run the rules backward
Add this to the same file. The backward function first orders the graph so that each value appears after its parents. Then it walks that order in reverse, starting from the loss.
def backward(root):
order, seen = [], set()
def visit(v):
if v not in seen:
seen.add(v)
for p in v.parents:
visit(p)
order.append(v)
visit(root)
root.grad = 1.0
for v in reversed(order):
v.backward_fn()
backward(L)
print("dL/da", a.grad, "dL/db", b.grad, "dL/dc", c.grad, "dL/dd", d.grad)Output:
dL/da 15.0 dL/db 10.0 dL/dc 5.0 dL/dd 10.0These match the hand calculation on the backprop overview: ∂L/∂a = d · b = 15, ∂L/∂b = d · a = 10, and so on.
Why the ordering matters
The reverse order guarantees that a node's gradient is complete before its backward_fn runs. If a node feeds two operations, both of them must add their contributions first. Visiting children before parents would pass along an incomplete gradient.
Common mistakes
- Calling
backwardtwice without resetting. Gradients accumulate. Running the example twice givesa.grad = 60, not15. Reset the grads to 0 before each backward pass. - Using
=instead of+=. Fory = x * xwithx = 3, there are two paths fromxtoy. With=, the second path overwrites the first, andx.gradcomes out as 3 instead of 6. - Forgetting that this is scalar-only. Real frameworks do the same work on tensors. The class is for learning the mechanism.
Check yourself
- Why does
visitappend a node after visiting its parents? Parents go into the list before their children. Reversing the list then puts each node before its inputs, so its gradient is complete when it is used. - What happens if you call
backward(L)twice without resetting? The gradients keep adding up, so the numbers grow with each call.
Key takeaways
- Each operation stores its parents and a rule for passing gradients back.
backwardorders the graph, sets the loss gradient to 1, and runs the rules in reverse.- Shared values need
+=so that every path contributes. - Reset gradients before each backward pass, or they keep growing.
Further learning
Real layers work on matrices rather than scalars. The next page gives those formulas: gradients for linear layers and softmax.
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.
Computational graphs and reverse mode
A forward pass records each operation as a node in a graph. The backward pass walks that graph in reverse and applies one local rule per node, so one sweep gives every gradient.
PyTorch fundamentals
Tensors, autograd, modules, data loaders and a clean training loop. The daily-driver API for deep learning. Start here for the map.