bm25s (BM25-Sparse) is an ultra-fast implementation of the BM25 lexical search algorithm in pure Python, primarily leveraging NumPy for sparse matrix operations. It focuses on high performance and low dependency, providing significant speedups over other Python implementations. The library is actively developed, with version 0.3.3 being the latest release, and receives regular updates including new features and performance enhancements.
pip install bm25sVerified import paths — ran on the pinned version, not inferred.
This quickstart demonstrates how to initialize BM25S, tokenize a corpus (with optional stemming and stopword removal), index the documents, and perform a query to retrieve the top-k most relevant documents. It showcases the core `BM25` class and `tokenize` utility.
Explicitly install `scipy` and pass `csc_backend="scipy"` to `BM25()`, or install with `pip install bm25s[indexing]`.
Update import statements from `from bm25s import selection` to `from bm25s import selection_np`.
Consider `pip install bm25` for a higher-level API, or explicitly manage `bm25s` dependencies (like `PyStemmer`) for advanced control.
Install recommended optional dependencies like `pip install PyStemmer`, `pip install numba`, or `pip install jax[cpu]` based on your performance and feature needs.
Ensure the `k` argument is passed as a keyword argument when calling `retrieve`. ```python import bm25s corpus = ["doc one", "document two"] tokenized_corpus = bm25s.tokenize(corpus) retriever = bm25s.BM25() retriever.index(tokenized_corpus) query_tokens = bm25s.tokenize(["query"]) # Query must also be tokenized # Incorrect (would cause the error): # results, scores = retriever.retrieve(query_tokens, 1) # Correct: results, scores = retriever.retrieve(query_tokens, k=1, corpus=corpus) print(results) ```
Install the library using pip. For full functionality, including stemming, use the `[full]` extra. ```bash pip install bm25s # For full features, including stemming and Numba backend (if available): pip install bm25s[full] ```
Instead of attempting to call `save_vocab` on a `Tokenizer` object, save the entire `BM25` retriever object, which encapsulates the vocabulary and other index data. If you specifically need to access the vocabulary for inspection or external use, you can obtain it from a `Tokenizer` instance.
```python
import bm25s
from bm25s.tokenizer import Tokenizer
corpus = ["apple banana", "orange pear"]
# If you create a Tokenizer instance directly and need its vocab:
tokenizer_instance = Tokenizer()
tokenized_corpus = tokenizer_instance(corpus)
# To get the vocabulary:
vocabulary_map = tokenizer_instance.get_vocab() # Returns a dict-like object
# You can then process/save this vocabulary manually if needed:
# import json
# with open("my_vocab.json", "w") as f:
# json.dump(list(vocabulary_map.keys()), f)
# The recommended way: The BM25 object handles saving the full index, including vocabulary.
retriever = bm25s.BM25()
retriever.index(tokenized_corpus)
# To save the entire index (which includes the vocabulary):
# retriever.save("my_bm25_index")
# To load it later:
# loaded_retriever = bm25s.BM25.load("my_bm25_index")
```Always tokenize your corpus first. Initialize `bm25s.BM25` without the raw corpus, then explicitly call `retriever.index(tokenized_corpus)`. When calling `retriever.retrieve()`, pass the original raw `corpus` if you want the actual document strings to be returned; otherwise, it will return document indices.
```python
import bm25s
corpus = [
"Machine learning is a subset of AI",
"Deep learning uses neural networks",
"Natural language processing handles text"
]
# 1. Tokenize the corpus
tokenized_corpus = bm25s.tokenize(corpus)
# 2. Initialize BM25 without passing the raw corpus to the constructor
retriever = bm25s.BM25()
# 3. Index the tokenized corpus
retriever.index(tokenized_corpus)
query = "What is AI"
tokenized_query = bm25s.tokenize([query])
# To get original document strings back:
results_docs, scores = retriever.retrieve(tokenized_query, k=1, corpus=corpus)
print("Retrieved documents:", results_docs)
# To get document indices back:
results_indices, scores = retriever.retrieve(tokenized_query, k=1)
print("Retrieved indices:", results_indices)
```