§7.4Improving Our Pipeline: Evaluating the Retriever

Part II NLPT pp. 55–62 · ~4 min read

  • recall
  • mean average precision
  • dense passage retrieval
  • sparse retriever
  • dense retriever

Our prototype works, but the answers weren’t perfectly ranked. Since the retriever sets an upper bound on the whole system — the reader can only extract from what it’s given — the first thing to measure and improve is retrieval.

Recall

The key retriever metric is recall : the fraction of questions for which a relevant document (one containing the answer) appears in the top-k results. Slide k below — recall climbs as we retrieve more documents:

0.880.941.001351020k (documents retrieved)
BM25recall@20 = 0.990
Top-k recall for the BM25 retriever. Drag k: an inflection around k=5, near-perfect from k=10.

At k = 3, BM25 already reaches 0.95 recall; there’s an inflection around k = 5, and recall is near-perfect from k = 10. But higher k means more documents for the reader to chew on, so we want good recall at small k.

Note — recall isn’t the whole story

A complementary metric is mean average precision (mAP), which additionally rewards putting the correct documents higher in the ranking, not just somewhere in the top-k.

To measure this we build a small evaluation Pipeline — each node has a run() method and named inputs — that pairs the retriever with an EvalDocuments node, then sweep k over the test set:

from haystack.pipeline import Pipeline
from haystack.eval import EvalDocuments

class EvalRetrieverPipeline:
    def __init__(self, retriever):
        self.retriever = retriever
        self.eval_retriever = EvalDocuments()
        pipe = Pipeline()
        pipe.add_node(component=self.retriever, name="ESRetriever", inputs=["Query"])
        pipe.add_node(component=self.eval_retriever, name="EvalRetriever",
                      inputs=["ESRetriever"])
        self.pipeline = pipe

# sweep k and record recall over the whole test set
def evaluate_retriever(retriever, topk_values=[1, 3, 5, 10, 20]):
    ...
    return pd.DataFrame.from_dict(topk_results, orient="index")
# Recall@3: 0.95

Dense Passage Retrieval

BM25 has a well-known weakness: it matches on exact terms, so a question phrased differently from the review can miss. The state-of-the-art alternative is Dense Passage Retrieval (DPR), a dense retriever built from two BERT encoders — a bi-encoder: one embeds the question, one embeds the passage, each into a d-dimensional [CLS] vector, and their dot product is the relevance score.

Document ScoreDot Product Similarityquestion vectorQuestion EncoderWhy is the cameraof poor quality?passage vectorPassage EncoderItem like the picture……there is no flash…
Figure 7-10 (recreated). DPR’s bi-encoder: two BERT encoders embed the question and passage; their dot product is the document score.

The encoders are trained on (question, positive passage, negative passage) triples to make relevant pairs score higher. In Haystack, DPR plugs in like any retriever, then we re-embed every indexed document:

from haystack.retriever.dense import DensePassageRetriever
dpr_retriever = DensePassageRetriever(document_store=document_store,
    query_embedding_model="facebook/dpr-question_encoder-single-nq-base",
    passage_embedding_model="facebook/dpr-ctx_encoder-single-nq-base",
    embed_title=False)
document_store.update_embeddings(retriever=dpr_retriever)

Comparing recall head-to-head, though, DPR gives no boost here — it edges ahead at k = 1 but saturates around k = 3:

0.880.941.001351020k (documents retrieved)
BM25recall@20 = 0.990DPRrecall@20 = 0.955
BM25 vs DPR. DPR edges ahead at k=1 but saturates around k=3 — no boost over BM25 on this dataset.

Note — making DPR faster and better

Embedding similarity search can be sped up with Facebook’s FAISS as the document store, and DPR’s recall can be improved by fine-tuning its encoders on the target domain.

The retriever is in good shape. Now for the other half of the system — the reader, and how well it actually answers.

Check yourself: evaluating the retriever

1.What does top-k recall measure for a retriever?

2.Why care about recall at *small* k rather than just large k?

3.How does mean average precision (mAP) complement recall?

4.How does DPR's bi-encoder compute the relevance of a question and a passage?

5.What weakness of BM25 motivates trying DPR?

6.On this dataset, how did DPR compare to BM25 in recall?

6 questions