Tianyu Gao, Xingcheng Yao, Danqi Chen (Princeton NLP & Tsinghua) — arXiv:2104.08821, EMNLP 2021

SimCSE: Sentence Embeddings from Dropout

Contrastive learning needs a pair of views of the same thing. Text has no safe augmentation. So SimCSE stopped looking at the input and looked inside the model — where a random mask was already generating a second view, for free, on every forward pass.

Prerequisites: what a dot product is + what softmax does + rough familiarity with BERT. Contrastive losses, dropout, alignment, uniformity and anisotropy are all built from zero.
10
Chapters
7
Interactive Sims
0
Labels Required
76.25
Unsup. STS Avg

Chapter 0: The Label Is the Bottleneck

You have forty thousand support tickets and a duplicate problem. Somebody writes "my card was declined at checkout" on Monday. Somebody else writes "payment failed when I tried to pay" on Tuesday. These are the same ticket. No word except "pay" is shared, and even that one appears in a different form.

Keyword matching will not find this. You need a function that eats a sentence and produces a fixed-length vector, such that two sentences meaning the same thing land near each other and two sentences meaning different things land far apart. That function is a sentence embedding — a map from variable-length text to a fixed point in Rd, where geometric closeness is supposed to mean semantic closeness.

Everything downstream is built on that one function. Retrieval, clustering, deduplication, semantic search, RAG, recommendation, spam grouping, near-duplicate detection. Get the embedding right and all of them are a nearest-neighbour query away. Get it wrong and every one of them fails in the same quiet, hard-to-debug way: the numbers all look plausible and none of them mean anything.

How we measure whether it works

The field's shared yardstick is STS — Semantic Textual Similarity. A dataset of sentence pairs, each scored by human annotators from 0 (unrelated) to 5 (equivalent). The model produces a cosine similarity for each pair. You then ask: does the model's ranking of the pairs agree with the humans' ranking?

The agreement number is Spearman's rank correlation — the Pearson correlation computed on the ranks rather than the raw values. Rank-based is the right choice here, because nobody claims a cosine of 0.8 should mean a human score of 4.0. All we want is that pairs the humans put higher, the model also puts higher. Spearman runs from −1 (perfectly reversed) through 0 (no relationship) to 1 (perfect agreement), and the literature reports it ×100.

The seven STS benchmarks. STS12, STS13, STS14, STS15, STS16 (five yearly SemEval tasks), STS-B (the SemEval-2017 benchmark, which additionally has train/dev/test splits), and SICK-R. Papers report all seven and an average. SimCSE's headline numbers — the ones this whole lesson is about — are that average.

The result that should not have happened

Here is the state of the world when SimCSE was written, and it is genuinely embarrassing for deep learning.

GloVe is a 2014 method: a static lookup table with one vector per word type, trained by factorising a word co-occurrence matrix. To embed a sentence you average the word vectors. It has no parameters that know about word order, negation, syntax, or context. It cannot tell "dog bites man" from "man bites dog".

BERT-base is a 110-million-parameter transformer pretrained on 3.3 billion words with masked language modelling. It is contextual, deep, and revolutionised almost every NLP task on contact. To embed a sentence you average its final-layer token vectors (or take the [CLS] token).

MethodAvg. Spearman over 7 STS tasks
GloVe embeddings, averaged61.32
BERT-base, averaged token embeddings (first + last layer)56.70
BERT-base, [CLS] tokenworse still

Read that again. A 2014 bag of static word vectors beats a 2018 transformer by four and a half points, on the task of deciding whether two sentences mean the same thing. The transformer knows vastly more about language. It is simply not storing that knowledge in a place where cosine similarity can read it.

This is the whole problem, in one sentence. BERT's representation space is excellent at supporting a trained classifier head and terrible at supporting a fixed geometric operation like cosine similarity. Masked language modelling never once asked the model to make "distance" mean anything. So distance means nothing. Chapter 6 will show you the exact geometric pathology — every embedding crammed into a narrow cone — and Chapter 3 will give you the two-number diagnostic that names it.
The STS scatter — what "56.70" actually looks like

Each dot is one sentence pair from a semantic-similarity benchmark: human score on the horizontal axis, model cosine on the vertical. A perfect model would produce a rising line. Switch encoders and watch the cloud change shape. Notice what is wrong with raw BERT: it is not that the correlation is weak, it is that the entire vertical range has collapsed into a band near the top. Unrelated sentences score 0.7.

Encoder:

This is a schematic reconstruction, not the paper's raw predictions: the dots are generated so that each panel's Spearman correlation matches the reported number and the cosine range matches the known behaviour of each method. It is here to make a number visible, not to substitute for the number.

Two failure shapes are worth separating, because they need different fixes and people constantly conflate them.

Failure shape one: no ranking signal. The cloud is a blob. High-similarity and low-similarity pairs get the same cosine. The model genuinely does not know.

Failure shape two: compressed range. The cloud rises but occupies a sliver — every pair, related or not, scores between 0.65 and 0.95. The model does know something, but the information is buried under a huge constant offset that swamps it. Raw BERT is mostly this second kind, which is why post-processing tricks (Chapter 6) can rescue a surprising amount of it without any training.

The fix that worked, and what it cost

By 2019 the field had an answer: SBERT (Sentence-BERT). Take BERT, put two copies in a siamese arrangement, and fine-tune on labelled sentence pairs so that the geometry is forced to mean something. The labels come from NLI — Natural Language Inference — datasets, where humans wrote, for each premise, one sentence that follows from it (entailment), one that contradicts it, and one unrelated (neutral).

SBERT works. It takes the 7-task STS average from 56.70 to 74.89. And it costs exactly what supervised learning always costs.

The bill for supervisionWhat it means concretely
570,152 SNLI + 392,702 MNLI annotated triplesRoughly a million human judgements, collected over years by two large funded projects
English onlySwahili, Tamil, Basque: no NLI corpus of comparable size. Start from zero
General domain onlyYour legal contracts, your radiology reports, your product SKUs — no NLI corpus exists
StaticNew jargon, new product names, new failure modes: re-annotate or accept drift

So the question that opens this paper is not "can we do better than SBERT". It is: can we get that geometry without paying that bill at all?

Contrastive learning wants pairs, and text will not give them

The obvious idea is contrastive learning: instead of labels, use pairs of views of the same object. Show the model two versions of the same thing and one version of a different thing, and train it to tell which is which. Vision had made this work spectacularly — SimCLR, MoCo — using image augmentations. Crop the photo, jitter the colours, flip it horizontally: the content is unchanged, the pixels are completely different. Two views, no labels.

Text has no such augmentation, and the reason is worth stating precisely rather than hand-waving. Images are continuous and redundant; text is discrete and brittle. A photograph's meaning survives moving every pixel by a few percent, because meaning lives in a heavily overdetermined signal. A sentence's meaning does not survive changing one token, because meaning lives in a code with almost no redundancy.

Proposed text augmentationWhy it seems fineWhy it breaks
Delete a random wordSentences are redundant, surely"The film was not good" → "The film was good". One deletion, meaning inverted. And the model is being trained to call those two identical
Crop a spanAnalogous to an image cropA sentence's clauses are not interchangeable regions. Cropping "she denied that he left" yields "he left"
Synonym replacementSynonyms mean the same thingRarely exactly. "Cheap" / "inexpensive" / "shoddy" all appear in a thesaurus. Context decides, and the augmenter does not read context
Mask-and-refill with an MLMUse the language model to keep it fluentFluent is not faithful. The refiller happily writes a grammatical sentence with a different subject
Back-translation (EN→DE→EN)Genuinely meaning-preserving, mostlyNeeds two translation models, is orders of magnitude slower than a forward pass, and its paraphrases are systematically biased toward MT-flavoured phrasing
The augmentation dilemma, stated sharply. A useful positive pair must be different enough that matching it is a non-trivial task, and similar enough that the meaning is genuinely preserved. In pixel space, that window is wide — almost any mild transform lands in it. In token space, the window is nearly empty: every edit large enough to matter is large enough to change what the sentence says. Chapter 2 measures this with the paper's own ablation, where cropping 10% of tokens costs five Spearman points and cropping 30% costs nineteen.

The move

SimCSE's answer is one of those ideas that sounds like a joke until you check the number.

Stop trying to perturb the input. The model is already perturbing its own internals, on every single forward pass, for free, and has been since 2017.

That perturbation is dropout: during training, a transformer randomly zeroes a fraction p of the activations in each layer (and rescales the survivors), a regularisation trick that has been switched on by default in every BERT-family model ever released. It means that feeding the same sentence twice through the same model in training mode produces two different vectors.

What everyone tried
Edit the tokens to make a second view. Meaning leaks out through every edit.
↓ stop editing the input…
SimCSE
Send the identical token sequence through twice. Independent dropout masks make the two hidden representations differ. That is the pair.
↓ and the meaning is untouched, because the tokens never changed
The consequence
A minimal, continuous, semantics-preserving perturbation — the thing text augmentation could never provide — obtained by writing zero lines of augmentation code.

The whole unsupervised method is: take a million raw sentences from Wikipedia, feed each one through BERT twice, call the two outputs a positive pair, call every other sentence in the batch a negative, and minimise a contrastive loss. There is no augmentation module. There is no label. There is no second network. The implementation change relative to a standard fine-tuning loop is roughly four lines.

MethodHuman labels usedAvg. STS (7 tasks)
BERT-base, averagednone56.70
BERT-flow (post-hoc normalising flow)none66.55
BERT-whitening (post-hoc linear transform)none66.28
CT-BERT (previous best unsupervised)none72.05
SBERT-base (supervised on NLI)~1M triples74.89
Unsupervised SimCSE-BERT-basenone76.25
Supervised SimCSE-BERT-base314k NLI pairs81.57
Supervised SimCSE-RoBERTa-large314k NLI pairs83.76

The row that stops people is the sixth. An unsupervised model, trained on unlabelled Wikipedia sentences, beats the supervised SBERT that consumed a million human annotations. Not by a lot — 1.4 points — but the direction is the shock. And when SimCSE is also given the NLI labels, it takes another 5.3 points on top, so the two contributions stack rather than compete.

The claim, stated now so you can hold the rest of the lesson to account. (1) The positive pair problem in text is solved by dropout, not by augmentation. (2) The reason it works can be measured with two numbers — alignment and uniformity — and dropout is precisely the perturbation that improves the second without destroying the first. (3) When labels are available, the same objective absorbs them by swapping in entailment pairs as positives and contradiction pairs as explicit hard negatives. Every one of those three claims gets a chapter, a derivation, and a number.

Why this paper is worth a full lesson in 2026

SimCSE is not important because 76.25 is a large number. It is important because it is the point at which the modern embedding recipe crystallised. Open the training code of essentially any text embedder shipped since — sentence-transformers v2, E5, GTE, BGE, Instructor, the commercial embedding APIs — and you will find the same four ingredients, in the same arrangement:

1
A pretrained transformer, pooled to one vector, L2-normalised
2
An InfoNCE loss with in-batch negatives and a small temperature
3
Cheap, label-free positive pairs for a first stage
4
Curated pairs plus explicit hard negatives for a second stage

Ingredients 2, 3 and 4 are Chapters 1, 2 and 4 of this lesson. Chapter 9 traces exactly which later systems changed which ingredient and why.

Where we are going

Chapters 1–2 — build the method
Derive InfoNCE symbol by symbol from the collapse problem → then the dropout-as-augmentation idea, with the ablations that prove discrete augmentation is worse
Chapters 3–6 — understand why it works
The alignment/uniformity lens → the supervised variant with hard negatives → what temperature actually does to the gradient → the anisotropy story, with the singular-value derivation
Chapters 7–9 — verify and inherit
A complete hand-worked forward pass and loss on three sentences → every result table including the awkward ones → the recipe that every modern embedder took from here
Averaged GloVe vectors beat averaged BERT vectors on STS (61.32 vs 56.70). What is the correct conclusion?

Chapter 1: InfoNCE, Symbol by Symbol

Before we can talk about where positive pairs come from, we need to know what we are going to do with them. This chapter builds the contrastive objective from nothing, one repair at a time, until we arrive at the exact equation SimCSE optimises. Every symbol will be named, the gradient will be derived, and we will run a small batch through by hand.

Attempt 1: just pull positives together

We want an encoder f such that a sentence x and its paraphrase x+ get nearby vectors. The most direct way to say that is to write it as a loss and minimise it:

Lnaive = (1/N) ∑i ‖ f(xi) − f(xi+) ‖2

Where N is the number of pairs in the batch, f is the encoder (BERT plus whatever pooling), and the double bars are the Euclidean norm. Lower means the pair is closer. Minimise it and the pairs get closer. Done?

No. There is a solution with loss exactly zero that requires no understanding of language at all:

f(x) = 0   for every x

Map everything to the origin. Every pair distance is zero. The loss is zero. It is the global minimum, it is trivially reachable by gradient descent, and it is completely useless. This is representational collapse — the failure mode where a model satisfies an objective by discarding all information rather than by organising it.

Collapse is not a rare pathology, it is the default. Any objective built purely from "make these things similar" has a constant function as its optimum. Gradient descent finds it fast, because it is the smoothest possible path: shrinking all outputs toward a common point reduces every pair distance simultaneously. If you have ever watched a siamese network's loss plummet to near zero in the first hundred steps and then produce garbage embeddings, you have watched this happen.

Watch collapse happen, in numbers

"Gradient descent finds the constant function" deserves a demonstration rather than an assertion, and for a linear encoder we can do it exactly.

Let the encoder be a matrix: f(x) = A x. Write δi = xi − xi+ for the difference between a pair. Then

Lnaive = (1/N) ∑iAδi2 = tr( ATA S ),   where S = (1/N) ∑i δiδiT

Differentiate: ∂L/∂A = 2AS. So one gradient step with learning rate η gives

AA − 2ηAS = A(I − 2ηS)

The update multiplies A by a fixed contraction. Suppose S = diag(0.5, 0.2) and η = 0.5, so I − 2ηS = diag(0.5, 0.8). After ten steps every column of A has been scaled by 0.510 = 0.00098 in the first direction and 0.810 = 0.107 in the second. After a hundred steps, 7.9 × 10−31 and 2.0 × 10−10.

The encoder is being annihilated, exponentially, in every direction where the data has any variation at all. And the loss is dropping the whole time — monotonically, smoothly, exactly as a healthy training curve looks. This is why collapse is so dangerous in practice: the loss curve gives you no warning whatsoever.

Attempt 2: add something to push against

The repair is to make the objective relative. It should not be enough for the positive pair to be close; it must be closer than the alternatives. Now the constant function fails, because if everything is at the origin, the positive is not closer than anything — it ties with all of them.

There are two classical ways to write "closer than the alternatives". The first is a triplet margin loss:

Ltriplet = max( 0,  d(x, x+) − d(x, x) + m )

Read it as: the positive must be closer than the negative by at least a margin m; if it already is, the loss is zero and nothing happens. This works — it is what SBERT and most of the 2015–2019 metric-learning literature used — and it has two properties that InfoNCE fixes.

Triplet margin lossInfoNCE
Negatives per anchorOne, chosen explicitlyAll N−1 others in the batch, free
Gradient once satisfiedExactly zero — the hinge closesSmall but non-zero, and it re-focuses automatically
Hard-negative miningMandatory. Without it most triplets are already satisfied and training stallsImplicit — the softmax weights each negative by how confusable it is
Extra hyperparametersMargin m, plus a mining strategyTemperature τ
Failure modeSilence: the loss reads 0.00 while the model has learned littleSaturation at very low τ (Chapter 5) — but it is visible in the loss

The "exactly zero" row is the one that decides it. With a margin of 0.2 and a batch where 90% of triplets are already separated by more than 0.2, ninety percent of your compute produces no gradient at all. Teams spend enormous effort on mining pipelines to fix this. InfoNCE makes the mining a property of the loss instead of a property of the data loader, which is why it took over.

The second way, which is what SimCSE uses, is to turn the whole thing into a multiple-choice test.

Attempt 3: contrastive learning as an N-way classification

Here is the reframing, and it is the single most useful mental model in this lesson.

Take a batch of N pairs: (x1, x1+), (x2, x2+), …, (xN, xN+). Now scramble the second elements and hand the model a quiz:

"Here is sentence x1. Here are N candidate partners: x1+, x2+, …, xN+. Which one is x1's actual partner?"

The correct answer is candidate number 1. For sentence x2, the correct answer is candidate 2. In general, for row i, the correct answer is i.

That is an ordinary N-way classification problem, and we already know how to train those: produce a score for each candidate, softmax the scores into a distribution, take the cross-entropy against the correct index. The labels are free — they are just 0, 1, 2, ..., N-1, the identity permutation. No human wrote them.

This is the entire idea. The in-batch negatives trick means every other example in the batch serves as a negative for every example, so a batch of size N yields N positive constraints and N(N−1) negative constraints at the cost of N forward passes. Negatives are free.

The score function: why cosine, and what it costs

We need a number saying how compatible two embeddings are. The obvious choice is the dot product hiThj. It is the wrong choice, for a reason worth seeing concretely.

Suppose the encoder produces h1 = (3, 4) for a sentence and h1+ = (6, 8) for its paraphrase. Those point in exactly the same direction — the second is the first doubled. Semantically the encoder has done a perfect job. The dot product is 3(6) + 4(8) = 18 + 32 = 50. Now take a genuinely unrelated sentence whose embedding happens to be large: h2+ = (20, 1). The dot product with h1 is 3(20) + 4(1) = 64. The unrelated sentence wins, purely because it is long.

Worse, the model can exploit this. Under a raw dot product, one way to reduce the loss is to grow ‖h‖ for confident examples, which inflates every score in that row. The optimiser will happily spend capacity on a magnitude channel that has nothing to do with meaning.

The fix is cosine similarity: divide out the lengths, keeping only direction.

sim(h1, h2) = h1Th2 / ( ‖h1‖ · ‖h2‖ )

Check the example: ‖(3,4)‖ = 5, ‖(6,8)‖ = 10, so the cosine is 50/(5×10) = 1.000, correctly reporting a perfect match. For (20,1): ‖(20,1)‖ = √401 = 20.025, so the cosine is 64/(5 × 20.025) = 64/100.12 = 0.639. Order restored.

Equivalently: L2-normalise every embedding onto the unit sphere first, then the plain dot product is the cosine. That is how it is implemented, and it is the reason you will see F.normalize in every contrastive codebase.

Normalisation has a price, and the price is the whole of Chapter 5. Once you normalise, every score lives in [−1, 1]. A softmax over numbers in that range is almost flat: the biggest possible gap between the best and worst candidate is 2, so exp(1)/exp(−1) = 7.39, which spread over N candidates is barely a preference at all. The model is now unable to express confidence, and the gradient signal is weak. The repair is a divisor called the temperature, and it is far more consequential than it looks.

The objective, assembled

Put the pieces together. For example i in a batch of N:

i = − log [  exp( sim(hi, hi+) / τ )  /  ∑j=1N exp( sim(hi, hj+) / τ )  ]

This is the InfoNCE loss (Noise-Contrastive Estimation with an information-theoretic reading, from van den Oord et al. 2018), and it is exactly the loss SimCSE minimises. Every symbol:

SymbolMeaningShape / value
NBatch size — and therefore the number of candidates in the multiple-choice test64 for unsupervised SimCSE, 512 for supervised
hiEmbedding of the i-th sentence (the "query" view)R768 for BERT-base
hi+Embedding of its positive partner (the "key" view)R768
sim(·,·)Cosine similarityscalar in [−1, 1]
τTemperature — divides every score before the softmax0.05 in SimCSE, so the multiplier is 20
the sum over jRuns over all N keys in the batch, including the correct oneN terms
iLoss for row iscalar, ≥ 0
LBatch loss = mean of ℓiscalar

Two details people get wrong. First, the correct pair appears in the denominator as well as the numerator — the sum is over all N, not the N−1 negatives. That is what makes the ratio a genuine probability and bounds the loss below by 0. Second, the loss is written per row: row i asks "which key belongs to query i". The unsupervised SimCSE objective as published is one-directional, exactly like this. (CLIP-style symmetric losses also add the transpose direction; Chapter 5 says when that matters.)

What the floor and the ceiling mean

Two reference values make the loss readable at a glance.

The chance level is ln N. If the model has no idea — all N scores equal — then every probability is 1/N, and ℓi = −log(1/N) = log N. For N = 64 that is ln 64 = 4.159. If your contrastive loss is sitting at 4.16 with a batch of 64, your model is guessing, and the most common cause by far is a mis-set temperature.

The floor is 0, reached when pii = 1, i.e. the positive score is infinitely far above all others. With cosine scores bounded in [−1, 1] and a finite temperature, this is unreachable: the best possible row has sim = 1 on the diagonal and −1 everywhere else, giving

min = log[ 1 + (N−1) · e−2/τ ]

At τ = 0.05 and N = 64: e−40 = 4.25 × 10−18, times 63, gives 2.7 × 10−16. So the practical floor is zero to sixteen decimal places. At τ = 1 the same expression gives log(1 + 63 × 0.1353) = log(9.525) = 2.254 — the loss literally cannot go below 2.25 no matter how perfect the encoder is. That single calculation is the most compelling argument for a small temperature there is.

The information-theoretic reading, in one line. InfoNCE is a lower-bound estimator of the mutual information between the two views: I(x; x+) ≥ log N − L. Minimising L therefore maximises a lower bound on how much the two views tell you about each other. This is where "log N" comes from as a natural scale, and it is why bigger batches raise the ceiling on how much mutual information the objective can certify — a fact Chapter 5 turns into practical advice.

Worked example: one batch of three, by hand

Numbers make this concrete. Suppose a batch of N = 3 produces the following cosine similarity matrix, where row i is query i and column j is key j:

coskey 1key 2key 3
query 10.720.540.19
query 20.540.720.15
query 30.36−0.270.78

Take row 1 at τ = 1 first, so the arithmetic is transparent. Logits are just the cosines: 0.72, 0.54, 0.19.

e0.72 = 2.0544,   e0.54 = 1.7160,   e0.19 = 1.2092

Sum: 2.0544 + 1.7160 + 1.2092 = 4.9796. Probabilities:

p11 = 2.0544/4.9796 = 0.4126,   p12 = 0.3446,   p13 = 0.2428

Loss for row 1: ℓ1 = −ln(0.4126) = 0.8853.

Compare to chance, ln 3 = 1.0986. The model is only 0.21 nats better than guessing, even though the correct answer has the highest cosine by a comfortable 0.18 margin. That is the flatness problem: with τ = 1 a decisive geometric win translates into a feeble probabilistic one.

Now the same row at τ = 0.05, multiplying every cosine by 20. Logits: 14.4, 10.8, 3.8. Subtract the max (a standard numerical-stability step that leaves the softmax unchanged) to get 0, −3.6, −10.6:

e0 = 1,   e−3.6 = 0.027324,   e−10.6 = 0.0000248

Sum: 1.0273488. So p11 = 1/1.0273488 = 0.97338, and ℓ1 = ln(1.0273488) = 0.02698.

Same geometry. Same encoder. Thirty-three times less loss, purely from the divisor. Chapter 5 unpacks what that does to the gradients — because "less loss" is not automatically "better training", and the trade-off is sharp.

The gradient, derived

To understand any loss you must know what it does to the parameters. Write sij = sim(hi, hj+) and the logit zij = sij/τ. Then

i = −zii + log ∑j ezij

Differentiate with respect to a single logit zik. The first term contributes −1 when k = i and 0 otherwise. The second term is the log-sum-exp, whose derivative is the softmax:

∂ℓi / ∂zik = pik − 1[k = i]

and therefore, chaining through the temperature,

∂ℓi / ∂sik = ( pik − 1[k = i] ) / τ

This little expression is worth sitting with, because it tells you the entire training dynamics.

CaseGradient w.r.t. the similarityWhat the update doesMagnitude
k = i (the positive)(pii − 1)/τ, which is negativeIncreases sii — pulls the pair togetherProportional to (1 − pii): vanishes once the model is already right
ki (a negative)pik/τ, positiveDecreases sik — pushes them apartProportional to pik: concentrated on the negatives the model currently finds confusing

Two consequences that people pay for when they forget them. First, the total force is balanced: ∑k ∂ℓi/∂zik = ∑k pik − 1 = 0. The attraction on the positive exactly equals the sum of repulsions on the negatives. Second, InfoNCE performs automatic hard-negative mining: you never have to select difficult negatives, because the softmax weights them by exactly how threatening they currently are.

Check that on the worked numbers. At τ = 1, row 1's probabilities were (0.4126, 0.3446, 0.2428). The attraction weight is 1 − 0.4126 = 0.5874, and the two repulsions are 0.3446 and 0.2428 — the harder negative gets 1.42× the push of the easier one. At τ = 0.05, the probabilities were (0.97338, 0.026596, 0.0000241); the ratio between the two repulsions is now 0.026596/0.0000241 = 1103. Sharpening the softmax does not just shrink the loss; it re-allocates virtually all of the repulsive force onto the single hardest negative. Hold that thought for Chapter 5.

The realisation: shapes and code

Here is the whole objective as it actually executes, with every tensor shape named. This is the code SimCSE runs; everything else in the paper is about what you feed it.

python — the contrastive core, end to endimport torch, torch.nn.functional as F

# z1, z2 are the two views of the SAME N sentences.
# z1: (N, 768)   the "query" view h_i
# z2: (N, 768)   the "key"   view h_i^+   (row i is row i's partner)

z1 = F.normalize(z1, dim=-1)          # (N, 768), every row now has norm 1
z2 = F.normalize(z2, dim=-1)          # (N, 768)

sim = z1 @ z2.T                          # (N, N)  sim[i,j] = cos(h_i, h_j^+)
logits = sim / 0.05                     # (N, N)  temperature = 0.05 -> multiply by 20

labels = torch.arange(z1.shape[0], device=z1.device)   # (N,) = [0,1,2,...,N-1]
loss = F.cross_entropy(logits, labels)                # scalar

Six lines. The one that carries the whole idea is the labels line: the supervision is arange(N). No human produced it; it is a consequence of how the batch was assembled. Everything creative in this paper happens upstream of these six lines, in the question of how z1 and z2 came to be two views of the same sentence.

Read the shapes as a story. (N, 768) meets (768, N) and becomes (N, N). That N×N matrix is the entire training signal: N diagonal entries the model must raise and N2−N off-diagonal entries it must lower. At N = 64 that is 64 constraints to satisfy and 4,032 to violate as little as possible, all from 64 forward passes. Doubling the batch to 128 gives 128 positives and 16,256 negatives — the negatives grow quadratically for a linear increase in compute. This is why contrastive learning is so batch-hungry, and Chapter 5 shows exactly where the returns stop.

A caution about what "loss went down" proves

Because the labels come from the batch construction rather than from the world, a falling contrastive loss proves only that the model can tell batch members apart. It is entirely possible to drive the loss to 0.001 while producing embeddings that are useless for STS — if the model discovers a shortcut feature (sentence length, first token, a punctuation quirk) that identifies each batch member without touching meaning.

And SimCSE is the method where that worry bites hardest, because its two views are the same token sequence. Sentence length, punctuation, rare tokens, casing — every surface feature is preserved exactly across the positive pair. A model that learned nothing but "match the sentence with the same number of tokens" would drive this loss down beautifully. This is the strongest objection to the whole method, and you should be holding it against the paper right now.

The answer is an ablation we will meet in Chapter 2, and it is clean. If surface identity were what the model exploits, then the variant with dropout switched off — where the two views are not merely surface-identical but bit-identical, making every shortcut maximally available — ought to do at least as well. It does not. It scores 71.1 on STS-B dev against 82.5 with dropout at p = 0.1. The shortcut is fully available in both settings; only one of them learns anything worth having. Whatever dropout supplies, it is not a shortcut, and Chapter 3 will name it precisely.

In InfoNCE, the gradient of the loss with respect to the similarity of a negative pair is pik/τ. Why does this make explicit hard-negative mining unnecessary in the basic setup?

Chapter 2: Dropout Is the Augmentation

Chapter 1 built the machine and left a hole in it: hi+. The loss needs a second view of every sentence, and Chapter 0 established that editing tokens to produce one is a losing game. This chapter fills the hole.

What dropout actually is

Dropout is a regularisation technique from 2014: during training, each activation in a layer is independently zeroed with probability p. Nothing more exotic than that. Let u be a layer's output vector and m a random binary mask with mk ~ Bernoulli(1 − p). Then the naive version is

hk = mk · uk

This has a problem you can see with one line of arithmetic. The expected value of the output has shrunk: E[hk] = E[mk] · uk = (1 − p) uk. At p = 0.1, every activation is 10% smaller in expectation than it would be without dropout. So a network trained with dropout and evaluated without it sees systematically inflated inputs at every layer, and the inflation compounds through depth.

The universal fix is inverted dropout: scale the survivors up during training so the expectation is preserved, and do nothing at all at test time.

hk = ( mk / (1 − p) ) · uk,    E[hk] = ((1−p)/(1−p)) uk = uk

Check it at p = 0.1: survivors are multiplied by 1/0.9 = 1.111. Nine tenths of the units survive and are 11.1% larger; one tenth is zero. The mean is unchanged; the variance is not. That injected variance is the regularisation — and, for our purposes, it is the augmentation.

The variance is the point. Compute it. For a single unit with value u, h = mu/(1−p) has E[h] = u and E[h2] = u2/(1−p), so Var(h) = u2(1/(1−p) − 1) = u2 · p/(1−p). At p = 0.1 that is 0.111 u2, a standard deviation of 0.333|u| — a third of the unit's own magnitude, on every activation, in every layer. Dropout is not a small perturbation. It is a large one applied in a place where the model has learned to tolerate it.

Where the masks actually live inside BERT

"BERT uses dropout" is too vague to reason about. Here is the real inventory for BERT-base, all at p = 0.1 by default in the released config:

SiteTensor it masksShape (batch 1, T = 32 tokens)Count per layer
Embedding outputToken + position + segment sum, after LayerNorm(32, 768)once, before layer 1
Attention probabilitiesThe softmax'd attention matrix, per head(12, 32, 32)1
Attention outputThe projected context vector, before the residual add(32, 768)1
Feed-forward outputThe second FFN projection, before the residual add(32, 768)1

Count the Bernoulli draws in one forward pass of a 32-token sentence through the 12 layers:

hidden masks:  (1 + 12×2) × 32 × 768 = 25 × 24,576 = 614,400
attention masks:  12 × 12 × 32 × 32 = 147,456
total ≈ 761,856 independent coin flips

Three quarters of a million random bits are sampled every time a sentence goes through the model. The probability that two forward passes draw the same mask is, for practical purposes, zero. So the following statement — which sounds like a bug report — is simply true:

In training mode, BERT is not a function. It is a distribution over functions. Feeding the identical token sequence twice gives two different 768-dimensional vectors, drawn from a cloud whose shape the model itself learned during pretraining. SimCSE's insight is that this cloud is exactly the augmentation distribution nobody could construct in token space: it is continuous, it is centred on the true representation, it is guaranteed to preserve meaning (because the tokens never changed), and its shape encodes which directions the model already considers unimportant.

The unsupervised SimCSE objective

Write hiz = fθ(xi, z) for the encoder's output on sentence xi under dropout mask z. Sample two independent masks, zi and zi′. Then the objective is Chapter 1's InfoNCE with the two views defined this way:

i = − log [  e sim(hizi, hizi)/τ  /  ∑j=1N e sim(hizi, hjzj)/τ  ]

That is the entire unsupervised method. The only difference from a loss you could have written in 2018 is the superscripts: the positive pair is one sentence under two masks, and the negatives are the other sentences in the batch under their own masks.

The realisation: how you actually get two masks

Here is the part that is genuinely charming. SimCSE does not run the model twice. It duplicates each sentence inside the batch and runs one forward pass, because dropout masks are sampled per element — two copies of the same row get independent masks automatically.

python — the four lines that make the positive pair# batch: N sentences, tokenised to length T
input_ids = batch["input_ids"]                    # (N, T)

# 1. duplicate: each sentence appears twice, adjacent
input_ids = input_ids.unsqueeze(1).repeat(1, 2, 1)   # (N, 2, T)
input_ids = input_ids.view(-1, input_ids.size(-1))     # (2N, T)  <- flatten

# 2. ONE forward pass in train mode. Dropout is sampled per row,
#    so rows 2i and 2i+1 (the same tokens!) get DIFFERENT masks.
out = encoder(input_ids).last_hidden_state[:, 0]     # (2N, 768)  [CLS] token
out = mlp(out)                                        # (2N, 768)  pooler: Linear + Tanh

# 3. split back into the two views
out = out.view(-1, 2, out.size(-1))                  # (N, 2, 768)
z1, z2 = out[:, 0], out[:, 1]                       # (N, 768) each

# 4. Chapter 1's six lines, unchanged.
loss = F.cross_entropy(F.normalize(z1,dim=-1) @ F.normalize(z2,dim=-1).T / 0.05,
                       torch.arange(z1.size(0), device=z1.device))

Notice what is absent: there is no augmentation function, no synonym dictionary, no translation model, no data preprocessing step. The augmentation is a side effect of model.train(). The compute cost is one extra forward pass per sentence — the batch is 2N rows instead of N — and nothing else.

Two shape traps that will cost you an afternoon. (1) The repeat must interleave (N,2,T then flatten) so that each pair is adjacent and the later view(-1,2,d) recovers the right partner. If you instead concatenate two copies of the batch (cat([x,x]), giving 2N rows in blocks), you must split as out[:N], out[N:] — mixing the two conventions silently pairs sentence i with sentence i+N/2 and your loss will sit stubbornly at ln N. (2) The model must be in train() mode. Call eval() anywhere in the loop and dropout switches off, the two views become bit-identical, and you have silently reproduced the paper's worst ablation.

See the twins

The dropout twin visualiser

One sentence, one encoder, two forward passes. The grid is a slice of hidden units; a dark cell was dropped, a bright cell survived and was scaled up by 1/(1−p). Below, the two resulting embeddings and the cosine between them. Resample to see how much the twin pair moves, change p, and switch on fixed mask to see the pathological case where both passes draw the same mask — the twins become identical and the positive term stops existing.

dropout p:

Play with it until three things are obvious. At p = 0 the two views are identical and the cosine is exactly 1.000, forever, no matter how many times you resample. At p = 0.5 the views are wildly different and the cosine wanders down toward 0.5 — the "positive pair" is now barely a pair at all. At p = 0.1 the cosine sits high but never at 1: the twins are close, and reliably not identical. That narrow band is where the method lives.

How far apart are the twins? A closed form

The sim shows the twin cosine hovering near 0.9 at p = 0.1 and near 0.5 at p = 0.5. Those numbers are not accidents. For our simplified single-site dropout there is an exact answer, and it is startlingly clean.

Write the two views component-wise, with mA and mB independent Bernoulli(1−p) masks:

hkA = mkA uk / (1−p),    hkB = mkB uk / (1−p)

The numerator. A unit contributes to the dot product only if it survived both masks, which happens with probability (1−p)2 by independence. The 1/(1−p) factors contribute 1/(1−p)2. They cancel exactly:

E[ hA · hB ] = ∑k uk2 · (1−p)2 / (1−p)2 = ‖u2

The denominator. Each norm involves only one mask, so only one factor of (1−p) survives against the squared rescaling:

E[ ‖hA2 ] = ∑k uk2 · (1−p) / (1−p)2 = ‖u2 / (1−p)

For large d these quantities concentrate tightly around their means, so ‖hA‖ ‖hB‖ ≈ ‖u2/(1−p), and

E[ cos(hA, hB) ] ≈ ‖u2 ÷ ( ‖u2/(1−p) ) = 1 − p

The expected cosine between a sentence and its dropout twin is one minus the dropout rate. Check it against a Monte-Carlo simulation over 4,000 random vectors:

pSimulated E[cos], d = 32Simulated E[cos], d = 768Prediction 1 − p
0.050.94950.94990.9500
0.100.89900.89970.9000
0.150.84950.85090.8500
0.200.79550.80070.8000
0.500.49240.49970.5000
What this buys you. The dropout rate is a linear dial on positive-pair difficulty. Using Chapter 3's identity ℓalign = 2(1 − cos), the training-time alignment target is simply 2p: at p = 0.1 the model is being asked to hold its twins at squared distance 0.2, at p = 0.5 at distance 1.0 (a 60° separation). That is why the dropout sweep in the paper behaves like a clean inverted U rather than a cliff — you are sliding a difficulty knob linearly, and there is an optimum in the middle. It also tells you where to start when you port this to a different architecture: pick p so that the twin cosine lands near 0.9.

One honest correction before you over-apply it. Real BERT applies dropout at 37 sites inside the network, and each perturbation is partially absorbed by the LayerNorms and residual connections that follow it — the model was trained to be robust to exactly this noise. So the observed twin cosine in a real encoder is higher than 1−p, not equal to it. What survives is the monotone relationship and the shape of the curve, which is all the argument needs.

The ablation that settles it

An idea this cheap deserves scepticism, so the paper ran the comparison directly: replace dropout with each of the token-level augmentations everyone had been proposing, keep everything else identical, and read the STS-B development score.

Positive pair constructionSTS-B dev (Spearman ×100)Cost relative to the best
None — two dropout masks (unsup. SimCSE)82.5
Crop 10% of tokens77.8−4.7
Crop 20%71.4−11.1
Crop 30%63.6−18.9
Word deletion 10%75.9−6.6
Word deletion 20%72.2−10.3
Word deletion 30%68.8−13.7
Delete exactly one word75.9−6.6
Synonym replacement77.4−5.1
MLM 15% (mask and refill)62.2−20.3
Augmentation ablation — every alternative, ranked

The same bars, sorted, with the dropout result marked. Tap a bar to see what that augmentation does to a real sentence and why it costs what it costs.

Two rows deserve individual attention.

"Delete exactly one word" scores 75.9. This is as gentle as a discrete augmentation can possibly be — a single token, out of maybe twenty. It still costs 6.6 points. The reason is that "one token" is not a small perturbation in a code with almost no redundancy. Delete not, and the sentence means the opposite. Delete the, and nothing happens. The augmenter cannot tell those cases apart, so a fraction of your positive pairs are actively teaching the model that a sentence and its negation are the same thing. Every one of those is a wrong label, and Chapter 5 will show that at τ = 0.05 wrong labels are punished with enormous force.

MLM refill scores 62.2, the worst of all. This is the most surprising row, because it is the most sophisticated method — use a language model to keep the sentence fluent. But fluency is not fidelity. Mask 15% of "the concert was cancelled because of the storm" and a well-trained MLM will cheerfully produce "the concert was cancelled because of the singer". Grammatical, plausible, and a different fact. The better your refiller, the more confidently wrong the augmentation becomes.

Rank the augmentations by how much they touch the tokens, and the scores fall in exactly that order. Dropout (touches zero tokens) 82.5 → synonym replacement (touches a few, meaning usually preserved) 77.4 → crop 10% 77.8 → delete one word 75.9 → crop 30% 63.6 → MLM refill 62.2. This is not a coincidence and it is not a statement about clever engineering. It is a statement about text: in a discrete, low-redundancy code, the size of the semantic damage is roughly the size of the edit, so there is no window in which the perturbation is large enough to be interesting and small enough to be safe. Dropout escapes the trade-off entirely by perturbing a different space.

Other things the paper tried, and why they lost

Two more families of alternative are worth recording, because they are the ideas most people propose next.

Use a neighbouring sentence as the positive. If the sentence after this one is on the same topic, it is a kind of positive. This is the idea behind several 2020 methods. As reported in the paper's second ablation table, taking the next sentence as the positive scores 67.1, and taking one of the next three scores 67.4 — both far below 82.5. The reason is a mismatch between "related" and "equivalent". STS asks whether two sentences mean the same thing; discourse adjacency only says they are about the same thing. Training on the second and evaluating on the first blurs precisely the distinction being measured.

Use two different encoders. Contrastive frameworks often use a query encoder and a separate key encoder (MoCo's momentum encoder, for instance). The paper tried it: with a shared encoder, dropout-SimCSE scores 82.5; with two independently parameterised encoders, 80.1. Sharing wins. And it wins for a structural reason: with two encoders, the model can satisfy the positive term by making the two encoders agree rather than by making the representation good, which is a cheaper and less useful solution. A single encoder removes that escape route.

The data: one million random Wikipedia sentences

The unsupervised model trains on 106 sentences sampled from English Wikipedia. Not filtered for quality, not paired, not deduplicated by topic — just a million sentences. That deserves a moment, because it is where the "no labels" claim cashes out.

KnobValueWhy
Corpus106 English Wikipedia sentencesDiverse, freely available, and needs no annotation of any kind
Max sequence length32 tokensSentence-level task; short sequences make the quadratic attention cost trivial
Batch size64 (so 128 rows after duplication)Each row gets 63 in-batch negatives
Epochs1106/64 ≈ 15,625 optimiser steps. That is a single-GPU afternoon
Learning rate3e-5 (base models)Standard BERT fine-tuning territory
Temperature0.05Fixed, not learned. Chapter 5 explains the number
Pooling[CLS] with the MLP head during training, MLP dropped at testAn empirical finding; Chapter 8 has the ablation

Fifteen thousand steps on unlabelled text, and the resulting model beats a supervised system trained on a million human annotations. The asymmetry between the effort and the result is the reason this paper has 4,000+ citations.

One honest asterisk, raised now rather than buried. The unsupervised recipe evaluates on the STS-B development set every 250 steps and keeps the best checkpoint. That is a form of supervision — a few hundred labelled pairs' worth of model selection. It is standard practice and the paper is open about it, but "zero labels" is strictly "zero labels in the loss", not "zero labels anywhere in the pipeline". Chapter 8 returns to this when we count what the numbers actually establish.

The first hint of why it works

Before the next chapter builds the proper lens, here is the ablation that will drive it — the dropout-rate sweep, on STS-B dev:

p0.00.010.050.10.150.20.5Fixed 0.1
STS-B71.172.681.182.581.480.571.043.6

Read the two ends. At p = 0 the two views are identical, and the score falls 11 points. At p = 0.5 the two views are barely related, and the score falls 11 points. There is a sweet spot, and BERT's default happens to sit almost exactly on it — a coincidence worth noticing, since nobody chose 0.1 with this in mind.

But the column that should stop you is the last one. Fixed 0.1 means dropout is on, at the standard rate, but the same mask is reused for both views. The two representations are again identical, exactly as at p = 0 — and yet the score is 43.6 rather than 71.1. Same "identical twins" situation, twenty-seven points worse. Chapter 3 is the machinery that explains this, and Chapter 8 comes back for the full reading.

Why is a dropout mask a better positive-pair generator for text than deleting a single word, even though deleting one word is a very small edit?

Chapter 3: Alignment and Uniformity

We now have a method that works and no account of why. "Dropout is a good augmentation" is a restatement, not an explanation. This chapter installs the lens the paper borrows from Wang & Isola (2020), and it is the most portable idea in the whole lesson: two numbers that between them diagnose almost every failure of a representation space.

The two things a good embedding space must do

Think about what you are actually asking for. Two requirements, and they pull in opposite directions.

One: things that mean the same thing must be close. Otherwise similarity search misses obvious matches. Call this alignment.

Two: the space must be used. If every sentence maps to nearly the same point, then requirement one is trivially satisfied and the embedding is worthless — that is the collapse from Chapter 1. The embeddings should spread out and cover the sphere. Call this uniformity.

Alignment alone is maximised by the constant function. Uniformity alone is maximised by a random hash, which spreads everything perfectly and puts paraphrases in unrelated places. Any useful embedding is a compromise, and the value of Wang & Isola's framework is that it lets you measure where on that compromise you are, with two scalars, on a held-out set.

Alignment, defined

Assume every embedding has been L2-normalised, so it lives on the unit sphere. Let ppos be the distribution of semantically equivalent pairs. Then

align = E(x, x+) ~ ppos  ‖ f(x) − f(x+) ‖2

The expected squared distance between the embeddings of positive pairs. Lower is better. Zero would mean every paraphrase pair maps to exactly the same point.

Because the vectors are unit length, this quantity has a much friendlier form. Expand the square:

‖a − b‖2 = (a−b)T(a−b) = aTa − 2aTb + bTb = 1 − 2cos(a,b) + 1 = 2(1 − cos)

So alignment is just a rescaled cosine: ℓalign = 2(1 − E[cos]). A positive-pair cosine of 0.99 gives ℓalign = 0.02; a cosine of 0.80 gives 0.40. The range is [0, 4], with 4 meaning every pair is antipodal.

Uniformity, defined

Uniformity is subtler, because "spread out on a sphere" needs a functional form. Wang & Isola use the Gaussian potential — the same object physicists use for repulsive energy:

uniform = log  Ex, y ~ pdata, i.i.d.  e−2‖ f(x) − f(y) ‖2

Take two independent samples, measure their squared distance, and evaluate a decaying exponential of it. Pairs that are close contribute nearly 1; pairs that are far contribute nearly 0. Average, then take the log. Lower is better here too.

Three questions people always have, answered.

Why the exponential? Because it is a soft nearest-neighbour count. e−2d2 asks "is there another point right here?" and the average answers "on average, how crowded is a random point's neighbourhood?" A collapsed representation is maximally crowded.

Why the log? Purely for scale. The raw average lives in (0, 1]; the log maps it to (−∞, 0], which makes differences between well-spread configurations legible instead of all being squashed near zero.

Why is the maximum 0? If every point maps to the same place, every distance is 0, every exponential is 1, the average is 1, and log 1 = 0. So uniform = 0 is total collapse, and every real model produces a negative number. The more negative, the more spread.

The critical detail that most summaries omit. These two numbers are measured on a held-out evaluation set — specifically, alignment over the STS-B pairs that humans scored highly, and uniformity over all STS-B sentences. They are not measured on the training positives. That distinction is the difference between a tautology and a finding: a model trained on dropout twins trivially aligns its own dropout twins, and that tells you nothing. Whether it aligns genuinely different sentences that mean the same thing is the whole question.

Worked example: computing both by hand

Four sentences, embedded on a unit circle so the arithmetic is visible. We will compute uniformity over all six distinct pairs, and alignment on one positive pair.

Configuration A — the cone (what pretrained BERT looks like): the four points sit at 0°, 10°, 20°, 30°. Every pair is close. The six pairwise angles are 10°, 20°, 30°, 10°, 20°, 10°.

Squared distances, using ‖a−b‖2 = 2(1 − cosθ):

θ = 10°: cos = 0.98481 → d2 = 0.03038 → e−2d2 = e−0.06077 = 0.94104
θ = 20°: cos = 0.93969 → d2 = 0.12061 → e−0.24123 = 0.78566
θ = 30°: cos = 0.86603 → d2 = 0.26795 → e−0.53590 = 0.58514

Average over the six pairs (three at 10°, two at 20°, one at 30°):

(3 × 0.94104 + 2 × 0.78566 + 0.58514) / 6 = (2.82312 + 1.57132 + 0.58514)/6 = 4.97958/6 = 0.82993
uniform = ln(0.82993) = −0.186

Barely below zero. This configuration is nearly collapsed.

Configuration B — spread: the same four points at 0°, 90°, 180°, 270°. Four of the six pairs are 90° apart (d2 = 2) and two are antipodal (d2 = 4).

e−4 = 0.018316,   e−8 = 0.00033546
average = (4 × 0.018316 + 2 × 0.00033546)/6 = 0.073933/6 = 0.012322
uniform = ln(0.012322) = −4.396

From −0.19 to −4.40. The same four points, rearranged, and the uniformity number moves by 4.2 nats. This measure is extremely sensitive to exactly the pathology we care about.

Now alignment. Suppose sentence 1, at 0°, has a paraphrase that the model places at 5°. Then

align = 2(1 − cos 5°) = 2(1 − 0.99619) = 0.0076

Excellent alignment. But suppose that in the process of spreading the points out to reach configuration B, the model also pushed that paraphrase to 25° away:

align = 2(1 − cos 25°) = 2(1 − 0.90631) = 0.1874

Twenty-five times worse. That is the trade-off, quantified: the spreading that fixed uniformity destroyed alignment. Everything in this chapter is about which methods pay that price and which do not.

A caveat about absolute values. Computed on a finite sample over distinct pairs, ℓuniform depends on how many points you have — four well-separated points can score lower than a continuous uniform distribution, because a continuum always contains near-coincident pairs. So the number is a relative instrument: compare models on the same evaluation set, never compare across sample sizes. The paper does exactly this, evaluating every model on the same STS-B sentences.

Reading InfoNCE as an alignment/uniformity machine

Here is the connection that makes the lens more than a diagnostic. Split Chapter 1's loss into its two pieces using log(a/b) = log a − log b:

i = − sii/τ  +  log ∑j esij

The first term wants sii (the positive-pair cosine) as large as possible. That is alignment, up to the constant factors in ℓalign = 2(1 − sii).

The second term is a log-sum-exp of all similarities, which is dominated by the largest ones; minimising it pushes down whichever pairs are currently closest. That is a soft-max flavoured cousin of uniformity, whose log-mean-exp of −2d2 = −4(1−cos) has the same structure with a different constant. Wang & Isola made this rigorous: as the number of negatives goes to infinity, the contrastive loss converges to a weighted sum of exactly these two objectives.

limN→∞ [ InfoNCE ]  ≈  −(1/τ) E[sii]  +  Ex log Ey esxy   =   (alignment term) + (uniformity term)

So the contrastive objective is not related to alignment and uniformity. It is them, added together, with τ setting the exchange rate.

The derivation that explains the whole dropout-rate table

Now we can prove — not describe, prove — why removing dropout wrecks the model.

Suppose the two views are produced by the same deterministic function: hi+ = hi exactly, for every i, for every setting of the parameters. Then the positive similarity is

sii = cos(hi, hi) = 1   for all θ  ⇒   ∂sii/∂θ = 0

The alignment term is a constant. Its gradient is identically zero. It contributes nothing to any update, ever. The loss reduces to the uniformity term alone:

i = −1/τ + log ∑j esij  →   minimised by pushing everything apart, with nothing pulling back

And "everything" includes sentence pairs that genuinely mean the same thing. The model has no term telling it that some pairs should stay together, so it separates them along with all the others. Held-out alignment collapses. That is the mechanism, and it is exact: the missing gradient is not weak, it is zero.

Dropout restores it. With independent masks, hizhiz′, so sii < 1 and depends on θ. The model must now actively arrange its parameters so that a sentence's representation is stable under its own internal noise. That stability requirement is a smoothness constraint, and smoothness generalises: a model that cannot distinguish a sentence from its noisy self also cannot violently separate two sentences that differ only slightly in meaning. Alignment on held-out semantic pairs survives.

And "Fixed 0.1" scoring 43.6, below no-dropout's 71.1? With one mask reused for both views, the same zero-gradient argument applies — sii = 1 identically, alignment is again a constant. So the collapse is expected. The extra 27-point drop has a second cause layered on top: a fixed mask means the model spends its whole training run optimising one specific randomly-chosen sub-network with its activations scaled by 1/0.9, and then at evaluation time dropout is switched off and the full network runs. Training and inference are now different functions. Collapse plus a train/test mismatch beats collapse alone. (The paper reports the number; this reading of the gap is ours, and it is the reading the numbers support.)

The map of the field, in two coordinates

Alignment / uniformity: train it yourself

A working toy. Twelve sentences live on a circle; six pairs of them are secretly semantic twins (the thin links) and the training objective never sees those links — it only sees each sentence paired with its own dropout-jittered self, exactly as in unsupervised SimCSE. Press Run to descend on the InfoNCE loss and watch both coordinates move. The scatter on the right plots (uniformity, alignment) with a trail. Set dropout to 0 to reproduce the paper's collapse: uniformity plunges beautifully while alignment falls apart.

τ =
dropout:

Run the three dropout settings and watch the trail take three different shapes.

SettingWhat the trail doesWhy
noneUniformity drops fast; alignment climbs badly. The trail heads down-and-rightThe alignment gradient is exactly zero. Nothing resists the spreading, so semantic twins are separated along with everyone else
p = 0.1Uniformity drops; alignment stays roughly flat. The trail heads down-and-left, into the good cornerThe positive term is alive and demands local stability, which generalises to nearby meanings
p = 0.5Alignment stays excellent — and almost nothing spreads. The trail barely leaves the right-hand edgeThe two views are now so different that the only way to make them agree is to make the encoder nearly constant. Perfect alignment is bought by re-creating the collapse of Chapter 1 from the opposite direction

This is the paper's Figure 3, reproduced from scratch in a toy small enough to read. The real version tracks the two measures every ten steps of actual BERT training and shows the same three shapes.

Where the real models sit

Applying the lens to the actual methods produces a map that explains why each one succeeds or fails, and why the post-hoc fixes plateau.

ModelAlignmentUniformityAvg. STSReading
Avg. BERTgood (≈ 0.17)terrible (≈ −1.3)56.70Everything is close to everything — including the paraphrases, which is why alignment looks fine. The space is a cone
BERT-flowworsemuch better66.55A post-hoc transform buys uniformity by spreading the cone, and pays for it in alignment
BERT-whiteningworsemuch better66.28Same trade, different transform. Both land ~10 points above raw BERT and then stop
SBERT (supervised)goodsomewhat better74.89Labelled pairs fix alignment properly but do comparatively little for spreading
Unsup. SimCSEkeptmuch better76.25The only method that moves one coordinate without sacrificing the other
Sup. SimCSEimprovedmuch better81.57Entailment pairs sharpen alignment on top of the contrastive spreading

(The two numeric anchors are approximate coordinates from the paper's Figure 2; read the pattern rather than the digits. The STS column is exact.)

The story the table tells is clean. Raw BERT's problem was never alignment — it was uniformity. The post-hoc methods correctly diagnosed that and fixed it, but they operate on frozen embeddings and can only redistribute what is already there, so improving uniformity necessarily costs alignment. SimCSE fixes uniformity by training, which lets it simultaneously hold alignment in place with the positive term. Two coordinates, one method that moves the right one.

Diagnosing your own model with two numbers

Because both quantities are cheap to compute — one pass over a held-out set and a pairwise matrix — they make a genuine diagnostic instrument. Here is the decision table.

Uniformity good (very negative)Uniformity bad (near 0)
Alignment good (near 0)Working. Ship it and go measure your real taskThe pretrained-encoder starting point. Your negatives are too weak, your batch too small, or τ too large. Add repulsion.
Alignment bad (large)Your positives are wrong. The augmentation or pair-mining is producing pairs that are not actually equivalent — or, as in the no-dropout ablation, are so identical that the positive term contributes no gradient. Fix the pairs.Everything is broken. Usually a bug: mis-aligned batch reshape, wrong labels, or an encoder in eval() mode
python — the two numbers, on a held-out setimport torch, torch.nn.functional as F

def align_uniform(model, pairs, singles):
    # pairs:   list of (sent_a, sent_b) that HUMANS called equivalent
    # singles: a flat list of sentences from the same distribution
    A = F.normalize(model.encode([p[0] for p in pairs]), dim=-1)   # (P, d)
    B = F.normalize(model.encode([p[1] for p in pairs]), dim=-1)   # (P, d)
    align = (A - B).norm(dim=-1).pow(2).mean()                        # scalar, lower better

    S = F.normalize(model.encode(singles), dim=-1)                 # (M, d)
    d2 = torch.cdist(S, S).pow(2)                                  # (M, M)
    iu = torch.triu_indices(len(S), len(S), offset=1)              # distinct pairs only
    unif = torch.log(torch.exp(-2 * d2[iu[0], iu[1]]).mean())        # scalar, lower better
    return align.item(), unif.item()

Log these two next to your loss on every evaluation. A contrastive loss that falls while alignment climbs is the single most common silent failure in this whole family of methods, and no other instrument catches it.

The transferable lesson, worth more than the paper. Whenever an embedding model underperforms, measure these two numbers before changing anything. Bad alignment and good uniformity means your positives are wrong — your augmentation or pair-mining is producing pairs that are not actually equivalent. Good alignment and bad uniformity means your negatives are too weak or your batch is too small or your temperature is too high. The two failure modes need opposite fixes, and without the measurement you will guess wrong roughly half the time.
A model trained with no dropout on the positive pair achieves a much better (lower) uniformity than the pretrained encoder, yet its STS score collapses. What is the exact mechanism?

Chapter 4: Supervised SimCSE — Entailment In, Contradiction Against

Everything so far assumed you have no labels. Now suppose you do. The interesting question is not "can labels help" — obviously they can — but "does the same objective absorb them, or do you need a different method?" SimCSE's answer is that you change one thing: where the positive comes from. And then you get a bonus that turns out to be worth more than the positive itself.

What NLI data is

Natural Language Inference is the task of deciding whether one sentence follows from another. Annotators are given a premise and asked to write three hypotheses:

LabelDefinitionExample (premise: "Two dogs are running through a field.")
EntailmentMust be true if the premise is true"There are animals outdoors."
NeutralMight be true; the premise does not decide"Some puppies are running to catch a stick."
ContradictionCannot be true if the premise is true"The pets are sitting on a couch."

Two large corpora exist: SNLI (570,152 pairs, premises taken from image captions) and MNLI (392,702 pairs, spanning ten genres from fiction to government reports). SimCSE takes the union and keeps the entailment pairs — 314,315 of them — as its supervised training set.

The move: an entailment pair is a ready-made positive

Set (xi, xi+) = (premise, entailment hypothesis) and run Chapter 1's loss unchanged. That is the entire supervised method, at first pass.

It is worth being uncomfortable about this for a moment, because entailment is not equivalence. Entailment is asymmetric and lossy: "There are animals outdoors" follows from the premise but is far more general. If you embed them at the same point, you are asserting something the logic does not license.

The paper's defence is empirical, and it is the right kind of defence: this pair source beats every alternative that is symmetric.

Source of positive pairsWhat the pair isSTS-B dev
Unsupervised SimCSE (dropout)Same sentence, two masks82.5
QQPTwo Quora questions marked duplicate≈ 81.8
Flickr30kTwo human captions of the same photo≈ 81.5
ParaNMTBack-translated paraphrase pairs≈ 79.7
SNLI + MNLI entailmentPremise and a sentence it entails84.1
… plus contradiction as a hard negativeSee below86.2

(The ordering is the finding; the middle decimals are as reported in the paper's Table 4.) Note first that three of these labelled sources are no better than free dropout twins. Collecting paraphrase data is not automatically worth the money.

Why does entailment win? Two reasons, and both are about difficulty.

The surface forms are maximally different. A dropout twin shares every token with its partner. A back-translated paraphrase shares most of them. An entailment hypothesis was written from scratch by a human who read the premise and produced a genuinely different sentence — different words, different length, often different syntactic frame. Matching it requires the model to work in meaning space, because there is nothing else to work with.

The annotation is checked. ParaNMT pairs are machine-generated and inherit MT artefacts; QQP duplicates were crowd-labelled with known noise; Flickr30k captions of the same photo often describe different aspects of it ("a man in a red shirt" / "someone climbing a rock face"). NLI entailment was written under an explicit logical instruction and validated by multiple annotators.

The general principle, which outlives this paper. A positive pair is a training constraint, and its value is (how hard the constraint is to satisfy) × (how likely it is to be correct). Dropout twins are always correct but easy. Back-translation is hard but sometimes wrong. Entailment pairs are hard and usually correct, which is why 314k of them beat a million of most other things. When you build your own embedder, this product is the quantity to maximise — not the raw pair count.

The asymmetry, taken seriously

The empirical defence is not a licence to stop thinking, so let us look at what the asymmetry actually costs. Entailment is a one-way relation:

PremiseEntailment hypothesisDoes the reverse hold?
"A woman is slicing a red onion with a chef's knife.""A person is preparing food."No. Preparing food does not imply onions or a knife
"The train from Leeds arrived eleven minutes late.""A train arrived."No. Enormously more general
"Three children are playing in a fountain.""Children are outside."No.

Train on these as positives and you are asserting similarity where the data only licenses implication. The visible consequence is a real and documented one: models trained this way place a specific sentence very close to its own generalisation, so retrieval over a mixed corpus tends to surface short generic sentences for specific queries. "A person is preparing food" becomes a strong match for a great many cooking queries, because it sits near all of them.

Two things keep the damage contained, and both are worth knowing because they generalise.

The loss only ever sees relative comparisons. InfoNCE never asks "is this pair similar in absolute terms?" It asks "is this pair more similar than those other N−1 pairs?" A premise and its generalisation genuinely are more related than a premise and a random Wikipedia sentence, so the constraint being imposed is true even though the equality it implies is not.

The errors are unbiased in direction. Because NLI annotators wrote hypotheses at many levels of generality, the corpus contains generalisations, specialisations and rephrasings in roughly comparable measure. What the model absorbs is not "specific implies general" but the union of the ways two sentences can be semantically linked — averaged over 314,315 human judgements. The systematic component largely cancels; the shared semantic component survives. This is the same "five noisy targets around a truth beat one clean target of the wrong kind" argument that made multiple captions per clip valuable in the caption-supervision literature.

The bonus: contradiction pairs are free hard negatives

Now the part that matters more than the positive. Each NLI premise comes with a contradiction hypothesis, and it has a remarkable property: it is topically identical, lexically overlapping, and semantically opposite.

premise: "Two dogs are running through a field."
entailment (positive): "There are animals outdoors."
contradiction (hard negative): "The pets are sitting on a couch."

Compare that negative to what an in-batch negative actually looks like: "The 1957 Chevrolet Bel Air was produced in Flint, Michigan."

An in-batch negative is a random sentence. Separating it from the premise is nearly free — different topic, different vocabulary, different everything. The contradiction shares dogs/pets, the outdoor-scene frame, the animal-activity frame, and differs on exactly the thing that matters. Learning to separate those two is where a similarity model earns its keep.

A hard negative is a negative example that the current model finds confusable with the positive. In Chapter 1 we saw that InfoNCE mines them automatically, weighting each negative by pik. But the softmax can only weight negatives that are present in the batch, and a random batch of Wikipedia sentences contains almost none that are genuinely hard. NLI contradiction hypotheses inject hard negatives directly, one per example, for free, because a human already wrote them.

The objective with hard negatives

Each training example is now a triple (xi, xi+, xi) = (premise, entailment, contradiction). The loss extends by adding the hard negatives to the denominator:

i = − log  [  esim(hi, hi+)/τ  /  ∑j=1N ( esim(hi, hj+)/τ + esim(hi, hj)/τ )  ]

Read the denominator carefully, because the detail is easy to miss: the sum over j runs to N in both terms. Row i's denominator contains not only its own contradiction xi, but every row's contradiction. The multiple-choice test now has 2N candidates instead of N, and exactly one of them is right.

QuantityUnsupervisedSupervised with hard negatives
Candidates per rowN2N
Chance-level lossln N = 4.16 at N = 64ln 2N = 6.24 at N = 512 → ln 1024 = 6.93
Rows in the forward pass2N (sentence duplicated)3N (premise, entailment, contradiction)
Similarity matrix(N, N)(N, 2N) — a positive block and a hard-negative block, concatenated

Worked example: what one hard negative is worth

This is the calculation that justifies the whole design. Take a batch of two supervised examples and the following cosines for row 1, at τ = 0.05 (multiplier 20):

CandidateWhat it iscoslogit (cos × 20)
h1+the correct entailment0.7515.0
h2+another row's entailment — a random in-batch negative0.306.0
h1this row's contradiction — the hard negative0.6212.4
h2another row's contradiction0.255.0

Factor e15 out of the denominator so the arithmetic stays on human scale. The denominator becomes e15(1 + e−9 + e−2.6 + e−10):

e−9 = 0.00012341,   e−2.6 = 0.0742736,   e−10 = 0.0000454
sum = 1 + 0.00012341 + 0.0742736 + 0.0000454 = 1.0744424
1 = ln(1.0744424) = 0.07181

Now delete the two hard negatives and recompute with in-batch negatives only:

sum = 1 + e−9 = 1.00012341  →   ℓ1 = ln(1.00012341) = 0.00012340
ratio = 0.07181 / 0.00012340 = 582×

The same encoder, the same premise, the same positive. Adding one human-written contradiction multiplies this row's loss — and therefore its gradient — by nearly six hundred.

Look at where the repulsive force goes, using Chapter 1's gradient formula (the weight on negative k is pik):

p(contradiction) = 0.0742736 / 1.0744424 = 0.06913
p(random in-batch) = 0.00012341 / 1.0744424 = 0.00011486
ratio = 602×

Ninety-nine point eight percent of the repulsion in this row is being spent on the one negative that is worth learning from. Random negatives are not merely less useful — once a hard negative is present, they are numerically invisible.

This is why "add more negatives" and "add better negatives" are not the same intervention. Doubling the batch from 512 to 1024 doubles the number of random negatives, which the softmax will weight at roughly 10−4 each. Adding one contradiction per example adds a negative weighted at 7 × 10−2. The batch increase costs twice the memory; the contradiction costs one extra forward pass. In the paper's numbers, hard negatives are worth 2.1 STS-B points (84.1 → 86.2) and no batch size buys that.
Hard negatives on the sphere

The anchor (premise) sits at the top. Its entailment is the green point; the contradiction is the red one, deliberately placed nearby because it shares vocabulary and topic; grey points are random in-batch negatives. Arrow thickness is the actual gradient weight pik from Chapter 1. Toggle the hard negatives off and watch every arrow but one vanish — that is the 582×, drawn.

Things the authors tried that did not help

A good ablation section tells you where the idea stops working, and this one does.

Weighting the hard negatives. You can add a coefficient α on the hard-negative term, eα·sim/τ — turning their influence up or down. The paper swept α and found α = 1 (no weighting at all) to be the best or tied-best setting. The softmax's own weighting was already correct; a second knob on top of it just distorts the mining that Chapter 1 showed happens automatically.

Adding the neutral hypotheses as extra negatives. Every NLI premise also has a neutral hypothesis, and it is sitting right there. Using it as another hard negative does not help. The reason is a labelling subtlety: "neutral" means the premise does not decide, which is entirely compatible with the two sentences being very similar. Training the model to push neutrals away teaches it something false.

Adding ANLI. ANLI is an adversarially-collected NLI dataset, built specifically so that models fail on it. Adding it does not improve STS. Adversarially hard inference examples are not the same thing as informative similarity examples; ANLI's difficulty comes from requiring multi-step reasoning, which a bag-of-meaning sentence vector is never going to represent anyway.

The realisation: shapes for the supervised loop

python — supervised SimCSE, one step# Each example is a TRIPLE. features has 3 sentences per row.
# input_ids: (N, 3, T)  ->  [premise, entailment, contradiction]
ids = input_ids.view(-1, T)                   # (3N, T)

out = mlp(encoder(ids).last_hidden_state[:, 0])  # (3N, 768)
out = out.view(-1, 3, 768)                      # (N, 3, 768)
z1, z2, z3 = out[:,0], out[:,1], out[:,2]      # anchor / positive / hard neg

z1 = F.normalize(z1, dim=-1)
z2 = F.normalize(z2, dim=-1)
z3 = F.normalize(z3, dim=-1)

sim_pos  = z1 @ z2.T / 0.05                    # (N, N)  diagonal = the answers
sim_hard = z1 @ z3.T / 0.05                    # (N, N)  EVERY row's contradiction
logits   = torch.cat([sim_pos, sim_hard], dim=1)  # (N, 2N)

labels = torch.arange(N, device=z1.device)      # (N,) — answer still on the diagonal
loss = F.cross_entropy(logits, labels)          # scalar

Two lines carry the whole chapter. torch.cat([sim_pos, sim_hard], dim=1) widens the multiple-choice test from N options to 2N, and the labels do not change — the correct answer is still at index i, because the hard negatives were appended after the positive block. Append them before it and every label is wrong by N; the loss will sit near ln 2N and you will spend an hour blaming the learning rate.

The supervised recipe otherwise differs from the unsupervised one in exactly three numbers: batch size 512 instead of 64, learning rate 5e-5 instead of 3e-5, and 3 epochs over 314k triples instead of 1 epoch over 1M sentences. And unlike the unsupervised setting, the MLP pooling head is kept at test time.

Why the bigger batch here and not there? Because the two settings need different things from their negatives. Unsupervised SimCSE has only random negatives, and the paper found it insensitive to batch size — adding more random Wikipedia sentences to a batch adds almost no information once you have a few dozen. Supervised SimCSE already has a strong hard negative per row, so the batch's job changes: it is now supplying the background distribution against which the hard negative is judged, and a wider background sharpens the estimate. Different negatives, different scaling behaviour.
In the worked example, adding one contradiction hypothesis multiplied the row's loss by 582×. What does that ratio actually measure?

Chapter 5: Temperature and the Shape of the Batch

τ = 0.05 looks like a detail. It is not. It is the single hyperparameter most likely to be responsible for a contrastive model that will not train, and the mechanism by which it breaks things is worth understanding in full, because the same τ appears in CLIP, MoCo, SimCLR, E5, BGE and every embedding model shipped since.

What temperature does, from zero

The softmax over scaled scores is

pij = esij / ∑k esik

Consider what happens at the two extremes, because the limits tell you what the knob is for.

τ → ∞. Every score is divided by an enormous number, so every logit approaches 0, so every exponential approaches 1, so pij → 1/N. The distribution is uniform: the model expresses no preference at all, and the loss is pinned at ln N regardless of what the encoder does.

τ → 0. Differences between scores are multiplied by a huge factor, so the largest score's exponential dwarfs all others: p → a one-hot vector on the argmax. The softmax becomes a hard maximum, which is where the name comes from.

So τ interpolates between "no opinion" and "absolute certainty". The name is borrowed from statistical physics, where the Boltzmann distribution p ∝ e−E/kT has exactly this form: high temperature means a system spread across many states, low temperature means it settles into the ground state.

Why cosine similarity forces τ to be small

Chapter 1 gave the argument; here is the number that makes it undeniable. Cosine similarities live in [−1, 1]. At τ = 1, the very best possible row — positive at cosine 1, all N−1 negatives at cosine −1 — produces

min = log( 1 + (N−1) e−2/τ ) = log( 1 + 63 × e−2 ) = log( 1 + 63 × 0.13534 ) = log(9.526) = 2.254

A perfect encoder cannot get the loss below 2.254, while a random one sits at ln 64 = 4.159. The entire dynamic range available to learning is 1.9 nats, and the gradient is correspondingly feeble everywhere in it.

At τ = 0.05 the same expression gives log(1 + 63 e−40) = log(1 + 2.7 × 10−16) ≈ 2.7 × 10−16. The floor is zero for all practical purposes, and the full range from 4.159 down to 0 is available.

The rule of thumb, and where it comes from. Temperature exists to restore the dynamic range that L2 normalisation destroyed. Roughly, you want 1/τ to be comparable to the reciprocal of the typical similarity gap your encoder can produce. For cosine scores separated by ~0.1, a multiplier of 20 turns that into a logit gap of 2, which is a healthy softmax margin. That is the reasoning behind 0.05, and it is why unnormalised-dot-product models (whose logits are already large) use temperatures near 1 or none at all.

Worked example: the same row at four temperatures

Take the row-1 cosines from Chapter 1: positive 0.72, negatives 0.54 and 0.19. Nothing about the encoder changes across the four columns below. Only the divisor changes.

τlogits (cos/τ)p11 (positive)1|∂ℓ/∂s11| = (1−p11)/τp12 : p13 (hard : easy)
1.000.72, 0.54, 0.190.41260.88530.5871.42 : 1
0.203.60, 2.70, 0.950.67690.39021.6165.75 : 1
0.107.20, 5.40, 1.900.85450.15731.45533.1 : 1
0.0514.4, 10.8, 3.800.97340.02700.5321100 : 1
0.0172.0, 54.0, 19.01.00001.5 × 10−81.5 × 10−61.6 × 1015 : 1

Walk the τ = 0.20 row by hand so you trust the rest. Multiply the cosines by 5: 3.60, 2.70, 0.95. Subtract the max: 0, −0.90, −2.65. Exponentiate: 1, 0.40657, 0.07065. Sum: 1.47722. So p11 = 1/1.47722 = 0.67695 and ℓ1 = ln(1.47722) = 0.39016. The positive-pair gradient magnitude is (1 − 0.67695)/0.20 = 0.32305/0.20 = 1.6153.

Now read the fifth column, which is the one that matters, and notice that it is not monotonic.

The gradient magnitude peaks at an intermediate temperature, and both failure modes look like "nothing is happening". At large τ the factor 1/τ is small, so even though the model is far from confident, each step is tiny. At small τ the model is already at p11 ≈ 1 on this row, so (1 − p11) collapses and the gradient dies from the other direction. Between them — here at τ = 0.158 for this three-candidate row — sits a maximum. And because the gap widens as training proceeds, the optimal τ is not a constant of the problem; it is a function of how good your encoder currently is.

Where the peak is, in closed form

That maximum can be located exactly, which turns a vague "tune it" into a rule of thumb worth carrying around.

Take the simplest case: one positive at similarity s and one negative at s − Δ, so Δ is the cosine gap the encoder currently achieves. The softmax over two candidates is a logistic function:

p11 = 1 / (1 + e−Δ/τ)  ⇒   1 − p11 = 1 / (1 + eΔ/τ)

So the positive-pair gradient magnitude is

g(τ) = (1 − p11)/τ = 1 / [ τ (1 + eΔ/τ) ]

Substitute x = Δ/τ (so τ = Δ/x) and the Δ factors out:

g = (1/Δ) · x / (1 + ex)

Maximise the bracket. Differentiating x/(1+ex) and setting the numerator to zero gives (1 + ex) − x ex = 0, that is

ex(x − 1) = 1  ⇒   x* = 1.2785  (by Newton's method)

Check: e1.2785 = 3.5917, and 3.5917 × 0.2785 = 1.0003. So

τ* = Δ / 1.2785 = 0.782 · Δ

Set the temperature to about three quarters of the cosine gap your model currently produces. Verify it on our numbers: with Δ = 0.72 − 0.54 = 0.18, the formula predicts τ* = 0.1408, and a numerical sweep over the two-candidate row finds the peak at 0.1410. Adding the third candidate shifts it to 0.158, because a second negative adds probability mass off the diagonal.

Read SimCSE's τ = 0.05 backwards through this formula and it implies a working cosine gap of Δ ≈ 0.064 — positives around 0.85, hardest negatives around 0.79. That is exactly the regime a well-trained sentence encoder operates in, and it explains why 0.05 keeps reappearing across papers that never coordinated on it: everyone's normalised transformer embeddings end up with similar-sized gaps, so everyone's optimal temperature lands in the same place. It also tells you when to break with convention — if your encoder produces a gap of 0.3 (an easy, well-separated domain), τ = 0.05 is far below optimal and you are training in the saturated regime.

The final column is the other half of the story: as τ falls, the ratio of attention paid to the hard negative versus the easy one explodes from 1.42 to 1015. Small τ does not make the model push harder overall; it makes the model push almost exclusively on whatever is currently closest. Contrastive learning at low temperature is hard-negative mining with extra steps.

The temperature reshaper

Six candidates with fixed cosine scores — the encoder never changes. The left column is the raw cosines; the middle is the softmax at your chosen τ; the right is the gradient weight each candidate receives. Sweep τ and watch the probability mass and the repulsive force migrate. The loss and the chance level (ln 6) are printed below.

τ =

The dark side: what a small τ does to a wrong label

Press that last button. The scenario is one every practitioner meets: a negative in the batch is actually a paraphrase of the anchor, so the model correctly scores it above the designated positive. The loss now punishes the model for being right.

Suppose the positive scores 0.50 and this false negative scores 0.70, with a third candidate at 0.20. At τ = 1:

e0.5 = 1.64872,  e0.7 = 2.01375,  e0.2 = 1.22140  →   sum = 4.88387
ℓ = ln(4.88387) − 0.5 = 1.58601 − 0.5 = 1.086

At τ = 0.05 the logits are 10, 14, 4. Factor out e14:

ℓ = [14 + ln(1 + e−4 + e−10)] − 10 = 14 + ln(1.018361) − 10 = 4 + 0.018194 = 4.018

The penalty for the same mislabelling is 3.7 times larger. And since this row's loss now dominates the batch, its (incorrect) gradient dominates the update.

The trade-off, stated once and for all. Low temperature is a magnifier. It magnifies the useful signal from hard negatives and it magnifies the damage from false negatives by roughly the same factor. Whether a small τ helps therefore depends entirely on the purity of your batch. SimCSE gets away with τ = 0.05 because it samples from a million diverse Wikipedia sentences, where two randomly drawn sentences are almost never paraphrases. Run the identical recipe on a corpus of 50,000 near-duplicate support tickets and the same τ will actively destroy your model.

Batch composition, quantified

Let q be the probability that two randomly drawn sentences from your corpus are semantically equivalent. In a batch of N, the expected number of false negatives is N(N−1)q.

CorpusPlausible qExpected false negatives, N = 64Expected, N = 512
1M random Wikipedia sentences10−50.04 — effectively never2.6
Product reviews, one category10−34.0261
Support tickets, one product10−240 — two thirds of the batch2,614

Read row three. On a support-ticket corpus with a batch of 64, roughly forty of your sixty-three "negatives" are things the model should be pulling closer. The loss is fighting itself, and lowering τ makes the fight fiercer. This single table explains why so many teams reproduce SimCSE perfectly on Wikipedia and then watch it fail on their own data.

The mitigations, in order of how often they are the right answer:

FixWhat it doesWhen to reach for it
Deduplicate and diversify the samplingLowers q directly — sample across clusters, not within themAlways. Cheapest and most effective
Raise τSoftens the penalty on every negative, false ones includedWhen you cannot clean the corpus
Mask known duplicates out of the denominatorRemoves the wrong constraints exactlyWhen you have duplicate labels or a clustering
Smaller batchesN(N−1)q falls quadraticallyCounter-intuitive but correct on dirty corpora

On batch size, the paper is worth quoting for what it does not say. Unsupervised SimCSE uses a batch of 64 — tiny by contrastive standards, where vision models routinely use 4,096 — and the appendix reports that the model is not very sensitive to batch size provided the learning rate is adjusted with it. That is a meaningful negative result. In image contrastive learning, batch size is a headline hyperparameter; here the negatives arrive already-diverse from a million-sentence pool, so more of them add little.

Symmetric or not?

One structural difference from CLIP is worth naming since people port code between them. CLIP's loss is symmetric: it computes cross-entropy along the rows and the columns of the similarity matrix and averages. SimCSE's unsupervised loss, as published, is one-directional — rows only.

The asymmetry is defensible here and not there. In CLIP the two towers are different networks over different modalities, so "which caption matches this image" and "which image matches this caption" are genuinely different questions with different failure modes. In SimCSE both views come from the same encoder applied to the same sentence, so the similarity matrix is nearly symmetric already and the second direction adds little beyond compute. Adding it is harmless; expecting it to matter is not.

Your contrastive model trains fine on Wikipedia but collapses on your company's 40,000 support tickets, using the identical recipe with τ = 0.05. What is the first thing to suspect?

Chapter 6: The Narrow Cone

Chapter 3 gave us a name for BERT's problem — bad uniformity — and a number for it. This chapter explains where that pathology comes from, why two post-hoc fixes almost solve it, and derives, from the contrastive loss itself, the reason training fixes it better than any transform can.

The measurement that started it

Take two sentences with nothing in common. "The 1957 Chevrolet Bel Air was produced in Flint, Michigan." and "She practises the cello every morning before school." Embed both with BERT and take the cosine.

You will get something in the neighbourhood of 0.6 to 0.8. Not 0.0, which is what "unrelated" ought to look like on a sphere. Repeat with a thousand random pairs and the average barely moves. Ethayarajh (2019) measured this systematically across BERT, ELMo and GPT-2 and found that in the upper layers the expected cosine similarity between randomly chosen words and sentences is high — often above 0.5, sometimes far above.

The name for this is anisotropy. A distribution is isotropic if it looks the same in every direction — its second moment is a multiple of the identity, so variance is spread evenly across all d axes. It is anisotropic when the mass concentrates in a small subspace: a narrow cone rather than a ball.

Why a cone is catastrophic for cosine similarity, in one worked line. Suppose every embedding is a shared direction plus a small individual deviation: hi = μ + δi, with ‖μ‖ = 1 and the deviations small and mutually orthogonal. Take μ = (1, 0), δ1 = (0, 0.3) and δ2 = (0, −0.3) — deviations pointing in exactly opposite directions, as different as this geometry allows. Then h1 = (1, 0.3) and h2 = (1, −0.3), each of norm √1.09 = 1.0440, and

cos = (1 × 1 + 0.3 × (−0.3)) / (1.0440 × 1.0440) = 0.91 / 1.09 = 0.8349

Maximally opposed content, and the cosine still reads 0.83. The shared component μ contributes 1 to every numerator and swamps the ±0.09 that actually distinguishes the two sentences. Now subtract the mean: h1μ = (0, 0.3) and h2μ = (0, −0.3), whose cosine is −1.000. The information was there the entire time, buried under a constant.

That toy is the whole of BERT-whitening in two lines, and it also tells you why raw BERT looked like it had "good alignment" in Chapter 3's table: when everything is at cosine 0.8 from everything, paraphrases are close — but so is every other pair, which is precisely what uniformity measures.

Where the cone comes from

Anisotropy is not a bug in BERT's code. It is a predictable consequence of how language models are trained, and the mechanism was identified by Gao et al. (2019) under the name representation degeneration.

A language model ends in a softmax over the vocabulary: for hidden state h and output embedding wv of token v, the score is hTwv. Now think about a rare token — one that appears as the target almost never. What gradient does its output embedding receive?

Almost every update in which it participates is a negative one: it appears in the softmax denominator, so the loss pushes hTwrare down, for whatever hidden states happen to be in the batch. Over millions of steps, the accumulated push drives wrare toward the direction that minimises its dot product with the typical hidden state — roughly, toward the negative of the average hidden direction. Every rare token is pushed toward the same place, because they all see the same average. The rare tokens pile into a shared cone.

Meanwhile hidden states are pushed to have large dot products with the output embeddings of frequent tokens, which are themselves few and concentrated. Both halves of the model converge on a preferred direction, and the whole representation drifts off-centre.

ContributorEffect on the geometry
Rare tokens in the softmax denominatorTheir embeddings receive almost only negative gradient, collapsing into a common direction
Frequent tokens dominating the targetsHidden states are optimised to align with a handful of directions
No objective term anywhereNothing in MLM or next-token prediction rewards spreading. Only relative scores matter, and a global offset changes none of them
LayerNorm's constraintPuts representations on a sphere (of radius √d, after scaling) but says nothing about how they distribute over it
The deep reason, stated once. A softmax over the vocabulary is invariant to adding any constant vector c to every output embedding, because the resulting hTc shifts every logit identically and cancels in the softmax. The pretraining objective is therefore blind to the common component. It never penalises it, so it never removes it, and stochastic gradient descent has no reason to keep it small. Cosine similarity, by contrast, is exquisitely sensitive to exactly that component. The mismatch is not a failure of learning; it is a mismatch between what was optimised and what we are reading out.

The post-hoc fixes, and their ceiling

If the problem is a common component, subtract it. Two 2020–21 methods do essentially that, without touching the encoder.

BERT-whitening (Su et al. 2021). Collect embeddings over a corpus, compute the mean μ and covariance Σ, eigendecompose Σ = UΛUT, and transform

h̃ = (h − μ) U Λ−1/2

Subtracting μ removes the shared offset; multiplying by UΛ−1/2 rotates onto the principal axes and rescales each so that every direction has unit variance. The transformed covariance is exactly the identity. Perfect isotropy, achieved with a matrix multiply and no gradient steps. Result: 66.28.

BERT-flow (Li et al. 2020). Same goal, more machinery: train an invertible normalising flow that maps the BERT embedding distribution onto a standard Gaussian, then use the mapped vectors. Result: 66.55.

Both take raw BERT from 56.70 to about 66.4, a ten-point gain for essentially no cost. And then both stop, ten points short of SimCSE. Chapter 3 told us why in the abstract — they trade alignment for uniformity. Here is the concrete version:

Whitening is too blunt an instrument, and you can say exactly how. Setting the covariance to the identity declares that every direction in the space is equally important. That is false. Some directions genuinely carry more semantic variance than others — that is what it means for a representation to have structure. Whitening amplifies the low-variance directions, which are disproportionately noise, by 1/√λ where λ is tiny. It fixes the offset (good) and destroys the informative anisotropy along with the uninformative kind (bad). Contrastive training flattens the spectrum too — but under the supervision of an alignment term that protects the directions distinguishing paraphrases from non-paraphrases. Same flattening, selective about which structure survives.

The derivation: how the loss flattens the spectrum

This is the paper's theoretical contribution and it is short enough to do in full. Assemble the m embeddings of your corpus as rows of a matrix W ∈ Rm×d, all rows L2-normalised.

Step 1: isolate the repulsive term. Chapter 3 split the loss as −sii/τ + log∑jesij. Write the second term as an expectation over the data:

(1/m) ∑i log [ (1/m) ∑j e hiThj / τ ]

Step 2: apply Jensen's inequality. For any random variable Z, log E[eZ] ≥ E[Z], because the exponential is convex. Setting Z = hiThj/τ with j uniform:

log [ (1/m) ∑j ehiThj ] ≥ (1/m) ∑j hiThj / τ

Averaging over i as well:

(second term) ≥ (1 / τ m2) ∑ij hiThj = Sum(WWT) / (τ m2)

where Sum(·) means the sum of all entries of the matrix. So the repulsive term, times τm2, is an upper bound on Sum(WWT). Driving the term down drives the bound down.

Step 3: recognise what Sum(WWT) is. Expand the double sum:

ij hiThj = ( ∑i hi )T ( ∑j hj ) = ‖ ∑i hi2 ≥ 0

It is the squared norm of the mean embedding (times m2). Minimising it is centring the embeddings. The contrastive loss derives, all by itself, the first half of what whitening does by hand.

Step 4: the trace is pinned. Because every row is a unit vector, the diagonal of WWT is all ones, so

tr(WWT) = m = ∑k λk

The eigenvalues must always sum to m, no matter what the encoder does. This is the constraint that makes the whole argument work: you cannot shrink the spectrum, only redistribute it.

Step 5: Merikoski's bound. For a matrix all of whose entries are non-negative, the sum of the entries is an upper bound on the largest eigenvalue: Sum(WWT) ≥ λ1. So minimising Sum squeezes λ1 downward — while ∑λk stays fixed at m. Mass taken from the top eigenvalue must reappear in the smaller ones. The spectrum flattens.

The honest caveat, which the paper states and most summaries drop. Merikoski's bound requires all entries of WWT to be non-negative — that is, every pairwise cosine positive. That is precisely the anisotropic regime: when all embeddings sit in a cone, all cosines are positive and the bound bites hard. Once the space has opened up and negative cosines appear, the bound stops being informative (our worked example below has Sum = 0 while λ1 = 2, so the inequality plainly fails there). Read the derivation as an account of the escape from the cone — exactly the phase where BERT starts — rather than a global statement about the optimum.

Worked example: three configurations, by hand

Four unit vectors in two dimensions. We compute Sum(WWT), the eigenvalues of WTW (which are the non-zero eigenvalues of WWT, and whose square roots are the singular values of W), and the uniformity from Chapter 3.

Configuration A — the cone, at 0°, 10°, 20°, 30°. Rows: (1, 0), (0.98481, 0.17365), (0.93969, 0.34202), (0.86603, 0.50000).

Sum of rows: x-component 1 + 0.98481 + 0.93969 + 0.86603 = 3.79053; y-component 0 + 0.17365 + 0.34202 + 0.50000 = 1.01567. So

Sum(WWT) = 3.790532 + 1.015672 = 14.3681 + 1.0316 = 15.400

Now WTW, a 2×2 matrix of column inner products:

∑x2 = 1 + 0.96985 + 0.88302 + 0.75000 = 3.60287
∑y2 = 0 + 0.03015 + 0.11698 + 0.25000 = 0.39713
∑xy = 0 + 0.17101 + 0.32139 + 0.43301 = 0.92542

Trace = 3.60287 + 0.39713 = 4.000, exactly m as promised. Determinant = 3.60287 × 0.39713 − 0.925422 = 1.43075 − 0.85640 = 0.57436. Eigenvalues of a 2×2 with trace T and determinant D are (T ± √(T2 − 4D))/2:

√(16 − 2.29744) = √13.70256 = 3.70170
λ1 = (4 + 3.70170)/2 = 3.8508,   λ2 = (4 − 3.70170)/2 = 0.1492

Condition number λ12 = 25.8. Singular values √λ = 1.962 and 0.386. One direction carries 96% of the energy; the space is effectively one-dimensional. And check Merikoski: 15.400 ≥ 3.851. It holds, because every cosine here is positive.

Configuration B — a quarter turn, at 0°, 30°, 60°, 90°. Here ∑x2 = ∑y2 = 2.000 and ∑xy = 0.86603, so λ = 2 ± 0.86603:

Sum(WWT) = 2 × 2.366032 = 11.196,   λ1 = 2.866, λ2 = 1.134,   ratio 2.53

Configuration C — fully spread, at 0°, 90°, 180°, 270°. The rows sum to the zero vector, so Sum(WWT) = 0. And WTW = 2I, giving λ1 = λ2 = 2 and ratio 1.00 — a perfectly flat spectrum.

ConfigurationSum(WWT)λ1λ2λ12uniform
A — cone (0–30°)15.4003.8510.14925.8−0.186
B — quarter (0–90°)11.1962.8661.1342.53−1.077
C — spread (0–270°)0.0002.0002.0001.00−4.396

Every column moves together. Sum(WWT) falls, λ1 falls while the trace stays at 4, the condition number collapses toward 1, and uniformity improves by more than four nats. The theoretical quantity the loss provably minimises, the spectral quantity we care about, and the empirical measure from Chapter 3 are three views of one thing.

The cone flattener

Sixty-four embeddings in eight dimensions, drawn from a distribution whose cone width you control. Left: a 2-D projection onto the top two principal directions. Right: the eight singular values as a bar chart, with the trace (their squared sum) pinned at 64 — watch the mass slide from the first bar into the others without the total changing. Below: Sum(WWT), the mean pairwise cosine, the condition number, and the Chapter 3 uniformity, all live.

Preset:

Two things to notice while you play. First, "+ centring" alone — just subtracting the mean, no rescaling — recovers most of the improvement, which is the same lesson as the (1, ±0.3) example: the dominant pathology is a shared offset. Second, "+ whitening" produces a perfectly flat spectrum, flatter than the contrastive preset, and yet we know it scores ten points worse on STS. Flatness is not the goal. Flatness with the right structure preserved is the goal, and only a trained objective with an alignment term can tell the difference.

Measure it on your own model, in nine lines

Anisotropy is not a theoretical concern; it is a property of whatever encoder you happen to be holding, and it takes under a minute to check.

python — is your embedding space a cone?import numpy as np

E = model.encode(sentences)                       # (m, d), m ≈ 2000 is plenty
E = E / np.linalg.norm(E, axis=1, keepdims=True)   # unit rows — required

G = E @ E.T                                        # (m, m) all pairwise cosines
iu = np.triu_indices(len(E), 1)
print("mean random-pair cosine :", G[iu].mean())      # > 0.4 = a cone. Should be ≈ 0
print("||mean embedding||      :", np.linalg.norm(E.mean(0)))
#   Sum(WW^T) = m^2 * ||mean||^2  — the exact quantity Chapter 6 minimises

s = np.linalg.svd(E, compute_uv=False)              # (d,) singular values, descending
print("spectrum decay          :", (s[:6] / s[0]).round(3))
q = s**2 / (s**2).sum()                            # eigenvalue shares, sums to 1
print("effective rank          :", np.exp(-(q * np.log(q + 1e-12)).sum()))

The last line is worth explaining because it is the most useful single number. Treating the normalised eigenvalue shares qk as a probability distribution, its spectral entropy H = −∑ qk log qk measures how many directions the mass is spread over, and eH — the effective rank — converts that back into a dimension count.

Sanity-check it on our three toy configurations. For configuration C, q = (0.5, 0.5), so H = ln 2 and the effective rank is exactly 2 — both dimensions fully used. For configuration A, q = (3.8508/4, 0.1492/4) = (0.9627, 0.0373), giving H = −(0.9627 ln 0.9627 + 0.0373 ln 0.0373) = −(−0.0366 − 0.1227) = 0.1593 and an effective rank of e0.1593 = 1.17. Four vectors in two dimensions, using 1.17 of them.

Run this on raw BERT with d = 768 and the effective rank comes out in the low tens rather than the hundreds. That number, more than any plot, is what "the space is a cone" means operationally: you are paying to store 768 floats and getting the discriminative power of a couple of dozen.

What the paper measured

The paper plots the singular value distribution of the sentence embedding matrix for BERT, BERT-whitening, and SimCSE, and the picture matches the derivation exactly: BERT's spectrum decays steeply, SimCSE's is markedly flatter. It also reports the alignment/uniformity coordinates we saw in Chapter 3, showing SimCSE moving down the uniformity axis while holding its horizontal position.

And there is a satisfying consistency check available. Chapter 3 defined uniformity through a pairwise Gaussian potential; this chapter derived flattening through a matrix spectrum. They are the same phenomenon in different coordinates — the pairwise view and the second-moment view of "is the mass spread out". Our three-configuration table demonstrates the equivalence numerically, and it is the reason you can diagnose anisotropy either way: measure the average cosine of random pairs (cheap, one number) or eigendecompose the embedding matrix (informative, tells you how many directions are being used).

BERT-whitening makes the embedding covariance exactly the identity — a perfectly flat spectrum, flatter than SimCSE achieves. Why does it still score ten points lower on STS?

Chapter 7: The Toy, By Hand

Six chapters of machinery. Now we run the whole thing on paper: three sentences, two dropout masks each, every cosine, every exponential, and a final loss number you can check against real PyTorch. If you can reproduce this page with a pencil, you understand SimCSE.

What is a caricature and what is exact. The caricature: our encoder has four hidden units instead of 768, and dropout is applied once at the output instead of at 37 sites inside 12 transformer layers. The exact parts: inverted dropout with its 1/(1−p) rescaling, L2-normalised cosine similarity, temperature τ = 0.05, softmax cross-entropy with labels arange(N), and the mean over the batch. Every formula below is the one that runs in the real thing; only the dimensions shrink.

The setup

Three sentences in the batch:

iSentencePre-dropout representation ui
1"A man is playing a guitar."(4, 3, 2, 1)
2"A person plays a musical instrument."(3, 4, 1, 2)
3"The stock market closed lower today."(1, −2, 4, −3)

The four units are not arbitrary. Read them as: unit 1 ≈ "human agent", unit 2 ≈ "music", unit 3 ≈ "finance/abstract", unit 4 ≈ "temporal reference". Sentences 1 and 2 have similar profiles because they mean nearly the same thing; sentence 3 does not. This is what a good encoder is supposed to produce, and our toy encoder already has it — we are studying what the loss does, given a representation.

Dropout at p = 0.5, so each mask keeps two of the four units and multiplies survivors by 1/(1 − 0.5) = 2. (Real SimCSE uses 0.1; we use 0.5 so the masks are visible.) The six masks — two per sentence, drawn independently:

Sentencemask z (view A, the "query")hA = 2 · u ⊙ zmask z′ (view B, the "key")hB
1keep {1, 2}(8, 6, 0, 0)keep {1, 3}(8, 0, 4, 0)
2keep {1, 2}(6, 8, 0, 0)keep {2, 4}(0, 8, 0, 4)
3keep {3, 4}(0, 0, 8, −6)keep {1, 3}(2, 0, 8, 0)

Check one cell before continuing. Sentence 1, view A, keeps units 1 and 2 of (4, 3, 2, 1) and doubles them: (2×4, 2×3, 0, 0) = (8, 6, 0, 0). Units 3 and 4 were dropped, so they are exactly zero.

Norms, which we will need for every cosine:

h1A‖ = √(64+36) = 10    ‖h2A‖ = √(36+64) = 10    ‖h3A‖ = √(64+36) = 10
h1B‖ = √(64+16) = √80 = 8.94427    ‖h2B‖ = √(64+16) = 8.94427    ‖h3B‖ = √(4+64) = √68 = 8.24621
Notice that the factor of 2 will cancel. Both the numerator and the two norms in a cosine are homogeneous of degree one in each vector, so multiplying h by any positive constant leaves the cosine unchanged. Inverted dropout's rescaling therefore has no effect at all on the similarities — what matters is purely which units survived. That is worth knowing: dropout's contribution to SimCSE is entirely combinatorial (which subspace you project onto), not a matter of magnitude.

Step 1: the 3 × 3 similarity matrix

We need sij = cos(hiA, hjB) for all nine combinations. Three in full, the rest in the table.

The positive pair, s11. h1A = (8, 6, 0, 0), h1B = (8, 0, 4, 0). Only unit 1 survived in both views:

dot = 8(8) + 6(0) + 0(4) + 0(0) = 64
s11 = 64 / (10 × 8.94427) = 64 / 89.4427 = 0.715542

The near-neighbour, s12. h1A = (8, 6, 0, 0) against h2B = (0, 8, 0, 4). The overlap is unit 2 only:

dot = 8(0) + 6(8) + 0 + 0 = 48
s12 = 48 / (10 × 8.94427) = 48 / 89.4427 = 0.536656

The far one, s13. h1A = (8, 6, 0, 0) against h3B = (2, 0, 8, 0). Overlap on unit 1:

dot = 8(2) + 6(0) + 0(8) + 0 = 16
s13 = 16 / (10 × 8.24621) = 16 / 82.4621 = 0.194029

Continuing the same way for rows 2 and 3 (row 3's second entry involves the only negative product, 8 × 0 + 0 × 8 + 8 × 0 + (−6)(4) = −24, over 89.4427):

sijkey 1key 2key 3
query 10.7155420.5366560.194029
query 20.5366560.7155420.145521
query 30.357771−0.2683280.776114

(If these look familiar: Chapter 1's three-sentence worked example used exactly this matrix, rounded to two decimals. It was a preview of this page.)

Three sanity checks. The diagonal is the largest entry in every row — the model already prefers the right answer, so this batch is "easy" and we expect a small loss. The 1–2 cross terms (0.5367) are much larger than the 1–3 cross terms (0.194), which is correct: the guitar and the musical instrument really are related. And s32 is negative, which is only possible because u3 has negative components — the finance sentence actively points away from the music direction.

The diagonal is not automatically the maximum, and that is the whole game. s11 = 0.7155 rather than 1.0 because the two masks kept different units: view A saw units 1 and 2, view B saw units 1 and 3, and they overlap only on unit 1. The model is being asked to make a sentence recognisable from two different halves of its own representation. If a lazy encoder concentrated all its information in unit 1, the dropout that kills unit 1 would destroy it — so the objective pushes information to be redundantly distributed across units. That redundancy is, mechanically, what dropout-based contrastive training buys.

Step 2: scale by 1/τ

τ = 0.05, so every entry is multiplied by 20:

logitskey 1key 2key 3
query 114.310810.73313.8806
query 210.733114.31082.9104
query 37.1554−5.366615.5223

Step 3: softmax and per-row loss

Row 1. Subtract the row maximum (14.3108) from every entry — this changes nothing about the softmax and keeps the exponentials on human scale:

differences: 0,  −3.5777,  −10.4302
e0 = 1    e−3.5777 = 0.0279392    e−10.4302 = 0.0000295
sum = 1.0279687
p11 = 1 / 1.0279687 = 0.9727897    p12 = 0.0271790    p13 = 0.0000287
1 = −ln(0.9727897) = ln(1.0279687) = 0.0275852

Row 2. Maximum is again 14.3108, on the diagonal. Differences: −3.5777, 0, −11.4004.

e−3.5777 = 0.0279392    e0 = 1    e−11.4004 = 0.0000112
sum = 1.0279504  →   ℓ2 = ln(1.0279504) = 0.0275673

Row 3. Maximum 15.5223. Differences: −8.3669, −20.8889, 0.

e−8.3669 = 0.0002324    e−20.8889 = 8.5 × 10−10    e0 = 1
sum = 1.0002324  →   ℓ3 = ln(1.0002324) = 0.0002324

Step 4: the batch loss

L = (ℓ1 + ℓ2 + ℓ3) / 3 = (0.0275852 + 0.0275673 + 0.0002324) / 3 = 0.0553849 / 3 = 0.0184616

That is the number. Chance level for a batch of three is ln 3 = 1.0986, so this batch is being solved almost perfectly — which is what we expected from a similarity matrix whose diagonal already dominates.

Row 3 contributes essentially nothing (0.00023). The finance sentence is trivially distinguishable from the two music sentences, so it is done: no gradient will flow from it. All the learning in this batch happens in rows 1 and 2, where the two music sentences confuse each other. This is InfoNCE's automatic curriculum from Chapter 1, visible in three numbers.

The same batch at τ = 1

Rerun step 2 with no scaling and everything else identical:

Rowsoftmax at τ = 1ii at τ = 0.05
10.4116, 0.3441, 0.24430.88780.0276
20.3482, 0.4164, 0.23550.87620.0276
30.3274, 0.1751, 0.49750.69810.0002
mean0.82070.01846

At τ = 1 the batch loss is 0.821 against a chance level of 1.099 — the model is barely a quarter of a nat better than random, despite the diagonal winning every row. Row 3's probability of being right is 0.4975: a coin flip, on a pair the geometry separates by 0.42 in cosine. Chapter 5's argument, now on our own numbers.

What happens with identical masks

Rerun the whole thing with z′ = z for every sentence — the paper's "Fixed 0.1" pathology. Now hB = hA, so the diagonal is exactly 1 and the off-diagonals are computed within the same two-unit subspace:

sijkey 1key 2key 3
query 11.0000000.960000−0.178885
query 20.9600001.000000−0.447214
query 3−0.178885−0.4472141.000000

Row 1 at τ = 0.05: logits 20, 19.2, −3.578; differences 0, −0.8, −23.578; exponentials 1, 0.449329, 4 × 10−11; sum 1.449329; so p11 = 0.689975 and ℓ1 = ln(1.449329) = 0.371101. By symmetry ℓ2 is the same and ℓ3 ≈ 0, giving L = 0.24740.

The loss is thirteen times larger than the dropout version. A naive reading would call that a harder, better training signal. It is the opposite, and here is the precise statement of why:

∂ℓ1/∂s11 = (p11 − 1)/τ = (0.689975 − 1)/0.05 = −6.2005    (large!)
∂s11/∂θ = 0    (because s11 = 1 identically, for every θ)
⇒  ∂ℓ1/∂θ from the positive term = −6.2005 × 0 = 0

The loss wants very badly to raise s11, and it structurally cannot. Every gradient that actually reaches the parameters comes from the repulsive terms, pushing sentences 1 and 2 apart — two sentences that mean nearly the same thing. Chapter 3's argument, on our own numbers, with the two factors of the chain rule laid side by side.

Verify it yourself

python — reproduces every number on this pageimport torch, torch.nn.functional as F

u = torch.tensor([[4., 3, 2, 1], [3., 4, 1, 2], [1., -2, 4, -3]])   # (3, 4)

# the six masks from the table, as 0/1 rows; inverted dropout scale = 1/(1-0.5) = 2
zA = torch.tensor([[1.,1,0,0], [1.,1,0,0], [0.,0,1,1]])
zB = torch.tensor([[1.,0,1,0], [0.,1,0,1], [1.,0,1,0]])

hA, hB = 2 * u * zA, 2 * u * zB                 # (3, 4) each
sim = F.normalize(hA, dim=-1) @ F.normalize(hB, dim=-1).T   # (3, 3)

print(sim)                     # matches the table to 6 dp
print(F.cross_entropy(sim / 0.05, torch.arange(3)))   # 0.018462
print(F.cross_entropy(sim / 1.00, torch.arange(3)))   # 0.820716

# the pathology: same mask for both views
hB2 = 2 * u * zA
sim2 = F.normalize(hA, dim=-1) @ F.normalize(hB2, dim=-1).T
print(F.cross_entropy(sim2 / 0.05, torch.arange(3)))  # 0.247400 — and zero useful gradient
The exit test for this chapter. Cover the page and answer four things. (1) Why is s11 equal to 0.7155 rather than 1.0? (2) Why does the factor of 2 from inverted dropout not appear anywhere in the final loss? (3) Which row contributes almost nothing to the loss, and what does that tell you about where the gradient goes? (4) The identical-mask variant has a larger loss — explain in one sentence why that is bad news rather than good. If all four come easily, the rest of this lesson is commentary.
In the worked toy, the identical-mask variant produced a larger loss (0.2474) than the two-mask version (0.0185). Why is the larger loss a symptom of failure rather than a sign of a harder, more useful objective?

Chapter 8: What the Numbers Say

A method is only as good as the evaluation that measured it, and this paper's evaluation section contains a methodological correction that is arguably as valuable as the model. Let us do the numbers properly: what was measured, how, what it establishes, and what it does not.

First, an evaluation bug the paper fixed

Each STS task is not one dataset but several sub-datasets. STS12, for instance, contains five topical subsets. When you report "STS12 Spearman", you have three defensible options, and they give different numbers:

AggregationWhat it doesWhat it implicitly assumes
allConcatenate every subset's pairs into one list and compute a single SpearmanThe model's scores are comparable across subsets — a real and demanding requirement
meanCompute Spearman per subset, then average unweightedOnly within-subset ranking matters. A model can use a different scale per topic and lose nothing
wmeanSame, weighted by subset sizeAs above, plus large subsets count more

Papers before SimCSE were not consistent about which they used, and some reported mean while comparing against numbers computed with all. That is not a rounding-level discrepancy: mean is a strictly easier setting, because it forgives a model whose similarity scale drifts between topics.

SimCSE's appendix documents this, adopts all throughout, and re-evaluates every baseline under the same protocol. The 56.70 for averaged BERT and the 74.89 for SBERT in this lesson are the paper's re-computed figures, not the ones from the original publications.

Take this one home even if you forget SimCSE. When a leaderboard's numbers cannot be reproduced, the cause is far more often an aggregation or preprocessing difference than a modelling one. The gap between all and mean on STS is several points — larger than the margin separating many published "improvements". A paper that stops to fix its own benchmark before reporting its result is telling you something about how much to trust the rest of it.

The headline table

ModelSupervisionAvg. Spearman, 7 STS tasksDelta vs. raw BERT
GloVe embeddings (avg.)none61.32+4.62
BERT-base (first-last avg.)none56.70
BERT-flownone (post-hoc)66.55+9.85
BERT-whiteningnone (post-hoc)66.28+9.58
IS-BERTnone66.58+9.88
CT-BERTnone72.05+15.35
Unsup. SimCSE-BERT-basenone76.25+19.55
SBERT-baseNLI labels74.89+18.19
SBERT-whiteningNLI + post-hoc77.00+20.30
Sup. SimCSE-BERT-baseNLI labels81.57+24.87
Sup. SimCSE-RoBERTa-largeNLI labels83.76

Four readings worth extracting.

The unsupervised model beats a supervised one. 76.25 over 74.89. This is the number that made the paper famous, and the honest framing is: dropout twins are a better positive-pair source than the paraphrase supervision SBERT was built on, once you pair them with in-batch negatives and a sensible temperature.

The post-hoc methods plateau together. BERT-flow 66.55, BERT-whitening 66.28, IS-BERT 66.58 — three quite different techniques landing within 0.3 of each other. When several independent approaches converge on the same ceiling, the ceiling is a property of the setup rather than of any one method. Here the setup is frozen embeddings: you can only redistribute the information BERT already put in the vector, and Chapter 6 explained why that trades alignment for uniformity.

Supervision and the contrastive objective stack. 76.25 unsupervised, 81.57 supervised. If NLI labels merely duplicated what dropout provides, the two would not add. They are doing different work: dropout supplies local stability, entailment pairs supply semantic identity across different surface forms, and contradiction pairs supply hard negatives. Three different jobs.

Scale still helps, but modestly. RoBERTa-large gets 83.76 versus BERT-base's 81.57 — 2.2 points for roughly three times the parameters. Compare that with the 24.9 points the objective bought. In sentence embedding, the loss function has historically mattered far more than model size.

The dropout ablation, read properly

We have met this table three times. Here is the full reading, with each row explained by the machinery we have built.

SettingSTS-B devAlignment gradient?Explanation
p = 0.0 (no dropout)71.1Exactly zeroViews identical ⇒ sii = 1 for all θ ⇒ only repulsion acts. Held-out alignment collapses (Chapter 3)
p = 0.0172.6Nearly zeroOne activation in a hundred differs; the positive term is technically alive but carries almost no information
p = 0.0581.1HealthyEnough perturbation to make the matching task non-trivial
p = 0.1 (BERT's default)82.5HealthyThe sweet spot — and, remarkably, nobody chose it for this purpose
p = 0.1581.4StrongSlightly past the peak
p = 0.280.5StrongViews drifting apart; the positive is becoming a weaker constraint
p = 0.571.0OverwhelmingHalf the units gone. Satisfying the positive now requires near-invariance to the encoder's own content
Fixed 0.1 (one mask, reused)43.6Exactly zeroCollapse (as at p = 0), plus a train/test mismatch: training optimises one scaled sub-network, evaluation runs the full one

The curve is an inverted U with a hard floor at both ends, and the two ends fail for opposite reasons — too little perturbation kills the gradient, too much kills the meaning. That is the signature of a genuine sweet spot rather than a monotone "more is better" knob.

The luckiest coincidence in the paper. BERT's dropout rate is 0.1 because that value regularises masked language modelling well. Nobody in 2018 was thinking about contrastive sentence embeddings. Three years later, 0.1 turns out to be almost exactly optimal for a completely different purpose. It is worth being clear-eyed about this: the method's central hyperparameter was already tuned, by accident, before the method existed. Had BERT shipped with p = 0.5, unsupervised SimCSE would have scored 71.0 and quite possibly never been published.

The other ablations

Pooling. Options are the [CLS] token with BERT's MLP pooler, [CLS] with the MLP used only during training, and mean-pooling over tokens. The paper's finding is asymmetric and slightly odd: for the unsupervised model, keeping the MLP during training and discarding it at test time is best; for the supervised model, keeping it throughout is best. The differences are around a point. The practical advice is to treat pooling as a hyperparameter to check, not a principle — and to note that this is a real reproducibility trap, because "which pooler at test time" is exactly the kind of detail that gets lost between a paper and a re-implementation.

An MLM auxiliary objective. Adding λ · (masked language modelling loss) to the contrastive loss, with a small λ, improves transfer tasks and slightly hurts STS. The reason is legible: MLM preserves token-level information that a purely contrastive objective is free to discard, and downstream classification probes benefit from that information while sentence-level similarity does not. It is a clean demonstration that "a better embedding" is task-relative.

Transfer tasks. Evaluated on the SentEval classification suite (MR, CR, SUBJ, MPQA, SST-2, TREC, MRPC), SimCSE is roughly on par with previous methods — not the blowout it achieves on STS. This is honest reporting and it makes sense: those tasks train a logistic-regression probe on top of the frozen embedding, and a probe can undo an arbitrary linear transformation. The anisotropy that wrecks cosine similarity is largely invisible to a trained probe, so fixing it buys little there.

The unifying principle behind those three ablations. Contrastive training reshapes the geometry so that a fixed, untrained read-out — cosine similarity — works. Wherever the downstream consumer is a trained read-out, the reshaping matters much less, because training can compensate. That single sentence predicts every result in this section: huge gains on STS (fixed read-out), modest gains on SentEval probes (trained read-out), and a small STS cost from MLM (which preserves information the fixed read-out cannot use).

What the numbers do not establish

A lesson that only reports the wins is advertising. Four genuine limitations, in rough order of how likely they are to bite you.

1. STS is symmetric; retrieval is not. Every result here concerns pairs of sentences of similar length and register. Real retrieval matches a short question against a long passage — an asymmetric task with a completely different similarity structure. The BEIR benchmark (2021) measured exactly this and found that SimCSE-style models trained on STS-flavoured objectives frequently underperform BM25, a keyword-matching algorithm from the 1990s, on out-of-domain retrieval. Strong STS is not evidence of strong retrieval, and treating it as such has cost real teams real quarters.

2. Checkpoint selection uses labels. The unsupervised recipe evaluates on STS-B dev every 250 steps and keeps the best checkpoint. With a single training run this is mild; across a hyperparameter sweep it is not negligible. "Unsupervised" here means "no labels in the loss", and the number of labelled pairs touched by model selection is small but not zero.

3. English, general domain, short sentences. Max sequence length 32 tokens. Wikipedia prose. If your text is 500-token legal clauses, none of the reported numbers transfer, and Chapter 5's false-negative table says the failure will be sharp rather than gradual.

4. Sentence embeddings are a bag of meaning. A single vector cannot represent compositional structure with much fidelity. SimCSE improves the geometry; it does not give the representation a syntax. Negation, quantifier scope, and argument order remain systematically underserved — "the dog bit the man" and "the man bit the dog" will be close under any of these models.

The cost, and why that matters

ResourceUnsupervised SimCSE-BERT-base
Training data106 unlabelled Wikipedia sentences
Optimiser steps106 / 64 ≈ 15,625 (one epoch)
Rows per step128 (batch 64, duplicated) at 32 tokens
New parametersZero. Only the existing encoder plus its pooler are fine-tuned
Human annotationZero in the loss
Wall clockSingle mid-range GPU, hours not days

Fifteen thousand steps of ordinary fine-tuning, on free text, moving a well-studied benchmark by twenty points. That ratio — not the absolute score — is the paper's real claim on your attention.

If you are reproducing this and it will not work

SymptomMost likely causeCheck
Loss stuck at ln NTemperature far too large, or the labels do not match the batch layoutPrint the similarity matrix. Is the diagonal the row max? Is 1/τ = 20?
Loss near zero from step 1, embeddings uselessDropout is off — the model is in eval() mode, or you re-used one maskForward the same sentence twice and assert the outputs differ
Trains well, STS barely movesPairs mis-aligned by the reshape (i paired with i + N/2)Assert ids.view(-1,2,T)[:,0] == ids.view(-1,2,T)[:,1]
Great on Wikipedia, collapses on your corpusFalse negatives (Chapter 5)Sample 100 random in-batch pairs and read them. How many are duplicates?
Numbers 2–4 points below the paperAggregation setting, or the test-time poolerConfirm all aggregation; try with and without the MLP head at test
SimCSE gains ~20 points on STS but is only on par with prior methods on the SentEval classification probes. What single principle explains both results?

Chapter 9: The Recipe Everyone Inherited

SimCSE's lasting contribution is not 76.25. It is a template. Every text embedding model shipped since is a variation on four decisions this paper fixed, and knowing which of the four a new model changed is usually enough to understand it in a paragraph.

The template

#DecisionSimCSE's choiceStill standard in 2026?
1Encoder and poolingPretrained transformer, [CLS] or mean pool, L2-normalisedYes — universally
2ObjectiveInfoNCE, in-batch negatives, τ = 0.05Yes — almost without exception
3Cheap positivesDropout twins on unlabelled textReplaced — by mined web pairs at 109 scale
4Curated positives + hard negativesNLI entailment / contradictionGeneralised — retriever-mined hard negatives

Rows 1 and 2 did not move at all. Rows 3 and 4 are where five years of progress happened, and the direction of travel is consistent: the objective was right; the data was the bottleneck.

What the descendants changed

SystemWhat it kept from SimCSEWhat it changed, and why
sentence-transformers v2The whole loss (MultipleNegativesRankingLoss is InfoNCE with in-batch negatives)Packaged it. The library made the recipe the default way anyone trains an embedder
E5 (2022)InfoNCE, small τ, in-batch negatives, two-stage structureStage 1 becomes ~270M mined web pairs (post–comment, title–body, question–answer) instead of dropout twins. Adds query: / passage: prefixes so one model can serve asymmetric retrieval
GTE (2023)Same objective, same normalisationMulti-stage training over a deliberately diverse mixture, arguing that data variety matters more than volume
BGE / C-Pack (2023)Same objective; NLI-style curated stage survives as the final stageAdds a RetroMAE-style pretraining stage before contrastive training, plus instruction prefixes and a Chinese-language suite
Instructor (2022)InfoNCE unchangedConditions the embedding on a natural-language task instruction, so one vector space serves many notions of "similar"
E5-Mistral, LLM embedders (2023–)InfoNCE unchangedSwaps BERT for a decoder LLM and generates the training pairs synthetically — the positive-pair problem solved by asking a model to write them
Matryoshka embeddings (2022)InfoNCE unchangedApplies the loss at several truncation lengths at once, so the first 64 dimensions are usable on their own. A storage and latency innovation, not a semantic one
GradCache / cross-device negativesThe loss, exactlyEngineering that decouples batch size from GPU memory, so "more negatives" stops being a hardware question

Read the middle column. In every row it says the same thing.

Where SimCSE's specific trick still earns its keep. Dropout twins were superseded as the main training signal, but they remain the strongest thing you can do with no data at all. The standard move when adapting an embedder to a new domain is still: take an off-the-shelf model, run unsupervised SimCSE on a few hundred thousand unlabelled in-domain sentences, then fine-tune on whatever labelled pairs you can scrape together. The first stage costs one GPU-afternoon and no annotation, and it is the highest-leverage thing available when the answer to "how many labelled pairs do you have" is "none".

The honest limits, carried forward

Two of Chapter 8's limitations shaped the field's next five years, so they are worth restating as forward-looking facts rather than caveats.

Symmetric similarity is not retrieval. BEIR made this unmissable in 2021, and the entire prefix-and-instruction line of work (E5's query:/passage:, Instructor, BGE's instructions) exists to give one model two different notions of similarity. If you take one design lesson from this lesson into your own system, make it this: decide whether your task is symmetric or asymmetric before you pick an objective, because the same InfoNCE loss trained on the wrong pair type produces a model that benchmarks well and retrieves badly.

The bottleneck moved from method to data. After SimCSE, the objective stopped being where the gains were. MTEB (2022) turned embedding evaluation into a 50-task suite precisely because single-task STS had been saturated by a method anyone could implement in an afternoon. The frontier moved to pair mining, hard-negative mining, instruction conditioning, and multilingual coverage — all data questions.

Cheat sheet

The symbols.

SymbolMeaningValue / shape
xiInput sentencetokenised to (T,), T ≤ 32
z, z′Two independent dropout masks~7.6 × 105 Bernoulli draws each, p = 0.1
hizEncoder output for xi under mask zR768 (base), L2-normalised
sijcos(hiz, hjz′)scalar in [−1, 1]; matrix is (N, N)
τTemperature0.05, fixed → logit multiplier 20
NBatch size = number of candidates64 unsupervised, 512 supervised
x+, xEntailment / contradiction hypothesis (supervised only)314,315 NLI triples
alignE ‖f(x) − f(x+)‖2 = 2(1 − E[cos])[0, 4]; lower better; measured on held-out pairs
uniformlog E e−2‖f(x)−f(y)‖2(−∞, 0]; lower better; 0 = total collapse

The four equations.

(1)  unsupervised:  ℓi = −log [ esim(hizi, hizi)/τ / ∑j esim(hizi, hjzj)/τ ]
the same sentence twice; negatives are the rest of the batch
(2)  supervised:  ℓi = −log [ esim(hi,hi+)/τ / ∑j ( esim(hi,hj+)/τ + esim(hi,hj)/τ ) ]
2N candidates: every row's entailment and every row's contradiction
(3)  gradient:  ∂ℓi/∂sik = ( pik − 1[k = i] ) / τ
attraction weighted by (1 − pii); repulsion weighted by pik — automatic hard-negative mining
(4)  the spectrum:  repulsive term ≥ Sum(WWT)/(τm2) = ‖∑ihi2/(τm2),  tr(WWT) = m
minimise the sum with the trace pinned ⇒ λ1 squeezed ⇒ flatter spectrum ⇒ isotropy

The numbers worth remembering.

NumberWhat it is
76.25Unsupervised SimCSE-BERT-base, avg. over 7 STS tasks — above supervised SBERT's 74.89
81.57 / 83.76Supervised SimCSE, BERT-base / RoBERTa-large
56.70Raw averaged BERT — below GloVe's 61.32
82.5 vs 71.1 vs 43.6STS-B dev at p = 0.1, p = 0, and one fixed mask. The whole argument in three numbers
84.1 → 86.2Supervised STS-B dev, before and after adding contradiction hard negatives
0.05Temperature — multiplier 20 on cosine logits
106 / 314,315Unlabelled Wikipedia sentences / labelled NLI triples
64 / 512Batch size, unsupervised / supervised
582×How much one hard negative multiplied a row's loss in Chapter 4's worked example
ln NChance-level loss. 4.159 at N = 64. If you sit here, check τ first

Build it yourself — the weekend recipe

StepWhat to doThe decision that matters
1. Text200k–1M unlabelled sentences from your own domainDiversity, not volume. Deduplicate hard — Chapter 5's false-negative table is the reason
2. EncoderAny pretrained transformer. Keep its dropout at its defaultDo not disable dropout "for stability". It is the method
3. Pooling[CLS] + a Linear–Tanh head, or mean poolingTry dropping the head at test time; it is worth a point and costs one experiment
4. PairsDuplicate each sentence inside the batch; one forward passInterleave, then view(-1,2,d). Assert the two token rows are equal
5. LossChapter 1's six lines, τ = 0.05If your corpus is narrow, start at τ = 0.1 and lower it only if the loss stalls
6. Batch64 to startBigger is not automatically better here; sample across topics rather than within one
7. ScheduleOne epoch, lr 3e-5, evaluate oftenContrastive fine-tuning overfits fast. The best checkpoint is usually early
8. InstrumentLog ℓalign and ℓuniform on a held-out pair setChapter 3. These two numbers tell you which half is broken. Nothing else does
9. Then superviseAdd whatever curated pairs you have, with explicit hard negativesOne good hard negative beats a thousand random ones (Chapter 4)
10. Evaluate honestlyYour own retrieval task, not STSIf your task is asymmetric, STS will lie to you (Chapter 8)

References

  1. Gao, T., Yao, X., Chen, D. "SimCSE: Simple Contrastive Learning of Sentence Embeddings," EMNLP 2021 — arXiv:2104.08821. The paper this lesson is built on.
  2. Wang, T., Isola, P. "Understanding Contrastive Representation Learning through Alignment and Uniformity on the Hypersphere," ICML 2020 — arXiv:2005.10242. Chapter 3's entire lens.
  3. van den Oord, A., Li, Y., Vinyals, O. "Representation Learning with Contrastive Predictive Coding," 2018 — arXiv:1807.03748. Where InfoNCE and the log N bound come from.
  4. Reimers, N., Gurevych, I. "Sentence-BERT," EMNLP 2019 — arXiv:1908.10084. The supervised baseline SimCSE overtakes without labels.
  5. Ethayarajh, K. "How Contextual are Contextualized Word Representations?," EMNLP 2019 — arXiv:1909.00512. The anisotropy measurement.
  6. Gao, J. et al. "Representation Degeneration Problem in Training Natural Language Generation Models," ICLR 2019 — arXiv:1907.12009. Why the cone forms.
  7. Li, B. et al. "On the Sentence Embeddings from Pre-trained Language Models" (BERT-flow), EMNLP 2020 — arXiv:2011.05864.
  8. Su, J. et al. "Whitening Sentence Representations for Better Semantics and Faster Retrieval," 2021 — arXiv:2103.15316.
  9. Chen, T. et al. "SimCLR: A Simple Framework for Contrastive Learning of Visual Representations," 2020 — arXiv:2002.05709; He, K. et al. "MoCo," 2019 — arXiv:1911.05722. The vision lineage whose augmentations text could not copy.
  10. Bowman, S. et al. "SNLI," 2015 — arXiv:1508.05326; Williams, A. et al. "MultiNLI," 2017 — arXiv:1704.05426. The 314k triples.
  11. Thakur, N. et al. "BEIR: A Heterogeneous Benchmark for Zero-shot Evaluation of Information Retrieval Models," 2021 — arXiv:2104.08663. Why strong STS is not strong retrieval.
  12. Muennighoff, N. et al. "MTEB: Massive Text Embedding Benchmark," 2022 — arXiv:2210.07316. What replaced STS as the yardstick.
  13. Wang, L. et al. "Text Embeddings by Weakly-Supervised Contrastive Pre-training" (E5), 2022 — arXiv:2212.03533; Li, Z. et al. "GTE," 2023 — arXiv:2308.03281; Xiao, S. et al. "C-Pack / BGE," 2023 — arXiv:2309.07597. The direct descendants.
  14. Su, H. et al. "One Embedder, Any Task" (Instructor), 2022 — arXiv:2212.09741; Kusupati, A. et al. "Matryoshka Representation Learning," 2022 — arXiv:2205.13147; Gao, L. et al. "Scaling Deep Contrastive Learning Batch Size under Memory Limited Setup" (GradCache), 2021 — arXiv:2101.06983.
  15. Srivastava, N. et al. "Dropout: A Simple Way to Prevent Neural Networks from Overfitting," JMLR 2014. The regulariser that turned out to be an augmentation. Merikoski, J. K. "On the trace and the sum of elements of a matrix," Linear Algebra and its Applications, 1984 — the bound in Chapter 6.

Where to go from here

If you want…Go to
Contrastive objectives in general, from the ground upContrastive learning
The same recipe across two modalitiesCLIP and CLAP
The encoder SimCSE fine-tunesBERT and embedding layers
What the vectors are for once you have themVector embeddings, similarity metrics, vector databases
The system that consumes themRAG and multimodal RAG
Dropout itself, in depthDropout variants and regularisation
Cross-domain bridge
Dropout twins are a Monte-Carlo estimate of the model's own uncertainty
Running a network several times with dropout on and looking at the spread of the outputs is MC dropout — a standard cheap approximation to Bayesian predictive uncertainty. SimCSE draws exactly two samples from that same predictive distribution and, instead of measuring the spread, trains against it: the loss demands that two draws from the model's own uncertainty be close together. Seen this way, SimCSE is a self-consistency objective — "be confident under your own noise" — and it belongs to the same family as consistency regularisation in semi-supervised learning (Π-model, mean teacher) and self-consistency decoding in LLMs. The pattern generalises: whenever a model has an internal source of stochasticity, that stochasticity is a free augmentation, and you can train on the agreement between two draws. See dropout variants for the uncertainty reading of the same mechanism.
"What I cannot create, I do not understand."
A pretrained encoder, six lines of loss, and a text file of unlabelled sentences. One afternoon on one GPU. Then Chapter 7's toy on paper, and the 76.25 stops being a number you read.
Exit gate — teach it back before you leave.

Without scrolling up: (1) write the unsupervised objective and say what z and z′ are; (2) define alignment and uniformity, and state which one raw BERT fails; (3) prove that removing dropout makes the attractive gradient exactly zero; (4) compute the loss of a 2×2 batch whose cosines are 0.8 on the diagonal and 0.3 off it, at τ = 0.05; (5) explain why one contradiction hypothesis is worth more than doubling the batch. If any of the five stalls, its chapter is one tap away.

Which sentence best captures why SimCSE mattered more than its 4.2-point margin over the previous unsupervised best suggests?