Ch 3.5 CrossEntropyLoss: Softmax Loss vs. MNR LossApply
Same PyTorch loss function, two very different goals, inputs, and targets.
Core concepts
- One loss function, two jobs. Both Softmax loss (Ch 3) and MNR loss (Ch 4) are ultimately computed with PyTorch’s
nn.CrossEntropyLoss— but they hand it very different logits and targets. - Softmax loss classifies a pair. The logits are 3 class scores (entailment / neutral / contradiction) for one sentence pair; the target is a fixed label.
- MNR loss classifies a row of a similarity matrix. The logits are the cosine-similarity scores between one anchor and every candidate in the batch; the target is “the positive is at this index.”
- Why this matters. Recognizing
CrossEntropyLossunderneath both objectives demystifies “contrastive loss” — it’s classification, just reframed so the batch itself supplies the classes.
Softmax Loss (Ch 3)
- Goal
- Classify a sentence pair as entailment / neutral / contradiction, shaping the embedding space as a side effect.
- Input to CrossEntropyLoss
logits: shape(batch, 3)— raw scores from the classifier head overconcat(u, v, |u−v|).- Target
labels: shape(batch,)— the ground-truth NLI class index (0, 1, or 2) from the dataset.
import torch.nn as nn
# u, v: sentence embeddings from the shared BERT encoder, shape (batch, dim)
features = torch.cat([u, v, torch.abs(u - v)], dim=1)
logits = classifier_head(features) # (batch, 3)
labels = batch["nli_label"] # (batch,) in {0, 1, 2}
loss_fn = nn.CrossEntropyLoss()
loss = loss_fn(logits, labels)MNR Loss (Ch 4)
- Goal
- Rank each anchor’s true positive above every other in-batch candidate, directly optimizing retrieval quality.
- Input to CrossEntropyLoss
logits: shape(batch, batch)— cosine similarity of every anchor against every positive in the batch.- Target
labels: shape(batch,)—[0, 1, 2, …, batch-1], since each anchor’s positive sits on the diagonal.
import torch
import torch.nn as nn
import torch.nn.functional as F
# a, p: anchor and positive embeddings, shape (batch, dim)
a_norm = F.normalize(a, dim=1)
p_norm = F.normalize(p, dim=1)
logits = a_norm @ p_norm.T * scale # (batch, batch) similarity matrix
labels = torch.arange(logits.size(0)) # diagonal = correct positive
loss_fn = nn.CrossEntropyLoss()
loss = loss_fn(logits, labels)The pattern to remember: CrossEntropyLoss always wants (logits, target_class_indices). Softmax loss fixes the number of classes at 3 and reads the label from the dataset; MNR loss lets the batch size define the number of classes and derives the label from position (the diagonal) — no human labelling required.
What you must master
- Explain that both objectives ultimately reduce to a classification cross-entropy call Level 1
- Identify the shape and meaning of the logits and targets each loss expects Level 2
- Read PyTorch training code and recognize which loss (Softmax vs. MNR) is in use Level 2
Architect’s lens
When you review a client’s fine-tuning script, the tell is in what gets passed into CrossEntropyLoss:
a small fixed-width classifier head means Softmax loss (older, weaker for retrieval); a
batch × batch similarity matrix with arange labels means MNR loss (the
modern default). Spotting this in code review tells you instantly whether the embedder was trained for
classification or for ranking — and therefore whether it will perform well in your vector search stack.