TAI BUI
Back to curriculum
BuildPython

Open-Vocabulary Vision — CLIP

Train an image encoder and a text encoder together until matching (image, caption) pairs land at the same point in a shared space. That's the whole trick.

TIME: ~45 minutesPREREQUISITES: Phase 4 Lesson 14 (ViT), Phase 4 Lesson 17 (Self-Supervised)

Learning Objectives

  • Explain CLIP's two-tower architecture and contrastive training objective
  • Use a pretrained CLIP (or SigLIP) for zero-shot classification without any task-specific training
  • Implement zero-shot classification from scratch: encode class prompts, compute cosine similarity, take argmax
  • Distinguish CLIP, SigLIP, OpenCLIP, and LLaVA/LLaMA-vision models — what each is for in 2026

The Problem

Traditional classifiers are closed-vocabulary. A 1000-class ImageNet model can only ever predict 1000 labels, and every new category means new labelled data and a retrained head.

CLIP (Radford et al., OpenAI 2021) broke that constraint. Train on 400M (image, caption) pairs scraped from the web and you get a model that classifies into any set of categories at inference, described purely in natural language. Give it a new class by writing a sentence, nothing else.

That's zero-shot transfer, and it's why every modern vision system starts with a CLIP-family checkpoint. Detection (Grounding DINO, OWL-ViT), segmentation (CLIPSeg, SAM), retrieval, content moderation, VLMs, and text-to-image generation all build on CLIP-style joint embeddings.

Warm-up0/3
1. Contrastive training pulls matching pairs together in embedding space and pushes everything else apart. If you trained one encoder on images and a second encoder on their captions with that idea, what would the shared embedding space let you do?Warm-up
An image box and a caption box each with an arrow to a point in a small space, the two points linked by a dashed line captioned that training pulls them together, above a dashed question mark box asking what a shared point lets you do.
2. Two embedding vectors point in almost exactly the same direction. What does their cosine similarity come out close to?Warm-up
Two arrows from one point, almost overlapping, with a note that the angle between them is small, next to a dashed question mark box asking what the cosine similarity of the two vectors comes out close to.
3. A standard image classifier is trained on a fixed set of 1000 labels. What does adding a 1001st label normally require?Warm-up
A row of filled output boxes c0 through c999 for a trained classifier, next to a dashed outline box labelled c1000 marked not wired up yet, above a dashed question mark box asking what is needed before c1000 can work.
Check: The Problem0/3
1. What does zero-shot classification mean for CLIP?Define
Three solid boxes labelled dog, cat and car next to a dashed outline box labelled giraffe, with a note that the trained classes are dog, cat and car but the test image is a giraffe, above a dashed question mark box asking how the system handles a class it was never trained to output.
2. What is CLIP?Define
A card reproducing the lesson's Key Terms row for CLIP, with the term, the phrase people say, image plus text model, and the meaning column masked as a question mark, asking what kind of model it is.
3. CLIP was trained on roughly 400M web (image, caption) pairs rather than a labelled class list. What does that data choice give it?Explain
A box labelled ImageNet-style, a labelled class list, next to a box labelled CLIP, 400M web image caption pairs, above a dashed question mark box asking what capability the right-hand choice unlocks.

The Concept

Two towers

CLIP is two encoders trained as one system: an image tower and a text tower, pushed until matching pairs land at the same point in a shared embedding space.

Mermaid diagram

Both towers end in a linear projection to that shared dimension (512 for CLIP-B/32, 1024 for CLIP-L/14), then get L2-normalised so a plain cosine similarity is all that's left to compute.

The objective

Take a batch of N (image, caption) pairs and build an NxN similarity matrix. Train both encoders so the diagonal (the matching pairs) comes out high and everything off the diagonal comes out low.

sim_matrix = image_embeddings @ text_embeddings.T / tau

loss_i2t = cross_entropy(sim_matrix,       targets=arange(N))
loss_t2i = cross_entropy(sim_matrix.T,     targets=arange(N))
loss = (loss_i2t + loss_t2i) / 2

The loss is symmetric because both directions need to work at inference: image-to-text and text-to-image retrieval. tau (temperature) is typically a learned scalar, initialised to 0.07.

SigLIP: a better loss

SigLIP (Zhai et al., 2023) swaps the softmax for a per-pair sigmoid:

loss = mean over pairs of log(1 + exp(-y_ij * sim_ij))
y_ij = +1 if matching, -1 otherwise

Because the loss is per-pair, it drops the batch-level normalisation CLIP's softmax needs. That lets SigLIP train well at small batch sizes, and it matches or beats CLIP given equal data.

Zero-shot classification

Given a trained CLIP:

  1. For each class, compose a prompt: "a photo of a {class}".
  2. Encode all class prompts with the text encoder -> T shape (C, d).
  3. Encode the test image -> I shape (1, d).
  4. Similarity = I @ T.T shape (1, C).
  5. Argmax -> predicted class.

Prompt engineering matters here. OpenAI published 80 prompt templates for ImageNet ("a photo of a {}", "a blurry photo of a {}", "a sketch of a {}", ...), and averaging the embeddings of all templates per class buys an extra 1-3% top-1 accuracy.

Where CLIP-style models are used in 2026

  • Zero-shot classification: direct use.
  • Image retrieval: encode all images once, embed the query at inference.
  • Text-conditioned detection: Grounding DINO and OWL-ViT wrap a CLIP text tower around a detector.
  • Text-conditioned segmentation: CLIPSeg; SAM also takes text-prompt inputs via CLIP.
  • VLMs: LLaVA, Qwen-VL, InternVL, and Llama's own vision-language releases all wire a CLIP-family vision encoder into an LLM.
  • Text-to-image generation: Stable Diffusion and DALL-E 3 condition on CLIP text embeddings.
  • Content moderation: safety classifiers score an image against CLIP embeddings of policy categories instead of training a bespoke classifier per policy.

Once you have a shared embedding space, every vision+language task becomes a distance computation.

Check: The Concept0/17
1. What is a two-tower model?Define
An image box and a caption box both feeding one large dashed question mark box, which leads to a box reading one score, how well they match, with a line saying the lesson calls this arrangement a two-tower model and asking what is inside.
2. What is a prompt template in zero-shot CLIP?Define
A card stating that OpenAI published 80 prompt templates for zero-shot ImageNet and that which ones you pick changes the zero-shot accuracy, above a dashed question mark box asking what one of them is concretely.
3. What is SigLIP?Define
A card listing SigLIP's paper, Zhai and colleagues 2023, the checkpoint name google slash siglip base patch16 224, and a note that Hugging Face ships it alongside CLIP, above a dashed question mark box asking what kind of thing SigLIP is.
4. What is a VLM (vision-language model)?Define
A card listing the lesson's four examples, LLaVA, Qwen-VL, InternVL and Llama's own vision-language releases, above a dashed question mark box asking what the VLM label means.
5. Which pairing correctly matches a task to the models the lesson says wrap a CLIP text tower around it?Define
A card listing six tasks from the lesson with their model names blanked out as question marks: zero-shot classification, image retrieval, text-conditioned detection, text-conditioned segmentation, VLMs, and text-to-image generation, asking which models the lesson names for text-conditioned detection.
6. CLIP's contrastive loss is symmetric (image-to-text plus text-to-image). Why both directions?Explain
A code box showing loss_i2t and loss_t2i computed from sim_matrix and its transpose, averaged into one loss, with a line asking why both terms are needed instead of just one.
7. Why are both embeddings L2-normalised before the similarity is computed?Explain
A box labelled raw embedding, length 2.3, illustrative, an arrow through a dashed question mark box labelled normalise, into a box labelled similarity score, with a line asking why normalise to unit length first.
8. SigLIP replaces CLIP's softmax with a sigmoid loss per pair. What benefit does that give?Explain
A code box with CLIP's symmetric cross-entropy loss on one line and SigLIP's per-pair formula, the mean over pairs of log of 1 plus exp of minus y_ij times sim_ij, on the next, asking what specific benefit the per-pair design gives.
9. How does zero-shot classification with CLIP produce a prediction?Explain
A test image box next to three prompt boxes reading a photo of a dog, a photo of a cat and a photo of a car, above a dashed question mark box asking how a prediction comes out of these four things.
10. Why does adding a new class to zero-shot CLIP need no retraining?Explain
A card for a deployed zero-shot CLIP listing the class list today as dog, cat and car, tomorrow as dog, cat, car and giraffe, with model weights unchanged, above a dashed question mark box asking what has to happen before giraffe works.
11. A practitioner reports 88% zero-shot top-1 on CIFAR-10 with a single template and 90% with 80 templates averaged per class. Why does averaging help?Explain
A box labelled single template, 88 percent top-1, next to a box labelled 80 templates averaged, 90 percent top-1, both numbers given in the stem, with a line asking why averaging gives the boost.
12. Why do VLMs such as LLaVA, Qwen-VL and InternVL use a CLIP-family vision encoder?Explain
A box labelled supervised ImageNet encoder, trained on 1000 fixed labels, next to a box labelled CLIP-family encoder, trained on image caption pairs, above a dashed question mark box asking why a VLM takes the second one.
13. A zero-shot ImageNet setup uses 1,000 classes and the 80 published templates, averaging the embeddings per class. What shape is T after averaging?Apply
A card stating 80 templates times 1000 classes equals 80,000 raw prompts, averaged 80 per class, with a line asking what shape T comes out to after averaging.
14. A lab trains a CLIP-style model with the softmax loss on a small GPU, so batches hold only 32 pairs, and results are poor. Which diagnosis fits?Analyze
A card stating the loss is CLIP's softmax, the batch size is 32 pairs on a small GPU, and the result is poor, with a line asking for the most likely cause.
15. A platform adds new moderation policy categories every few weeks and has no labelled images for them yet. Which design fits the lesson's ideas?Design
A card stating a platform adds moderation policy categories often and has no labelled images yet for the new ones, above a dashed question mark box asking which design fits.
16. You must support text search over 1M product photos with low latency per query. Which design fits?Design
A card stating 1,000,000 product photos with text search and low latency required per query, above a dashed question mark box asking for the best design.
17. You must label photos into 30 product categories that change often, with no labelled photos. Which plan best uses zero-shot CLIP?Design
A card stating 30 product categories that change often with zero labelled photos, above a dashed question mark box asking for the best plan using zero-shot CLIP.

Build It

Step 1: A tiny two-tower model

Real CLIP pairs a ViT with a transformer. Here the towers are small MLPs over pre-extracted features, so the training signal stays visible on CPU.

python
import torch
import torch.nn as nn
import torch.nn.functional as F
 
 
class TwoTower(nn.Module):
    def __init__(self, img_in=128, txt_in=64, emb=64):
        super().__init__()
        self.image_proj = nn.Sequential(nn.Linear(img_in, 128), nn.ReLU(), nn.Linear(128, emb))
        self.text_proj = nn.Sequential(nn.Linear(txt_in, 128), nn.ReLU(), nn.Linear(128, emb))
        self.logit_scale = nn.Parameter(torch.ones([]) * 2.6592)  # ln(1/0.07)
 
    def forward(self, img_feats, txt_feats):
        i = F.normalize(self.image_proj(img_feats), dim=-1)
        t = F.normalize(self.text_proj(txt_feats), dim=-1)
        return i, t, self.logit_scale.exp()

Two projections, a shared output dimension, a learned temperature: same shape as the real CLIP API, just smaller.

Step 2: Contrastive loss

The two towers only learn anything once a loss rewards matching pairs and punishes mismatched ones. This is the symmetric objective from the Concept section, translated directly into PyTorch:

python
def clip_loss(image_emb, text_emb, logit_scale):
    N = image_emb.size(0)
    sim = logit_scale * image_emb @ text_emb.T
    targets = torch.arange(N, device=sim.device)
    l_i = F.cross_entropy(sim, targets)
    l_t = F.cross_entropy(sim.T, targets)
    return (l_i + l_t) / 2

A higher logit_scale sharpens the softmax: more confident predictions, but also more risk of instability.

Step 3: Zero-shot classifier

Once the towers are trained, classification needs no retraining at all. This is the five-step zero-shot procedure from the Concept section, translated directly into code:

python
@torch.no_grad()
def zero_shot_classify(model, image_feats, class_text_feats, class_names):
    """
    image_feats:      (N, img_in)
    class_text_feats: (C, txt_in)   one averaged embedding per class
    """
    i = F.normalize(model.image_proj(image_feats), dim=-1)
    t = F.normalize(model.text_proj(class_text_feats), dim=-1)
    sim = i @ t.T
    pred = sim.argmax(dim=-1)
    return [class_names[p] for p in pred.tolist()]

One line per step, and it's exactly what you'd call against a production CLIP checkpoint.

Step 4: Sanity check

Before training on anything real, confirm the loss behaves the way the math predicts on random weights:

python
torch.manual_seed(0)
model = TwoTower()
 
img = torch.randn(8, 128)
txt = torch.randn(8, 64)
i, t, scale = model(img, txt)
loss = clip_loss(i, t, scale)
print(f"batch size: {i.size(0)}   loss: {loss.item():.3f}")

Loss should land close to log(N) = log(8) = 2.08 for a randomly initialised model. That's the symmetric cross-entropy target when no structure has been learned yet.

Check: Build It0/8
1. What is the temperature (logit_scale) in a CLIP-style model?Define
A code box listing the TwoTower module's three attributes, image_proj, text_proj and a highlighted logit_scale whose value is masked as a question mark with a note that people call it tau, asking what kind of quantity logit_scale is.
2. What embedding dimension does CLIP-B/32 project both towers into, and why does the lesson's TwoTower model initialise logit_scale to 2.6592?Explain
A code box showing the logit_scale line with a comment ln of 1 over a masked value, and a card stating CLIP-B/32 projects both towers into a masked embedding dimension.
3. In the lesson's sanity check, an untrained TwoTower model sees random features for a batch of 8. Which loss value does the lesson say it should land close to, and why?Apply
A code box building an untrained TwoTower and running clip_loss on a random batch of 8, above a card quoting the lesson's Build It sentence with the loss value masked as a question mark, asking what value the lesson gives and why.
4. In clip_loss, a batch has N=4 pairs, so sim is 4x4. targets = arange(4). Which entries of sim does cross_entropy treat as the correct ones?Apply
A plain 4 by 4 grid labelled img 0 to 3 on the rows and cap 0 to 3 on the columns with no cells highlighted, next to a dashed question mark box asking which entries cross_entropy treats as correct.
5. zero_shot_classify gets image_feats of shape (5, 128) and class_text_feats of shape (10, 64). What are the shape of sim and the output of argmax(dim=-1)?Apply
A card listing image_feats of shape 5 by 128 and class_text_feats of shape 10 by 64, with a line asking what shape sim comes out to and what argmax over the last dimension gives.
6. The TwoTower model's logit_scale parameter holds 2.6592 at initialisation. What multiplier does clip_loss actually apply to the cosine similarities?Apply
A code box showing logit_scale stored as 2.6592 in __init__, a highlighted forward line whose third return value is masked as a question mark, and clip_loss multiplying the similarities by that value, asking what multiplier clip_loss actually applies.
7. Someone runs the sanity check with a batch of 16 on a random model and sees a loss near 2.77. They worry it is a bug because the lesson showed 2.08. Using the lesson's rule for an untrained model, what is the right call?Analyze
A box labelled batch 8 with the lesson's figure of 2.08, next to a box labelled batch 16 with the colleague's figure of 2.77, with a line asking whether this is a bug or expected.
8. A run uses a very large logit_scale and produces confident predictions but unstable training. Which statement explains the symptom?Analyze
A flow from cosine similarities through a highlighted box reading times a very large logit_scale into a dashed question mark box, with a line asking about the effect on predictions and on training stability.

Use It

OpenCLIP is the community default in 2026:

python
import open_clip
import torch
from PIL import Image
 
model, _, preprocess = open_clip.create_model_and_transforms("ViT-B-32", pretrained="laion2b_s34b_b79k")
tokenizer = open_clip.get_tokenizer("ViT-B-32")
 
image = preprocess(Image.open("dog.jpg")).unsqueeze(0)
text = tokenizer(["a photo of a dog", "a photo of a cat", "a photo of a car"])
 
with torch.no_grad():
    image_features = model.encode_image(image)
    text_features = model.encode_text(text)
    image_features = image_features / image_features.norm(dim=-1, keepdim=True)
    text_features = text_features / text_features.norm(dim=-1, keepdim=True)
    probs = (100.0 * image_features @ text_features.T).softmax(dim=-1)
 
print(probs)

SigLIP is newer, trains better at small scales, and is the better pick for new work: google/siglip-base-patch16-224. Hugging Face ships both.

Check: Use It0/4
1. What is OpenCLIP?Define
A code box showing the call open_clip.create_model_and_transforms with model name ViT-B-32 and a pretrained argument masked as a question mark, asking where these particular weights come from.
2. For new work that trains a joint embedding model, which loss does the lesson recommend, and why?Explain
A box labelled CLIP's softmax, 2021, the original loss, next to a box labelled SigLIP's sigmoid, 2023, the newer loss, with a line asking which the lesson recommends for training a new model.
3. In the OpenCLIP example, one dog.jpg is scored against three prompts (dog, cat, car). What does probs look like for a clear dog photo?Apply
A card stating the image is dog.jpg, a clear dog photo, scored against three prompts: a photo of a dog, a photo of a cat, a photo of a car, with a line asking what probs comes out to.
4. Using OpenCLIP's create_model_and_transforms, which call turns class-name strings into token tensors the text encoder can consume?Apply
A code box with a call to open_clip.create_model_and_transforms, a highlighted line reading tokenizer equals question mark, and a call to tokenizer on a list of prompt strings, asking which call fills the blank.

Ship It

This lesson produces:

  • outputs/prompt-zero-shot-class-picker.md, a prompt that designs class templates for zero-shot CLIP given a list of classes and a domain.
  • outputs/skill-image-text-retriever.md, a skill that builds an image embedding index with any CLIP checkpoint and supports query-by-text and query-by-image.
Author's Note

The gap between CLIP's zero-shot numbers and how it behaves on your own class list is where people get burned. CLIP is closer to a bag-of-words matcher than a model that understands a scene: it's excellent at "is this a dog or a cat," and it quietly falls apart on anything that needs counting, spatial relationships (left of, on top of, inside), or telling two classes apart by a fine-grained attribute instead of a different object entirely. If zero-shot accuracy tanks on a new domain, check whether the task secretly needs compositional reasoning before you blame the prompt templates.

The other thing worth knowing going in: by 2026, SigLIP is the default for anyone training a new joint embedding model from scratch. CLIP is mostly what you inherit as the vision tower inside an existing VLM or diffusion pipeline. Learn both, but if you're training something new, start from SigLIP's loss, not CLIP's.

Exercises

  1. (Easy) Use a pretrained OpenCLIP ViT-B/32 and do zero-shot classification on CIFAR-10 with the 80-template prompt set. Report top-1 accuracy; it should be around 85-90%.
  2. (Medium) Compare single-template ("a photo of a {}") vs 80-template averaged embeddings on the same CIFAR-10 task. Quantify the gap and explain why templates help.
  3. (Hard) Build a zero-shot image retrieval index: embed 1,000 images with CLIP, build a FAISS index (Meta's library for fast nearest-neighbor vector search), query with a natural language description. Report retrieval recall@5 for 20 held-out queries you write by hand.

Key Terms

TermWhat people sayWhat it actually means
Two-tower"Dual encoder"Separate image and text encoders ending in a shared-dim projection head
Zero-shot"No task-specific training"Classify into classes described only by text at inference; no labels touched
Temperature / logit_scale"tau"Learned scalar that scales the similarity matrix before softmax
Prompt template"A photo of a {}"Natural-language wrapper around class names; averaging many templates boosts zero-shot accuracy
CLIP"Image+text model"The 2021 OpenAI model; vocabulary of the field in 2026
SigLIP"Sigmoid CLIP"Swaps softmax for per-pair sigmoid; trains better at small batches
OpenCLIP"Open reproduction"Community-trained CLIP variants on LAION; production default for open-source pipelines
VLM"Vision-language model"A CLIP-family encoder plus an LLM, trained to answer questions about images

Further Reading