The problem
In dense retrieval, a neural encoder turns every passage, and the query, into a vector of numbers (an embedding). Passages whose vectors point in the same direction as the query's are about the same thing. You will rank passages by the cosine similarity of their embeddings with the query's, and measure how good the ranking is.
Cosine similarity
The cosine similarity of two vectors is their dot product after scaling each to unit length. It is 1 if they point the same way, 0 if they are unrelated (perpendicular), and if opposite. Only the direction counts, never the length.
cosine_scores(queries, passages) returns the table with one row per query and one column per passage.
Example. Two queries and , and four passages , , and . Cosine similarities:
- query 0 — passage 0: ; passage 1: ; passage 2: ; passage 3:
- query 1 — passage 0: ; passage 1: ; passage 2: ; passage 3:
Passage 0, , is twice as long as the query but points the same way: similarity exactly 1.
The top
top_k(scores, k) gives, for every query, the indices of the k highest scores, best first. On ties, the lower index comes first.
Example. For the table above and : query 0 gets
[0, 1], query 1 gets[2, 1].
Recall at
The recall@k of a query is the fraction of its relevant passages that appear in its top . recall_at_k(retrieved, relevant) takes the table from top_k and a list of sets of relevant passage indices, and returns the mean over queries. A query with no relevant passage is skipped.
Example. Relevant passages: for query 0 and for query 1. Query 0 retrieved
[0, 1]: both found, recall . Query 1 retrieved[2, 1]: passage 2 found, 3 missed, recall . Mean: .
What to implement
cosine_scores(queries, passages):querieshas shape andpassages. Return the table of cosine similarities.top_k(scores, k): shape .recall_at_k(retrieved, relevant): the mean recall.
Requirements
- No loops over passages. A loop over queries is fine in
recall_at_k.
Hint
np.argsort(-scores, axis=1, kind="stable") sorts each row from the highest score down, with ties in index order. To scale rows to unit length, divide by np.linalg.norm(x, axis=-1, keepdims=True).