AI Knowledge Tree

Contrastive self-supervision

Pretrain representations by pulling augmented views of the same image together (SimCLR, MoCo) using the InfoNCE loss.

⏱ 26 minPrerequisites:Multi-layer perceptron

Labels are expensive; data is cheap. Contrastive self-supervised learning turns this asymmetry into representations: take an unlabeled image, make two augmented copies of it (a "positive pair"), and train the encoder to give them similar features while pushing apart features of different images. The InfoNCE loss formalizes this as a classification problem where each positive must beat many negatives.

The InfoNCE loss

van den Oord et al. (2018)[3]. Given an anchor representation zz, its positive partner z+z^+, and KK negative partners z1−,…,zK−z^-_1, \dots, z^-_K, the contrastive loss is

L=−log⁡exp⁡(z⊤z+/τ)exp⁡(z⊤z+/τ)+∑kexp⁡(z⊤zk−/τ).\mathcal{L} = -\log \frac{\exp(z^\top z^+ / \tau)}{\exp(z^\top z^+ / \tau) + \sum_k \exp(z^\top z^-_k / \tau)}.

It is exactly the cross-entropy of identifying the positive in a (K+1)(K + 1)-way classification. The temperature τ\tau (typically 0.07-0.5) controls how sharply the model focuses on near-positives versus all negatives.

The loss minimizes a lower bound on the mutual information between the anchor and positive views (hence "InfoNCE" = "Noise Contrastive Estimation for Information").

SimCLR

Chen et al. (2020)[1].

Given a batch of NN images, apply two random augmentations to each, producing 2N2N views. Treat each view's other-augmented version as its positive; the other 2(N−1)2(N-1) views are negatives.

Architecture:

  • Backbone ff: a ResNet that produces a representation hh.
  • Projection head gg: a small MLP that produces z=g(h)z = g(h). The contrastive loss is computed in projection space.
  • After pretraining, discard gg and use hh for downstream tasks.

Why a projection head? Empirically, putting the loss directly on hh hurt performance; an MLP gives the loss its own subspace and protects the representation from over-specialization.

Augmentations matter enormously. Standard ones for vision:

  • Random resized crop.
  • Random color jitter (brightness, contrast, saturation, hue).
  • Gaussian blur.
  • Random horizontal flip.

The composition is what generates "different views of the same scene". Stronger augmentations (more aggressive crops, more color jitter) give better representations.

SimCLR needs large batches (4096+) to provide enough negatives. This is its main practical limitation.

MoCo (Momentum Contrast)

He et al. (2020)[2]. Avoid SimCLR's huge-batch requirement by maintaining a queue of past representations as negatives.

Two encoders:

  • Query encoder fqf_q: trained normally.
  • Key encoder fkf_k: parameters are an exponential moving average of fqf_q's.

For each batch:

  1. Compute zqz_q from the anchor view using fqf_q.
  2. Compute zkz_k from the positive view using fkf_k.
  3. Pull from the queue of past zkz_k values for many additional negatives.
  4. Apply InfoNCE loss with zqz_q as anchor, zkz_k as positive, and the queue as negatives.
  5. After the update, enqueue zkz_k and dequeue the oldest entry.

Why the moving-average key encoder? Without it, recent queue entries come from a stale version of the network, hurting consistency. The slowly-updated key encoder keeps the queue features approximately consistent.

MoCo gets SimCLR-level performance with batch sizes of 256.

What contrastive learning learns

Empirically, contrastive features:

  • Cluster by semantic class without ever seeing labels.
  • Transfer linearly: a linear classifier on frozen features approaches supervised accuracy.
  • Generalize across domains: features from ImageNet contrastive pretraining transfer to medical imaging, satellite imagery, etc.
  • Improve with more data and bigger models: scaling laws similar to language models.

The features are alignment-aware: similar augmentations of the same image are close. They are also uniform on the sphere: trained features spread out, avoiding mode collapse.

Collapse failure mode

The pathological solution: encode every image to the same constant vector. Negatives don't help because they collapse too.

InfoNCE-style losses don't collapse because the negative-pulling term provides explicit pressure to spread features apart. Methods without negatives (BYOL, DINO, the next lesson) avoid collapse via architectural tricks instead.

CLIP

Radford et al. (2021). Contrastive learning across image-text pairs:

  • Batch of (image,text)(image, text) pairs (e.g., from web alt-text).
  • An image encoder produces zimgz_{\text{img}}.
  • A text encoder produces ztxtz_{\text{txt}}.
  • InfoNCE pulls the ii-th image's features toward the ii-th text and away from the other N−1N - 1 texts (and vice versa).

CLIP learns aligned image-text representations from 400M web pairs. The trained encoders enable zero-shot classification: given a new classification task with class names, embed each class name and find the closest image embedding.

CLIP is the foundation of every modern multimodal system (text-to-image diffusion, vision-language models). The same recipe applies to other modality pairs: audio-text (CLAP), video-text (VideoCLIP), etc.

The next lesson covers a different SSL paradigm with no negatives at all: non-contrastive methods.

Contrastive learning aligns two augmented views of each item and repels different items, so the view-to-view similarity matrix has a bright diagonal (positives) and a dim off-diagonal (negatives).
Contrastive learning aligns two augmented views of each item and repels different items, so the view-to-view similarity matrix has a bright diagonal (positives) and a dim off-diagonal (negatives).

Check it on arrays

python · numpy
import numpy as np

rng = np.random.default_rng(0)

# Toy "views": augmented features for N images
N, d = 64, 16
z1 = rng.normal(size=(N, d))
# Positive views: small perturbation of z1
z2 = z1 + 0.1 * rng.normal(size=(N, d))
# Normalize
z1 /= np.linalg.norm(z1, axis=1, keepdims=True)
z2 /= np.linalg.norm(z2, axis=1, keepdims=True)

# InfoNCE: each row i should match z2[i] as the positive among all z2[j]
def info_nce(z1, z2, tau=0.1):
    sim = z1 @ z2.T / tau                              # (N, N)
    logsumexp = np.log(np.exp(sim).sum(axis=1) + 1e-12)
    pos = np.diag(sim)
    return -(pos - logsumexp).mean()

loss_aligned = info_nce(z1, z2)
print(f"InfoNCE loss with aligned positives: {loss_aligned:.3f}")

# If we shuffle z2 (positives randomized), the loss should be much worse
perm = rng.permutation(N)
loss_shuffled = info_nce(z1, z2[perm])
print(f"InfoNCE loss with shuffled positives: {loss_shuffled:.3f}")

# Show top-1 accuracy of "find positive among N" classification
def top1(z1, z2):
    sim = z1 @ z2.T
    return (sim.argmax(axis=1) == np.arange(len(z1))).mean()

print(f"\ntop-1 accuracy on aligned positives:  {top1(z1, z2):.3f}")
print(f"top-1 accuracy on shuffled positives: {top1(z1, z2[perm]):.3f}")

The aligned positives produce a low loss and high top-1 classification accuracy among the batch; shuffling breaks both.

Exercises

Exercise 1. A single anchor has one positive and KK negatives, all sharing the same similarity score ss with the anchor (the worst case for the model, positive indistinguishable from negatives). Show that the InfoNCE loss equals log⁡(K+1)\log(K + 1), independent of τ\tau, and explain why this is the random-guess floor.

Solution

Step 1. Substitute equal scores into the loss. Every logit s/τs/\tau is the same, so each exponential is es/τe^{s/\tau}. There are K+1K + 1 terms in the denominator (one positive, KK negatives) and one in the numerator:

L=−log⁡es/τ(K+1) es/τ=−log⁡1K+1=log⁡(K+1).\mathcal{L} = -\log \frac{e^{s/\tau}}{(K+1)\,e^{s/\tau}} = -\log \frac{1}{K+1} = \log(K+1).

Step 2. The es/τe^{s/\tau} factors cancel, so τ\tau drops out and the score level ss is irrelevant. The loss depends only on the count.

Step 3. Interpret it. InfoNCE is the cross-entropy of a (K+1)(K+1)-way classification. A model that cannot separate the positive from the negatives assigns probability 1/(K+1)1/(K+1) to the correct class, giving cross-entropy log⁡(K+1)\log(K+1). For a SimCLR batch with 2(N−1)2(N-1) negatives, this floor grows with batch size, which is why a useful loss value must be read against log⁡(number of candidates)\log(\text{number of candidates}), not against zero.

Exercise 2. Take a perfectly aligned positive (z⊤z+=1z^\top z^+ = 1 for unit vectors) and a single negative at similarity z⊤z−=cz^\top z^- = c with −1≤c<1-1 \le c < 1. Write the loss as a function of τ\tau and show it is increasing in cc and decreasing as τ→0\tau \to 0. What does the τ→0\tau \to 0 limit say about which negatives dominate the gradient?

Solution

Step 1. With one positive (similarity 1) and one negative (similarity cc):

L(τ)=−log⁡e1/τe1/τ+ec/τ=log⁡ ⁣(1+e(c−1)/τ).\mathcal{L}(\tau) = -\log \frac{e^{1/\tau}}{e^{1/\tau} + e^{c/\tau}} = \log\!\left(1 + e^{(c-1)/\tau}\right).

Step 2. Monotonic in cc. Since c−1<0c - 1 < 0 and τ>0\tau > 0, the exponent (c−1)/τ(c-1)/\tau rises with cc, so e(c−1)/τe^{(c-1)/\tau} rises, so L\mathcal{L} rises. A negative that sits closer to the anchor (larger cc) costs more, as it should.

Step 3. Behaviour in τ\tau. Hold c<1c < 1 fixed. As τ→0+\tau \to 0^+, (c−1)/τ→−∞(c-1)/\tau \to -\infty, so e(c−1)/τ→0e^{(c-1)/\tau} \to 0 and L→0\mathcal{L} \to 0: a small temperature makes the gap between the positive and a non-maximal negative look enormous, and the loss saturates. The gradient then concentrates on the hardest negatives, those with cc closest to 1, because only their (c−1)/τ(c-1)/\tau stays near zero. Low τ\tau is a hard-negative emphasis; high τ\tau treats all negatives more evenly.

Exercise 3 (code). Implement the cosine-similarity matrix and the symmetric InfoNCE loss directly in numpy. For a batch of NN unit embeddings where each anchor's positive is the row of a second matrix at the same index, verify that (a) when the two matrices are identical the loss is below its shuffled value, and (b) the per-row argmax recovers the diagonal as the predicted positive.

Solution
python · numpy
import numpy as np

rng = np.random.default_rng(0)
N, d = 32, 8

# View 1 and an aligned View 2 (small perturbation), both L2-normalized
z1 = rng.normal(size=(N, d))
z2 = z1 + 0.05 * rng.normal(size=(N, d))
z1 /= np.linalg.norm(z1, axis=1, keepdims=True)
z2 /= np.linalg.norm(z2, axis=1, keepdims=True)

def cosine_matrix(a, b):
    # rows already unit norm, so the dot product is cosine similarity
    return a @ b.T                                   # (N, N), S[i,j] = cos(a_i, b_j)

def info_nce(a, b, tau=0.1):
    s = cosine_matrix(a, b) / tau
    labels = np.arange(len(a))                       # positive of row i is column i
    # row-wise cross-entropy: -log softmax(s)[i, i]
    logsumexp = np.log(np.exp(s).sum(axis=1))
    row_loss = -(s[labels, labels] - logsumexp)
    return row_loss.mean()

def symmetric_info_nce(a, b, tau=0.1):
    return 0.5 * (info_nce(a, b, tau) + info_nce(b, a, tau))

loss_aligned = symmetric_info_nce(z1, z2)
perm = rng.permutation(N)
loss_shuffled = symmetric_info_nce(z1, z2[perm])

# (a) aligned loss is lower than shuffled
assert loss_aligned < loss_shuffled
# (b) argmax over each row recovers the diagonal positive
preds = cosine_matrix(z1, z2).argmax(axis=1)
assert np.array_equal(preds, np.arange(N))
print(round(loss_aligned, 4), round(loss_shuffled, 4))

The aligned loss prints well below the shuffled loss, and the similarity matrix's row-wise argmax lands exactly on the diagonal, the (K+1)(K+1)-way classification picking each true positive.

The next lesson extends contrastive learning to cross-modal alignment with CLIP-style training, then surveys the non-contrastive alternatives.


References

  1. [1]Chen, T., Kornblith, S., Norouzi, M., & Hinton, G. (2020). "A simple framework for contrastive learning of visual representations (SimCLR)". ICML. arXiv:2002.05709. Augmented-view positives, large-batch negatives, and the projection head.↩
  2. [2]He, K., Fan, H., Wu, Y., Xie, S., & Girshick, R. (2020). "Momentum contrast for unsupervised visual representation learning (MoCo)". CVPR. arXiv:1911.05722. The momentum-encoder queue that decouples negatives from batch size.↩
  3. [3]Oord, A. van den, Li, Y., & Vinyals, O. (2018). "Representation learning with contrastive predictive coding (InfoNCE)". arXiv:1807.03748. Introduces the InfoNCE loss as a mutual-information lower bound.↩