Medical AI · Retrieval-augmented generation (RAG) · Lesson 4 of 7
Evaluating retrieval
Reviewed by Dr. Ram Kumar G, MBBS, MD (Internal Medicine), MPH (USA), on · Last updated
- Intermediate
- Technical focus
- About 30 minutes
- Includes exercise
Before this lesson: Embeddings and vector stores
In short
If retrieval fails, the model never sees the right evidence, so evaluate retrieval separately from answers. Build a set of realistic questions labelled with the passages that answer them, then measure how often the right passage appears in the top results (recall@k) and how high it ranks (mean reciprocal rank). Compare methods on the same set, and read every miss: small test sets are noisy, and the misses tell you what to fix.
You will learn to
- Build a labelled retrieval test set with clinicians
- Compute recall@k and mean reciprocal rank (MRR) in Python
- Compare keyword, embedding and hybrid retrieval fairly
- Use failure analysis to decide what to improve
Why evaluate retrieval on its own
An end-to-end check ('was the answer right?') mixes two failures: the right passage was not retrieved, or it was retrieved but the model misused it. Measuring retrieval separately shows which part of the system to fix. It is also cheap: once questions are labelled, every change can be re-scored in seconds.
| Metric | Meaning | Use |
|---|---|---|
| Recall@k | Share of questions whose correct passage appears in the top k results | Main metric when the model sees the top k passages |
| Precision@k | Share of the top k results that are relevant | When irrelevant passages crowd the context |
| Mean reciprocal rank (MRR) | Average of 1 / rank of the first correct passage | Rewards putting the right passage first |
| nDCG@k | Rank-weighted score allowing graded relevance (fully, partly, not relevant) | When several passages are partly relevant |
Building a labelled question set
- Collect questions in the words real users would use: clinicians' shorthand, patients' plain language, typos.
- For each question, have a clinician mark which passage or passages answer it. Include questions that the documents cannot answer, labelled as such.
- Cover every section and the most safety-critical rules, not only easy questions.
- Keep the set separate from any examples used to design the system, to avoid tuning to the test.
Scoring three retrieval methods
import re
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from sentence_transformers import SentenceTransformer
sections = [p.strip() for p in re.split(r"\n(?=## )", PROTOCOL)[1:]]
ids = ["glucose", "kidney", "foot", "hypo", "eye"]
tests = [ # (question, id of the section that answers it)
("How often should HbA1c be checked after changing treatment?", "glucose"),
("What HbA1c target suits most adults?", "glucose"),
("My sugar dropped really low, what now?", "hypo"),
("What should I do about a hypo?", "hypo"),
("How many grams of carbohydrate treat a low?", "hypo"),
("Is the diabetes pill safe with weak kidneys?", "kidney"),
("When do we need to stop the biguanide?", "kidney"),
("How often is urine albumin checked?", "kidney"),
("Numb toes test", "foot"),
("When is a foot ulcer an emergency?", "foot"),
("How often are retinal photos needed?", "eye"),
("When should eye screening start?", "eye"),
]
questions = [q for q, _ in tests]
relevant = [ids.index(r) for _, r in tests]
vec = TfidfVectorizer(stop_words="english").fit(sections + questions)
keyword = cosine_similarity(vec.transform(questions), vec.transform(sections))
model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
embedding = (model.encode(questions, normalize_embeddings=True)
@ model.encode(sections, normalize_embeddings=True).T)Recall@k and MRR
def ranks(scores):
"""1-based rank of the correct section for each question."""
order = np.argsort(-scores, axis=1)
return np.array([list(order[i]).index(relevant[i]) + 1 for i in range(len(relevant))])
def report(name, scores):
r = ranks(scores)
print(f"{name:<10} recall@1 {np.mean(r <= 1):.2f} recall@3 {np.mean(r <= 3):.2f} "
f"MRR {np.mean(1 / r):.2f}")
def reciprocal_rank_fusion(*score_sets, k=60):
"""Hybrid search: add 1 / (k + rank) from each method."""
fused = np.zeros_like(score_sets[0])
for s in score_sets:
order = np.argsort(-s, axis=1)
for i in range(s.shape[0]):
for rank, j in enumerate(order[i], start=1):
fused[i, j] += 1 / (k + rank)
return fused
report("Keyword", keyword)
report("Embedding", embedding)
report("Hybrid", reciprocal_rank_fusion(keyword, embedding))
# Keyword recall@1 0.83 recall@3 0.92 MRR 0.88
# Embedding recall@1 0.92 recall@3 1.00 MRR 0.94
# Hybrid recall@1 0.92 recall@3 0.92 MRR 0.94- Embeddings put the right section first for 11 of 12 questions and within the top 3 for all of them.
- The hybrid did not beat embeddings here; it was slightly worse at recall@3. Hybrid search often helps on larger, more varied collections with exact terms such as drug names, but whether it helps on your data is an empirical question.
- With 12 questions, one question is 8 percentage points. These differences are far too small to be confident about; real test sets need many more questions.
Reading the misses
for name, s in [("Keyword", keyword), ("Embedding", embedding),
("Hybrid", reciprocal_rank_fusion(keyword, embedding))]:
r = ranks(s)
misses = [(questions[i], int(r[i])) for i in np.where(r > 1)[0]]
print(name, "misses:", misses)
# Keyword misses: [('What should I do about a hypo?', 4), ('Is the diabetes pill safe with weak kidneys?', 3)]
# Embedding misses: [('What should I do about a hypo?', 3)]
# Hybrid misses: [('What should I do about a hypo?', 4)]- Every method missed the 'hypo' question, pointing to the abbreviation problem from the previous lesson rather than to the choice of method. Fixing the cause (expansion) beats switching methods.
- Keyword search also missed 'weak kidneys', a paraphrase, which embeddings handle.
- Keep a log of misses with their cause; it becomes the to-do list for chunking, expansion and model choice.
Retrieval quality is a safety property
- In a clinical assistant, the questions most likely to be missed are often those phrased in shorthand or by worried patients. Include them deliberately, and track recall on safety-critical rules separately.
Exercise
Build and use a retrieval test set to choose and improve a retrieval method.
Data: Synthetic protocol and labelled questions in the notebook (Created for this course; not clinical guidance)
- Reproduce the three methods' recall@1, recall@3 and MRR.
- Add twelve more questions, including three the protocol cannot answer, and decide how to score 'no answer' questions.
- Apply the abbreviation expansion from the embeddings lesson and re-score all methods.
- Bootstrap the question set (resample with replacement 1,000 times) to get confidence intervals for recall@1 and see whether the methods really differ.
- Implement nDCG@3 with graded labels (2 = answers fully, 1 = partly relevant) and compare rankings.
The runnable notebook and worked solution will be published soon. You can work through the steps with the code on this page in the meantime.
Key takeaways
- Evaluate retrieval separately: the model cannot use evidence it never sees.
- Label realistic questions, including shorthand and unanswerable ones, with clinicians.
- Use recall@k and MRR to compare methods on the same questions.
- Small test sets are noisy; read every miss and fix causes, not just methods.
Terms used: BM25, Embedding, Recall@k, Reciprocal rank fusion (hybrid search)
Related: Embeddings and vector stores, Diagnostic accuracy metrics, Medical LLMs and their benchmarks
FAQ
What is recall@k?
The proportion of test questions for which a correct passage appears among the top k retrieved results.
What is mean reciprocal rank?
The average, across questions, of 1 divided by the rank of the first correct result. It is 1 when the right passage is always ranked first.
How many test questions do I need?
Enough to cover every important topic and phrasing style, and enough that differences between methods are larger than chance. Tens is a start; hundreds are better for decisions.
What is hybrid search?
Combining keyword and embedding retrieval, for example with reciprocal rank fusion, so that exact-term matches and meaning-based matches both contribute.
Who should label the test questions?
People who know the domain and the documents, typically clinicians, ideally with a second reviewer for safety-critical questions.
Sources
- Manning CD, Raghavan P, Schütze H. Introduction to Information Retrieval, chapter 8: Evaluation in information retrieval. Cambridge University Press, 2008
- Järvelin K, Kekäläinen J. Cumulated gain-based evaluation of IR techniques. ACM Transactions on Information Systems 2002;20(4):422–446. doi:10.1145/582415.582418
- Cormack GV, Clarke CLA, Büttcher S. Reciprocal rank fusion outperforms Condorcet and individual rank learning methods. SIGIR 2009:758–759. doi:10.1145/1571941.1572114
- Thakur N, et al. BEIR: a heterogeneous benchmark for zero-shot evaluation of information retrieval models. NeurIPS 2021 Datasets and Benchmarks. arXiv:2104.08663
- Xiong G, et al. Benchmarking retrieval-augmented generation for medicine. Findings of ACL 2024. arXiv:2402.13178
Educational content only. This material teaches methods; it is not clinical advice. Code, models and examples here are not validated medical devices and must not be used to make decisions about real patients without proper clinical validation, governance and any required regulatory approval. Examples use synthetic or openly licensed de-identified data only. Never paste identifiable patient information into public AI tools. Mentions of models, tools or vendors are for identification, not endorsement.