ml-models / Core ML concepts / Loss functions intro

Loss functions

A loss turns "how wrong is this prediction" into one number that gradient descent can shrink. This page shows that nearly every common loss is a negative log-likelihood under some assumed noise, walks through the regression, classification, margin, ranking and contrastive families, explains reduction and ignore_index, and ends with how to choose one.

On this page
  1. Problem
  2. Intuition
  3. Mechanics
  4. Regression: squared, absolute, Huber
  5. Classification: cross-entropy over logits
  6. Margin losses: hinge and logistic
  7. Ranking and contrastive losses
  8. Reduction and ignore_index
  9. Worked example
  10. Implementation
  11. Choosing a loss
  12. Tradeoffs
  13. Check yourself
  14. Connections
  15. Read first
  16. Read next
  17. References

Problem

Training needs a target to minimize. The thing you actually care about, such as accuracy, revenue, or whether a ranking put the right item first, is usually a step function of the model's output: change a weight slightly and it doesn't move, so its gradient is zero almost everywhere and useless for learning. A loss function is a smooth stand-in that is small when predictions are good and has a gradient that points toward better ones.

The choice matters more than it looks. The same data fit with squared error and with absolute error gives different models: one chases outliers, the other ignores them. A classifier trained on squared error over probabilities learns slowly when it is confidently wrong. And a loss averaged over a batch quietly changes meaning when some targets are padding. You need to know what each loss assumes so you can pick the one that matches the problem.

Intuition

Pick a probability model for how targets scatter around your prediction, then score the prediction by how surprised that model is by the real target: the negative log-likelihood. Each noise model gives a familiar loss:

  • Gaussian noise → −log p is ½(y − ŷ)²/σ² plus a constant: mean squared error. Its minimizer is the mean.
  • Laplace noise → |y − ŷ|/b plus a constant: mean absolute error. Its minimizer is the median.
  • Bernoulli outcome with probability σ(z) → binary cross-entropy.
  • Categorical outcome with probabilities softmax(z) → cross-entropy.

The shape of a loss tells you how it treats errors. Squared error grows quadratically, so one residual of 10 costs as much as a hundred residuals of 1. Absolute error grows linearly, so large errors get no extra weight. Huber is squared near zero and linear past a threshold δ: smooth where errors are small, robust where they are large.

Squared, absolute and Huber loss as the residual grows Three curves of loss against the residual y minus y-hat, from −4 to 4. Squared error is a parabola that leaves the plot past a residual of about 2.8. Absolute error is a V. Huber loss with delta 2 follows half the parabola for residuals inside ±2 and a straight line outside. At a residual of 3 the losses are 9, 3 and 4. -4 -2 0 2 4 0 4 8 squared absolute Huber δ = 2 −δ δ residual r = y − ŷ

Squared error punishes large residuals hardest; Huber keeps its smooth bottom but grows only linearly past δ, so outliers can't dominate the gradient.

Mechanics

Regression: squared, absolute, Huber

MSE = (1/N) Σ (yᵢ − ŷᵢ)²    MAE = (1/N) Σ |yᵢ − ŷᵢ|
Huberδ(r) = ½ r² if |r| ≤ δ, otherwise δ(|r| − ½δ)

The gradients tell the story. Squared error's gradient with respect to ŷ is −2r: it grows with the error, so a big outlier pulls hard. Absolute error's gradient is −sign(r): the same push for every error, which is robust but never shrinks near the answer, so it bounces unless the learning rate decays. Huber's gradient is −r inside δ and −δ·sign(r) outside: proportional when close, clipped when far. PyTorch's SmoothL1Loss is Huber divided by δ (its beta plays the role of δ).

Classification: cross-entropy over logits

A binary classifier outputs a logit z, and p = σ(z) = 1/(1 + e−z). The Bernoulli negative log-likelihood of a label y ∈ {0, 1} is −[y log p + (1 − y) log(1 − p)]. Computed literally, σ(z) rounds to exactly 1 for large z and the log of 1 − p becomes −∞. Rewriting in terms of z gives a form that never overflows:

BCE(z, y) = max(z, 0) − z·y + log(1 + e−|z|)

This is what BCEWithLogitsLoss computes, and why it takes logits rather than probabilities. The gradient is simply σ(z) − y. Multi-class is the same idea with softmax: cross-entropy over logits, CE(z, k) = −zk + log Σj ezj, with gradient softmax(z) − onehot(k). The entropy page derives it in full.

Margin losses: hinge and logistic

With labels y ∈ {−1, +1}, the margin m = y·z is positive when the sign of z is right and large when it is right with room to spare. The loss you'd like is 0-1: 1 if m ≤ 0, else 0. It has no useful gradient, so it is replaced by a convex upper bound:

  • Hinge, max(0, 1 − m): zero once the margin passes 1, so correct and confident points stop contributing. This is the support vector machine's loss.
  • Logistic, log(1 + e−m): binary cross-entropy rewritten in margin form. It never reaches zero, so it keeps nudging every point and gives calibrated probabilities.
Zero-one, hinge and logistic loss as functions of the margin Loss against the margin m = y times z, from −3 to 3. The zero-one loss is a step: 1 for m at or below 0, then 0. Hinge loss is a straight line falling from 4 at m = −3 to 0 at m = 1, then flat at 0. Logistic loss is a smooth curve from about 3.05 at m = −3 through 0.69 at m = 0, approaching but never reaching 0. Both hinge and logistic lie on or above the zero-one step. -3 -2 -1 0 1 2 3 0 1 2 3 4 hinge logistic 0-1 correct with margin wrong side margin m = y · z

Hinge and logistic are convex upper bounds on the 0-1 loss: minimize them and you push the error rate down while still getting gradients.

Ranking and contrastive losses

Sometimes there is no target value, only "this should score higher than that". Ranking losses compare pairs or sets of scores:

  • Margin ranking: max(0, −y(s₁ − s₂) + margin), for "s₁ should beat s₂ by at least the margin".
  • Triplet: max(0, d(a, p) − d(a, n) + margin), pulling an anchor toward a positive and away from a negative in embedding space.
  • InfoNCE (contrastive): cross-entropy where the "classes" are one positive and many negatives, scored by similarity divided by a temperature τ. CLIP and SimCLR use it with every other item in the batch as a negative.

The temperature sets how sharply the loss focuses. A small τ magnifies differences in similarity, so the loss concentrates on the hardest negatives; a large τ spreads attention across all of them.

Reduction and ignore_index

Losses are computed per element, then reduced: reduction="none" keeps the vector, "sum" adds it, and "mean" (the default) divides by the count. For classification, ignore_index marks targets to skip, usually padding tokens. Skipped elements contribute 0 and are also left out of the mean's denominator, so the mean is over real tokens only. With class weights, the mean divides by the sum of the weights of the kept targets instead.

TermShape/ParamsMeaningSource
_Loss reduction Base class: stores the reduction every loss module uses. _Loss
nn.MSELoss input, target any matching shape Squared error; Gaussian NLL up to scale. MSELoss, F.mse_loss
nn.L1Loss input, target any matching shape Absolute error; Laplace NLL up to scale. L1Loss, F.l1_loss
nn.HuberLoss delta (default 1.0) Quadratic inside δ, linear outside. HuberLoss, F.huber_loss
nn.SmoothL1Loss beta Huber divided by β; equals L1 far from zero. SmoothL1Loss
nn.BCEWithLogitsLoss logits and targets same shape; pos_weight Sigmoid plus binary cross-entropy in the stable form. BCEWithLogitsLoss, F.binary_cross_entropy_with_logits
nn.CrossEntropyLoss logits (N, C), target (N,); ignore_index, label_smoothing Log-softmax plus NLL, over logits. CrossEntropyLoss, F.cross_entropy
nn.NLLLoss log-probabilities (N, C), target (N,) Picks −log p of the target; the second half of cross-entropy. NLLLoss, F.nll_loss
nn.HingeEmbeddingLoss, nn.MultiMarginLoss margin, p Hinge losses for pairs and for multi-class scores. HingeEmbeddingLoss, MultiMarginLoss, F.multi_margin_loss
nn.MarginRankingLoss x1, x2, y ∈ {−1, 1} Pairwise ranking with a margin. MarginRankingLoss, F.margin_ranking_loss
nn.TripletMarginLoss anchor, positive, negative (N, D); margin, p Embedding loss: anchor nearer the positive than the negative. TripletMarginLoss, F.triplet_margin_loss
nn.CosineEmbeddingLoss x1, x2, y ∈ {−1, 1} Pull similar pairs to cosine 1, push others below a margin. CosineEmbeddingLoss
InfoNCE similarities / τ, positive index Cross-entropy over one positive and many negatives. van den Oord et al., 2018

Worked example

One outlier, three losses. Fit a single constant c to the targets 2.0, 2.5, 3.0, 3.2, 3.6, 12.0, where the last value is an outlier. Each loss picks a different c:

  • Squared error is minimized by the mean: c = 4.383. The outlier drags it above five of the six targets.
  • Absolute error is minimized by the median: c = 3.1, the midpoint of 3.0 and 3.2. The outlier's size doesn't matter, only which side it is on.
  • Huber with δ = 2 sets the clipped residuals to sum to zero: c = 3.26. The outlier still counts, but only with force δ.
Where each loss puts the best constant A number line from 1 to 13 with six targets marked as dots at 2.0, 2.5, 3.0, 3.2, 3.6, 12.0. Markers show the constant each loss chooses: the median at 3.1 for absolute error, Huber at 3.26, and the mean at 4.383 for squared error, which is pulled toward the outlier at 12. 1 2 3 4 5 6 7 8 9 10 11 12 13 outlier median 3.1 (MAE) Huber 3.26 mean 4.38 (MSE) target value

The mean moves toward the outlier; the median ignores its size; Huber sits in between, close to the median because only one point lies beyond δ.

Binary cross-entropy over logits. For logit z = 2, σ(z) ≈ 0.881: the loss is 0.127 if the label is 1 and 2.127 if it is 0. At z = 0, the model is undecided and the loss is ln 2 = 0.693 either way. At z = −3 with label 1 the loss is 3.049. At z = 1000 a naive σ then log gives −log 0 = ∞ for label 0; the stable form gives exactly 1000.

Triplet. Anchor (0, 0), positive (1, 0), negative (1.5, 1), margin 1. d(a, p) = 1 and d(a, n) = √3.25 = 1.803, so the loss is max(0, 1 − 1.803 + 1) = 0.197. The negative is farther than the positive but not by the full margin, so it still pushes.

InfoNCE and temperature. An image's similarities to three captions are 0.9, 0.3, 0.1, with the matching caption first. At τ = 1 softmax gives 0.5, 0.275, 0.225 and the loss is 0.692: the model barely prefers the match. At τ = 0.1 the same similarities give 0.9972, 0.0025, 0.0003 and a loss of 0.0028. Dividing by a small τ magnifies the 0.6 gap into 6 nats of logit.

Reduction with padding. Per-token losses 0.5, 1.2, 0.3, 2.0 with targets 4, 7, -100, 2 and ignore_index=-100. With reduction="none" the third loss becomes 0. The sum is 3.7, and the mean is 3.7 / 3 = 1.233, not 3.7 / 4: padding is excluded from the count.

Implementation

import torch
import torch.nn as nn
import torch.nn.functional as F

y = torch.tensor([2.0, 2.5, 3.0, 3.2, 3.6, 12.0])
c = torch.full_like(y, 3.0)
F.mse_loss(c, y), F.l1_loss(c, y), F.huber_loss(c, y, delta=2.0)

# binary: pass logits, never sigmoid outputs
z = torch.tensor([2.0, 0.0, -3.0, 1000.0])
t = torch.tensor([1.0, 1.0, 1.0, 0.0])
F.binary_cross_entropy_with_logits(z, t, reduction="none")
# tensor([0.1269, 0.6931, 3.0486, 1000.0000])

# multi-class with padding: -100 is the default ignore_index
logits = torch.randn(4, 10)
target = torch.tensor([4, 7, -100, 2])
F.cross_entropy(logits, target)              # mean over the 3 real targets

# embeddings
a, p, n = torch.tensor([[0.0, 0.0]]), torch.tensor([[1.0, 0.0]]), torch.tensor([[1.5, 1.0]])
nn.TripletMarginLoss(margin=1.0)(a, p, n)    # tensor(0.1972)

# InfoNCE: row i of the similarity matrix should pick column i
img, txt = F.normalize(torch.randn(8, 64), dim=-1), F.normalize(torch.randn(8, 64), dim=-1)
tau = 0.07
sim = img @ txt.T / tau
loss = (F.cross_entropy(sim, torch.arange(8)) + F.cross_entropy(sim.T, torch.arange(8))) / 2

The same losses in plain Python:

from math import exp, log1p, log, dist

def huber(r, delta=1.0):
    return 0.5 * r * r if abs(r) <= delta else delta * (abs(r) - 0.5 * delta)

def bce_with_logits(z, y):
    return max(z, 0) - z * y + log1p(exp(-abs(z)))

def hinge(margin):
    return max(0.0, 1 - margin)

def triplet(a, p, n, margin=1.0):
    return max(0.0, dist(a, p) - dist(a, n) + margin)

def info_nce(sims, tau, target=0):
    logits = [s / tau for s in sims]
    top = max(logits)
    return -logits[target] + top + log(sum(exp(x - top) for x in logits))

huber(3, 2), bce_with_logits(1000, 0), triplet((0, 0), (1, 0), (1.5, 1))
# (4.0, 1000.0, 0.197)

Choosing a loss

TaskStart withSwitch when
Regression, clean noiseMSEOutliers dominate: Huber or L1. You need the median or a quantile: L1 or pinball loss.
Binary or multi-labelBCEWithLogitsLossClasses are rare: set pos_weight or use focal loss.
Multi-classCrossEntropyLossOverconfident: add label_smoothing. Imbalanced: class weight.
Max-margin classifierHingeYou need probabilities: logistic.
Embeddings, retrievalInfoNCE with in-batch negativesSmall batches: triplet with mined hard negatives.
Sequences with paddingCross-entropy with ignore_indexVariable lengths bias per-sequence means: sum and divide by total tokens.

Tradeoffs

  • Robustness versus efficiency. MSE is the most efficient estimator when the noise really is Gaussian, and the worst when it has heavy tails. L1 is robust but its gradient never shrinks, which makes the final steps noisy. Huber needs a δ, which has to match the scale of the targets.
  • Units matter. MSE is in squared target units, so rescaling targets rescales the loss and the gradient; normalize targets or adjust the learning rate.
  • Surrogates aren't the metric. Lower cross-entropy usually means higher accuracy, but not always; and a loss that ignores class balance can reach a low value by ignoring a rare class. Track the metric you care about separately.
  • Reductions change the effective learning rate. Switching from mean to sum multiplies gradients by the batch size. Averaging per sequence and then over sequences weights short sequences' tokens more than averaging over all tokens.
  • Contrastive losses depend on the batch. InfoNCE with in-batch negatives gets better with bigger batches, and its temperature is a sensitive hyperparameter (often learned, as in CLIP).

Check yourself

Which constant minimizes the mean absolute error to a set of targets?

The median: moving c past a target changes how many residuals are positive versus negative, and the slope is zero when they balance.

Why should a model feed logits, not sigmoid outputs, to its binary loss?

For large |z|, σ(z) rounds to exactly 0 or 1 and log gives −∞. The logit form max(z, 0) − zy + log(1 + e−|z|) never overflows.

A point is classified correctly with margin 2. What do hinge and logistic loss give it?

Hinge: max(0, 1 − 2) = 0, so it no longer affects training. Logistic: log(1 + e−2) ≈ 0.127, a small push to grow the margin further.

Per-token losses are 1, 1, 1, 1 and two of the four targets are padding. What does mean cross-entropy with ignore_index report?

1: the sum over the two real tokens is 2, divided by 2 real tokens, not by 4.

Connections

References

  • Huber. Robust Estimation of a Location Parameter. Annals of Mathematical Statistics, 1964.
  • Hastie, Tibshirani and Friedman. The Elements of Statistical Learning, 2nd ed., sections 10.6 and 12.3. Springer, 2009.
  • Bishop. Pattern Recognition and Machine Learning, sections 1.5 and 4.3. Springer, 2006.
  • Cortes and Vapnik. Support-Vector Networks. Machine Learning, 1995.
  • Schroff, Kalenichenko and Philbin. FaceNet: A Unified Embedding for Face Recognition and Clustering. 2015. arXiv:1503.03832
  • van den Oord, Li and Vinyals. Representation Learning with Contrastive Predictive Coding. 2018. arXiv:1807.03748
  • Lin et al. Focal Loss for Dense Object Detection. 2017. arXiv:1708.02002
  • PyTorch v2.15.0: torch/nn/modules/loss.py and torch/nn/functional.py.