Six questions stall almost every PyTorch beginner: what is a tensor, why matrix multiplication, what loss.backward() actually does, how gradient descent "learns", what nn.Module is for, and how a training loop fits together. The 3D model below is gradient descent — the rest of the page maps each line of a real loop onto it.
Try lr = 0.10 (smooth descent), lr = 0.95 (oscillation), lr = 1.20 (divergence — loss explodes). This is exactly why learning rate is the most important hyperparameter.
for xb, yb in dataloader:
pred = model(xb) # forward pass
loss = loss_fn(pred, yb) # scalar error
loss.backward() # compute ∂loss/∂w
optimizer.step() # w -= lr * grad
optimizer.zero_grad() # reset for next batch
The 3D ball is optimizer.step(). The surface height is the loss; the ball's (x, z) position is the two weights (w₁, w₂). Each click of step() runs one update: w ← w − lr·∇L(w). loss.backward() is what computed that arrow — the red gradient vector — via the chain rule, walking backward through the computation graph autograd recorded during the forward pass.
The six answers
What even is a tensor?
[32, 3, 224, 224]. Shapes are the #1 source of beginner bugs — print x.shape constantly.Why matrix multiplication?
y = xWⅤ + b. One matrix multiply applies every neuron's weighted sum to every sample in the batch simultaneously — a [32, 784] @ [784, 128] multiply does 32 × 128 = 4,096 dot products of length 784 (~3.2M multiply-adds) in one GPU-optimized call. Deep learning is fast because GPUs are matrix-multiply machines.What does loss.backward() do?
loss.backward() traverses that graph in reverse, applying the chain rule at each node, and deposits ∂loss/∂w into each parameter's .grad attribute. It does not change any weights — that's the optimizer's job. Gradients accumulate by default, which is why you must call zero_grad() each iteration.How does gradient descent actually learn?
What is nn.Module?
nn.Parameter you assign so model.parameters() can hand them all to the optimizer, (2) defines forward() so calling model(x) runs your computation, and (3) nests — a ResNet is Modules inside Modules inside Modules. It's bookkeeping, not magic.How does the loop fit together?
optimizer.step() nudges every weight downhill by lr × its gradient. zero_grad() wipes the slate. One pass over all batches = one epoch. Training is just this loop until the loss stops improving on data the model has never seen.