rumblr Work in progressWIP

● The AI Primer · Lesson 19 · Part 1: how the model works inside

Loss functions

turning "how wrong were we?" into one number

This lesson covers Cross-entropy, perplexity, MSE/MAE, contrastive losses

Members · open during launch 29 min8 figures and diagrams
How it works builds the idea from scratch. Math & code adds the formulas and the Python.

At a glance

Key takeaways

  1. Cross-entropy is −ln(probability on the right answer): small when confident and right, huge when confident and wrong.
  2. Perplexity = e^(average cross-entropy): the effective number of choices per token.
  3. Compute it from logits with log-sum-exp; its gradient is softmax − onehot.
  4. MSE punishes big misses (mean); MAE is robust to outliers (median).
  5. Contrastive (InfoNCE) loss is cross-entropy over "which passage in this batch is mine?"; hard negatives drive retrieval quality.

Level 2

How it works, from scratch

A loss is a scorecard with one number on it. Imagine a coach who, after every practice, must summarise the whole team's performance in a single score so the players know whether they're improving. Training a model does exactly this: it computes the loss, then adjusts the weights to make that number smaller (see primer.ml.neural_net for the loop).

What the scorecard rewards is what the model learns. Language models are scored on how much probability they gave the real next word; price predictors on how far off their numbers were; search models on whether they ranked the right passage above the wrong ones.

Figure 1 · Diagram

Reading it: a prediction and the right answer go into the loss function and one number comes out. Which branch you take depends on what the model predicts: a choice among classes (or the next token) uses cross-entropy, a number uses squared or absolute error, and "which of these belong together" uses a contrastive loss. Each section below climbs one branch.

Chapter 1

Cross-entropy: how surprised were we by the truth?

Picture a weather forecaster scored every evening. If they said "90% chance of rain" and it rained, they lose a point or two. If they said "1% chance of rain" and it poured, they are humiliated. Cross-entropy is that humiliation meter: it charges according to how little probability you gave to what actually happened.

Worked example: the model assigned these probabilities to the correct next token.

Probability on the correct token Loss (−ln p)
0.9 0.11
0.5 0.69
0.1 2.30
0.01 4.61

Being confidently wrong (1%) costs about 44× more than being mostly right (90%): 4.61 / 0.105 ≈ 44.

Figure 2 · Drawn from the lesson's code

0.0 0.2 0.4 0.6 0.8 1.0 probability the model gave the correct answer 0 1 2 3 4 5 loss = −ln p Cross-entropy punishes confident mistakes p=0.9 loss=0.11 p=0.5 loss=0.69 p=0.1 loss=2.30 p=0.01 loss=4.61

The loss −ln p is near zero and flat above 90% but climbs steeply to 4.61 at p = 0.01

Reading it: the horizontal axis is how much probability the model put on the right answer; the vertical axis is the loss it pays. Start at the right edge: at p = 1 the loss is 0 and the curve is nearly flat, so going from 90% to 99% buys little. Now slide left: the curve bends sharply upward, and at p = 0.01 the loss is 4.61. The red dots are the table above. The shape is the whole story: small, diminishing rewards for being more right, and an unbounded bill for being confidently wrong.

The natural logarithm ln(p) answers "e to what power gives p?" For a probability between 0 and 1 the answer is negative (ln 0.5 = −0.69, because e^−0.69 = 0.5), so we flip the sign to get a positive cost. (Every symbol used in these lessons is built from zero in primer.notation.)

Level 3: the formula and its symbols

Symbols

Symbol Meaning here In the example
the loss for one prediction 0.69
probability the model gave to the right answer, between 0 and 1 0.5
natural logarithm: the power you'd raise to, to get the input
flips the sign so the loss is positive

In words: "the loss is minus the natural log of the probability the model gave the right answer."

With the numbers: ; .

Level 3: in Python
import math
p_correct = 0.5
# −ln p_correct
round(-math.log(p_correct), 2)  # → 0.69
# confidently wrong costs far more
round(-math.log(0.01), 2)  # → 4.61

cross_entropy_from_prob is this one line.

Why it matters a language model predicts the next token by choosing among its whole vocabulary, so this is the pretraining loss of every LLM. The steep penalty for confident mistakes is what pushes models toward calibrated probabilities.

Chapter 2

From logits: the numerically safe way

A model doesn't output probabilities directly. It outputs raw scores called logits, which softmax turns into probabilities (raise e to each score, divide by the total; see primer.ml.attention). Computing that literally is like measuring everyone's height in millimetres from the centre of the Earth: the numbers get astronomically large and your calculator overflows. Measure relative to the tallest person instead, and every number stays small. That trick is log-sum-exp.

Worked example: logits (2, 1, 0.1), correct class 0. e² + e¹ + e^0.1 = 7.389 + 2.718 + 1.105 = 11.212, ln 11.212 = 2.417, so the loss is 2.417 − 2 = 0.417. And with logits (1000, 0) and class 1 correct, e^1000 overflows any computer, but log-sum-exp gives exactly 1000 − 0 = 1000.

Figure 3 · Diagram

Reading it: solid arrows are the forward pass, dotted arrows the backward pass. The logits never go through an explicit softmax on the way forward: the largest logit is subtracted first so no exponent can overflow, and the loss is "log of the total" minus "the right class's score". Coming back, the gradient for every class is its predicted probability, minus 1 for the correct class. So the right logit is pushed up by (1 − p), and each wrong logit is pushed down by exactly the probability it took.
Level 3: the formula and its symbols

Symbols

Symbol Meaning here In the example
the logits, one raw score per class (2, 1, 0.1)
the score of class
the index of the correct class 0
the probability softmax gives the correct class 7.389 / 11.212 = 0.659
"add up over every class " three terms
e ≈ 2.718 raised to the score
the largest logit, subtracted for safety 2
"the biggest value over all " 2

In words: "the loss is the log of the sum of e-to-every-score, minus the correct class's score; to compute that log safely, pull the biggest score out front first."

With the numbers: ; $\sum_j e^{z_j - 2} = e^0 + e^{-1} + e^{-1.9} = 1 + 0.368 + 0.150 = 1.518\ln 1.518 = 0.417= 2.417$ and the loss is (and too).

Level 3: in Python
import math
def log_sum_exp(z):
    # m = max_j z_j
    m = max(z)
    # m + ln Σ_j e^(z_j − m)
    return m + math.log(sum(math.exp(z_j - m) for z_j in z))
z, y = [2.0, 1.0, 0.1], 0
round(log_sum_exp(z), 3)  # → 2.417
# ln Σ_j e^(z_j) − z_y
round(log_sum_exp(z) - z[y], 3)  # → 0.417
z, y = [1000.0, 0.0], 1
# e^1000 is never computed: no overflow
log_sum_exp(z) - z[y]  # → 1000.0

The gradient, which is how each logit should move:

Level 3: the formula and its symbols

Symbols

Symbol Meaning here In the example
the gradient: one slope per logit (0.25, −0.25)
the predicted probabilities (0.25, 0.75) for logits (0, ln 3)
1 at the correct class, 0 elsewhere (0, 1)

In words: "each logit's slope is its predicted probability, minus one if it's the right answer."

With the numbers: logits (0, ln 3) give softmax (1/4, 3/4); with class 1 correct, the gradient is (0.25 − 0, 0.75 − 1) = (0.25, −0.25).

Level 3: in Python
import math
z, y = [0.0, math.log(3)], 1
exps = [math.exp(z_j) for z_j in z]
# (1/4, 3/4)
softmax = [e / sum(exps) for e in exps]
# (0, 1)
onehot = [1 if j == y else 0 for j in range(len(z))]
# softmax(z) − onehot(y)
[round(s_j - o_j, 2) for s_j, o_j in zip(softmax, onehot)]  # → [0.25, -0.25]

In code: log_sum_exp pulls the largest logit out front, log_softmax subtracts that total from every logit, and softmax_cross_entropy returns the batch's mean loss together with its softmax − onehot gradient.

Why it matters this is why every framework fuses softmax and cross-entropy into one operation that takes logits (torch.nn.functional.cross_entropy). Computing softmax first and then the log is the classic source of NaN losses.

Chapter 3

Perplexity: how many options is the model torn between?

Imagine a game show with doors, one hiding the prize. If the model is as unsure as someone picking between two doors at random, its perplexity is 2; between ten doors, 10. Perplexity converts an average loss back into that "number of doors".

Worked example: a model that gives the right token 50% every time has loss 0.69 per token and perplexity e^0.69 = 2. One that gives 10% every time has perplexity 10. Three confident tokens (0.9) and one disaster (0.001) give perplexity about 6: one bad guess drags the whole average.

Figure 4 · Diagram

Reading it: start with the probability the model gave each correct token, turn each into a loss with −ln, average them, then undo the log with e^x. The result is back in "number of options" units.
Level 3: the formula and its symbols

Symbols

Symbol Meaning here In the example
perplexity 2
number of tokens 3
a counter over the tokens 1, 2, 3
probability given to the correct token at position 0.5 each
"the average over all tokens"
another way to write

In words: "perplexity is e raised to the average cross-entropy per token."

With the numbers: .

Level 3: in Python
import math
p = [0.5, 0.5, 0.5]
N = len(p)
# (1/N) Σ −ln p_i
average = sum(-math.log(p_i) for p_i in p) / N
round(average, 2)  # → 0.69
# exp(...)
round(math.exp(average), 1)  # → 2.0

In code: perplexity averages cross_entropy_from_prob over the tokens and raises e to the result.

Why it matters perplexity is the standard training metric for language models. It's comparable only between models that use the same tokenizer on the same text, and it says nothing direct about whether answers are helpful or correct.

Chapter 4

Regression losses: fines for being off by an amount

When the prediction is a number (a price, a temperature), think of fines for arriving late. MAE (mean absolute error) is a flat rate: every minute late costs the same. MSE (mean squared error) squares the minutes: 10 minutes late costs 100, not 10, so one very late arrival outweighs many slightly late ones.

Worked example: four predictions miss by 1 and one misses by 10. MSE = (1 + 1 + 1 + 1 + 100) / 5 = 20.8, and the outlier is 100/104 = 96% of it. MAE = (1 + 1 + 1 + 1 + 10) / 5 = 2.8, and the outlier is 10/14 = 71%.

Figure 5 · Drawn from the lesson's code

−4 −2 0 2 4 error y − ŷ 0.0 2.5 5.0 7.5 10.0 12.5 15.0 penalty Penalty per error squared (MSE) absolute (MAE) 0 2 4 6 8 10 12 constant prediction c 0.4 0.5 0.6 0.7 0.8 0.9 1.0 loss (scaled to max 1) Data 1,2,2,3,3,4 and outlier 20 MSE (min at mean 5.0) MAE (min at median 3)

The squared penalty pulls away past an error of 1, and one outlier drags MSE's best constant to the mean 5.0 while MAE's stays at the median 3

Reading it: the left panel is the price of a single error. Near zero the two curves are similar, but past an error of 1 the squared penalty climbs away from the absolute one. The right panel asks: if the model could predict only one constant for the data 1, 2, 2, 3, 3, 4 plus an outlier of 20, which constant minimizes each loss? MSE's valley sits at the mean (5.0), dragged right by the outlier; MAE's valley sits at the median (3), where most of the data is.
Level 3: the formula and its symbols

Symbols

Symbol Meaning here In the example
number of predictions 5
the true value for example 0 for all five
"y-hat", the predicted value 1, −1, 1, −1, 10
the error (residual) −1, 1, −1, 1, −10
square: multiply by itself (always positive)
absolute value: drop the sign

In words: "MSE is the average of the squared errors; MAE is the average of the errors ignoring their sign."

With the numbers: MSE ; MAE .

Level 3: in Python
y = [0, 0, 0, 0, 0]
y_hat = [1, -1, 1, -1, 10]
N = len(y)
# MSE
sum((y_i - y_hat_i) ** 2 for y_i, y_hat_i in zip(y, y_hat)) / N  # → 20.8
# MAE
sum(abs(y_i - y_hat_i) for y_i, y_hat_i in zip(y, y_hat)) / N  # → 2.8

In code: mse and mae are the two averages, and outlier_share measures how much of each total the single largest error contributes (the 96% and 71% above).

Why it matters pick the loss whose valley is where you want your predictions. MSE chases outliers (its best constant is the mean); MAE shrugs them off (its best constant is the median). For noisy data with occasional wild values, MAE or a blend (Huber loss) is safer.

Chapter 5

Contrastive loss (InfoNCE): find your partner in a crowd

Picture a party game. Everyone arrives in pairs, gets separated, and must pick their partner out of the whole room. Everyone else in the room is a decoy. A contrastive loss scores how confidently each person picks their own partner over every decoy. The hardest decoy is your partner's lookalike twin: same topic, wrong person. That's a hard negative.

Embedding models (and CLIP) learn this way: a query and the passage that answers it are "partners"; the other passages in the same batch are free decoys (in-batch negatives).

Worked example: two queries and two passages, as vectors, scored with the dot product (multiply matching entries, add them up). Query 1 = (1, 0), query 2 = (0, 1), passage 1 = (1, 0), passage 2 = (0, 1), temperature 1. Query 1 scores 1 against its partner and 0 against the decoy. Its loss is −ln(e¹ / (e¹ + e⁰)) = ln(1 + e^−1) = 0.3133. Swap the passages and each query now prefers the decoy: the loss rises to ln(1 + e¹) = 1.3133.

Figure 6 · Diagram

Reading it: a batch of queries and the passages that answer them are turned into vectors, then every query is scored against every passage, giving a square grid. Each row becomes a classification ("which of these passages is mine?") whose right answer is on the diagonal. Cross-entropy on those rows pulls each query toward its own passage and away from all the others at once.

Figure 7 · Drawn from the lesson's code

0 1 2 3 4 document j −0.5 0.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 query i InfoNCE: softmax over each row (τ = 0.1) 0.92 0.05 0.03 0.00 0.00 0.01 0.95 0.00 0.03 0.00 0.00 0.02 0.07 0.00 0.91 0.00 0.00 0.00 1.00 0.00 0.0 0.2 0.4 0.6 0.8 1.0

Rows 0, 1 and 3 put over 0.9 on their own passage; row 2 puts 0.91 on the hard negative and only 0.07 on its own

Reading it: rows are queries, columns are passages, and each cell is the probability that query i "picks" passage j. A well-trained model shows a bright diagonal. Look at row 2: it puts most of its probability on column 4, an extra mined hard negative built to resemble query 2, and only a few percent on its own passage (column 2). The model is fooled, and that row is exactly where the loss, and therefore the gradient, concentrates. Rows 0, 1 and 3 are already confident and contribute almost nothing to learning.
Level 3: the formula and its symbols

Symbols

Symbol Meaning here In the example
the loss for query 0.3133
query 's vector
query 's own passage (its positive)
passage : every passage in the batch, the positive included
dot product: how aligned query and passage are ,
"tau", the temperature: divides scores; small sharpens, large softens 1
number of passages scored (batch plus any extra negatives) 2
raised to the power

In words: "for each query, take e-to-the-score of its own passage, divide by the sum of e-to-the-score over every passage in the batch, and charge minus the log of that share."

With the numbers: $-\ln \frac{e^{1}}{e^{1} + e^{0}} = -\ln \frac{2.718}{3.718} = -\ln 0.731 = 0.3133$. At temperature 0.1 the same vectors give : sharper.

Level 3: in Python
import math
# the queries
q = [[1, 0], [0, 1]]
# their passages: d[i] is q[i]'s partner
d = [[1, 0], [0, 1]]
def dot(a, b):
    return sum(a_k * b_k for a_k, b_k in zip(a, b))
def info_nce(i, tau):
    # exp(q_i · d_j / τ) for every j
    scores = [math.exp(dot(q[i], d_j) / tau) for d_j in d]
    # −ln (own passage's share)
    return -math.log(scores[i] / sum(scores))
round(info_nce(0, tau=1.0), 4)  # → 0.3133
print(f"{info_nce(0, tau=0.1):.6f}")  # → 0.000045

It's just cross-entropy where the "classes" are the passages in the batch, so the gradient is the same softmax − onehot, pushed back through the dot products into both sets of vectors.

In code: info_nce builds the score grid, hands it to softmax_cross_entropy with the diagonal as the right answers, and returns the gradients for both sets of vectors; info_nce_gradient_check confirms those gradients against small nudges of every number.

Why it matters this is how search and RAG embedding models are trained (see primer.ml.embeddings.contrastive). Bigger batches mean more free negatives. Easy negatives are already far away and contribute almost no gradient; hard negatives produce most of the learning signal, which is why mining them is the biggest lever on retrieval quality.

Chapter 6

Label smoothing: never say 100%

A good forecaster never says "100% chance of rain", because the one day they're wrong would be infinitely embarrassing. Label smoothing teaches the model the same humility: instead of "the answer is class 2, with total certainty", the target says "class 2, with 92.5% certainty, and a sliver of doubt spread over everything".

Worked example: 4 classes, correct class 2, smoothing ε = 0.1. Take 0.9 of the one-hot target (0, 0, 0.9, 0) and add 0.1/4 = 0.025 to every class: (0.025, 0.025, 0.925, 0.025). A model that puts logit 50 on the right class has plain cross-entropy ≈ 0, but smoothed cross-entropy 3 × 0.025 × 50 = 3.75.

Figure 8 · Diagram

Reading it: the hard one-hot label and a uniform distribution are blended with weight ε, giving a target that is still 92.5% sure but never 100%. Training against it means the loss can never reach zero, so the model gains nothing by pushing its logits toward infinity.
Level 3: the formula and its symbols

Symbols

Symbol Meaning here In the example
the smoothed target distribution (0.025, 0.025, 0.925, 0.025)
"epsilon", how much certainty to give away 0.1
number of classes 4
a counter over the classes 1 to 4
target weight on class 0.925 on the right class
predicted probability of class ≈1 on the right class

In words: "the target keeps (1 − ε) on the right answer and spreads ε evenly over all classes; the loss is cross-entropy against that softer target."

With the numbers: on the right class and elsewhere; with logits (0, 0, 50, 0) the three wrong classes each have , so .

Level 3: in Python
import math
eps, K, y = 0.1, 4, 2
# t_k
t = [(1 - eps) * (1 if k == y else 0) + eps / K for k in range(K)]
[round(t_k, 3) for t_k in t]  # → [0.025, 0.025, 0.925, 0.025]
z = [0.0, 0.0, 50.0, 0.0]
m = max(z)
log_total = m + math.log(sum(math.exp(z_k - m) for z_k in z))
# ln softmax(z)_k
log_softmax = [z_k - log_total for z_k in z]
# −Σ_k t_k ln softmax(z)_k
round(-sum(t_k * ls_k for t_k, ls_k in zip(t, log_softmax)), 2)  # → 3.75

In code: smoothed_targets builds the soft target t, and smoothed_cross_entropy scores the logits against it.

Why it matters it curbs over-confidence and often improves calibration (how well the model's stated confidence matches how often it's right). It was used to train the original transformer.

Test yourself

6 questions

Answer each one out loud or on paper before you open it. If you can explain it, you know it.

Question 1The model puts 1% on the right token. What's the loss, and why so large?Think it through, then reveal

−ln(0.01) = 4.61, about 44× the loss at 90% (0.105). The log punishes confident mistakes steeply, which pushes the model toward calibrated probabilities.

Question 2Loss is 0.69 per token. What's the perplexity?Think it through, then reveal

e^0.69 = 2: as uncertain as a coin flip between two options.

Question 3Why compute cross-entropy from logits instead of from softmax outputs?Think it through, then reveal

Softmax of large logits overflows, and the log of a tiny probability underflows to −∞. Log-sum-exp subtracts the max first and never exponentiates a large number. The fused gradient is simply softmax − onehot.

Question 4When would you pick MAE over MSE?Think it through, then reveal

When outliers are noise you don't want to chase. MSE squares errors, so one huge miss dominates; MAE weighs errors linearly.

Question 5How does InfoNCE get negatives without labelling them?Think it through, then reveal

It uses the other items in the batch: for each query, every other query's positive passage is a negative. Bigger batches mean more (and harder) negatives for free.

Question 6Why do hard negatives matter?Think it through, then reveal

Easy negatives are already far away and contribute almost no gradient. Hard negatives score high and produce most of the learning signal, teaching the model to separate "on topic" from "actually answers the question".

Primary sources

The papers behind this lesson

van den Oord, Li & Vinyals, Representation Learning with Contrastive Predictive Coding (2018): Named and analysed InfoNCE, the "pick the positive out of N" contrastive loss.

The paper ↗

Chen et al., A Simple Framework for Contrastive Learning of Visual Representations (SimCLR, 2020): Showed how much in-batch negatives, large batches and the temperature matter for contrastive training.

The paper ↗

Radford et al., Learning Transferable Visual Models From Natural Language Supervision (CLIP, 2021): Trained image and text encoders with a symmetric InfoNCE loss on 400 million pairs, putting both in one vector space.

Read the annotated companion →The paper ↗

Szegedy et al., Rethinking the Inception Architecture for Computer Vision (2015): Introduced label smoothing as a regularizer against over-confident predictions.

The paper ↗

Researcher's shelf

Further reading

  • Goodfellow, Bengio & Courville, Deep Learning, ch. 6 (cost functions): https://www.deeplearningbook.org/contents/mlp.html
  • CS231n notes, Linear classification (softmax and cross-entropy): https://cs231n.github.io/linear-classify/
  • PyTorch cross_entropy: https://pytorch.org/docs/stable/generated/torch.nn.functional.cross_entropy.html
  • van den Oord et al., Representation Learning with Contrastive Predictive Coding (InfoNCE, 2018): https://arxiv.org/abs/1807.03748
  • Chen et al., SimCLR (in-batch negatives and temperature, 2020): https://arxiv.org/abs/2002.05709
  • Radford et al., CLIP (2021): https://arxiv.org/abs/2103.00020
  • Szegedy et al., Rethinking the Inception Architecture (label smoothing, 2015): https://arxiv.org/abs/1512.00567
  • Müller et al., When Does Label Smoothing Help? (2019): https://arxiv.org/abs/1906.02629

About this lesson. This is the illustrated edition of a lesson from the open-source AI Primer. Its text, figures and numbers are generated from the Primer's source at commit c8d5c21, so the two always agree: the explanation, the code that builds it and the tests that prove it.