This module implements a two-stage retrieval system that combines the strengths of BM25 and dense vector retrieval.
The hybrid search approach:
- Stage 1 (Retrieval): Use BM25 to retrieve top-K candidates (e.g., top-1000)
- Stage 2 (Reranking): Rerank the candidates using semantic similarity (dot product of embeddings)
This combines:
- BM25's strength: Fast retrieval and exact keyword matching
- Vector search's strength: Semantic similarity and understanding
# For dev queries
python -m python.reranking.hybrid_search \
--bm25-results results/bm25_dev.txt \
--queries data/queries_dev_eval_embeddings.h5 \
--output results/hybrid_dev.txt
# For TREC 2019 eval
python -m python.reranking.hybrid_search \
--bm25-results results/bm25_eval_one.txt \
--queries data/queries_dev_eval_embeddings.h5 \
--output results/hybrid_eval_one.txt
# For TREC 2020 eval
python -m python.reranking.hybrid_search \
--bm25-results results/bm25_eval_two.txt \
--queries data/queries_dev_eval_embeddings.h5 \
--output results/hybrid_eval_two.txt--bm25-results: Path to BM25 results file--queries: Path to query embeddings h5 file--output: Output file path for reranked results--doc-embeddings: Path to document embeddings (default:./data/collection_embeddings_subset.h5)--top-k: Number of results to return after reranking (default: 100)--run-name: Run identifier in output (default: HYBRID)
The script reads BM25 results (typically top-100 or top-1000 per query):
query_id\tpassage_id\trank\tscore\tBM25
- Query embeddings from h5 file
- Document embeddings from h5 file
- Both are normalized to unit length for dot product similarity
For each query:
- Get the query embedding
- For each BM25 candidate:
- Get the document embedding
- Compute similarity:
score = query_emb · doc_emb
- Sort by semantic similarity (descending)
- Return top-K results
Output format matches BM25 and vector search:
query_id\tpassage_id\trank\tscore\tHYBRID
Typical performance on 1M document collection:
- Reranking speed: ~300-700 queries/sec
- Memory: ~4GB (for 1M documents with 384-dim embeddings)
- Time:
- Dev (1000 queries): ~1.5s
- Eval (43-54 queries): ~0.15s
Based on MS-MARCO passage ranking:
| System | MRR@10 | Recall@100 | MAP |
|---|---|---|---|
| BM25 | 0.3946 | 0.7860 | 0.3963 |
| Vector | 0.5709 | 0.9091 | 0.5696 |
| Hybrid | 0.5766 | 0.8763 | 0.5736 |
| System | MRR@10 | Recall@100 | NDCG@10 | NDCG@100 |
|---|---|---|---|---|
| BM25 | 0.7863 | 0.5172 | 0.4578 | 0.5358 |
| Vector | 0.9535 | 0.5582 | 0.6973 | 0.6544 |
| Hybrid | 0.9651 | 0.5723 | 0.7019 | 0.6619 |
| System | MRR@10 | Recall@100 | NDCG@10 | NDCG@100 |
|---|---|---|---|---|
| BM25 | 0.8308 | 0.5248 | 0.4969 | 0.5209 |
| Vector | 0.9230 | 0.6167 | 0.6632 | 0.6397 |
| Hybrid | 0.9259 | 0.6070 | 0.6592 | 0.6332 |
-
MRR@10: Hybrid performs best, showing excellent top-result quality
- Hybrid improves MRR over BM25 by ~46% (dev), ~23% (TREC 2019), ~11% (TREC 2020)
- Slight improvements over pure vector search
-
Recall@100: Vector search has the best recall
- Vector: Best at finding all relevant documents
- Hybrid: Good balance (better than BM25, slightly lower than Vector)
- BM25: Lower recall due to vocabulary mismatch issues
-
NDCG: Hybrid shows strong ranking quality
- Consistently outperforms BM25
- Competitive with or slightly better than pure vector search
-
Trade-offs:
- BM25: Fast, interpretable, but lower accuracy
- Vector: Best recall, but expensive to search 1M docs
- Hybrid: Best overall accuracy, efficient (only reranks BM25 candidates)
Uses normalized dot product (equivalent to cosine similarity):
similarity = query_emb · doc_emb # Both normalized to unit length- Loads document embeddings once at startup
- Builds passage ID → index mapping for fast lookup
- Processes queries in batches with progress tracking
The hybrid approach is more efficient than pure vector search because:
- BM25 quickly narrows down candidates (e.g., 1000 out of 1M)
- Only computes semantic similarity for candidates
- Avoids expensive nearest neighbor search over all 1M documents
h5py- Reading embeddingsnumpy- Numerical operationstyro- CLI argument parsingtqdm- Progress bars
python/semantic_search/search_index.py- Pure vector searchpython/evaluate_batch.py- Batch evaluation of all systemspython/filter_results.py- Filter results by query IDs