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 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.
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.
| Term | Shape/Params | Meaning | Source |
|---|---|---|---|
_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 δ.
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
| Task | Start with | Switch when |
|---|---|---|
| Regression, clean noise | MSE | Outliers dominate: Huber or L1. You need the median or a quantile: L1 or pinball loss. |
| Binary or multi-label | BCEWithLogitsLoss | Classes are rare: set pos_weight or use focal loss. |
| Multi-class | CrossEntropyLoss | Overconfident: add label_smoothing. Imbalanced: class weight. |
| Max-margin classifier | Hinge | You need probabilities: logistic. |
| Embeddings, retrieval | InfoNCE with in-batch negatives | Small batches: triplet with mined hard negatives. |
| Sequences with padding | Cross-entropy with ignore_index | Variable 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
Read first
Read next
- Generalization intro
- Regularization core
- Regression metrics intro
- Linear regression intro
- The training loop intro
- Generative adversarial networks core
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.pyandtorch/nn/functional.py.