Build Your Own NLP-Powered Search Engine with BM25
Introduction
Search engines have become an indispensable part of our daily lives. Whenever we need to find information on a topic, look up a fact, or get an answer to a question, we instinctively turn to Google or another search engine. But how do these search engines actually work under the hood to find the most relevant web pages and content for our queries?
At a high level, most search engines rely on two key processes:
-
Crawling – Automated programs called "web crawlers" or "spiders" systematically browse the internet, discovering and indexing new and updated web pages. They extract key information like the page URL, title, headings, and content.
-
Indexing – The data gathered by the web crawlers is processed and stored in a optimized format that allows the search engine to quickly look up relevant pages for a given query. Indexing analyzes factors like the key terms on the page, headings, page structure, links, and media.
When you enter a search query, the search engine doesn‘t scan the entire web in real-time – that would take far too long. Instead, it searches through the pre-built index to find the pages that best match your query, ranks them by relevance, and returns the results, usually in a fraction of a second. For example, a query like "What is BM25?" on Google returns about 600,000 results in 0.46 seconds by searching the index.
Modern search engines have grown incredibly sophisticated, using advanced algorithms and machine learning to better understand search intent and rank results. However, the core concepts of crawling and indexing still power the search experience behind the scenes.
In this post, we‘ll explore how to build your own basic search engine using natural language processing (NLP) techniques, powered by the BM25 algorithm. We‘ll use a dataset of tweets as an example, but these concepts can be applied to any text dataset, from web pages to PDF documents.
What is Natural Language Processing (NLP)?
Natural language processing is a field of artificial intelligence focused on enabling computers to understand, interpret, and generate human language. NLP combines concepts from computer science, linguistics, and machine learning to build intelligent systems that can comprehend natural language as humans do.
Some common applications of NLP include:
- Sentiment analysis (determining the emotion or opinion expressed in a piece of text)
- Named entity recognition (identifying names of people, places, organizations, etc. mentioned in text)
- Machine translation (translating between languages)
- Text summarization
- Chatbots and conversational AI
- Text classification and clustering
- Information retrieval and search
NLP-powered search leverages techniques like tokenization, named entity recognition, stemming, part-of-speech tagging, and more to better understand the meaning and intent behind search queries. By analyzing both the search query and the indexed documents with NLP, search engines can significantly improve the relevance of the results returned.
For example, let‘s say you searched for "NLP articles". A basic keyword search might look for documents containing the exact query terms "NLP" and "articles". But an NLP-based search would recognize that "articles" and related terms like "blog posts", "papers", "publications", etc. all map to a similar meaning in this context. It would also recognize "NLP" as referring to the specific field of natural language processing, not just a sequence of characters. By taking this additional context and meaning into account, an NLP-enhanced search engine can return more relevant results.
Understanding the BM25 Algorithm
One of the most popular and effective algorithms used in NLP-based search is BM25, which stands for "Best Match 25". BM25 provides a way to rank a set of documents based on the query terms appearing in each document, taking into account factors like term frequency, inverse document frequency, and document length.
At its core, BM25 builds off of the key concept of TF-IDF:
-
Term Frequency (TF): The number of times a term (word) appears in a given document. The more frequently a term appears, the more likely the document is to be relevant for that term.
-
Inverse Document Frequency (IDF): A measure of how common or rare a term is across all documents. Terms that appear in many documents are less valuable for search than rare terms. IDF gives higher weight to terms that are rare across the corpus.
The intuition is that the best documents for a given search query will be those that have a high frequency of the query terms (high TF), especially if those terms are relatively rare overall (high IDF). However, raw TF-IDF has some limitations. Very long documents tend to have higher TF values simply by virtue of their length. And common terms can have high TF values even if they aren‘t especially relevant.
BM25 improves on TF-IDF in a few key ways:
-
Term frequency saturation: After a certain point, additional occurrences of a term in a document provide diminishing returns. BM25 models this with a logarithmic function that "saturates" for high TF values.
-
Document length normalization: BM25 normalizes the term frequency by the length of the document, so that longer documents aren‘t unfairly favored over shorter ones.
-
Tunable parameters: BM25 has two free parameters (k1 and b) that control the impact of term frequency and document length. These allow BM25 to be tuned for different use cases and types of data.
The exact BM25 formula gets a bit hairy (you can dive into the details in the references at the end if you‘re feeling brave), but conceptually, it calculates a relevance score for each document by summing the TF-IDF-like scores of each query term found in the document, normalized by document length. Documents are then ranked by this relevance score.
Importantly, BM25 is fairly fast to compute over even large datasets, making it feasible to use in real-time search applications. Let‘s see how to implement it in practice with Python.
Indexing and Searching Tweets with BM25
To illustrate how to use BM25 to build a search engine, we‘ll walk through an example using a dataset of tweets about the COVID-19 pandemic. Our goal will be to index the text of the tweets and allow users to find the most relevant tweets for a given search query. While we‘re using Twitter data here, keep in mind that this approach can easily be adapted to other datasets like web pages, blog posts, or PDF documents.
We‘ll be using the excellent rank-bm25 Python package to handle the heavy lifting of the BM25 algorithm. Make sure you have it installed before proceeding:
pip install rank_bm25
Step 1: Load and Preprocess the Data
First, let‘s load our tweet dataset into a Pandas DataFrame:
import pandas as pd
df = pd.read_csv(‘covid_tweets.csv‘)
Before we can use BM25, we need to preprocess the text of the tweets to clean up noise and standardize the data. This is a critically important step for any NLP task. Some common preprocessing steps include:
- Tokenization: Split text into individual words or tokens.
- Lowercasing: Convert all text to lowercase to avoid treating "The" and "the" as different words.
- Removing punctuation and special characters
- Removing stopwords: Filter out common words like "the", "and", "a" that add little meaning.
- Stemming/Lemmatization: Reduce words to their base or dictionary forms (e.g. convert "running", "runs", "ran" to "run").
Here‘s an example of applying some of these steps to our tweet data using the built-in functionality in rank-bm25:
from rank_bm25 import BM25Okapi
# Tokenize the text
tokenized_corpus = [doc.split(" ") for doc in df[‘text‘]]
# Remove stopwords
stopwords = [‘the‘, ‘and‘, ‘are‘, ‘a‘, ‘in‘, ‘to‘, ‘of‘, ‘with‘, ‘as‘, ‘on‘, ‘for‘, ‘by‘, ‘is‘]
filtered_corpus = [[token for token in doc if token.lower() not in stopwords] for doc in tokenized_corpus]
Step 2: Generate the BM25 Index
With our data preprocessed, we‘re ready to create the BM25 index:
bm25 = BM25Okapi(filtered_corpus)
That‘s it! The BM25Okapi class will handle calculating the necessary statistics (IDF values, document lengths, etc.) to be able to efficiently score documents for any given query.
Step 3: Search the Index
Now comes the fun part – actually searching our tweet corpus. To do so, we simply pass our search query to the BM25Okapi object‘s get_top_n method, specifying how many of the top results we want to retrieve:
query = "covid vaccine"
tokenized_query = query.split(" ")
# Search for the top 5 most relevant documents
top_docs = bm25.get_top_n(tokenized_query, df[‘text‘].values, n=5)
# Print the results
for doc in top_docs:
print(doc)
The get_top_n method will tokenize our query, calculate the BM25 relevance scores of each document, rank the documents by score, and return the text of the top N matching documents. The output might look something like:
"Latest updates on COVID-19 vaccine development and distribution: [URL]"
"Just got my first dose of the Pfizer COVID vaccine! Feeling hopeful. #vaccinated"
"Moderna vs Pfizer vs Johnson & Johnson - comparing the 3 COVID vaccines: [URL]"
"When will COVID vaccines be available for children under 12? Here‘s what we know. [URL]"
"Debunking common myths and misconceptions about the COVID vaccine: [URL]"
And there you have it – a basic NLP-powered search engine using BM25, in just a few lines of Python! Of course, there are many ways this example could be extended and improved. You could apply additional NLP techniques like named entity recognition or part-of-speech tagging during preprocessing. You might experiment with different BM25 parameters or more sophisticated querying to handle multi-word phrases. And you‘d probably want to add some kind of front-end interface to make it easy for users to input queries and view results.
Other BM25 Use Cases
Beyond searching social media posts, BM25 can be used to add search functionality to all sorts of text datasets. Some examples:
- Internal company knowledge bases and documentation
- Digital libraries and PDF collections
- E-commerce product catalogs
- Legal documents and contracts
- Scientific research papers and journals
- News archives and article databases
Anytime you have a large collection of text data and need a way for users to quickly find relevant information for a given query, BM25 can be a powerful tool in your arsenal. And by combining it with NLP techniques for preprocessing and query understanding, you can build search engines that are even more intelligent and user-friendly.
Conclusion
We‘ve only scratched the surface of what‘s possible with NLP and search in this post. But hopefully this has given you a taste of how you can use Python and open source tools like rank-bm25 to build your own search applications powered by natural language processing.
While the core algorithms like BM25 are important, it‘s crucial not to overlook the impact of proper preprocessing and thoughtful NLP when building any text-based application. Well-structured, cleaned, and normalized data will always yield better results than raw, noisy text.
As you experiment with search and NLP, don‘t be afraid to explore more advanced techniques like word embeddings, transformers, and machine learning. The field of NLP is evolving rapidly, and new innovations are constantly pushing the boundaries of what‘s possible.
But even with the latest and greatest in AI, the fundamentals of information retrieval exemplified by BM25 remain as relevant as ever. By deeply understanding these core building blocks, you‘ll be well-equipped to harness the power of NLP and search in your own projects. Happy searching!
References and Resources
- rank-bm25 Python package: https://pypi.org/project/rank-bm25/
- How Google Search Works: https://www.google.com/search/howsearchworks/
- Overview of TF-IDF: http://www.tfidf.com/
- Original BM25 paper: https://www.staff.city.ac.uk/~sb317/papers/foundations_bm25_review.pdf
- NLP Overview: https://web.stanford.edu/~jurafsky/slp3/1.pdf
- Intro to Information Retrieval (free online book): https://nlp.stanford.edu/IR-book/pdf/irbookonlinereading.pdf