Self-Supervised Vision, CLIP and Retrieval
Pretrain without labels, align images and text, classify zero-shot, shape embeddings and search them. PyTorch.
- Concepts and terms
- Tasks and commands
- Annotated code
- Diagrams
- Defaults and numbers
- Troubleshooting
- Questions and tips
Families and models#
- Contrastive (SimCLR, MoCo, CLIP)
- pull two views together. Push the rest of the batch away.
- Teacher-student (DINO, BYOL, iBOT)
- the student matches an EMA teacher. No negatives.
- Masked (MAE, BEiT, SimMIM)
- mask 75% of patches. Reconstruct the pixels.
- CLIP
- image and text towers in one space. 400M web pairs, no labels.
- SigLIP
- per-pair sigmoid. No all-gather. Trains better at small batches.
- Linear probe
- frozen encoder plus one linear layer. Measures feature quality.
- Probe top-1
- SimCLR ~71%, DINO ~77%, MAE ~76%, DINOv2 ~86%.
- Category or instance retrieval
- off-the-shelf embeddings work for category. Instance needs metric learning.
CLIP two towers#
From Lesson 04.18, Lesson 12.02
Formulas#
- InfoNCE
- -\log \frac{\exp(\mathrm{sim}(z_i, z_j)/\tau)}{\sum_{k \ne i} \exp(\mathrm{sim}(z_i, z_k)/\tau)}
- CLIP matrix
- S_{ij} = \cos(f(x_i), g(y_j)) / \tau. Shape N x N.
- CLIP loss
- CE over rows plus CE over columns, divided by 2.
- SigLIP
- \log(1 + \exp(-y_{ij} \, \mathrm{sim}_{ij})) per pair, with y_{ij} = \pm 1.
- DINO EMA
- \theta_t \leftarrow m \, \theta_t + (1 - m) \, \theta_s
- Triplet
- \max(0, \lVert f(a) - f(p) \rVert^2 - \lVert f(a) - f(n) \rVert^2 + \mathrm{margin})
- Cosine and L2
- \lVert a - b \rVert^2 = 2 - 2 \cos(a, b) for unit vectors.
- Recall@K
- fraction of queries with one correct match in the top K.
InfoNCE and MAE masking#
def info_nce(z1, z2, tau=0.1):z1, z2 L2-normalised N, D = z1.shape z = torch.cat([z1, z2], dim=0)(2N, D) sim = z @ z.T / tau(2N, 2N) mask = torch.eye(2 * N, dtype=torch.bool, device=z.device) sim = sim.masked_fill(mask, float("-inf"))no self match targets = torch.cat([torch.arange(N, 2 * N), torch.arange(0, N)]).to(z.device) return F.cross_entropy(sim, targets)num_patches = 196visible, masked = random_mask_indices(num_patches, mask_ratio=0.75)keep 25%CLIP loss and zero-shot#
sim = logit_scale * image_emb @ text_emb.T(N, N)targets = torch.arange(N, device=sim.device)match on the diagonall_i = F.cross_entropy(sim, targets)image to textl_t = F.cross_entropy(sim.T, targets)text to imageloss = (l_i + l_t) / 2random init: near log(N)i = F.normalize(model.image_proj(image_feats), dim=-1)t = F.normalize(model.text_proj(class_text_feats), dim=-1)pred = (i @ t.T).argmax(dim=-1)one class per imageTriplet loss and recall@K#
def triplet_loss(anchor, positive, negative, margin=0.2): d_ap = F.pairwise_distance(anchor, positive, p=2) d_an = F.pairwise_distance(anchor, negative, p=2) return F.relu(d_ap - d_an + margin).mean()pos_idx, neg_idx = semi_hard_negatives(emb, y)hardest positive, semi-hard negativeloss = triplet_loss(emb, emb[pos_idx], emb[neg_idx])sim = query_emb @ gallery_emb.Tcosine on unit vectors_, top_k = sim.topk(k, dim=-1)matches = (gallery_labels[top_k] == query_labels[:, None]).any(dim=-1)matches.float().mean().item()recall@KZero-shot classification#
Tasks in code#
| Task | Code |
|---|---|
| Contrastive loss on two views | info_nce(z1, |
| DINO teacher with centring | DinoHead().teacher(x, |
| CLIP symmetric loss | clip_loss(i, |
| Zero-shot labels | zero_shot_classify(model, |
| Mine semi-hard negatives | semi_hard_negatives(emb, |
| Recall at K | recall_at_k(query_emb, |
| Symmetric InfoNCE in pure Python | infonce_loss(S) |
| SigLIP per-pair loss | sigmoid_loss(S, |
From 04.17 code/main.py, 04.18 code/main.py, 04.20 code/main.py, 12.02 code/main.py
Defaults and numbers#
| Setting | Default | Note |
|---|---|---|
SimCLR tau | 0.1 | Lower is sharper, needs more negatives |
CLIP tau init | 0.07 | Learned. logit_scale starts at ln(1/0.07) |
| SimCLR batch | 512-8192 | Many negatives per positive |
MAE mask_ratio | 0.75 | BERT masks 15% of tokens |
| MAE decoder | 8-layer, 512-dim | Encoder sees visible patches only |
| DINO EMA m | 0.996 | Teacher is an EMA of the student |
DINO teacher temp | 0.04 | Student temp 0.1 |
Triplet margin | 0.2 | Same margin for semi-hard mining |
From Lesson 04.17, 04.17 code/main.py, Lesson 04.18, 04.20 code/main.py
Pick a FAISS index#
| Command | What it does |
|---|---|
IndexFlatIP | Exact inner product. Cosine on 100k vectors |
IndexFlatL2 | Exact L2, brute force, no training |
IndexIVFFlat | K cells, searches the closest few. Needs training. 10M |
IndexHNSW | Graph index. Fastest for many queries. Large index |
IndexIVFPQ | Adds product quantisation. 100M+ vectors |
| DINOv2 + FAISS | General visual retrieval, off-the-shelf |
| CLIP + FAISS | Queries are text |
| Fine-tuned DINOv2 + FAISS | Instance retrieval, re-ID, e-commerce |
From Lesson 04.20
Troubleshooting#
| Symptom | Cause | Fix |
|---|---|---|
| InfoNCE fails at batch 32 | Too few negatives | Batch 512-8192 or a momentum queue |
| DINO gives a constant vector | Representation collapse | Centre and sharpen the teacher |
| Masked task too easy | Mask ratio too low | Mask 75% of patches |
| High recall@10, low recall@1 | Noisy ranking | Longer fine-tune or re-rank |
| Nearest neighbours change | Cosine and L2 mixed | Match the embedding training |
| Instance search misses | Off-the-shelf features | Fine-tune with metric learning |
| Triplet loss near 0 | Easy triplets | Semi-hard mining |
| Softmax loss slow on many GPUs | All-gather of embeddings | Use the sigmoid loss |
From Lesson 04.17, Lesson 04.20, Lesson 12.02
Questions#
- Why 75% for MAE but 15% for BERT?
- Image patches have low entropy. Neighbours give the pixels away.
- InfoNCE on random pairs?
- About log(2N - 1). That is 3.4 for 16 pairs.
- CLIP loss at random init?
- Close to log(N). For N = 8 it is 2.08.
- Linear probe or fine-tune?
- Fine-tuning adds 2-5 points. The probe measures the features only.
- DINOv2 embedding?
outputs.last_hidden_state[:, 0], the CLS token, 768-dim.- Zero-shot, probe or fine-tune?
- A probe beats zero-shot in domain. Fine-tuning can hurt zero-shot transfer.
From Lesson 04.17, Lesson 04.18, Lesson 12.02