Source: Robertson et al., "Okapi at TREC-3" (1995); Robertson and Zaragoza, "The Probabilistic Relevance Framework: BM25 and Beyond" (2009); production implementations in Lucene, Elasticsearch, OpenSearch, Solr
Classification — Scoring function family that remains foundational to production ranking, used both as first-stage retrieval scoring and as features in learned ranking models.
Apply BM25 and its production variants to score query-document pairs in ways that work as standalone first-stage retrieval scoring and as input features to learning-to-rank models.
Volume 1 introduced BM25 as a retrieval pattern; this entry covers the variants production teams use in ranking contexts and the parameter tuning that makes BM25 effective as both standalone scoring and as LTR features. The variants address specific weaknesses of vanilla BM25 (multi-field documents, very long documents, length-normalization artifacts) and the parameter tuning addresses the workload-specific calibration that defaults don't handle.
Vanilla BM25 recap. The formula computes per-term scores that combine term frequency (saturating, controlled by k1), inverse document frequency (rare terms weighted higher), and document length normalization (controlled by b, ratio to average document length). Per-term scores sum to produce the document's BM25 score for the query. The k1 parameter (typical 1.2–2.0) controls how fast term frequency saturates; the b parameter (typical 0.5–1.0, default 0.75) controls how aggressively length is normalized.
BM25F (multi-field). Vanilla BM25 treats a document as a single bag of words. Real documents have fields: title, body, brand, category, tags. BM25F extends BM25 to weight per-field contributions: a term match in the title contributes more than a match in the body, with per-field b (length normalization) and per-field weight parameters. The configuration is per-field weight (how much each field matters) and per-field b (how length normalization applies within each field). In LTR contexts, BM25F per-field scores often appear as separate features rather than combined into one BM25F score; the model learns the right combination.
BM25+ (long-document correction). Vanilla BM25 has a known weakness with very long documents: even with length normalization, very long documents can fail to score competitively against shorter relevant documents due to score saturation artifacts. BM25+ adds a small constant (delta, typically 1.0) to the term score that addresses this. The variant is appropriate when document length distribution is heavily skewed; for most production workloads the difference is marginal.
Parameter tuning. The default BM25 parameters (k1=1.2, b=0.75) come from the Okapi system's TREC experiments in the 1990s. They're reasonable defaults but not optimal for every workload. Production teams tune the parameters per workload: grid search over k1 and b ranges, with NDCG@10 (Volume 5) as the optimization target on the team's judgment list. The tuning often produces 2–5% NDCG improvement over defaults; the marginal benefit per hour of tuning effort is high for the first round and diminishes after.
BM25 as LTR features. In production LTR systems, BM25-derived scores are typically among the most important features. Common feature decompositions: overall BM25 score; per-field BM25F scores (title BM25, body BM25, brand BM25, etc.); BM25 against different analyzers (BM25 against stemmed text vs. raw text); BM25 with different parameter settings (k1=1.2 BM25 and k1=2.0 BM25 as separate features). The LTR model learns to weight these features per query class; the decomposition lets the model use lexical signals more flexibly than a single combined BM25 score would allow.
Caching considerations. Computing BM25 over millions of documents at query time is expensive; production retrieval typically precomputes term-level statistics (document frequency, total tokens) at index time, then BM25 is computed only for the candidate documents that match the query terms. Lucene-based engines (Elasticsearch, OpenSearch, Solr) implement this efficiently through inverted-index skip lists; custom implementations need to handle this correctly.
Every production search system uses BM25-derived scoring somewhere. As first-stage retrieval scoring when lexical match is appropriate. As features in LTR models. As reference scoring for hybrid retrieval (Volume 1 Section C). The pattern is foundational; the question isn't whether to use it but how to integrate it.
Alternatives — pure vector scoring (next entry) for cases where lexical matching is less appropriate (very short queries, heavily synonymized domains). Hybrid retrieval (Volume 1 Section C) for the dominant production pattern combining both. BM25-only retrieval is becoming less common in production as hybrid patterns mature; BM25 as one of multiple paths remains the working pattern.
- Robertson and Zaragoza, "The Probabilistic Relevance Framework: BM25 and Beyond" (2009)
- Manning, Raghavan, Schütze, Introduction to Information Retrieval, ch. 11
- Elasticsearch / OpenSearch / Solr documentation
Code
// Elasticsearch / OpenSearch: per-field BM25 with custom parameters
// This produces BM25F-style scoring with per-field weights
PUT /products
{
"settings": {
"index": {
"similarity": {
// Custom BM25 parameters - tuned per workload via judgment-list
evaluation
"bm25_tuned": {
"type": "BM25",
"k1": 1.5,
"b": 0.7
}
}
}
},
"mappings": {
"properties": {
"title": { "type": "text", "similarity": "bm25_tuned" },
"description": { "type": "text", "similarity": "bm25_tuned"
},
"brand": { "type": "text", "similarity": "bm25_tuned" },
"category": { "type": "keyword" }
}
}
}
// Query with per-field weighting (BM25F-style)
GET /products/_search
{
"query": {
"multi_match": {
"query": "trail running shoes",
"fields": [
"title^4", // Title 4x weight
"brand^2", // Brand 2x weight
"description^1" // Body baseline weight
],
"type": "best_fields"
}
}
}
// For LTR features: extract per-field BM25 scores separately
// using Elasticsearch\'s "explain" or separate queries per field
// Each becomes a feature in the LTR model
GET /products/_search?explain=true
{
"query": {
"bool": {
"should": [
{ "match": { "title": "trail running shoes" }},
{ "match": { "brand": "trail running shoes" }},
{ "match": { "description": "trail running shoes" }}
]
}
},
"size": 100
}
// Each clause\'s score becomes a feature; the LTR model combines
them