Skip to the lesson
FixTheRobot Home / VLA / Phase 2 / 2.3

VLA · Phase 2 · Vision-Language Foundations

2.3 — CLIP and Vision-Language Alignment

Learning a shared space where images and text can be compared.

  • Roadmap phase 2
  • Vision-Language Foundations
  • About 14 minutes to read

CLIP teaches one model to read pictures and another to read captions so that matching pairs end up close together. This lesson builds the idea from cosine similarity up to a tiny trained example.

Claims about papers, models and textbooks carry numbered references, listed with what each one is cited for in the References. Code marked “Toy model” is our own illustration, not the cited system.

Learning objectives

After this lesson, you should be able to:

  • Explain what a shared image-text space is.
  • Compute a table of cosine similarities.
  • Describe the contrastive objective: matching pairs up, others down.
  • Explain the difference between a softmax and a pairwise sigmoid loss.
  1. An image encoder turns a photo into a vector
  2. A text encoder turns a caption into a vector
  3. Compare every image with every caption
  4. Raise the scores of true pairs, lower the rest
  5. Use the aligned space to match new images and words
Contrastive image-text training.

The one-sentence idea

Train two encoders so that a picture and its caption land close together.

Comparing a picture with a sentence becomes comparing two vectors.

The idea: put pictures and words in one space

Imagine two translators. One reads a photo and writes down a list of numbers. The other reads a caption and writes down a list of numbers too. Training makes them agree: a photo of a red cube and the caption “a red cube” should get nearly the same numbers, and a photo of a blue ball should get different ones. After training, comparing a picture with a sentence is just comparing two lists of numbers.

That is CLIP. It learns image representations from raw text paired with images, using the simple task of predicting which caption goes with which image; natural language can then refer to the learned visual concepts, which allows zero-shot transfer to new tasks.[1] It was pre-trained on 400 million image-text pairs collected from the internet,[1] and its zero-shot accuracy on ImageNet matched the original ResNet-50 without using any of that dataset’s 1.28 million training examples.[1]

Comparing by angle: cosine similarity

“Agree” needs a number. The usual choice is cosine similarity: the cosine of the angle between two vectors. It is 1 when they point the same way, 0 when they are unrelated and −1 when they are opposite. For a batch, compare every image with every caption to get a table of scores. Training pushes the matching pairs, the diagonal, up and everything else down.

import numpy as np

def normalise(v):
    return v / np.linalg.norm(v, axis=1, keepdims=True)

images = np.array([[0.9, 0.1, 0.0],       # pretend outputs of the image encoder
                   [0.1, 0.8, 0.2],
                   [0.0, 0.2, 0.9]])
texts  = np.array([[1.0, 0.0, 0.1],       # pretend outputs of the text encoder
                   [0.0, 1.0, 0.1],
                   [0.1, 0.1, 1.0]])
scores = normalise(images) @ normalise(texts).T
print(np.round(scores, 2))
print("best caption for each image:", scores.argmax(axis=1).tolist())

Output:

[[0.99 0.11 0.11]
 [0.14 0.98 0.35]
 [0.1  0.31 0.99]]
best caption for each image: [0, 1, 2]

Toy model. The vectors are made up. Real encoders output hundreds of numbers each.

The best score in each row is on the diagonal, so every image finds its own caption. Real training is about reaching this state from random starting vectors.

Training one: a tiny contrastive learner

Below is a very small version of the training loop. The “images” are noisy 8-number features of four things (red cube, blue cube, red ball, blue ball), and the “captions” are one-hot ids. Each encoder is just a matrix. Each step takes one picture of every thing, scores all pairs, and nudges the matrices so that matching pairs score higher than non-matching ones, in both directions (image-to-text and text-to-image). Nothing here is hidden; the gradients are written out.

import numpy as np
rng = np.random.default_rng(0)

names = ["red cube", "blue cube", "red ball", "blue ball"]
base = np.array([[1,0,1,0], [0,1,1,0], [1,0,0,1], [0,1,0,1]], float)   # colour and shape facts
mix = rng.normal(size=(4, 8))
def images(idx):                                   # noisy "photos" of each thing
    return base[idx] @ mix + 0.3 * rng.normal(size=(len(idx), 8))
def texts(idx):                                    # captions as one-hot ids
    return np.eye(4)[idx]

def unit(x):  return x / np.linalg.norm(x, axis=1, keepdims=True)
def softmax(x):
    e = np.exp(x - x.max(1, keepdims=True)); return e / e.sum(1, keepdims=True)

Wi = rng.normal(size=(8, 3)) * 0.3                 # image encoder (a matrix)
Wt = rng.normal(size=(4, 3)) * 0.3                 # text encoder (a matrix)
temp = 0.1

def accuracy():                                    # does each fresh photo pick its own caption?
    idx = np.arange(4).repeat(50)
    S = unit(images(idx) @ Wi) @ unit(texts(np.arange(4)) @ Wt).T
    return float((S.argmax(1) == idx).mean())

print("accuracy before training:", round(accuracy(), 2))
for step in range(300):
    idx = rng.permutation(4)                       # one photo of each thing, shuffled
    X, T = images(idx), texts(idx)
    A, B = X @ Wi, T @ Wt
    An, Bn = unit(A), unit(B)
    S = An @ Bn.T / temp
    P, Q = softmax(S), softmax(S.T)                # image->text and text->image
    loss = -(np.log(np.diag(P)).mean() + np.log(np.diag(Q)).mean()) / 2
    I = np.eye(4)
    dS = ((P - I) + (Q - I).T) / (2 * 4) / temp
    def back(dn, x, xn):                           # gradient through the normalisation
        return (dn - (dn * xn).sum(1, keepdims=True) * xn) / np.linalg.norm(x, axis=1, keepdims=True)
    Wi -= 0.5 * X.T @ back(dS @ Bn, A, An)
    Wt -= 0.5 * T.T @ back(dS.T @ An, B, Bn)
    if step in (0, 100, 299):
        print("step", step, "loss", round(float(loss), 3))
print("accuracy after training:", round(accuracy(), 2))

Output:

accuracy before training: 0.07
step 0 loss 7.521
step 100 loss 0.003
step 299 loss 0.001
accuracy after training: 1.0

Toy model. Two matrices, four concepts and a made-up photo model. CLIP trains large neural networks on hundreds of millions of real image-text pairs, but the objective has the same shape: match the right pairs inside each batch.

The loss falls and the accuracy goes from 0.07 (worse than the 0.25 you would get by guessing) to 1.0 on this easy problem. “Zero-shot” in CLIP means the same kind of test on a new task: describe the choices in words, embed the words, and pick the closest. No extra training on that task is needed.

A variation: SigLIP’s sigmoid loss

The loss above compares each image against all captions in the batch at once (softmax). SigLIP proposes a pairwise sigmoid loss that operates on individual image-text pairs, so it needs no global view of all the pairwise similarities for normalisation, unlike the softmax-normalised contrastive loss in CLIP-style training.[2] In effect each pair gets its own question: “do these two match, yes or no?”. SigLIP-pretrained encoders appear inside the VLAs you will study, as OpenVLA fuses SigLIP and DINOv2 features[3] and PaliGemma uses a SigLIP image encoder.[4]

import numpy as np

scores = np.array([[ 0.9, 0.1, 0.0],
                   [ 0.1, 0.8, 0.2],
                   [ 0.0, 0.2, 0.9]])
t, b = 10.0, -5.0                                    # a scale and an offset (learnable in real models)
labels = 2 * np.eye(3) - 1                           # +1 for matching pairs, -1 for the others

pair_loss = np.log1p(np.exp(-labels * (t * scores + b)))     # each pair judged on its own
print("sigmoid-style loss, per pair:")
print(np.round(pair_loss, 2))
print("mean:", round(float(pair_loss.mean()), 3))

Output:

sigmoid-style loss, per pair:
[[0.02 0.02 0.01]
 [0.02 0.05 0.05]
 [0.01 0.05 0.02]]
mean: 0.026

Toy model. A simple pairwise logistic loss written for illustration. The numbers, the scale t and the offset b are made up; this shows the idea of judging each pair independently, not the SigLIP implementation.

The matching pairs (the diagonal) have small losses, the non-matching ones are also small, and no pair needs to know about the others. The paper reports that this allows further scaling of the batch size and also performs better at smaller batch sizes.[2]

Why this matters for robots

An instruction like “pick up the red block” must be linked to what the camera sees. A model whose image and text vectors already live in one space makes that link easier to learn. That is why vision encoders and language models in VLAs are usually pre-trained this way, and the next lesson shows how the two are joined.

Common misconceptions

Misconception 1 “CLIP generates captions.”

Correction It scores how well an image and a text match. It does not write text.

Misconception 2 “Zero-shot means the model has never seen anything like it.”

Correction It means no extra training on that task. The pre-training data can still contain similar things.

Misconception 3 “Softmax is the only way to train this.”

Correction SigLIP uses a pairwise sigmoid loss instead.

Engineering takeaways

  1. Two encoders, one shared space.
  2. Cosine similarity compares by angle.
  3. Contrastive training raises matching pairs and lowers the rest.
  4. Zero-shot = describe the options in words and pick the closest.
  5. SigLIP judges each pair independently.

CLIP makes a picture and its caption point the same way.

Knowledge check

Try each question first, then reveal the explanation.

Question 1

What is the cosine similarity of two vectors pointing in the same direction?

Explanation

1.

Question 2

In a batch of 8 image-caption pairs, how many scores are in the table and how many are matching pairs?

Explanation

64 scores, 8 of them matching.

Question 3

What does “zero-shot” mean in CLIP?

Explanation

Using the model on a new task by describing the choices in words, with no extra training on that task.

References

Numbered notes in the text, like [1], point here. Each entry says what it is cited for and how that was checked. Papers and books can be revised or misread, so follow the links before relying on a detail. Code marked “Toy model” is our own illustration of an idea, not the code of the cited system.

  1. Radford, A., et al. (2021). Learning transferable visual models from natural language supervision. ICML 2021 / arXiv. arXiv:2103.00020

    Cited for:

    • Learns image representations from raw text paired with images, by pre-training on the task of predicting which caption goes with which image; natural language can then be used to refer to the learned visual concepts, which allows zero-shot transfer. Checked against the paper’s abstract.
    • Pre-trained on a dataset of 400 million image-text pairs collected from the internet. Checked against the paper’s abstract.
    • Matches the accuracy of the original ResNet-50 on ImageNet zero-shot, without using any of its 1.28 million training examples. Checked against the paper’s abstract.
  2. Zhai, X., Mustafa, B., Kolesnikov, A. and Beyer, L. (2023). Sigmoid loss for language image pre-training. ICCV 2023 / arXiv. arXiv:2303.15343

    Cited for:

    • Proposes a pairwise sigmoid loss for image-text pre-training that operates on image-text pairs and does not need a global view of all pairwise similarities for normalisation, unlike the softmax-normalised contrastive loss used in CLIP-style training. Checked against the paper’s abstract.
    • Because the loss works on individual pairs, it allows further scaling of the batch size and also performs better at smaller batch sizes. Checked against the paper’s abstract.
  3. Kim, M. J., et al. (2024). OpenVLA: An open-source vision-language-action model. Conference on Robot Learning (CoRL) 2024. arXiv:2406.09246

    Cited for:

    • A 7-billion-parameter open-source VLA trained on 970,000 real-world robot demonstrations, built on a Llama 2 language model with a visual encoder that fuses pretrained DINOv2 and SigLIP features. Checked against the paper’s abstract.
  4. Beyer, L., et al. (2024). PaliGemma: A versatile 3B VLM for transfer. arXiv:2407.07726

    Cited for:

    • A vision-language model of about 3 billion parameters made from a SigLIP image encoder and the Gemma-2B language model. Checked against the paper’s abstract.

What’s next

Next: how vision encoders, language models and embeddings are fused into one sequence.

Next lesson

2.4 — Vision encoders, language models, embeddings and multimodal fusion

How separate modalities become one sequence a model can reason over.

Back to the VLA roadmap