TAI BUI
Back to curriculum
BuildPython

Loss Functions

Your network makes a prediction. The ground truth says otherwise. How wrong is it? That number, the loss, is the only thing your optimizer actually minimizes. Pick the wrong one and your model optimizes for the wrong thing entirely.

TIME: ~75 minutesPREREQUISITES: Lesson 03.04 (Activation Functions)

Learning Objectives

  • Implement MSE, binary cross-entropy, categorical cross-entropy, and contrastive loss (InfoNCE) from scratch with their gradients
  • Explain why MSE fails for classification by demonstrating the "predict 0.5 for everything" failure mode
  • Apply label smoothing to cross-entropy and describe how it prevents overconfident predictions
  • Choose the correct loss function for regression, binary classification, multi-class classification, and embedding learning tasks

The Problem

A model minimizing MSE on a classification problem will confidently predict 0.5 for everything. It's minimizing loss. It's also useless.

The loss function is the only thing your model actually optimizes. Not accuracy. Not F1 score. Not whatever metric you report to your manager. The optimizer takes the gradient of the loss function and adjusts weights to make that number smaller. If the loss function doesn't capture what you care about, the model will find the mathematically cheapest way to satisfy it, and that way is almost never what you wanted.

Here is a concrete example. You have a binary classification task. Two classes, 50/50 split. You use MSE as your loss. The model predicts 0.5 for every single input. The average MSE is 0.25, which is the minimum possible without actually learning anything. The model has zero discriminative ability but it has technically minimized your loss function. Switch to cross-entropy and the same model is forced to push predictions toward 0 or 1, because -log(0.5) = 0.693 is a terrible loss, while -log(0.99) = 0.01 rewards confident correct predictions. The choice of loss function is the difference between a model that learns and a model that games the metric.

It gets worse. In self-supervised learning, you don't even have labels. Contrastive loss defines the learning signal entirely: what counts as similar, what counts as different, and how hard the model should push them apart. Get contrastive loss wrong and your embeddings collapse to a single point: every input maps to the same vector. Technically zero loss. Completely worthless.

Warm-up0/3
1. Two students each get a multiple-choice question wrong. Student A said they were 95% sure of their answer. Student B said they were only 55% sure. Common sense says whose mistake is worse?Warm-up
Two cards for Student A and Student B. Both chose answer B when the correct answer was D and both are marked wrong. Student A says they were 95% sure, shown as a nearly full bar, and Student B says 55% sure, shown as a bar just over half full.
Generic illustration of the stem; the exam question and the answer letters are made up.
2. A network's final unit applies a sigmoid to its raw score. What range can the output take?Warm-up
A small network with three inputs, three hidden units and one final unit that produces a raw score z, followed by a box that applies sigmoid(z) = 1/(1+e^-z) to give p, with the value of p marked as a question mark.
Illustrative network; only the final sigmoid unit comes from the stem.
3. For a probability p between 0 and 1, how does -log(p) behave as p shrinks toward 0?Warm-up
A row of five probabilities p = 0.5, 0.1, 0.01, 0.001 and 0.0001, each with the value of -log(p) beneath it left as a question mark.
Check: The Problem0/5
1. What is a loss function?Define
A table of four predictions 0.9, 0.1, 0.7, 0.4 beside their targets 1, 0, 1, 0, feeding a box mse(predictions, targets) that outputs 0.0675, with the list of gradients -0.05, 0.05, -0.15, 0.2 printed below.
Output of the lesson's mse and mse_gradient functions.
2. A binary classifier trained with MSE on a balanced 50/50 dataset ends up predicting 0.5 for every input. What does this show?Explain
Two tables listing ten training inputs with labels (five 1s and five 0s) and the model's prediction for each, which is 0.500 for every input.
Typed to match the stem, not captured from a run.
3. A model predicts 0.5 for four samples with targets [1, 0, 1, 0]. What does the mse function return?Apply
A table of four predictions all equal to 0.5 beside targets 1, 0, 1, 0, passed to the lesson's mse function, with the return value left as a question mark.
4. A contrastive model's loss is almost zero, yet cosine similarity between every pair in the batch is 1.0. What is the most likely diagnosis?Analyze
A 6 by 6 grid of pairwise cosine similarities between embeddings in a batch, every cell reading 1.00, next to a note that the training loss is almost zero.
Typed to match the stem, not captured from a run.
5. A classifier trained with MSE on balanced data shows a smooth, falling loss curve that levels at 0.25, and accuracy stays near 50%. Which action addresses the cause?Analyze
Two small line charts over 100 epochs for a classifier trained with MSE on balanced data: the loss falls smoothly and levels at 0.25, and the accuracy stays near 50%.
Illustrative curves drawn to the stem's numbers; not a captured run.

The Concept

Mean Squared Error (MSE)

The default for regression. Compute the squared difference between prediction and target, average over all samples.

MSE = (1/n) * sum((y_pred - y_true)^2)

Why squaring matters: it penalizes large errors quadratically. An error of 2 costs 4x as much as an error of 1. An error of 10 costs 100x. This makes MSE sensitive to outliers: a single wildly wrong prediction dominates the loss.

Real numbers: if your model predicts housing prices and is off by $10,000 on most houses but off by $200,000 on one mansion, MSE will aggressively try to fix that one mansion, potentially hurting performance on the other 99 houses.

Analogy: it's like a parking fine that scales with the square of how far over the line you park. Drift two inches over and the fine barely registers. Drift two feet over and the fine doesn't just double, it explodes. MSE prices every miss the same way: distance from the target gets punished quadratically, not linearly.

The gradient of MSE with respect to a prediction is:

dMSE/dy_pred = (2/n) * (y_pred - y_true)

Linear in the error. Bigger errors get bigger gradients. This is a feature for regression (large errors need large corrections) and a bug for classification (you want to penalize confident wrong answers exponentially, not linearly).

Cross-Entropy Loss

The loss function for classification. It measures the divergence between the predicted probability distribution and the true distribution, an idea rooted in information theory.

Binary Cross-Entropy (BCE):

BCE = -(y * log(p) + (1 - y) * log(1 - p))

Where y is the true label (0 or 1) and p is the predicted probability.

Why -log(p) works: when the true label is 1 and you predict p = 0.99, the loss is -log(0.99) = 0.01. When you predict p = 0.01, the loss is -log(0.01) = 4.6. That 460x difference is why cross-entropy works. It brutally punishes confident wrong predictions while barely penalizing confident correct ones.

Analogy: it's like betting your whole paycheck on a coin flip you were "sure" about, versus hedging a dollar while admitting you're unsure. Confidently wrong costs you far more than modestly wrong, and cross-entropy prices that gap into the loss the same way a bookmaker prices an overconfident bet.

The gradient tells the same story:

dBCE/dp = -(y/p) + (1-y)/(1-p)

When y = 1 and p is near zero, the gradient is -1/p which approaches negative infinity. The model gets an enormous signal to fix its mistake. When p is near 1, the gradient is tiny. Already correct, nothing to fix.

Categorical Cross-Entropy:

For multi-class classification with one-hot encoded targets.

CCE = -sum(y_i * log(p_i))

Only the true class contributes to the loss (because all other y_i are zero). If there are 10 classes and the correct class gets probability 0.1 (random guessing), the loss is -log(0.1) = 2.3. If the correct class gets probability 0.9, the loss is -log(0.9) = 0.105. The model learns to concentrate probability mass on the right answer.

Why MSE Fails for Classification

Mermaid diagram

MSE gradients flatten when predictions are near 0 or 1 (due to sigmoid saturation). Cross-entropy gradients compensate for this: the -log cancels the sigmoid's flat regions, giving strong gradients exactly where they are needed most.

Label Smoothing

Label smoothing softens hard 0/1 targets instead of forcing the model to match them exactly. Standard one-hot labels say "this is 100% class 3 and 0% everything else," which is a strong claim:

smooth_label = (1 - alpha) * one_hot + alpha / num_classes

With alpha = 0.1 and 10 classes: instead of [0, 0, 1, 0, ...], the target becomes [0.01, 0.01, 0.91, 0.01, ...]. The model targets 0.91 instead of 1.0.

Why this works: a model trying to output exactly 1.0 through a softmax needs to push logits to infinity. This causes overconfidence, hurts generalization, and makes the model brittle to distribution shift. Label smoothing caps the target at 0.9 (with alpha=0.1), keeping logits in a reasonable range. GPT and most modern models use label smoothing or its equivalent.

Contrastive Loss

Contrastive loss learns representations without labels: it pulls similar pairs of inputs together in embedding space and pushes dissimilar pairs apart. No classes, no ground truth, just pairs and the question of whether they're similar or different.

SimCLR-style contrastive loss (NT-Xent / InfoNCE):

Take one image. Create two augmented views of it (crop, rotate, color jitter). These are the "positive pair": they should have similar embeddings. Every other image in the batch forms a "negative pair": they should have different embeddings.

L = -log(exp(sim(z_i, z_j) / tau) / sum(exp(sim(z_i, z_k) / tau)))

Where sim() is cosine similarity, z_i and z_j are the positive pair, the sum is over all negatives, and tau (temperature) controls how sharp the distribution is. Lower temperature = harder negatives = more aggressive separation.

Real numbers: batch size 256 means 255 negatives per positive pair. Temperature tau = 0.07 (SimCLR default). The loss looks like a softmax over similarities: it wants the positive pair's similarity to be highest among all 256 options.

Analogy: it's like a police lineup. Pull the true match tight against the witness's memory while every other face in the lineup gets pushed further away, and the more suspects standing in that lineup, the sharper the match has to be for the model to still pick the right one.

Triplet Loss:

Takes three inputs: anchor, positive (same class), negative (different class).

L = max(0, d(anchor, positive) - d(anchor, negative) + margin)

The margin (typically 0.2-1.0) enforces a minimum gap between positive and negative distances. If the negative is already far enough away, the loss is zero: no gradient, no update. This makes training efficient but requires careful triplet mining (choosing hard negatives that are close to the anchor).

Focal Loss

Focal loss down-weights easy examples so training focuses on the hard ones. That's what makes it the fix for imbalanced datasets, where standard cross-entropy treats every correctly classified example equally.

FL = -alpha * (1 - p_t)^gamma * log(p_t)

Where p_t is the predicted probability of the true class and gamma controls the focusing. With gamma = 0, this is standard cross-entropy. With gamma = 2 (the default):

  • Easy example (p_t = 0.9): weight = (0.1)^2 = 0.01. Effectively ignored.
  • Hard example (p_t = 0.1): weight = (0.9)^2 = 0.81. Full gradient signal.

Focal loss was introduced by Lin et al. for object detection, where 99% of candidate regions are background (easy negatives). Without focal loss, the model drowns in easy background examples and never learns to detect objects. With it, the model focuses its capacity on the hard, ambiguous cases that matter.

Loss Function Decision Tree

Mermaid diagram

Loss Landscape

Mermaid diagram

Check: The Concept0/24
1. What is MSE (mean squared error)?Define
A table of four predictions 2.5, 0, 2, 8 beside targets 3, -0.5, 2, 7, passed to the lesson's mse function, which returns 0.375.
Example numbers typed for illustration; 0.375 is the output of the lesson's mse.
2. What is cross-entropy loss?Define
Paired bars for three classes: predicted probabilities 0.7, 0.2, 0.1 and the true distribution 1, 0, 0. To the right a call to categorical_cross_entropy with target_index 0 returns 0.3567.
Logits are log of the probabilities, so softmax returns them; the value is from the lesson's function.
3. What is binary cross-entropy (BCE)?Define
A table of four samples with labels 1, 0, 1, 0, predictions 0.9, 0.1, 0.7, 0.4 and per-sample losses 0.1054, 0.1054, 0.3567, 0.5108, feeding binary_cross_entropy with mean 0.2696.
Per-sample values from the lesson's binary_cross_entropy called on one pair at a time.
4. What is label smoothing?Define
A code card with five logits 2.0, 1.0, 0.1, -1.0, 3.0 and true class 4, calling F.cross_entropy with label_smoothing=0.0, which returns 0.4552, and with label_smoothing=0.1, which returns 0.6532.
Printed from CPU torch.
5. What is contrastive loss?Define
A 2-D embedding space with five unlabelled points a to e at scattered positions, next to a list of pairs: (a,b) and (c,d) marked similar, (a,c), (b,e) and (d,e) marked dissimilar.
Illustrative positions and pairs, typed for this question.
6. What is InfoNCE (NT-Xent)?Define
A table of eight candidates for one anchor, the positive with cosine similarity 0.9946 and seven negatives with similarities 0.4161, 0.5445, 0.5705, 0.0445, -0.5513, -0.0406, -0.2231, feeding contrastive_loss at temperature 0.07, which returns 0.0042.
Run of the lesson's step 5 code.
7. What is focal loss?Define
A table of three examples with p_t 0.9 (easy), 0.5 (medium) and 0.1 (hard), their cross-entropy losses 0.1054, 0.6931, 2.3026 and their focal losses with gamma 2 and alpha 1: 0.0011, 0.1733, 1.8651.
p_t values typed for illustration; losses computed in the script.
8. What is triplet loss, and what does its margin do?Define
Three points in an embedding space: an anchor, a positive of the same class at distance 0.8 and a negative of another class at distance 1.1, with a side card listing the triplet and a margin of 0.2.
Illustrative distances.
9. In InfoNCE, what does the temperature do?Define
A table of one positive and seven negatives with their cosine similarities to an anchor, and a second table showing contrastive_loss on those same candidates: 0.0042 at temperature 0.07 and 0.9391 at temperature 0.5.
Run of the lesson's step 5 code at both temperatures.
10. Why does MSE make a regression model sensitive to outliers, as in the lesson's house-price example?Explain
A bar chart of prediction error for 100 houses: 99 equal short bars at $10,000 and one tall bar at $200,000 for the mansion.
The lesson's example, with 99 other houses as in the answer explanation.
11. Why is a gradient that is linear in the error good for regression but a problem for classification?Explain
Two tables of the MSE gradient for one sample: for regression, errors 0.5, 1, 2, 4 give gradients 1, 2, 4, 8; for a classification target of 1, predictions 0.9, 0.5, 0.1, 0.01 give gradients -0.20, -1.00, -1.80, -1.98.
Values from the lesson's mse_gradient with n = 1.
12. Why does cross-entropy train a sigmoid classifier better than MSE does when a prediction is confidently wrong?Explain
A sigmoid curve of p against raw score z from -8 to 8 with a red point at p = 0.01 (z about -4.6) and a dashed line at the target 1, next to a note that the loss on p is compared between MSE and BCE.
13. Why does BCE give a huge gradient when the true label is 1 and the model predicts p = 0.01?Explain
A 0-to-1 probability strip with a red marker at the model output p = 0.01 and a green marker at the label y = 1, the code call bce_gradient([0.01], [1.0]), and the loss -log(0.01) = 4.605.
14. Why does label smoothing reduce overconfidence?Explain
Two bar charts of the training target over 10 classes for an example whose true class is 0: a hard target with 1 on class 0 and 0 elsewhere, and a smoothed target with label_smoothing 0.1 giving class 0 the value 0.91 and every other class 0.01.
15. Why can triplet loss make training efficient, and what does it demand in return?Explain
An anchor at the centre with a green ring at the positive distance 0.5 and five negatives n1 to n5 at distances 0.6, 0.9, 1.4, 1.9 and 2.5, with a side card giving the margin 0.2.
Distances typed for illustration.
16. How does focal loss with gamma = 0 relate to standard cross-entropy?Explain
The focal loss formula with gamma set to 0, above a table of three p_t values 0.9, 0.5, 0.1, their cross-entropy values 0.1054, 0.6931, 2.3026 and a blank for the focal loss.
p_t values typed for illustration.
17. With gamma = 2, what focal-loss weight (1 - p_t)^gamma does an example with p_t = 0.5 get?Apply
The focal loss weight formula (1 - p_t)^gamma above three boxes: gamma = 2, p_t = 0.5, and weight left as a question mark.
18. A triplet has d(anchor, positive) = 0.5, d(anchor, negative) = 1.5 and margin = 0.2. What is the loss?Apply
A number line with an anchor at 0, a positive at distance 0.5 and a negative at distance 1.5, labelled with those distances, margin = 0.2 and the loss left as a question mark.
19. An object detector trained with plain cross-entropy never learns to find objects, because about 99% of candidate regions are easy background. Which change targets this?Analyze
A 10 by 10 grid of candidate regions where 99 cells are background and one orange cell is an object, with a legend and a note that the loss is plain cross-entropy and the detector never learns to find objects.
The 99% is from the stem; the 100 regions and the layout are illustrative.
20. A triplet-loss model's loss drops to nearly zero in a few epochs, yet its embeddings separate classes poorly. What is the likely cause?Analyze
Left, a line chart of the 10-step average of the mean triplet loss over 400 training steps: it starts at 0.19, falls to 0.03 by step 40 and then stays between 0.03 and 0.04. Right, a scatter of the final 2-D embeddings of six classes in colour, where classes 0 and 1, 2 and 3, and 4 and 5 form three tight pairs with the two classes of each pair sitting right next to each other.
Real run (CPU torch): 6 classes of 2-D points, margin 0.2, random triplets, small MLP with a near-zero output layer at init, SGD.
21. A 10-class model trained on hard one-hot targets reaches 100% training accuracy, but its logits keep growing and it is brittle under distribution shift. Which change fits?Analyze
Two line charts over 2000 steps for a 10-class classifier trained on hard one-hot targets: training accuracy reaches 100% early and stays there, while the mean top logit keeps climbing to about 12.
Real run (CPU torch): linear softmax model, 500 points, 5-D, separable clusters.
22. You are training product-search embeddings. There are no class labels, only pairs of items a user did or did not click together. Which loss family fits?Design
A click log table of item pairs, such as wireless mouse with usb-c hub marked yes and usb-c hub with garden hose marked no, with a note that there is no column of class labels.
Item names are illustrative.
23. A binary fraud detector sees about 0.5% positives, and plain BCE is dominated by easy legitimate transactions. Which design fits best?Design
A grid of 200 cells representing transactions, 199 legitimate in blue and one fraudulent in orange, which is 0.5%.
Illustrative sample at the stem's rate.
24. You must pick losses for four tasks: house prices, spam yes/no, 10-way digit recognition, and image embeddings with no labels. Which assignment is correct?Design
Four task cards: house prices with a dollar price as target, a spam filter with spam or not spam, digit recognition with digits 0 to 9, and image embeddings with no labels.

Build It

Step 1: MSE and Its Gradient

python
def mse(predictions, targets):
    n = len(predictions)
    total = 0.0
    for p, t in zip(predictions, targets):
        total += (p - t) ** 2
    return total / n
 
def mse_gradient(predictions, targets):
    n = len(predictions)
    grads = []
    for p, t in zip(predictions, targets):
        grads.append(2.0 * (p - t) / n)
    return grads

Step 2: Binary Cross-Entropy

The log(0) problem is real. If the model predicts exactly 0 for a positive example, log(0) = negative infinity. Clipping prevents this.

python
import math
 
def binary_cross_entropy(predictions, targets, eps=1e-15):
    n = len(predictions)
    total = 0.0
    for p, t in zip(predictions, targets):
        p_clipped = max(eps, min(1 - eps, p))
        total += -(t * math.log(p_clipped) + (1 - t) * math.log(1 - p_clipped))
    return total / n
 
def bce_gradient(predictions, targets, eps=1e-15):
    grads = []
    for p, t in zip(predictions, targets):
        p_clipped = max(eps, min(1 - eps, p))
        grads.append(-(t / p_clipped) + (1 - t) / (1 - p_clipped))
    return grads

Step 3: Categorical Cross-Entropy with Softmax

Softmax converts raw logits to probabilities. Then we compute the cross-entropy against one-hot targets.

python
def softmax(logits):
    max_val = max(logits)
    exps = [math.exp(x - max_val) for x in logits]
    total = sum(exps)
    return [e / total for e in exps]
 
def categorical_cross_entropy(logits, target_index, eps=1e-15):
    probs = softmax(logits)
    p = max(eps, probs[target_index])
    return -math.log(p)
 
def cce_gradient(logits, target_index):
    probs = softmax(logits)
    grads = list(probs)
    grads[target_index] -= 1.0
    return grads

The gradient of softmax + cross-entropy simplifies beautifully: it's just (predicted probability - 1) for the true class, and (predicted probability) for all other classes. This elegant simplification is not a coincidence: it's why softmax and cross-entropy are paired.

Step 4: Label Smoothing

python
def label_smoothed_cce(logits, target_index, num_classes, alpha=0.1, eps=1e-15):
    probs = softmax(logits)
    loss = 0.0
    for i in range(num_classes):
        if i == target_index:
            smooth_target = 1.0 - alpha + alpha / num_classes
        else:
            smooth_target = alpha / num_classes
        p = max(eps, probs[i])
        loss += -smooth_target * math.log(p)
    return loss

Step 5: Contrastive Loss (Simplified InfoNCE)

python
def cosine_similarity(a, b):
    dot = sum(x * y for x, y in zip(a, b))
    norm_a = math.sqrt(sum(x * x for x in a))
    norm_b = math.sqrt(sum(x * x for x in b))
    if norm_a < 1e-10 or norm_b < 1e-10:
        return 0.0
    return dot / (norm_a * norm_b)
 
def contrastive_loss(anchor, positive, negatives, temperature=0.07):
    sim_pos = cosine_similarity(anchor, positive) / temperature
    sim_negs = [cosine_similarity(anchor, neg) / temperature for neg in negatives]
 
    max_sim = max(sim_pos, max(sim_negs)) if sim_negs else sim_pos
    exp_pos = math.exp(sim_pos - max_sim)
    exp_negs = [math.exp(s - max_sim) for s in sim_negs]
    total_exp = exp_pos + sum(exp_negs)
 
    return -math.log(max(1e-15, exp_pos / total_exp))

Step 6: MSE vs Cross-Entropy on Classification

Train the same network from lesson 04 (circle dataset) with both loss functions. Watch cross-entropy converge faster.

python
import random
 
def sigmoid(x):
    x = max(-500, min(500, x))
    return 1.0 / (1.0 + math.exp(-x))
 
def make_circle_data(n=200, seed=42):
    random.seed(seed)
    data = []
    for _ in range(n):
        x = random.uniform(-2, 2)
        y = random.uniform(-2, 2)
        label = 1.0 if x * x + y * y < 1.5 else 0.0
        data.append(([x, y], label))
    return data
 
 
class LossComparisonNetwork:
    def __init__(self, loss_type="bce", hidden_size=8, lr=0.1):
        random.seed(0)
        self.loss_type = loss_type
        self.lr = lr
        self.hidden_size = hidden_size
 
        self.w1 = [[random.gauss(0, 0.5) for _ in range(2)] for _ in range(hidden_size)]
        self.b1 = [0.0] * hidden_size
        self.w2 = [random.gauss(0, 0.5) for _ in range(hidden_size)]
        self.b2 = 0.0
 
    def forward(self, x):
        self.x = x
        self.z1 = []
        self.h = []
        for i in range(self.hidden_size):
            z = self.w1[i][0] * x[0] + self.w1[i][1] * x[1] + self.b1[i]
            self.z1.append(z)
            self.h.append(max(0.0, z))
 
        self.z2 = sum(self.w2[i] * self.h[i] for i in range(self.hidden_size)) + self.b2
        self.out = sigmoid(self.z2)
        return self.out
 
    def backward(self, target):
        if self.loss_type == "mse":
            d_loss = 2.0 * (self.out - target)
        else:
            eps = 1e-15
            p = max(eps, min(1 - eps, self.out))
            d_loss = -(target / p) + (1 - target) / (1 - p)
 
        d_sigmoid = self.out * (1 - self.out)
        d_out = d_loss * d_sigmoid
 
        for i in range(self.hidden_size):
            d_relu = 1.0 if self.z1[i] > 0 else 0.0
            d_h = d_out * self.w2[i] * d_relu
            self.w2[i] -= self.lr * d_out * self.h[i]
            for j in range(2):
                self.w1[i][j] -= self.lr * d_h * self.x[j]
            self.b1[i] -= self.lr * d_h
        self.b2 -= self.lr * d_out
 
    def compute_loss(self, pred, target):
        if self.loss_type == "mse":
            return (pred - target) ** 2
        else:
            eps = 1e-15
            p = max(eps, min(1 - eps, pred))
            return -(target * math.log(p) + (1 - target) * math.log(1 - p))
 
    def train(self, data, epochs=200):
        losses = []
        for epoch in range(epochs):
            total_loss = 0.0
            correct = 0
            for x, y in data:
                pred = self.forward(x)
                self.backward(y)
                total_loss += self.compute_loss(pred, y)
                if (pred >= 0.5) == (y >= 0.5):
                    correct += 1
            avg_loss = total_loss / len(data)
            accuracy = correct / len(data) * 100
            losses.append((avg_loss, accuracy))
            if epoch % 50 == 0 or epoch == epochs - 1:
                print(f"    Epoch {epoch:3d}: loss={avg_loss:.4f}, accuracy={accuracy:.1f}%")
        return losses
Check: Build It0/8
1. Why does the BCE code clip predictions with eps before taking the log?Explain
The lesson's binary_cross_entropy function with the line p_clipped = max(eps, min(1 - eps, p)) highlighted just before the two math.log calls, and eps = 1e-15 as a default argument.
Code copied from the lesson.
2. What makes the gradient of softmax combined with categorical cross-entropy notable?Explain
A code card showing five logits 2.0, 1.0, 0.1, -1.0, 3.0, target_index 4, the softmax probabilities [0.2333, 0.0858, 0.0349, 0.0116, 0.6343] and the gradient left as a question mark.
softmax output from the lesson's code.
3. What does mse_gradient([0.8, 0.2], [1, 0]) return?Apply
A table of two samples with predictions 0.8 and 0.2 and targets 1 and 0, beside the call mse_gradient([0.8, 0.2], [1, 0]) with the result left as a question mark.
4. What does binary_cross_entropy([0.0], [1.0]) return with the default eps = 1e-15?Apply
A code card with the call binary_cross_entropy([0.0], [1.0]) at the default eps = 1e-15, the return value left as a question mark, and the clipping line p_clipped = max(eps, min(1 - eps, p)).
Code typed from the stem and the lesson.
5. What does cce_gradient return for logits [0, 0, 0, 0] with target_index = 2?Apply
Four class boxes each with logit 0, the box for class 2 highlighted as the target, and the call cce_gradient([0, 0, 0, 0], 2) with its result left as a question mark.
6. With alpha = 0.2 and num_classes = 5, what target value does label_smoothed_cce give the true class?Apply
The target-value lines of the lesson's label_smoothed_cce code, the settings alpha = 0.2 and num_classes = 5, and five boxes for the classes with every target value left as a question mark.
Code from the lesson.
7. The anchor and positive are identical vectors, and the three negatives have the same cosine similarity to the anchor as the positive does. What does contrastive_loss return?Apply
A table of one positive and three negatives for an anchor: the positive has cosine similarity 1.0 because it is identical to the anchor and each negative has the same similarity as the positive. Below is the contrastive_loss call with the result left as a question mark.
8. In the comparison network, the output is 0.01 and the target is 1. Which loss sends the larger gradient d_out into the output layer?Apply
The lesson's backward() lines that compute d_loss for MSE and for BCE and then d_sigmoid and d_out, with the situation self.out = 0.01 and target = 1.
Code copied from the lesson.

Use It

PyTorch provides all standard loss functions with numerical stability built in:

python
import torch
import torch.nn as nn
import torch.nn.functional as F
 
predictions = torch.tensor([0.9, 0.1, 0.7], requires_grad=True)
targets = torch.tensor([1.0, 0.0, 1.0])
 
mse_loss = F.mse_loss(predictions, targets)
bce_loss = F.binary_cross_entropy(predictions, targets)
 
logits = torch.randn(4, 10)
labels = torch.tensor([3, 7, 1, 9])
ce_loss = F.cross_entropy(logits, labels)
ce_smooth = F.cross_entropy(logits, labels, label_smoothing=0.1)

Use F.cross_entropy (not F.nll_loss plus manual softmax). It combines log-softmax and negative log-likelihood in one numerically stable operation. Applying softmax separately then taking the log is less stable: you lose precision in the subtraction of large exponentials.

For contrastive learning, most teams use custom implementations or libraries like lightly or pytorch-metric-learning. The core loop is always the same: compute pairwise similarities, create the softmax over positives and negatives, backpropagate.

Check: Use It0/3
1. Why use F.cross_entropy on raw logits instead of applying softmax yourself and then F.nll_loss?Explain
Two code paths side by side. Path A passes raw logits and labels to F.cross_entropy. Path B applies F.softmax, then torch.log, then F.nll_loss on the same logits and labels.
Code typed from the stem.
2. A teammate applies softmax to the logits, takes the log by hand, and then calls F.nll_loss. Losses look slightly off when the logits get large. What should change?Analyze
A three-line code card: F.softmax on the logits, torch.log of the result, then F.nll_loss with labels, with a note that the loss looks slightly off when the logits get large.
Code typed from the stem.
3. You train a 10-class image classifier in PyTorch from raw logits and want less overconfidence. Which design fits the lesson?Design
A pipeline: a batch of images goes into a model with no softmax layer, which outputs raw logits of shape [B, 10]; with integer labels of shape [B] these feed a loss left as a question mark. The goal is a less overconfident 10-class classifier.

Ship It

This lesson produces:

  • outputs/prompt-loss-function-selector.md, a reusable prompt for choosing the right loss function
  • outputs/prompt-loss-debugger.md, a diagnostic prompt for when your loss curve looks wrong
Author's Note

The loss curve lies to you in one specific, recurring way: it goes down smoothly while the model learns nothing useful. I've hit this exact failure twice. Once with MSE on a classification task, where the number looked great because the model had converged to predicting 0.5 for everything. Once with a contrastive setup where the embeddings quietly collapsed to a single point: cosine similarity between every pair in the batch sat at 1.0, and the loss was practically zero. Both times the fix was the same. Stop staring at the loss number and look at what the model actually outputs. Print ten predictions. Check the variance of your embeddings. Do that before you trust the curve. A falling loss curve proves the optimizer did its job on whatever function you handed it. It doesn't prove that function is the one you meant to write.

Exercises

  1. Implement Huber loss (smooth L1 loss), which is MSE for small errors and MAE for large errors. Train a regression network predicting y = sin(x) with MSE vs Huber when 5% of training targets have random noise added (outliers). Compare final test error.

  2. Add focal loss to the binary classification training loop. Create an imbalanced dataset (90% class 0, 10% class 1). Compare standard BCE vs focal loss (gamma=2) on the minority class recall after 200 epochs.

  3. Implement triplet loss with semi-hard negative mining. Generate 2D embedding data for 5 classes. For each anchor, find the hardest negative that is still farther than the positive (semi-hard). Compare convergence to random triplet selection.

  4. Run the MSE vs cross-entropy comparison but track gradient magnitudes at each layer during training. Plot the average gradient norm per epoch. Verify that cross-entropy produces larger gradients in early epochs when the model is most uncertain.

  5. Implement KL divergence loss and verify that minimizing KL(true || predicted) gives the same gradients as cross-entropy when the true distribution is one-hot. Then try soft targets (like knowledge distillation) where the "true" distribution comes from a teacher model's softmax output.

Key Terms

TermWhat people sayWhat it actually means
Loss function"How wrong the model is"A differentiable function mapping predictions and targets to a scalar that the optimizer minimizes
MSE"Average squared error"Mean of squared differences between predictions and targets; penalizes large errors quadratically
Cross-entropy"The classification loss"Measures divergence between predicted probability distribution and true distribution using -log(p)
Binary cross-entropy"BCE"Cross-entropy for two classes: -(y*log(p) + (1-y)*log(1-p))
Label smoothing"Softening the targets"Replacing hard 0/1 targets with soft values (e.g., 0.1/0.9) to prevent overconfidence and improve generalization
Contrastive loss"Pull together, push apart"A loss that learns representations by making similar pairs close and dissimilar pairs far in embedding space
InfoNCE"The CLIP/SimCLR loss"Normalized temperature-scaled cross-entropy over similarity scores; treats contrastive learning as classification
Focal loss"The imbalanced data fix"Cross-entropy weighted by (1-p_t)^gamma to down-weight easy examples and focus on hard ones
Triplet loss"Anchor-positive-negative"Pushes anchor closer to positive than negative by at least a margin in embedding space
Temperature"Sharpness knob"A scalar divisor on logits/similarities that controls how peaked the resulting distribution is; lower = sharper

Further Reading

  • Lin et al., "Focal Loss for Dense Object Detection" (2017) — introduced focal loss for handling extreme class imbalance in object detection (RetinaNet)
  • Chen et al., "A Simple Framework for Contrastive Learning of Visual Representations" (SimCLR, 2020) — defined the modern contrastive learning pipeline with NT-Xent loss
  • Szegedy et al., "Rethinking the Inception Architecture" (2016) — introduced label smoothing as a regularization technique, now standard in most large models
  • Hinton et al., "Distilling the Knowledge in a Neural Network" (2015) — knowledge distillation using soft targets and KL divergence, foundational for model compression