CLIP Contrastive (InfoNCE) Loss
Problem Statement
Given a batch of image embeddings and their paired text embeddings, compute CLIP's symmetric InfoNCE loss.
Background
CLIP trains two encoders so that the i-th image matches the i-th caption and nothing else in the batch. Both embeddings are first L2-normalised, so their dot product is a cosine similarity in [-1, 1], then scaled by a learned logit_scale (the inverse temperature, exp(t), roughly 100 at convergence):
The diagonal holds the n positive pairs; every off-diagonal entry is a negative. The loss is cross-entropy against the labels 0, 1, ..., n-1 computed in both directions and averaged:
The two directions differ: the first normalises over each row of the logit matrix, the second over each column. Averaging them is what makes CLIP usable for both image-to-text and text-to-image retrieval.
Because logit_scale is large, exp overflows on raw logits. Use the log-sum-exp trick: subtract each row's max before exponentiating.
Your Task
Implement:
def clip_loss(image_emb, text_emb, logit_scale):
image_emb and text_emb are n x d nested lists, row i of each being a positive pair. Return the scalar loss rounded to 4 decimals.
Input Format
- image_emb, text_emb - lists of n lists of d floats, NOT pre-normalised
- logit_scale - a positive float
Output Format
A single float rounded to 4 decimals.
Sample
img = [[1.0, 0.0], [0.0, 1.0]]
txt = [[0.9, 0.1], [0.2, 1.0]]
print(clip_loss(img, txt, 10.0))
Output:
0.0003
Example
img = [[1.0, 0.0], [0.0, 1.0]] txt = [[0.9, 0.1], [0.2, 1.0]] print(clip_loss(img, txt, 10.0))
0.0003
- Inputs:
imgholds two image vectors andtxttwo text vectors in ; row of each pairs up.logit_scaleis . - Normalise: the image rows already have length 1. The text rows have lengths and , giving and .
- Logits: entry is , so the matrix is . Positives sit on the diagonal.
- Image to text (rows): row 1 costs , row 2 costs ; mean .
- Text to image (columns): column 1 compares with (), column 2 compares with (); mean .
- Output: , which rounds to
0.0003. Notice the directions differ because the off-diagonals are not symmetric, and the loss is tiny because each positive beats its rival by about 8 logits.
Constraints
1 <= n <= 64,1 <= d <= 64; no row is the zero vector- Embeddings arrive unnormalised - you must L2-normalise the rows yourself
- The loss is the AVERAGE of the image-to-text and text-to-image cross-entropies
- Use a numerically stable log-sum-exp;
logit_scalecan be 100 - Round the returned loss to 4 decimals