1 What a loss function is
Imagine you're learning to throw a ball into a basket. Each throw, a friend tells you a single number: "You missed by 2 metres." A big number means a bad throw, a small number means a good one, and zero means you scored. You don't need to know everything about the throw β that one "how far off were you" number is enough to know which way to adjust. A loss function is exactly that friend: it watches the network's answer, compares it to the right answer, and shouts back one number β "here's how wrong you were."
A loss function (also called a cost function or objective) is a formula that takes the network's prediction and the true answer and returns a single number: the loss. The bigger the number, the more wrong the network is. Training has exactly one goal β make this number as small as possible.
A neural network doesn't "want" anything on its own. We give it a purpose by writing down a loss function. Everything the network learns is just a side effect of pushing that one number down. Choose a different loss and you get a different network β the loss is the goal.
Why a single number?
Recall from Session 4 that backpropagation needs a starting point to compute gradients β a quantity it can differentiate. The loss L is that quantity. Because it's one number, we can ask the crisp question "if I nudge this weight a tiny bit, does L go up or down?" That answer is the gradient, and the whole training machine runs on it.
Loss vs. cost vs. objective β same family
- Loss β usually the error on one training example.
- Cost β usually the average loss over a batch or the whole dataset.
- Objective β the thing we optimize overall (often the cost, sometimes plus extra penalty terms like the regularization we'll meet in Session 6).
People use these words loosely and often interchangeably. Don't lose sleep over it β they all mean "the number we're trying to shrink."
What makes a good loss function?
- Small when right, large when wrong β so minimizing it actually improves predictions.
- Differentiable (smooth) β backprop needs a slope at every point. A loss with no gradient gives the network no direction to move.
- Matched to the task β a loss for predicting house prices looks different from a loss for sorting photos into "cat / dog / bird." We unpack that in Topic 2.
Your network predicts a house will sell for $300k. It actually sold for
$250k. A simple loss is the squared error:
# prediction and truth, in thousands of dollars y_pred = 300 y_true = 250 error = y_pred - y_true # 50 loss = error ** 2 # 2500
If the next prediction is $260k, the error is 10 and the loss is 100 β
much smaller. The network "knows" it improved purely because that number dropped. (Why
square it? See Topic 2 β squaring keeps the number positive and punishes big misses extra hard.)
Imagine the loss as the height of a hilly landscape, where your position is the network's weights. Training is just walking downhill to the lowest valley. We'll lean on this "landscape" picture constantly β gradient descent (Topic 3) is literally how you walk down it.
2 Common losses: MSE & cross-entropy
There are two big kinds of questions. "How much?" questions β like guessing someone's height β where being off by a little is okay and being off by a lot is bad. And "which one?" questions β like "is this a cat or a dog?" β where you're not guessing a number, you're pointing at the right box. These two kinds of questions need two different "how wrong" rulers. The height ruler is called MSE; the picking-the-right-box ruler is called cross-entropy.
The two questions are the two great families of machine-learning tasks:
- Regression β predict a continuous number (price, temperature, age). Use Mean Squared Error.
- Classification β pick a category from a fixed set (cat/dog/bird, spam/not-spam). Use Cross-Entropy.
Mean Squared Error (MSE) β for regression
Mean Squared Error averages the squared difference between each prediction and its true value. For n examples:
# MSE formula MSE = (1/n) * Ξ£ (y_pred_i - y_true_i)Β²
Two design choices make it work:
- Squaring makes every error positive (so +5 and β5 don't cancel out) and punishes large errors disproportionately β a miss of 10 costs 100, a miss of 20 costs 400.
- Averaging (the
1/n) makes the loss comparable whether you have 10 examples or 10,000.
Three house-price predictions (in $k) vs. truth:
| Predicted | True | Error | ErrorΒ² |
|---|---|---|---|
| 300 | 250 | 50 | 2500 |
| 180 | 200 | β20 | 400 |
| 410 | 400 | 10 | 100 |
# average of the squared errors MSE = (2500 + 400 + 100) / 3 = 1000
Notice the single big miss (2500) dominates the result β that's the squaring at work, telling the network "fix the large errors first."
Mean Absolute Error (MAE) averages the absolute errors instead of the squared ones. It cares less about huge outliers (no squaring), so it's more robust when your data has a few wild values. Rule of thumb: MSE when big errors are truly bad; MAE when a few outliers shouldn't dominate.
Cross-Entropy (log loss) β for classification
For classification the network outputs probabilities β e.g. "I'm 70% sure it's a cat,
20% dog, 10% bird." Cross-entropy (also called log loss)
measures how far those predicted probabilities are from the truth. The truth is a
one-hot vector: 1 for the correct class, 0 for the rest (e.g. cat = [1, 0, 0]).
# Cross-entropy for one example, C classes CE = - Ξ£ y_true_c * log(y_pred_c) # Because y_true is one-hot, only the correct class survives: CE = - log(y_pred[correct_class])
So cross-entropy boils down to: take the probability the network assigned to the right answer,
and penalise it with βlog. Predict 1.0 (perfectly confident and correct) β loss is
βlog(1) = 0. Predict 0.01 for the right class (confidently wrong) β loss is
βlog(0.01) β 4.6, a big penalty. The βlog shape punishes confident mistakes
savagely, which is exactly what we want.
Softmax β turning scores into probabilities
The network's raw outputs are logits β arbitrary numbers, possibly negative. Softmax squashes them into a valid probability distribution (all positive, summing to 1) before cross-entropy looks at them:
# softmax over logits z
softmax(z_i) = exp(z_i) / Ξ£_j exp(z_j)
In practice the two are glued together (e.g. PyTorch's CrossEntropyLoss applies
softmax internally) because computing them jointly is more numerically stable.
A 3-class network (cat / dog / bird) outputs logits [2.0, 1.0, 0.1] and the
true class is cat.
# 1) softmax the logits exp([2.0, 1.0, 0.1]) = [7.39, 2.72, 1.11] sum = 11.22 probs = [0.659, 0.242, 0.099] # cat=66%, dog=24%, bird=10% # 2) cross-entropy = -log(prob of correct class = cat) CE = -log(0.659) = 0.417
If the network had instead been confident the answer was bird (say cat prob = 0.05), the loss would
be βlog(0.05) β 3.0 β over 7Γ larger. Cross-entropy rewards confident, correct
probabilities and hammers confident, wrong ones.
import torch import torch.nn as nn # regression mse = nn.MSELoss() loss_r = mse(pred, target) # pred, target are floats # classification (softmax applied INSIDE the loss) ce = nn.CrossEntropyLoss() logits = torch.tensor([[2.0, 1.0, 0.1]]) target = torch.tensor([0]) # class index 0 = cat loss_c = ce(logits, target) # β 0.417
When to use which
| Task | Output layer | Loss |
|---|---|---|
| Regression (predict a number) | Linear (no activation) | MSE (or MAE if outliers) |
| Binary classification (yes/no) | Sigmoid β 1 probability | Binary cross-entropy |
| Multi-class (pick 1 of C) | Softmax β C probabilities | Categorical cross-entropy |
Using MSE on a classification problem. It technically runs, but it trains slowly and badly β squared error gives weak gradients when the network is confidently wrong, while cross-entropy gives strong ones. Match the loss to the task.
3 Gradient descent variants
You're standing on a foggy hill and want to reach the bottom. You can't see far, but you can feel which way the ground slopes under your feet, so you take a small step downhill, feel again, step again. That's gradient descent. The only real questions are: how big a step do you take (the learning rate), and how often do you stop to re-check the slope β after looking at all the ground (batch), after a single patch (stochastic), or after a handful of patches (mini-batch)?
From Topic 1, the loss is a landscape and we want the lowest valley. The gradient points in the direction of steepest uphill, so we step the opposite way. That's the core update rule, repeated thousands of times:
# the heart of all training
w = w - learning_rate * gradient_of_loss_wrt_w
The learning rate (often written Ξ·, "eta") is the step
size. The three variants differ only in how much data they look at before each step.
The three variants
Batch gradient descent
Batch gradient descent computes the gradient over the entire training set before taking one step. Every step points in the true "average downhill" direction, so the path is smooth and stable. The catch: one step requires processing all your data β painfully slow and memory-hungry for large datasets (millions of examples β one tiny step).
Stochastic gradient descent (SGD)
Stochastic gradient descent (SGD) goes to the other extreme: it estimates the gradient from a single random example and steps immediately. ("Stochastic" just means "random.") Steps are lightning-fast and you make many of them, but each step is noisy β one example is a rough guess of the true slope, so the path zig-zags. Surprisingly, that noise is often helpful: the jitter can bounce the network out of shallow bad valleys.
Mini-batch gradient descent
Mini-batch gradient descent is the practical sweet spot used by virtually all modern deep learning. It computes the gradient over a small batch of examples (commonly 32, 64, 128, or 256) before stepping. You get most of the stability of batch GD and most of the speed of SGD, plus it maps perfectly onto GPUs, which love crunching a batch of numbers in parallel.
One epoch = one full pass over the training data. One iteration = one weight update (one batch). So with 10,000 examples and a batch size of 100, one epoch = 100 iterations. Confusingly, people often say "SGD" to mean "mini-batch SGD" β in practice almost nobody uses true one-example-at-a-time SGD.
The speed / noise trade-off
| Variant | Data per step | Step speed | Path | In practice |
|---|---|---|---|---|
| Batch | All N examples | Slow (rare steps) | Smooth, stable | Tiny datasets only |
| SGD (pure) | 1 example | Very fast, very noisy | Zig-zags wildly | Rarely used as-is |
| Mini-batch | ~32β256 | Fast, mild noise | Mostly smooth | The standard |
for epoch in range(num_epochs): for x_batch, y_batch in dataloader: # yields batches of, say, 64 preds = model(x_batch) loss = loss_fn(preds, y_batch) optimizer.zero_grad() # clear old gradients loss.backward() # backprop (Session 4): fill .grad optimizer.step() # w = w - lr * grad
Each optimizer.step() is one mini-batch update. Notice backprop (Session 4)
and the loss (Topics 1β2) are both right here, working together.
Learning-rate intuition β too big vs. too small
The learning rate is the single most important knob in all of training. Get it wrong and nothing else matters.
| Learning rate | What happens | Hill analogy |
|---|---|---|
| Too small | Loss creeps down agonisingly slowly; may never finish or get stuck. | Baby steps β takes forever to reach the valley. |
| Just right | Loss drops steadily and settles near the minimum. | Confident strides straight downhill. |
| Too big | Loss bounces around or explodes to infinity (diverges). | Giant leaps that overshoot the valley and fly up the far side. |
If your loss shoots up or becomes NaN, the number-one suspect is
a learning rate that's too high. Cut it by 10Γ and try again. A typical starting point is
0.001 for Adam (Topic 4) or 0.01β0.1 for plain SGD β but
you always tune it. Topic 5 shows how to change it during training for the best of both worlds.
w = w β Ξ·Β·gradient. Batch uses all
data (smooth but slow), SGD uses one example (fast but noisy), mini-batch
(~32β256) is the practical winner. The learning rate sets step size: too small = crawling,
too big = diverging, just right = steady descent.
4 Advanced optimizers
Plain gradient descent is like walking downhill one careful step at a time, forgetting every step the moment you take it. But think about a ball rolling down a hill: it builds up speed, rolls smoothly through little bumps, and doesn't get stuck in every tiny dip. The fancy optimizers in this topic add exactly that kind of "memory" and "smarts" β momentum so the ball keeps rolling, and automatic step-size tuning so it slows down on tricky terrain. Adam combines both and is the one you'll reach for most.
Plain SGD has two weaknesses: it can crawl through long shallow valleys, and it uses the
same learning rate for every weight. These optimizers each fix a specific problem. They're all
drop-in replacements for the optimizer in our training loop.
Momentum β keep rolling
Momentum keeps a running average of past gradients (a "velocity") and steps in that accumulated direction. Like a heavy ball, it powers through small bumps and flat patches and dampens the zig-zag of SGD.
# Momentum (Ξ² β 0.9)
v = Ξ² * v + gradient
w = w - Ξ· * v
Fixes: slow progress in long valleys and noisy zig-zagging β old gradients in the same direction reinforce; conflicting ones cancel out.
Nesterov momentum β look before you leap
Nesterov accelerated gradient is a smarter momentum: it first jumps ahead in the direction of the existing velocity, then measures the gradient at that lookahead point. By "peeking" where it's about to land, it corrects course earlier and overshoots less.
# Nesterov: gradient evaluated at the lookahead position (w + Ξ²Β·v)
v = Ξ² * v + gradient_at(w + Ξ² * v)
w = w - Ξ· * v
Fixes: momentum's tendency to overshoot the bottom of a valley β the lookahead acts like an early brake.
RMSProp β a custom step size per weight
RMSProp keeps a running average of the squared gradients for each weight, then divides the step by the square root of that. Weights with consistently big gradients get smaller steps; weights with tiny gradients get bigger ones. Every parameter gets its own adaptive learning rate.
# RMSProp (Ξ² β 0.9, Ξ΅ tiny, e.g. 1e-8) s = Ξ² * s + (1 - Ξ²) * gradientΒ² w = w - Ξ· * gradient / (sqrt(s) + Ξ΅)
Fixes: one global learning rate being wrong for differently-scaled weights; great on the noisy, non-stationary objectives common in deep nets.
Adam β momentum + RMSProp together
Adam (Adaptive Moment Estimation) combines momentum (a running average of gradients, the "first moment") and RMSProp (a running average of squared gradients, the "second moment"). It's the default optimizer for most deep learning today because it just works with little tuning.
Fixes: essentially everything above at once β directional smoothing and per-weight step sizes β plus a bias correction step so the early updates (when the running averages start at zero) aren't artificially tiny.
# Defaults: lr=0.001, Ξ²1=0.9, Ξ²2=0.999, Ξ΅=1e-8 m = 0; v = 0; t = 0 # m: momentum, v: RMS term for each batch: t = t + 1 g = gradient_of_loss m = Ξ²1 * m + (1 - Ξ²1) * g # 1st moment (mean of grads) v = Ξ²2 * v + (1 - Ξ²2) * g * g # 2nd moment (mean of squared grads) m_hat = m / (1 - Ξ²1 ** t) # bias correction v_hat = v / (1 - Ξ²2 ** t) w = w - lr * m_hat / (sqrt(v_hat) + Ξ΅)
The m_hat / sqrt(v_hat) shape is the whole trick: numerator = "which way" (momentum),
denominator = "how big a step is safe" (RMSProp). In PyTorch this entire algorithm is just
optimizer = torch.optim.Adam(model.parameters(), lr=0.001).
Comparison table
| Optimizer | Key idea | Problem it fixes | Adaptive LR? |
|---|---|---|---|
| SGD | Step opposite the gradient | (baseline) | No |
| Momentum | Accumulate past gradients | Slow valleys, zig-zag | No |
| Nesterov | Momentum with a lookahead | Overshooting the minimum | No |
| RMSProp | Scale step by recent grad size | Wrong global step size | Yes (per-weight) |
| Adam | Momentum + RMSProp + bias fix | All of the above | Yes (per-weight) |
If you're unsure what to use, start with Adam at lr = 0.001 β it's the robust default and rarely embarrasses you. Well-tuned SGD + momentum can sometimes generalise slightly better (especially in computer vision), which is why it's still popular for state-of-the-art image models. Both are excellent; Adam is just more forgiving.
AdamW is a small but important fix to how Adam handles weight decay (a regularization trick from Session 6). It's now the standard choice for training large models like Transformers. Same Adam machinery, cleaner weight-decay handling.
5 Learning-rate schedules
When you're far from home you walk in big, fast strides. As you get close to your front door, you slow down and take small, careful steps so you don't overshoot it and bonk into the wall. A learning-rate schedule does the same for training: take big steps early when you're far from the answer, then shrink the steps as you close in, so you settle gently into the lowest point instead of bouncing around it.
From Topic 3 we know a fixed learning rate forces a compromise: big enough to make progress, small enough not to diverge. A learning-rate schedule escapes that compromise by changing the learning rate over time β typically starting larger and decaying it.
Early in training you're far from the minimum, so a large learning rate makes fast progress. Late in training you're circling the bottom of the valley, where a large rate just bounces you around β a small rate lets you fine-tune into the minimum. A schedule gives you both: speed early, precision late.
Step decay
Step decay cuts the learning rate by a fixed factor every so many epochs β e.g. multiply by 0.1 every 30 epochs. Simple, classic, and easy to reason about (it shows up as clean "stairs" in a learning-rate plot).
# step decay: drop by 10Γ every 30 epochs lr = initial_lr * (0.1 ** (epoch // 30)) # epoch 0β29 -> 0.1 # epoch 30β59 -> 0.01 # epoch 60+ -> 0.001
Cosine annealing
Cosine annealing smoothly lowers the learning rate following a cosine curve from the initial value down toward zero over training β fast at first, gentle near the end. No abrupt jumps, and it's a favourite for modern models.
# cosine annealing from lr_max to ~0 over T total steps lr = lr_max * 0.5 * (1 + cos(Ο * step / T))
Warmup
Warmup does the opposite at the very start: it ramps the learning rate up from near zero over the first few hundred or thousand steps, then hands off to decay. Why? At the very beginning the weights are random and gradients are erratic; a big step immediately can destabilise training (especially with Adam, whose moment estimates haven't warmed up yet). Warmup eases in gently. It's nearly universal when training Transformers, and often paired with cosine decay ("warmup + cosine").
import torch optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # cosine annealing over 100 epochs scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( optimizer, T_max=100) for epoch in range(100): train_one_epoch(model, optimizer) scheduler.step() # nudge the lr along the cosine curve print(epoch, scheduler.get_last_lr())
Swap one line for a different policy: StepLR(optimizer, step_size=30, gamma=0.1)
for step decay, or LinearLR for a warmup ramp β schedules are decoupled from the
optimizer, so you can mix and match.
A schedule does not rescue a bad starting learning rate β if your peak rate is too high you'll still diverge in epoch 1. Pick a sane base rate first (Topic 3), then add a schedule to refine it. Tools like a "learning-rate range test" can help you find a good peak.
β Putting it all together
These five topics are the complete "learning engine" of a neural network. Here's the one-paragraph story that connects them:
A network learns by shrinking a single number, its loss β MSE for predicting numbers, cross-entropy (with softmax) for picking categories. The loss defines a landscape, and gradient descent walks downhill using the gradients that backpropagation (Session 4) computed: all the data at once (batch), one example (SGD), or β in practice β a mini-batch. How big each step is comes down to the learning rate, and smarter optimizers like Momentum, RMSProp, and the all-rounder Adam add memory and per-weight step sizes to make the descent faster and steadier. Finally a learning-rate schedule β step decay, cosine annealing, or warmup β takes big strides early and tiny ones late, settling the network gently into a good minimum. In Session 6 we'll see how regularization keeps that minimum from overfitting.
Quick self-check
What is a loss function, in one sentence?
A formula that turns "how wrong is the prediction vs. the truth?" into a single differentiable number that training tries to minimize.
You're predicting tomorrow's temperature. MSE or cross-entropy?
MSE β it's a regression task (predicting a continuous number). Cross-entropy is for classification (picking a category).
Why is mini-batch gradient descent the practical standard over batch and pure SGD?
It balances the trade-off: nearly the stability of batch GD with nearly the speed of SGD, and a batch of ~32β256 examples maps efficiently onto GPUs.
Your loss suddenly explodes to NaN. What's the most likely culprit?
A learning rate that's too high (the steps overshoot and diverge). Cut it by about 10Γ and retry.
What two ideas does Adam combine?
Momentum (a running average of gradients β the direction) and RMSProp (a running average of squared gradients β a per-weight adaptive step size), plus bias correction for the early steps.
Why use a learning-rate schedule instead of a fixed rate?
To get speed early and precision late: a large rate makes fast progress when you're far from the minimum, while shrinking it later lets you settle gently into the valley instead of bouncing.
π References & Further Reading
Class material
- Your course handout: "SST Deep Learning handout (Session 5)" β the notes that accompany this session, available through your class at priyanshsaxena.com/sst-deep-learning-nn.
Papers, docs & deep dives
- Adam: A Method for Stochastic Optimization (Kingma & Ba, 2014) β the original Adam paper; surprisingly readable and the source of the pseudocode in Topic 4.
- An overview of gradient descent optimization algorithms (Sebastian Ruder) β the single best tour of SGD, momentum, Nesterov, RMSProp, Adam and friends, with clear math and intuition.
- CS231n β Optimization notes (Stanford) β Stanford's classic course notes on loss landscapes and gradient descent; excellent visual intuition.
- Deep Learning Book β Ch. 8: Optimization (Goodfellow, Bengio, Courville) β the authoritative textbook chapter on training deep models, free online.
- PyTorch
torch.optimdocumentation β official reference for every optimizer and learning-rate scheduler used in this session's code.