Roadmap / Theory

Loss Functions

MSE, binary and multi-class cross-entropy from logits with their gradients, and the masked, label-smoothed loss of a language model.

To train a model we need one number that says how wrong it is: the loss. Training then follows the gradient of the loss (section 1) to make it smaller. A language model is trained with exactly one loss, the cross-entropy of the next token. This page builds it step by step, makes it safe to compute, and adds the two details every LLM (large language model) training loop has: ignored positions and label smoothing.

The idea: minus the log of the right answer's probability

Most models do not give one answer: they give a probability to every possible answer. The natural way to score such a model is to look at the probability it gave to the answer that turned out to be right. High probability: good. Low probability: bad.

The loss of one example is minus the log of that probability, −log⁡p-\log p. The log turns probabilities into a scale that punishes confident mistakes hard:

probability of the right answer −log⁡p-\log p
1 0 (perfect)
0.9 0.105
0.5 0.693
0.1 2.303
0.01 4.605

For a whole dataset, average over the examples:

L(θ)=−1N∑n=1Nlog⁡pθ(yn∣xn)\mathcal{L}(\theta) = -\frac{1}{N} \sum_{n=1}^{N} \log p_\theta(y_n \mid x_n)

where:

  • θ\theta (theta) are the model's parameters;
  • NN is the number of examples, and (xn,yn)(x_n, y_n) is example nn: an input xnx_n with its right answer yny_n;
  • pθ(yn∣xn)p_\theta(y_n \mid x_n), read "the probability of yny_n given xnx_n", is the probability the model gives to the right answer.

Example. On two examples the model gives the right answer probability 0.90.9 and 0.50.5. The losses are 0.1050.105 and 0.6930.693, and their average is L≈0.40\mathcal{L} \approx 0.40.

This is called the negative log-likelihood, and minimizing it is maximum likelihood: choosing the parameters under which the training data is most probable. Why a log and an average, rather than multiplying the probabilities directly? The probability of the whole dataset is the product of the probabilities of its examples, and the log turns a product into a sum, which is much easier to work with. The minus sign is there because optimizers minimize.

The familiar losses are all this one

Depending on what the model outputs, the same recipe gives the losses you may have seen under different names:

Model output Assumed distribution of yy Negative log-likelihood
a real number y^\hat y Gaussian, mean y^\hat y, fixed variance 12(y−y^)2\tfrac{1}{2}(y - \hat y)^2 + const: MSE (mean squared error)
one probability qq Bernoulli (y∈{0,1}y \in \{0, 1\}) −ylog⁡q−(1−y)log⁡(1−q)-y \log q - (1-y)\log(1-q): binary cross-entropy
a distribution qq over CC classes categorical −log⁡qy-\log q_y: cross-entropy

Here y^\hat y ("y hat") is the model's prediction. How to read the rows:

  • Predicting a number (a price, a reward): if you assume the true value is the prediction plus bell-shaped (Gaussian) noise, minus the log-probability is the squared error, up to constants. So the MSE is not an arbitrary choice.
  • Predicting yes or no: the model gives a probability qq of "yes", and the loss is −log⁡q-\log q when the answer is yes, −log⁡(1−q)-\log(1 - q) when it is no. The formula in the table is just these two cases in one line.
  • Choosing among CC classes: the model gives a probability qcq_c to each class cc, and the loss is −log⁡qy-\log q_y for the right class yy. For a language model the classes are the tokens of the vocabulary, so this is the row that matters.

Cross-entropy, KL divergence and perplexity

Sometimes the right answer is not a single class but a distribution, for example 70% "cat", 20% "dog", 10% "sun". Call the true distribution pp and the model's distribution qq, both over the same CC classes. The cross-entropy averages −log⁡qc-\log q_c with the true probabilities as weights:

H(p,q)=−∑cpclog⁡qcH(p, q) = -\sum_c p_c \log q_c

where pcp_c and qcq_c are the true and the model's probabilities of class cc. It splits into two parts:

H(p,q)=H(p)+DKL(p ∥ q)H(p, q) = H(p) + D_{\mathrm{KL}}(p \,\|\, q)

where:

  • H(p)=−∑cpclog⁡pcH(p) = -\sum_c p_c \log p_c is the entropy of the data: how unpredictable the answer is by itself. The model cannot change it;
  • DKL(p ∥ q)D_{\mathrm{KL}}(p \,\|\, q) is the KL (Kullback–Leibler) divergence: how far the model's distribution is from the true one. It is 0 only when q=pq = p.

Example. True p=(0.7,0.2,0.1)p = (0.7, 0.2, 0.1), model q=(0.5,0.3,0.2)q = (0.5, 0.3, 0.2).

  • Cross-entropy: −(0.7log⁡0.5+0.2log⁡0.3+0.1log⁡0.2)≈0.887-(0.7 \log 0.5 + 0.2 \log 0.3 + 0.1 \log 0.2) \approx 0.887.
  • Entropy: −(0.7log⁡0.7+0.2log⁡0.2+0.1log⁡0.1)≈0.802-(0.7 \log 0.7 + 0.2 \log 0.2 + 0.1 \log 0.1) \approx 0.802.
  • KL divergence: the difference, 0.887−0.802=0.0850.887 - 0.802 = 0.085.

If the model predicted q=pq = p exactly, the cross-entropy would drop to 0.8020.802 and the KL divergence to 0.

So minimizing the cross-entropy is the same as minimizing the KL divergence: the model is pulled towards the true distribution. In ordinary training the target is one-hot: the right class yy has py=1p_y = 1 and all others 0. Then the sum has a single term, −log⁡qy-\log q_y, which is the last row of the table above.

Two numbers help read a loss value:

  • The uniform baseline. A model that knows nothing gives every class 1/C1/C, so its loss is −log⁡(1/C)=log⁡C-\log(1/C) = \log C. For the 50,257-token vocabulary of GPT-2 (Generative Pre-trained Transformer 2) that is log⁡50257≈10.8\log 50257 \approx 10.8 nats (the unit of a loss computed with the natural log). A freshly initialized language model should start near this value; if yours starts far above it, the initialization is too large.
  • Perplexity is eLe^{\mathcal{L}}: the number of equally likely tokens the model is, on average, hesitating between. A loss of 2.3 nats is a perplexity of about 10: as uncertain as picking among 10 tokens.

Cross-entropy from logits

A network does not output probabilities directly: it outputs logits zz, one raw score per class, and softmax turns them into probabilities qq (see the NumPy section). Computing log(softmax(z)) is unsafe: softmax can round a tiny probability to exactly 0, and then the log is −∞-\infty. Instead, use the LSE (log-sum-exp) function, which stays finite:

ℓ=−log⁡qy=LSE(z)−zy\ell = -\log q_y = \mathrm{LSE}(z) - z_y

where:

  • ℓ\ell is the loss of one example;
  • zyz_y is the logit of the right class yy;
  • LSE(z)=log⁡∑cezc\mathrm{LSE}(z) = \log \sum_c e^{z_c}, computed with the stable trick of the NumPy section.

Example. Logits z=(2,1,0)z = (2, 1, 0), right class y=0y = 0.

  1. LSE(z)=log⁡(e2+e1+e0)≈2.408\mathrm{LSE}(z) = \log(e^2 + e^1 + e^0) \approx 2.408.
  2. ℓ=2.408−z0=2.408−2=0.408\ell = 2.408 - z_0 = 2.408 - 2 = 0.408.

Check: softmax gives q≈(0.665,0.245,0.090)q \approx (0.665, 0.245, 0.090), and −log⁡0.665≈0.408-\log 0.665 \approx 0.408.

The gradient: probabilities minus the target

The gradient of this loss with respect to the logits is remarkably simple:

∂ℓ∂zc=qc−[c=y]\frac{\partial \ell}{\partial z_c} = q_c - [c = y]

where qcq_c is the softmax probability of class cc, and [c=y][c = y] is 1 when cc is the right class and 0 otherwise. It comes from two facts: the derivative of LSE(z)\mathrm{LSE}(z) with respect to zcz_c is qcq_c, and the derivative of zyz_y is 1 for c=yc = y and 0 for the others.

In words: the gradient is the probabilities minus the one-hot target.

Example. Same logits, q≈(0.665,0.245,0.090)q \approx (0.665, 0.245, 0.090).

  • Right class y=0y = 0: gradient (0.665−1, 0.245, 0.090)=(−0.335, 0.245, 0.090)(0.665 - 1,\ 0.245,\ 0.090) = (-0.335,\ 0.245,\ 0.090).
  • Right class y=2y = 2 instead: gradient (0.665, 0.245, 0.090−1)=(0.665, 0.245, −0.910)(0.665,\ 0.245,\ 0.090 - 1) = (0.665,\ 0.245,\ -0.910).

Training moves against the gradient, so the right class's logit goes up (its gradient is negative) and every other logit goes down, each in proportion to its probability. The worse the right answer's probability, the bigger the push.

The gradient of each example adds up to zero, and every entry stays between −1-1 and 11, whatever the logits. This is why frameworks fuse softmax and cross-entropy into one operation: done separately, the softmax backward multiplies by tiny numbers and the log backward divides by them.

For a batch, the loss is the mean over the NN examples, so every example's gradient is also divided by NN. Forgetting that factor makes the right learning rate silently depend on the batch size.

Binary cross-entropy from a logit

For a yes/no answer the network outputs one logit xx, and the probability of "yes" is the sigmoid, which squashes any number into (0,1)(0, 1):

q=σ(x)=11+e−xq = \sigma(x) = \frac{1}{1 + e^{-x}}

Example. σ(2)≈0.881\sigma(2) \approx 0.881, σ(0)=0.5\sigma(0) = 0.5, σ(−3)≈0.047\sigma(-3) \approx 0.047.

The binary cross-entropy, −ylog⁡q−(1−y)log⁡(1−q)-y \log q - (1 - y) \log(1 - q), breaks for large logits: σ(40)\sigma(40) rounds to exactly 1.01.0, and then log⁡(1−q)=log⁡0=−∞\log(1 - q) = \log 0 = -\infty. It can be rewritten without ever computing qq:

ℓ(x,y)=max⁡(x,0)−xy+log⁡ ⁣(1+e−∣x∣)\begin{aligned} \ell(x, y) = {} & \max(x, 0) - x y \\ & + \log\!\left(1 + e^{-|x|}\right) \end{aligned}

where yy is the target (1 for yes, 0 for no) and ∣x∣|x| is the absolute value of xx.

Why this form is safe: the exponent −∣x∣-|x| is never positive, so e−∣x∣e^{-|x|} is between 0 and 1 and nothing overflows; and log⁡(1+something between 0 and 1)\log(1 + \text{something between 0 and 1}) is never log⁡0\log 0.

Example.

  • x=2x = 2, y=1y = 1: 2−2+log⁡(1+e−2)≈0.1272 - 2 + \log(1 + e^{-2}) \approx 0.127, the same as −log⁡σ(2)=−log⁡0.881-\log \sigma(2) = -\log 0.881.
  • x=40x = 40, y=0y = 0: the naive formula returns inf; the stable one gives 40−0+log⁡(1+e−40)=4040 - 0 + \log(1 + e^{-40}) = 40, the correct loss.

The gradient has the same shape as for cross-entropy, prediction minus target: ∂ℓ/∂x=σ(x)−y\partial \ell / \partial x = \sigma(x) - y. The target yy may also be a soft target, any number between 0 and 1, such as 0.80.8 for "probably yes".

The loss of a language model in practice

A language model predicts the next token at every position of every text in the batch: logits of shape (B,T,V)(B, T, V) (batch size, sequence length, vocabulary size) and targets of shape (B,T)(B, T). The loss is the mean cross-entropy over all positions, with two refinements.

Ignored positions

Some positions should not be learned: padding tokens, added only to make the texts of a batch the same length, and in fine-tuning the prompt tokens. Their target is set to a special value (PyTorch uses ignore_index=-100). These positions contribute nothing, and the mean is taken over the remaining positions only.

Example. A batch has 4 positions with losses 0.40.4, 1.21.2 and 0.80.8, and one padding position. The loss is (0.4+1.2+0.8)/3=0.8(0.4 + 1.2 + 0.8) / 3 = 0.8. Dividing by all 4 positions would give 0.60.6: the loss would look better just because the batch had more padding.

Label smoothing

Label smoothing replaces the one-hot target with a softer one: 1−ε1 - \varepsilon on the right class, plus ε\varepsilon spread evenly over all CC classes. The loss becomes

ℓ=(1−ε) (−log⁡qy)+εC∑c(−log⁡qc)\begin{aligned} \ell = {} & (1 - \varepsilon)\,(-\log q_y) \\ & + \frac{\varepsilon}{C} \sum_c (-\log q_c) \end{aligned}

where ε\varepsilon (epsilon), for example 0.10.1, is the amount of smoothing. In words: mostly the usual loss, plus a small penalty for giving any class a very low probability.

Example. Same logits as above, q≈(0.665,0.245,0.090)q \approx (0.665, 0.245, 0.090), y=0y = 0, ε=0.1\varepsilon = 0.1, C=3C = 3.

  1. −log⁡qc-\log q_c for the three classes: 0.4080.408, 1.4081.408, 2.4082.408.
  2. First part: 0.9⋅0.408≈0.3670.9 \cdot 0.408 \approx 0.367.
  3. Second part: 0.13(0.408+1.408+2.408)≈0.141\tfrac{0.1}{3}(0.408 + 1.408 + 2.408) \approx 0.141.
  4. Total: ℓ≈0.508\ell \approx 0.508, compared with 0.4080.408 without smoothing.

Label smoothing stops the model from pushing logits towards infinity to reach a probability of exactly 1, and it tends to improve calibration (Müller et al., below). Whether it helps depends on the task, but every framework exposes it, so it is worth knowing exactly what it computes.

References

Practice

0/4