TAI BUI
Back to curriculum
BuildPython

Self-Supervised Vision — SimCLR, DINO, MAE

Labels are the bottleneck in supervised vision. Self-supervised pretraining routes around it: learn visual features from 100M unlabelled images, then fine-tune on 10k labelled ones.

TIME: ~75 minutesPREREQUISITES: Phase 4 Lesson 04 (Image Classification), Phase 4 Lesson 14 (ViT)

Learning Objectives

  • Trace the three major self-supervised families — contrastive (SimCLR), teacher-student (DINO), masked reconstruction (MAE) — and state what each one optimises
  • Implement an InfoNCE loss from scratch and explain why a batch of 512 works but a batch of 32 fails
  • Explain why MAE's 75% masking ratio is not arbitrary and how it differs from BERT's 15% for text
  • Use DINOv2 or MAE ImageNet checkpoints for linear probing and zero-shot retrieval

The Problem

Supervised ImageNet has 1.3M labelled images, which cost an estimated $10M to annotate. Medical and industrial datasets are smaller and even more expensive to label. Every vision team ends up asking the same question: can we pretrain on cheap unlabelled data (YouTube frames, web crawls, webcam footage, satellite sweeps) and then fine-tune on a small labelled set?

Self-supervised learning solves this: train the model on a task built entirely out of the unlabelled data itself, no human annotation involved, then transfer the features it learns to the small labelled set you actually care about. A modern self-supervised ViT trained on LAION or JFT reaches or beats supervised ImageNet accuracy once fine-tuned, and it transfers better to downstream tasks (detection, segmentation, depth) than supervised pretraining does. DINOv2 (Meta, 2023) and MAE (Meta, 2022) are the current production defaults for transferable vision features.

The conceptual shift is that the pretext task, the objective the model actually trains on, doesn't have to match the downstream task. What matters is that it forces the model to learn useful features. Predicting the colour of grayscale images, rotating images and classifying the rotation, masking patches and reconstructing them: all of these have worked. The three approaches that scale to production are contrastive learning, teacher-student distillation, and masked reconstruction.

Warm-up0/3
1. You have a huge pile of photos, but almost nobody has labeled what's in any of them. Before learning any specific technique, does it seem plausible that a model could still pick up something useful about how images are structured just from the raw photos themselves?Warm-up
Four plain boxes labelled photo in a row, a note that none has ever been labelled by a person, and a dashed question mark box, with text asking whether a model could still pick up anything useful from them.
2. You take the same photo of a coffee mug twice: once in normal light, once slightly darker and cropped a bit tighter. Without being told anything about either photo, would you still recognize them as pictures of the same mug?Warm-up
Two boxes, photo A labelled normal light full frame and photo B labelled darker cropped tighter, a note that nobody has written down what either photo shows, and a dashed question mark box, asking whether they are pictures of the same mug.
3. In PyTorch you want to train a new classifier on top of a pretrained network without changing the pretrained network's weights. What do you do to the pretrained part?Warm-up
A box labelled pretrained network with its weights left as a question mark, an arrow to a box labelled new classifier that trains normally, and a note that the goal is for only the new classifier to learn, asking what you do to the pretrained part.
Check: The Problem0/2
1. What is a pretext task?Define
A box labelled phase 1 pretraining with its objective left as a question mark, an arrow to a box labelled phase 2 fine-tuning on the task you care about, with text asking which of the two the term names.
2. A team pretrains a model by rotating images and classifying the rotation, then uses the model for tumor detection. Why can the pretext task differ from the downstream task?Explain
A box labelled pretext task, predict image rotation, next to a box labelled downstream task, tumour detection, above a dashed question mark box, with text asking why these can be different tasks.

The Concept

Three families

Mermaid diagram

Contrastive learning (SimCLR)

Take one image, apply two random augmentations, and get two views. Feed both through the same encoder plus a projection head (a small MLP that maps the encoder's features into the space where the contrastive loss is computed). Minimise a loss that says "these two embeddings should be close" and "this embedding should be far from every other image's embeddings in the batch."

Loss for positive pair (z_i, z_j) among 2N views per batch:

   L_ij = -log( exp(sim(z_i, z_j) / tau) / sum_k in batch \ {i} exp(sim(z_i, z_k) / tau) )

sim = cosine similarity
tau = temperature (0.1 standard)

This is the InfoNCE loss. It needs many negatives per positive, so batch size matters: SimCLR runs on batches of 512 to 8192. MoCo introduced a momentum queue of past batches to decouple negative count from batch size.

Teacher-student (DINO)

Two networks share the same architecture: student and teacher. The teacher is an exponential moving average (EMA) of the student's weights. Both see augmented views of the image, and the student's output is trained to match the teacher's, with no explicit negatives.

loss = CE( student_output(view_1),  teacher_output(view_2) )
     + CE( student_output(view_2),  teacher_output(view_1) )

teacher_weights = m * teacher_weights + (1 - m) * student_weights   (m ≈ 0.996)

Why it doesn't collapse to "predict a constant": the teacher's output is centred (subtract the per-dimension mean) and sharpened (divide by a small temperature). Centring stops one dimension from dominating; sharpening stops the output from collapsing to uniform.

DINO is what DINOv2 scales up, on 142M curated images. The resulting features are the current SOTA for zero-shot visual retrieval and dense prediction.

Masked reconstruction (MAE)

Mask 75% of the patches in a ViT input. Pass only the visible 25% through the encoder. A small decoder receives the encoder's output plus mask tokens at the masked positions, and it's trained to reconstruct the pixels of the masked patches.

Encoder:  visible 25% of patches -> features
Decoder:  features + mask tokens at masked positions -> reconstructed pixels
Loss:     MSE between reconstructed and original pixels on masked patches only

Three design choices make MAE work:

  • 75% mask ratio: high, and deliberately so. It forces the encoder to learn semantic features; at a 25% mask ratio, reconstruction would be near-trivial, since neighbouring pixels are correlated enough that a plain CNN could nail it.
  • Asymmetric encoder/decoder: the big ViT encoder only ever sees visible patches, while a small decoder (8-layer, 512-dim) handles reconstruction. That's roughly 3x faster pretraining than naive BEiT (which runs every token, including the masked ones, through the full encoder).
  • Pixel-space reconstruction target: simpler than BEiT's target of predicting discrete visual tokens, and it works better on ViT.

After pretraining, discard the decoder. The encoder is the feature extractor.

Why 75% and not 15%

BERT masks 15% of tokens. MAE masks 75%. The difference comes down to information density.

  • Natural language has high entropy per token. Predicting 15% of tokens is still hard, because each masked position has many plausible completions.
  • Image patches have low entropy: an unmasked neighbourhood often determines the masked patch's pixels almost exactly. Making the prediction require real semantic understanding means masking aggressively.

75% is high enough that simple spatial extrapolation can't solve the task; the encoder has to actually represent the image content.

Linear-probe evaluation

After self-supervised pretraining, the standard evaluation is a linear probe: freeze the encoder, then train a single linear classifier on top of it using ImageNet labels, and report top-1 accuracy.

  • SimCLR ResNet-50: ~71% (2020)
  • DINO ViT-S/16: ~77% (2021)
  • MAE ViT-L/16: ~76% (2022)
  • DINOv2 ViT-g/14: ~86% (2023)

A linear probe is a pure measure of feature quality. Fine-tuning typically adds another 2-5 points, but it also mixes in the effect of retraining the head.

Check: The Concept0/25
1. What is self-supervised learning?Define
A box labelled cheap unlabelled photos from web crawls and video frames, an arrow to a dashed question mark box, an arrow to a box labelled a small labelled set to fine-tune on, with text asking what goes in the middle box.
2. What is a linear probe?Define
A box labelled pretrained encoder that outputs features, next to a box labelled a labelled set with class labels, above a dashed question mark box, with text asking what the standard evaluation in between is.
3. What is the InfoNCE loss?Define
Two rows of six dots representing the two augmented views of each image in a batch, with one matching pair highlighted and joined by a dashed line labelled two views of image 3, above a dashed question mark box, with text asking which loss is computed over this batch.
4. What is an EMA teacher?Define
A box labelled student, trained by backprop, next to a box labelled teacher whose weights are left as a question mark, above a dashed question mark box, with text asking where the teacher's new weights come from.
5. In MAE, what does the mask ratio measure?Define
A card listing the lesson's MAE settings: a mask ratio of 0.75 with BERT's 0.15 on text beside it, a ViT encoder, and an 8 layer 512 dimension decoder dropped afterwards, under a line asking what the 0.75 is a share of.
6. What is representation collapse?Define
Five boxes labelled img A through img E with arrows converging on one box labelled encoder, an arrow from it to a dashed question mark box, and text asking what exactly goes wrong when the run collapses.
7. What is DINOv2?Define
A code box loading the facebook dinov2-base processor and model from pretrained weights, with a line asking what kind of model sits behind that name.
8. In SimCLR, why are two random augmentations of the same image treated as a positive pair?Explain
A box labelled image, two arrows to boxes labelled view 1 augmented and view 2 augmented, both arrows continuing to a dashed question mark box, with text asking why the two views are treated as a positive pair.
9. Why does SimCLR need batch sizes of 512 to 8192 rather than a small batch of 32?Explain
A box labelled SimCLR, batch 512 to 8192, next to a box labelled a smaller run, batch 32, above a dashed question mark box, with text asking why SimCLR needs the bigger range.
10. In the InfoNCE loss, what does lowering the temperature tau do, and how does MoCo help?Explain
A code line showing sim equals z times z transpose divided by tau, with tau 0.1 noted as the SimCLR default, above two boxes, one for lowering tau and one for MoCo, each with its effect left as a question mark.
11. What prevents DINO from collapsing to a constant output?Explain
A box labelled teacher output raw with an arrow to a box labelled the loss, a note that a DINO run still does not collapse to a constant output, and a dashed question mark box under text asking what prevents it.
12. DINO has no explicit negatives. What is the student trained to do instead?Explain
A box labelled view 1 to student, output_s, next to a box labelled view 2 to teacher EMA, output_t, above a dashed question mark box, with text asking what output_s is trained to do.
13. Why does MAE use an asymmetric encoder-decoder design (big encoder on the visible 25% of patches, small decoder for reconstruction)?Explain
A box labelled big encoder, sees only the visible 25%, next to a box labelled small decoder that handles the reconstruction, above a dashed question mark box, with text asking why the work is split this way.
14. Why does MAE use a pixel-space reconstruction target rather than BEiT's discrete visual tokens?Explain
A box labelled pixel values next to a box labelled discrete visual tokens, BEiT's choice, above a dashed question mark box, with text asking which one MAE uses and why.
15. MAE masks 75% of patches. BERT masks 15% of tokens. Why the difference?Explain
A box labelled BERT text tokens masks 15%, next to a box labelled MAE image patches masks 75%, above a dashed question mark box, with text asking why there is a five-fold difference.
16. SimCLR, DINO, and MAE are called self-supervised rather than unsupervised. Why?Explain
Three boxes naming the lesson's three families, SimCLR contrastive, MAE masked reconstruction and DINO teacher-student, above a dashed question mark box, with text asking why all three are called self-supervised rather than unsupervised.
17. Why is a linear probe a cleaner measure of feature quality than fine-tuning the whole encoder?Explain
A box labelled linear probe, the standard SSL score, next to a box labelled fine-tune, usually 2 to 5 points higher, above a dashed question mark box, with text asking which is the cleaner measure of feature quality.
18. Which conclusion do the lesson's linear-probe top-1 numbers support?Apply
Four dashed question mark boxes in a row, labelled SimCLR ResNet-50 2020, DINO ViT-S/16 2021, MAE ViT-L/16 2022 and DINOv2 ViT-g/14 2023, with a note that the top-1 numbers are hidden here.
19. A DINO teacher weight is 1.0 and the matching student weight is 2.0. With m = 0.996, what is the teacher weight after one update?Apply
A code box listing teacher equals 1.0, student equals 2.0 and m equals 0.996, with the teacher's value after one EMA update left as a question mark.
20. A DINO-style run drives its loss to near 0 within a few epochs, but every image maps to the same output vector. Which ingredients were most likely missing?Analyze
A box labelled training loss, near 0 within a few epochs, next to a box labelled every image's output, the same vector, above a dashed question mark box, with text asking what was most likely missing.
21. A team trains SimCLR with InfoNCE on batches of 32 and gets weak features, though the code is correct. What is the most likely cause?Analyze
A code box reading InfoNCE loss implemented correctly, batch size 32, result weak features after training, above a dashed question mark box and a line asking for the most likely cause.
22. A team trains MAE with a 25% mask ratio. Reconstruction loss gets very low, yet the linear-probe accuracy is poor. What explains this?Analyze
A code box reading mask ratio 0.25, reconstruction loss very low, linear probe accuracy poor, above a dashed question mark box and a line asking what explains this.
23. A colleague feeds mask tokens through the full encoder together with the visible patches in an MAE-style setup. What do they lose?Analyze
A single wide box labelled encoder, sees visible patches and mask tokens together, above a dashed question mark box, with text asking what they lose compared to MAE's own split.
24. You must run contrastive pretraining, but GPU memory caps the batch at 64. Which design fits?Design
A box labelled contrastive pretraining, InfoNCE loss, next to a box labelled GPU memory, caps the batch at 64, above a dashed question mark box, with text asking which design change fits this limit.
25. You want to know which of two frozen pretrained encoders has better features. Which evaluation is most appropriate?Design
A box labelled encoder A frozen, next to a box labelled encoder B frozen, above a dashed question mark box, with text asking how to compare their feature quality.

Build It

Step 1: Two-view augmentation pipeline

python
import torch
import torchvision.transforms as T
 
two_view_train = lambda: T.Compose([
    T.RandomResizedCrop(96, scale=(0.2, 1.0)),
    T.RandomHorizontalFlip(),
    T.ColorJitter(0.4, 0.4, 0.4, 0.1),
    T.RandomGrayscale(p=0.2),
    T.ToTensor(),
])
 
 
class TwoViewDataset(torch.utils.data.Dataset):
    def __init__(self, base):
        self.base = base
        self.aug = two_view_train()
 
    def __len__(self):
        return len(self.base)
 
    def __getitem__(self, i):
        img, _ = self.base[i]
        v1 = self.aug(img)
        v2 = self.aug(img)
        return v1, v2

Each getitem call returns two augmented views of the same image; no labels needed.

Step 2: InfoNCE loss

python
import torch.nn.functional as F
 
def info_nce(z1, z2, tau=0.1):
    """
    z1, z2: (N, D) L2-normalised embeddings of paired views
    """
    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"))
 
    targets = torch.cat([torch.arange(N, 2 * N), torch.arange(0, N)]).to(z.device)
    return F.cross_entropy(sim, targets)

L2-normalise the embeddings before calling this. tau=0.1 is the SimCLR default: a lower value sharpens the loss and demands more negatives.

Step 3: Sanity check InfoNCE

python
z1 = F.normalize(torch.randn(16, 32), dim=-1)
z2 = z1.clone()
loss_same = info_nce(z1, z2, tau=0.1).item()
z2_random = F.normalize(torch.randn(16, 32), dim=-1)
loss_random = info_nce(z1, z2_random, tau=0.1).item()
print(f"InfoNCE with identical pairs:  {loss_same:.3f}")
print(f"InfoNCE with random pairs:     {loss_random:.3f}")

Identical pairs should produce a low loss (close to 0, for a large batch and a cold temperature). Random pairs should land near log(2N-1) = ~log(31) = ~3.4 for a 16-pair batch.

Step 4: MAE-style masking

python
def random_mask_indices(num_patches, mask_ratio=0.75, seed=0):
    g = torch.Generator().manual_seed(seed)
    n_keep = int(num_patches * (1 - mask_ratio))
    perm = torch.randperm(num_patches, generator=g)
    visible = perm[:n_keep]
    masked = perm[n_keep:]
    return visible.sort().values, masked.sort().values
 
 
num_patches = 196
visible, masked = random_mask_indices(num_patches, mask_ratio=0.75)
print(f"visible: {len(visible)} / {num_patches}")
print(f"masked:  {len(masked)} / {num_patches}")

This version is simple, fast, and deterministic for a given seed. Real MAE implementations batch it and keep a separate mask per sample.

Check: Build It0/5
1. Using the lesson's TwoViewDataset, what does indexing the dataset at position 3 return?Apply
A code box showing TwoViewDataset's constructor, which stores a labelled base dataset and an augmentation pipeline, with the body of __getitem__ replaced by an ellipsis and the result of indexing at position 3 left as a question mark.
2. In info_nce, z1 and z2 each have shape (16, 32). What is the shape of sim, and what does masked_fill with -inf do?Apply
A code box showing z built by concatenating z1 and z2 with its shape left as a question mark, sim computed as z times z transpose divided by tau with its shape also left open, and a masked_fill call with the mask itself left as a question mark.
3. In the sanity check, info_nce gets z1 and an unrelated random z2 for 16 pairs at tau = 0.1. Roughly what loss is expected?Apply
A code box listing z1 as 16 by 32 and L2 normalised, z2_random as 16 by 32 and unrelated to z1, tau equal to 0.1, and the info_nce call's result left as a question mark.
4. In the lesson's `random_mask_indices` function, what determines which patches end up visible versus masked?Apply
A code box calling random_mask_indices with 8 patches and a mask ratio of 0.5, showing that 4 indices land in visible and 4 in masked, with a highlighted line asking which 4 end up visible.
5. With num_patches = 196 and mask_ratio = 0.75, what do the two print statements show?Apply
A code box calling random_mask_indices with 196 patches and a mask ratio of 0.75, then printing the visible and masked counts, with both printed lines left as question marks.

Use It

DINOv2 is the production standard in 2026:

python
import torch
from transformers import AutoImageProcessor, AutoModel
 
processor = AutoImageProcessor.from_pretrained("facebook/dinov2-base")
model = AutoModel.from_pretrained("facebook/dinov2-base")
model.eval()
 
# Per-image embeddings for zero-shot retrieval
with torch.no_grad():
    inputs = processor(images=[pil_image], return_tensors="pt")
    outputs = model(**inputs)
    embedding = outputs.last_hidden_state[:, 0]  # CLS token

The resulting 768-dim embedding is the backbone of modern image retrieval, dense correspondence, and zero-shot transfer pipelines. Fine-tuning on a downstream task rarely needs more than a linear head.

For image-text embeddings, SigLIP and OpenCLIP fill the same role; for MAE-style fine-tuning, the timm repo ships every MAE checkpoint.

Check: Use It0/3
1. Almost nobody pretrains SimCLR or MAE from scratch anymore. Why still learn their mechanics?Explain
A box labelled DINOv2 checkpoint, released and downloadable, next to a box labelled SimCLR and MAE pretrained from scratch, above a dashed question mark box, with text noting almost nobody runs the right-hand path now and asking why learn it.
2. Using DINOv2 through `transformers`, what does the CLS-token embedding give you, and what is its size for dinov2-base?Apply
A code box showing outputs assigned from the model, embedding set from the CLS token at index 0 of last_hidden_state, with the embedding's shape left as a question mark, under a note naming the model facebook dinov2-base.
3. You have a few thousand labelled medical images, a modest GPU budget, and no budget to pretrain. Which plan fits best?Design
A code box listing a few thousand labelled images, a modest GPU budget, and no budget to pretrain from scratch, above a dashed question mark box and a line asking which plan fits.

Ship It

This lesson produces:

  • outputs/prompt-ssl-pretraining-picker.md: a prompt that picks SimCLR, MAE, or DINOv2 given dataset size, compute, and downstream task.
  • outputs/skill-linear-probe-runner.md: a skill that writes the linear-probe evaluation for any frozen encoder plus labelled dataset.
Author's Note

The distinction that trips people up first is "self-supervised" versus "unsupervised." Self-supervised training still uses labels, just labels the data manufactures for itself: the other augmented view in SimCLR, the unmasked patches in MAE, the teacher's own output in DINO. Nothing about the objective is unsupervised — the supervision signal is just synthetic. Once that clicks, SimCLR, DINO, and MAE stop looking like three unrelated tricks and start looking like three answers to the same question: what free label can I manufacture from this image alone?

In practice, almost nobody pretrains their own SimCLR or MAE from scratch anymore. DINOv2 checkpoints are strong enough, and cheap enough to fine-tune, that training an encoder from raw pixels is usually a poor use of a GPU budget. Learn the mechanics here because they explain why DINOv2's features are as good as they are, not because you'll run this exact pipeline in production.

Exercises

  1. (Easy) Verify that InfoNCE loss drops when you decrease temperature for well-aligned embeddings and rises when you decrease temperature for random embeddings. Produce a plot tau in [0.05, 0.1, 0.2, 0.5] vs loss.
  2. (Medium) Implement a DINO-style centre buffer. Show that without the centring, the student collapses to a constant vector within a few epochs.
  3. (Hard) Train MAE on CIFAR-100 using the TinyUNet from Lesson 10 as the backbone. Report linear-probe accuracy at 10, 50, and 200 epochs. Show that a MAE-pretrained linear probe beats a from-scratch supervised linear probe on the same 1,000-image subset.

Key Terms

TermWhat people sayWhat it actually means
Self-supervised"Label-free"A pretext task that produces useful representations from unlabelled data
Pretext task"The fake task"The objective used during SSL (reconstruct patches, match views); discarded after pretraining
Linear probe"Frozen encoder + linear head"Standard SSL evaluation: train only a linear classifier on top of frozen features
InfoNCE"Contrastive loss"softmax over cosine similarities; positive pair is the target class, all others are negatives
EMA teacher"Moving-average teacher"Teacher whose weights are an exponential moving average of the student's; used by BYOL, MoCo, DINO
Mask ratio"% of patches hidden"Fraction of patches masked during MAE; 75% for vision, 15% for text
Representation collapse"Constant output"SSL failure where the encoder outputs a constant vector for all inputs; prevented by centring, sharpening, or negatives
DINOv2"Production SSL backbone"Meta's 2023 self-supervised ViT; strongest general-purpose image features in 2026

Further Reading