Building makemore Part 4: Becoming a Backprop Ninja
Watch on YouTube →
Overview
Andrej Karpathy's "Building makemore Part 4" details the manual implementation of backpropagation for a two-layer neural network, moving beyond PyTorch's autograd. This "Backprop Ninja" exercise covers deriving gradients for operations like cross-entropy loss, batch normalization, linear layers, and embeddings, emphasizing the importance of understanding internals for debugging and optimization. The lecture culminates in a fully manual training loop, demonstrating that complex neural networks can be trained without relying on automatic differentiation frameworks.
Key takeaways
- Implementing backpropagation manually, even for simple networks, demystifies gradient flow and reveals potential issues masked by autograd.
- The derivative of a sum in the forward pass becomes a replication/broadcasting in the backward pass, and vice-versa for replication/broadcasting.
- Deriving gradients for complex operations like batch normalization requires careful application of the chain rule and understanding of vector calculus.
- Simplified analytical gradients for operations like cross-entropy and batch normalization significantly improve computational efficiency compared to breaking them into atomic steps.
- By manually implementing backpropagation, Andrej Karpathy demonstrates that achieving state-of-the-art results is possible without relying on automatic differentiation frameworks, fostering a deeper understanding of neural network mechanics.
Chapters
- Current implementation relies on PyTorch's `loss.backward()` for gradient calculation.
- Manual backpropagation provides deeper understanding, aids debugging, and prevents "leaky abstraction" issues.
- Historical context: manual backprop was standard practice ~10 years ago.
- Backpropagation is not magic; understanding internals is key to avoiding subtle bugs.
- Examples of issues: dead neurons, exploding/vanishing gradients, incorrect gradient clipping.
- Frameworks like PyTorch's autograd can mask fundamental misunderstandings.
- 1990s/2000s: manual backprop was pervasive, even in Matlab.
- Jeff Hinton's 2006 paper on Restricted Boltzmann Machines.
- Karpathy's 2010 Matlab library for RBMs and 2014 Numpy implementation for image/text alignment.
- Reusing the two-layer MLP architecture with batch normalization.
- Removing `loss.backward()` and implementing manual backward pass.
- Introducing a utility function `CNP` for gradient comparison with PyTorch.
- Biases initialized to small random numbers instead of zero to unmask potential gradient errors.
- Including a bias in the first layer despite batch normalization for exercise.
- Retaining gradients for intermediate tensors using `.retain_grad()`.
- Calculating the derivative of the loss with respect to `log_probs`.
- Loss is the negative mean of `log_probs` indexed by correct labels (`yb`).
- Derivative is -1/N for participating elements, 0 for others.
- Applying the chain rule: `d_probs = d_log_probs * d(log)/d(probs)`.
- Local derivative of log is `1/probs`.
- Gradient is boosted for low-probability correct characters.
- Breaking down the softmax normalization step.
- Differentiating through `counts = exp(logits - max_logit)`.
- Gradient calculation involves `counts` and `d_probs`.
- Differentiating through the inverse of the sum of counts (`1 / count_sum`).
- Local derivative is `- (count_sum)^-2`.
- Gradient is `d_count_sum_inv * local_derivative`.
- Differentiating through the sum of counts (`count.sum(dim=1, keepdim=True)`).
- Summation in forward pass becomes broadcasting/replication in backward pass.
- Gradient `d_count_sum` is `d_count_sum_inv` replicated across columns.
- Differentiating `counts = exp(logits - max_logit)` with respect to `counts`.
- Local derivative is `exp(logits - max_logit)` (i.e., `counts`).
- Gradient `d_counts` is `d_count_sum * counts` (with broadcasting).
- Differentiating `normal_logits = counts` (element-wise exponential).
- Local derivative of `exp(x)` is `exp(x)`.
- Gradient `d_normal_logits` is `d_counts * counts`.
- Differentiating `logits = normal_logits - logit_maxes`.
- Handles broadcasting of `logit_maxes`.
- Gradient `d_logits` is `d_normal_logits` (with summation for broadcasted `logit_maxes`).
- Subtracting `logit_maxes` is for numerical stability in softmax.
- Ideally, gradients w.r.t. `logit_maxes` should be near zero.
- Small non-zero gradients due to floating-point arithmetic are observed.
- Differentiating through the `max` operation using indices.
- Gradient `d_logits` is scattered from `d_logit_maxes` to the max positions.
- Uses `torch.nn.functional.one_hot` for scattering.
- Differentiating `logits = h @ W2 + b2`.
- Derivatives for `h`, `W2`, and `b2` calculated using matrix calculus principles.
- Shape matching and broadcasting are key for correct gradient calculation.
- Differentiating the `tanh` activation function.
- Local derivative is `1 - tanh(z)^2` or `1 - h^2`.
- Gradient `d_h_preact` is `d_h * (1 - h^2)`.
- Breaking down batch norm into intermediate steps: mean, variance, standardization, scaling, shifting.
- Differentiating `BN_gain * (x - mu) / sigma + BN_bias`.
- Complex derivatives for `BN_gain`, `BN_bias`, `mu`, `sigma^2`, and `x`.
- Debate on using biased (1/N) vs. unbiased (1/(N-1)) variance estimation.
- Paper uses biased during training, unbiased during inference (train-test mismatch).
- Karpathy prefers consistent unbiased estimation using `N-1`.
- Differentiating `h_bn = m @ W1 + b1`.
- Derivatives `d_m`, `dW1`, `db1` calculated based on shapes and matrix multiplication rules.
- Summation for `db1` due to broadcasting of `b1`.
Summary, takeaways, and chapters were generated by AI from the video's transcript and may contain errors. The video belongs to its creator, Andrej Karpathy.