Original source
---
id: self-supervised-clip
title: Self-Supervised Vision, CLIP and Retrieval
code: SS
subtitle: Pretrain without labels, align images and text, classify zero-shot, shape embeddings and search them. PyTorch.
hue: 285
sources: phases/04-computer-vision/17-self-supervised-vision, phases/04-computer-vision/18-open-vocab-clip, phases/04-computer-vision/20-image-retrieval-metric, phases/12-multimodal-ai/02-clip-contrastive-pretraining
source_hash: 7c8ba7f92fca8ec6
---
## Families and models
type: concepts
from: phases/04-computer-vision/17-self-supervised-vision/docs/en.md, phases/04-computer-vision/18-open-vocab-clip/docs/en.md, phases/04-computer-vision/20-image-retrieval-metric/docs/en.md, phases/12-multimodal-ai/02-clip-contrastive-pretraining/docs/en.md
- **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
type: architecture
from: phases/04-computer-vision/18-open-vocab-clip/docs/en.md, phases/12-multimodal-ai/02-clip-contrastive-pretraining/docs/en.md
```arch
node | img | 1,1 | Image | batch of N | client
node | ienc | 2,1 | Image encoder | ViT or ResNet | model
node | inorm | 3,1 | L2 normalise | (N, D)
node | txt | 1,2 | Caption | N captions | client
node | tenc | 2,2 | Text encoder | transformer | model
node | tnorm | 3,2 | L2 normalise | (N, D)
node | sim | 4,1 | Similarity S | N x N, over tau
node | loss | 4,2 | Symmetric CE | (i2t + t2i) / 2
edge | img | ienc |
edge | ienc | inorm |
edge | txt | tenc |
edge | tenc | tnorm |
edge | inorm | sim |
edge | tnorm | sim |
edge | sim | loss | diagonal targets
```
## Formulas
type: concepts
from: phases/04-computer-vision/17-self-supervised-vision/docs/en.md, phases/04-computer-vision/18-open-vocab-clip/docs/en.md, phases/04-computer-vision/20-image-retrieval-metric/docs/en.md, phases/12-multimodal-ai/02-clip-contrastive-pretraining/docs/en.md
- **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
type: annotated-code
lang: python
from: phases/04-computer-vision/17-self-supervised-vision/docs/en.md, phases/04-computer-vision/17-self-supervised-vision/code/main.py
```python
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 = 196
visible, masked = random_mask_indices(num_patches, mask_ratio=0.75) # keep 25%
```
## CLIP loss and zero-shot
type: annotated-code
lang: python
from: phases/04-computer-vision/18-open-vocab-clip/docs/en.md, phases/04-computer-vision/18-open-vocab-clip/code/main.py
```python
sim = logit_scale * image_emb @ text_emb.T # (N, N)
targets = torch.arange(N, device=sim.device) # match on the diagonal
l_i = F.cross_entropy(sim, targets) # image to text
l_t = F.cross_entropy(sim.T, targets) # text to image
loss = (l_i + l_t) / 2 # random 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 image
```
## Triplet loss and recall@K
type: annotated-code
lang: python
from: phases/04-computer-vision/20-image-retrieval-metric/docs/en.md, phases/04-computer-vision/20-image-retrieval-metric/code/main.py
```python
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 negative
loss = triplet_loss(emb, emb[pos_idx], emb[neg_idx])
sim = query_emb @ gallery_emb.T # cosine 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@K
```
## Zero-shot classification
type: flow
from: phases/04-computer-vision/18-open-vocab-clip/docs/en.md, phases/12-multimodal-ai/02-clip-contrastive-pretraining/outputs/skill-clip-zero-shot.md
1. **Prompts**: write "a photo of a {class}" for each class.
2. **Ensemble**: average many templates per class for 1-3% more top-1.
3. **Text tower**: encode the prompts. `T` has shape (C, d).
4. **Image tower**: encode the image. `I` has shape (1, d).
5. **Normalise**: make both sides unit length.
6. **Score**: `I @ T.T` gives shape (1, C).
7. **Predict**: argmax gives the class. Warn when the top-1 score is below 0.2.
## Tasks in code
type: task-table
span: 2
from: phases/04-computer-vision/17-self-supervised-vision/code/main.py, phases/04-computer-vision/18-open-vocab-clip/code/main.py, phases/04-computer-vision/20-image-retrieval-metric/code/main.py, phases/12-multimodal-ai/02-clip-contrastive-pretraining/code/main.py
| Task | Code |
|---|---|
| Contrastive loss on two views | `info_nce(z1, z2, tau=0.1)` |
| DINO teacher with centring | `DinoHead().teacher(x, temp=0.04)` |
| CLIP symmetric loss | `clip_loss(i, t, logit_scale)` |
| Zero-shot labels | `zero_shot_classify(model, image_feats, class_text_feats, class_names)` |
| Mine semi-hard negatives | `semi_hard_negatives(emb, labels, margin=0.2)` |
| Recall at K | `recall_at_k(query_emb, gallery_emb, query_labels, gallery_labels, k=1)` |
| Symmetric InfoNCE in pure Python | `infonce_loss(S)` |
| SigLIP per-pair loss | `sigmoid_loss(S, bias=0.0)` |
## Defaults and numbers
type: defaults
from: phases/04-computer-vision/17-self-supervised-vision/docs/en.md, phases/04-computer-vision/17-self-supervised-vision/code/main.py, phases/04-computer-vision/18-open-vocab-clip/docs/en.md, phases/04-computer-vision/20-image-retrieval-metric/code/main.py
| 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 |
## Pick a FAISS index
type: command-table
from: phases/04-computer-vision/20-image-retrieval-metric/docs/en.md
| 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 |
## Troubleshooting
type: troubleshooting
from: phases/04-computer-vision/17-self-supervised-vision/docs/en.md, phases/04-computer-vision/20-image-retrieval-metric/docs/en.md, phases/12-multimodal-ai/02-clip-contrastive-pretraining/docs/en.md
| 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 |
## Questions
type: qa
from: phases/04-computer-vision/17-self-supervised-vision/docs/en.md, phases/04-computer-vision/18-open-vocab-clip/docs/en.md, phases/12-multimodal-ai/02-clip-contrastive-pretraining/docs/en.md
- **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.