Given a query-by-gallery similarity matrix and the index of the correct gallery item for each query, compute Recall@K for several values of K.
Recall@K is the standard retrieval metric for CLIP-style models on COCO and Flickr30k: the fraction of queries whose ground-truth match appears anywhere in the top K results.
For query i, sort the gallery by descending similarity and find the 0-indexed position of ground_truth[i]:
ranki=∣{j:sim[i][j]>sim[i][gi]}∣(with ties broken by index)
R@K=n1∑i=1n1[ranki<K]
Two details decide whether your number matches a published one. First, ranks are 0-indexed against K, so R@1 means rank == 0. Second, ties must be broken deterministically - here, toward the lower gallery index - otherwise a model that returns identical scores appears to score differently on every run.
Recall@K is monotone non-decreasing in K, and R@m over an m-item gallery is always 1.0.
Implement:
def recall_at_k(sim_matrix, ground_truth, ks):
Return a list of floats, one per entry of ks in the given order, each rounded to 4 decimals.
A list of floats rounded to 4 decimals, same length and order as ks.
sims = [[0.9, 0.2, 0.1], [0.3, 0.4, 0.8], [0.5, 0.7, 0.6]]
print(recall_at_k(sims, [0, 2, 0], [1, 2]))
Output:
[0.6667, 0.6667]
sims = [[0.9, 0.2, 0.1], [0.3, 0.4, 0.8], [0.5, 0.7, 0.6]] print(recall_at_k(sims, [0, 2, 0], [1, 2]))
[0.6667, 0.6667]
Query 0 ranks its answer at position 0, query 1 at position 0, query 2 has answer index 0 with score 0.5 - behind 0.7 and 0.6 - so rank 2. Two of three queries are within the top 1, and the third is still outside the top 2, so both recalls are 2/3 = 0.6667.
1 <= n <= 200, 1 <= m <= 200rank < Kks in the output