File size: 2,880 Bytes
9efa0c5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import json
import torch
import numpy as np

from pathlib import Path
from tqdm import tqdm
from search import model, tokenizer

# 1. Load filenames.json and image_embeddings.npy
# build a lookup: filename → row number (0..999)

EMB_DIR = Path("embeddings")

image_embeddings = np.load(EMB_DIR / "image_embeddings.npy")              # (1000, 512)
filenames = json.loads((EMB_DIR / "filenames.json").read_text())         # 1000 names

row_of = {name: i for i, name in enumerate(filenames)}

# 2. Read Flickr8k.token.txt
#  for each line: split on "\t", strip the "#n" from the filename
#  keep it only if the filename is in the lookup
#  → captions:   list of 5,000 strings
#  → true_rows:  list of 5,000 row numbers (which image each caption belongs to)

CAPTIONS_FILE = Path("data/Flickr8k.token.txt")

captions = []     # the caption texts
true_rows = []    # for each caption, the row of the image it describes

for line in CAPTIONS_FILE.read_text().splitlines():
    if not line.strip():
        continue
    image_ref, caption = line.split("\t", 1)      # "abc.jpg#0", "A dog runs ..."
    filename = image_ref.split("#")[0]            # "abc.jpg"
    if filename in row_of:                        # only the 1,000 indexed images
        captions.append(caption.strip())
        true_rows.append(row_of[filename])

true_rows = np.array(true_rows)

# 3. Embed the 5,000 captions with CLIP's text encoder
#  in batches (e.g. 256), normalize, like build_index.py did for images
#  → text_embeddings: (5000, 512)

BATCH_SIZE = 256

text_vectors = []
for start in tqdm(range(0, len(captions), BATCH_SIZE)):
    batch = captions[start:start + BATCH_SIZE]
    tokens = tokenizer(batch)
    with torch.no_grad():
        vectors = model.encode_text(tokens)
        vectors = vectors / vectors.norm(dim=-1, keepdim=True)
    text_vectors.append(vectors.cpu().numpy())

text_embeddings = np.concatenate(text_vectors)

# 4. Similarity matrix
#  sims = text_embeddings @ image_embeddings.T      → (5000, 1000)

sims = text_embeddings @ image_embeddings.T          # (5000, 1000)

# 5. Rank of the true image for each caption
#  true_scores = sims[i, true_rows[i]] for each i
#  rank = how many images scored HIGHER than the true one
#        (0 = it came first, 4 = it came fifth, …)

true_scores = sims[np.arange(len(captions)), true_rows]          # (5000,)
ranks = (sims > true_scores[:, None]).sum(axis=1)                # (5000,)  0 = first place

# 6. Recall@K = fraction of captions with rank < K, for K = 1, 5, 10
#  print a small table

print()
print("Text → image retrieval on Flickr8k test (1,000 images, 5,000 captions)")
print("Model: CLIP ViT-B/32 (laion2b_s34b_b79k), zero-shot")
print("-" * 40)
for k in (1, 5, 10):
    recall = (ranks < k).mean()
    print(f"Recall@{k:<2}  {recall:6.1%}")
print("-" * 40)
print(f"Median rank: {int(np.median(ranks)) + 1}")