On this page
In Contrastive Learning, each view picks its positive, the other view of the same image, out of a batch with InfoNCE. CLIP (Contrastive Language-Image Pre-training, Radford et al. 2021) uses the same loss between a photo and its caption. It trains an image encoder and a text encoder so that, every training step, each photo in the batch picks its own caption and each caption its own photo, using captions people already wrote on the web. The two trained encoders then classify new photos by caption, with no extra training.
The web already pairs photos with text, so asking which caption goes with which photo needs no annotator.
| Data | 400 million (image, text) pairs, called WIT (WebImageText) |
|---|---|
| Gathered by | 500,000 search queries, up to 20,000 pairs each |
| Image encoder | a ResNet (a convolutional network) or a Vision Transformer (ViT) |
| Text encoder | a Transformer |
| Batch | 32,768 pairs |
The image encoder and the text encoder run separately, and their outputs meet only when they are compared.
Each encoder's output goes through a linear projection into one shared space and is scaled to length 1. Nothing crosses between the two encoders, unlike Cross-Attention, where one stream reads the other; that is what lets CLIP encode each caption once and reuse it. The Q-Former's image-text contrastive stage keeps its queries and text apart the same way.
CLIP trains each photo to pick its own caption out of the batch, and each caption to pick its own photo. A batch holds N photos and their captions. Their scores form an N by N table, rows for photos and columns for captions. The true pairs sit on the diagonal and the other N²−N pairings are wrong. Each row is cross entropy with the true caption as the class.
Figure 4. Six toy pairs; the dashed diagonal darkens as training runs.
CLIP learns τ as one more weight, updated by every gradient step with the encoders. It starts at 0.07, so 1/τ, a score multiplier, starts at about 14.3; the multiplier is capped at 100 (τ = 0.01) because larger ones made training unstable. Knowledge Distillation uses the same temperature in the opposite direction, raising it to soften a teacher’s output probabilities.
The snippet defines the loss L as clip_loss and calls it on toy features.
import torch
import torch.nn.functional as F
def clip_loss(img_feat, txt_feat, proj_img, proj_txt, log_scale):
I = F.normalize(img_feat @ proj_img, dim=1) # [N, D]
T = F.normalize(txt_feat @ proj_txt, dim=1)
scale = log_scale.exp().clamp(max=100.0) # 1/τ, capped
s = scale * I @ T.T # [N, N]
diag = torch.arange(len(s)) # true pairs
l_img = F.cross_entropy(s, diag)
l_txt = F.cross_entropy(s.T, diag)
return (l_img + l_txt) / 2
N, D_IMG, D_TXT, D = 8, 2048, 512, 64
img_feat, txt_feat = torch.randn(N, D_IMG), torch.randn(N, D_TXT)
proj_img = torch.randn(D_IMG, D, requires_grad=True)
proj_txt = torch.randn(D_TXT, D, requires_grad=True)
log_scale = torch.tensor(1 / 0.07).log().requires_grad_()
L = clip_loss(img_feat, txt_feat, proj_img, proj_txt, log_scale)
To classify a photo with no training on the task, zero-shot, write each class as a caption and ask which caption the photo matches best.
The snippet uses OpenAI’s clip package to classify one photo with four caption prompts.
import clip
import torch
import torch.nn.functional as F
from PIL import Image
device = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = clip.load("RN50", device=device)
names = ["dog", "cat", "car", "tree"]
prompts = [f"a photo of a {c}." for c in names]
tokens = clip.tokenize(prompts).to(device)
photo = Image.new("RGB", (224, 224)) # toy photo
x = preprocess(photo).unsqueeze(0).to(device)
with torch.no_grad():
W = F.normalize(model.encode_text(tokens), dim=-1) # [4, D]
h = F.normalize(model.encode_image(x), dim=-1) # [1, D]
pred = names[(h @ W.T).argmax()]
The class matrix W works like the weights of a linear probe (one linear layer trained on frozen features) with no bias, written by the text encoder from class names instead of learned from labeled photos (sec. 3.1.2). The paper’s main model, ViT-L/14@336px, is a large Vision Transformer that cuts a 336-pixel photo into 14-pixel patches.
One training step reuses clip_loss, the sizes and the parameters of the Section 3.1 snippet, with small stand-ins for the ResNet and the Transformer.
import torch
import torch.nn as nn
VOCAB, CTX = 49408, 76
image_tower = nn.Sequential(nn.Flatten(), nn.Linear(3 * 64 * 64, D_IMG))
text_tower = nn.EmbeddingBag(VOCAB, D_TXT) # stand-in Transformer
towers = [*image_tower.parameters(), *text_tower.parameters(), proj_img, proj_txt]
decay = [p for p in towers if p.ndim >= 2]
no_decay = [p for p in towers if p.ndim < 2] + [log_scale]
groups = [dict(params=decay, weight_decay=0.2), dict(params=no_decay, weight_decay=0.0)]
opt = torch.optim.AdamW(groups, lr=5e-4)
photos = torch.rand(N, 3, 64, 64) # one batch of pairs
tokens = torch.randint(0, VOCAB, (N, CTX))
L = clip_loss(image_tower(photos), text_tower(tokens), proj_img, proj_txt, log_scale)
opt.zero_grad()
L.backward()
opt.step()
clip_loss, Section 3 eq lines 1 to 4.| Acts | every optimizer step; τ is updated with the weights |
|---|---|
| Plugs in | pretraining only; then the encoders stay frozen for zero-shot or a probe |
| Keeps | the two encoders, the two projections and log(1/τ) |
| Settings | 32 epochs, Adam with weight decay 0.2 on all weights that are not gains or biases, learning rate 5×10−4 for RN50, 2000 warm-up steps (sec. 2.5, Tables 18 and 19) |
| Cost | 18 days on 592 V100 GPUs for the largest ResNet, 12 days on 256 for the largest ViT (sec. 2.5) |
| Code | openai/CLIP (released models), mlfoundations/open_clip (training) |
| Gotchas | without the cap on 1/τ training goes unstable; no weight decay on gains and biases (sec. 2.5); open_clip also skips the temperature, since decay drags 1/τ toward 1; each GPU holds part of the batch, so embeddings are gathered from all GPUs to score every pair; the paper needed mixed precision (16-bit arithmetic) and gradient checkpointing (recomputing activations instead of storing them) to fit |
| Knob, default | Raise it | Lower it |
|---|---|---|
| Start of τ in si,j; 0.07, then learned | a softer first guess; training moves τ anyway | a sharper first guess; training moves τ anyway |
| Cap on 1/τ; 100, so τ stays at 0.01 or more | shares may grow sharper; the cap exists to stop training going unstable | scores stay soft for every pair |
| N, the batch; 32,768 | more wrong captions per row and column, more memory | an easier choice per row; at N = 1 there is no wrong caption to reject; the paper gives no batch ablation |
Non-Contrastive Self-Supervised Learning comes next, where the negatives go away entirely.