From 949111f2caebe40659fca937a81e688d7c85263c Mon Sep 17 00:00:00 2001 From: David Sertic <62056091+davidmyriel@users.noreply.github.com> Date: Thu, 8 Jun 2023 10:59:10 +0200 Subject: [PATCH] write neural search tutorial - This tutorial is based on the Neural Search Article written by generall. - The document introduces a new simple format for short tutorials. - The aim of the tutorial is to let the user try things out quickly and with minimal interruption. --- .../documentation/tutorials/neural-search.md | 347 ++++++++++++++++++ .../static/docs/fastapi_neural_search.png | Bin 0 -> 30155 bytes 2 files changed, 347 insertions(+) create mode 100644 qdrant-landing/content/documentation/tutorials/neural-search.md create mode 100644 qdrant-landing/static/docs/fastapi_neural_search.png diff --git a/qdrant-landing/content/documentation/tutorials/neural-search.md b/qdrant-landing/content/documentation/tutorials/neural-search.md new file mode 100644 index 000000000..9b04954d0 --- /dev/null +++ b/qdrant-landing/content/documentation/tutorials/neural-search.md @@ -0,0 +1,347 @@ +--- +title: Create a Simple Neural Search Service +weight: 14 +--- + +# Create a Simple Neural Search Service + +| Time: 30 min | Level: Beginner | Output: [GitHub](https://github.com/qdrant/qdrant_demo/blob/master/qdrant_demo/init_vector_search_index.py) | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/drive/1kPktoudAP8Tu8n8l-iVMOQhVmHkWV_L9?usp=sharing) | +| --- | ----------- | ----------- |----------- | + +This tutorial shows you how to build and deploy your own neural search service. The created service will be searching through descriptions of companies from [startups-list.com](https://www.startups-list.com/). The website contains the company names, descriptions, their locations and a picture for each entry. + +To create a neural search service, you will need to process your raw data and then create a search function to manipulate it. First, you will download and prepare a sample dataset using a modified version of the BERT ML model. Then, you will load the data into Qdrant, create a Neural Search API and serve it using FastAPI. + +## Prerequisites + +To complete this tutorial, you will need: + +- Docker - The easiest way to use Qdrant is to run a pre-built Docker image. +- [Raw parsed data](https://storage.googleapis.com/generall-shared-data/startups_demo.json) from startups-list.com. +- Python version 3.8 + +> **Note**: The code for this tutorial can be found here: | [Step 1: Data Preparation Process](https://colab.research.google.com/drive/1kPktoudAP8Tu8n8l-iVMOQhVmHkWV_L9?usp=sharing) | [Step 2: Full Code for Neural Search](https://github.com/qdrant/qdrant_demo/blob/master/qdrant_demo/init_vector_search_index.py). | + +## Prepare sample dataset + +To conduct a neural search on startup descriptions, you must first encode the description data into vectors. To process text, you need to use a pre-trained language model - DistilBert. The [sentence-transformers](https://github.com/UKPLab/sentence-transformers) library lets you conveniently download and use many pre-trained models, such as DistilBERT. + +1. First you need to download the dataset. + +``` +wget https://storage.googleapis.com/generall-shared-data/startups_demo.json +``` + +2. Use the SentenceTransformer pre-trained model to convert the text into vectors. + +```python +pip install sentence-transformers +``` + +3. Import all relevant models. + +```python +from sentence_transformers import SentenceTransformer +import numpy as np +import json +import pandas as pd +from tqdm.notebook import tqdm +``` + +We will use a Machine Learning model called `distilbert-base-nli-stsb-mean-tokens`. +DistilBERT is a lightweight version of BERT, which speeds up our service and reduces resource loads. + +4. Download and create a pre-trained sentence encoder. + +```python +model = SentenceTransformer('distilbert-base-nli-stsb-mean-tokens', device="cuda") +``` +5. Read the raw data file. + +```python +df = pd.read_json('./startups_demo.json', lines=True) +``` +6. Encode all startup descriptions in batches, as this reduces overhead costs and significantly speeds up the process. + +```python +vectors = [] +batch_size = 64 +batch = [] +for row in tqdm(df.itertuples()): + description = row.alt + ". " + row.description + batch.append(description) + if len(batch) >= batch_size: + vectors.append(model.encode(batch)) # Text -> vector encoding happens here + batch = [] + +if len(batch) > 0: + vectors.append(model.encode(batch)) + batch = [] + +vectors = np.concatenate(vectors) +``` +All of our descriptions are converted into vectors. We have 40474 vectors of 768 dimentions. The output layer of the model has this dimension + +```python +vectors.shape +``` + +7. Download the saved vectors into a new file names `startup_vectors.npy` + +```python +np.save('startup_vectors.npy', vectors, allow_pickle=False) +``` + +## Run Qdrant in Docker + +Next, you need to manage all of your data using a vector engine. Qdrant lets you store, update or delete created vectors. Most importantly, it lets you search for the nearest vectors via a convenient API. + +> **Note:** Before you begin, create a project directory and a virtual python environment around it. + +1. Download the Qdrant image from DockerHub. + +``` +docker pull qdrant/qdrant +``` +2. Start Qdrant inside of Docker. + +``` +docker run -p 6333:6333 \ + -v $(pwd)/qdrant_storage:/qdrant/storage \ + qdrant/qdrant +``` +You should see output like this + +``` +... +[2021-02-05T00:08:51Z INFO actix_server::builder] Starting 12 workers +[2021-02-05T00:08:51Z INFO actix_server::builder] Starting "actix-web-service-0.0.0.0:6333" service on 0.0.0.0:6333 +``` + +Test the service by going to [http://localhost:6333/](http://localhost:6333/). You should see the Qdrant version info in your browser. + +All uploaded to Qdrant data is saved into the `./qdrant_storage` directory and will be persisted even if you recreate the container. + +## Upload data to Qdrant + +1. Install the official Python client to best interact with Qdrant. + +``` +pip install qdrant-client +``` + +At this point, you should have startup records in the `startups.json` file, encoded vectors in `startup_vectors.npy` and Qdrant running on a local machine. + +Now you need to write a script to upload all startup data and vectors into the search engine. + +2. Create a client object for Qdrant. + +```python +# Import client library +from qdrant_client import QdrantClient +from qdrant_client.models import VectorParams, Distance + +qdrant_client = QdrantClient(host='localhost', port=6333) +``` + +3. Related vectors need to be added to a collection. Create a new collection for our startup vectors. + +```python +qdrant_client.recreate_collection( + collection_name='startups', + vectors_config=VectorParams(size=768, distance=Distance.COSINE), +) +``` + + +4. Create an iterator over the startup data and vectors. + +The Qdrant client library defines a special function that allows you to load datasets into the service. +However, since there may be too much data to fit a single computer memory, the function takes an iterator over the data as input. + +```python +import numpy as np +import json + +fd = open('./startups.json') + +# payload is now an iterator over startup data +payload = map(json.loads, fd) + +# Here we load all vectors into memory, numpy array works as iterable for itself. +# Other option would be to use Mmap, if we don't want to load all data into RAM +vectors = np.load('./startup_vectors.npy') +``` + +5. Upload the data + +```python +qdrant_client.upload_collection( + collection_name='startups', + vectors=vectors, + payload=payload, + ids=None, # Vector ids will be assigned automatically + batch_size=256 # How many vectors will be uploaded in a single request? +) +``` + +Vectors are now uploaded to Qdrant. + +## Build the search API + +Now that all the preparations are complete, let's start building a neural search class. + +First, install all the requirements: + +``` +pip install sentence-transformers numpy +``` + +In order to process incoming requests neural search will need 2 things: 1) a model to convert the query into a vector and 2) the Qdrant client to perform search queries. + +Create a file named `neural_searcher.py` and specify the following. + +```python + +from qdrant_client import QdrantClient +from sentence_transformers import SentenceTransformer + + +class NeuralSearcher: + + def __init__(self, collection_name): + self.collection_name = collection_name + # Initialize encoder model + self.model = SentenceTransformer('distilbert-base-nli-stsb-mean-tokens', device='cpu') + # initialize Qdrant client + self.qdrant_client = QdrantClient(host='localhost', port=6333) +``` + +1. Write the search function. + +```python + def search(self, text: str): + # Convert text query into vector + vector = self.model.encode(text).tolist() + + # Use `vector` for search for closest vectors in the collection + search_result = self.qdrant_client.search( + collection_name=self.collection_name, + query_vector=vector, + query_filter=None, # We don't want any filters for now + top=5 # 5 the most closest results is enough + ) + # `search_result` contains found vector ids with similarity scores along with the stored payload + # In this function we are interested in payload only + payloads = [hit.payload for hit in search_result] + return payloads +``` + +2. Add search filters. + +With Qdrant it is also feasible to add some conditions to the search. +For example, if we wanted to search for startups in a certain city, the search query could look like this: + +```python +from qdrant_client.models import Filter + + ... + + city_of_interest = "Berlin" + + # Define a filter for cities + city_filter = Filter(**{ + "must": [{ + "key": "city", # We store city information in a field of the same name + "match": { # This condition checks if payload field have requested value + "keyword": city_of_interest + } + }] + }) + + search_result = self.qdrant_client.search( + collection_name=self.collection_name, + query_vector=vector, + query_filter=city_filter, + top=5 + ) + ... + +``` + +You have now created a class for neural search queries. Now wrap it up into a service. + +## Deploy the search with FastAPI + +To build the service you will use the FastAPI framework. + +1. Install FastAPI. + +To install it, use the command + +``` +pip install fastapi uvicorn +``` + +2. Implement the service. + +Create a file named `service.py` and specify the following. + +Our service will have only one API endpoint and will look like this: + +```python + from fastapi import FastAPI + +# The file where NeuralSearcher is stored +from neural_searcher import NeuralSearcher + +app = FastAPI() + +# Create a neural searcher instance +neural_searcher = NeuralSearcher(collection_name='startups') + +@app.get("/api/search") +def search_startup(q: str): + return { + "result": neural_searcher.search(text=q) + } + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) + +``` + +3. Run the service. + +``` +python service.py +``` + +4. Open your browser at [http://localhost:8000/docs](http://localhost:8000/docs). + +You should be able to see a debug interface for your service. + +![FastAPI Swagger interface](/docs/fastapi_neural_search.png) + +Feel free to play around with it, make queries and check out the results. + +## Next steps + +The code from this tutorial has been used to develop a [live online demo](https://qdrant.to/semantic-search-demo). +You can try it to get an intuition for cases when the neural search is useful. +The demo contains a switch that selects between neural and full-text searches. +You can turn the neural search on and off to compare your result with a regular full-text search. + +> **Note**: The code for this tutorial can be found here: | [Step 1: Data Preparation Process](https://colab.research.google.com/drive/1kPktoudAP8Tu8n8l-iVMOQhVmHkWV_L9?usp=sharing) | [Step 2: Full Code for Neural Search](https://github.com/qdrant/qdrant_demo/blob/master/qdrant_demo/init_vector_search_index.py). | + +Join our [Discord community](https://qdrant.to/discord), where we talk about vector search and similarity learning, publish other examples of neural networks and neural search applications. diff --git a/qdrant-landing/static/docs/fastapi_neural_search.png b/qdrant-landing/static/docs/fastapi_neural_search.png new file mode 100644 index 0000000000000000000000000000000000000000..72524dff47786857f678ab8a3d609361cdf903c5 GIT binary patch literal 30155 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

x4R@(^<94x^N8=b*Uhdwu$F8)2Z4eh+ov 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⪙Y5$M+5(^MReaQJ4L&5Sv9F-1u zQ(9fLC?npGsU*j4DfhS{3Z!0_z>dwV7DvO}Q;Vjottr9WULaxkpLyJQx{$*BS#URh zEIFEy&Rk|Lc4ou&k@}K=iOf)JE8{mVHFe~qRBpziECw4mnc*=)d>WvF?RB}sGiIIjiJ{ChuL3+^tlLC z@OzcJ8Ip09g#uNi-$vXl&=eo_dIu{7wf<+8drfHGFABrR7RS6k>GvO!P^%ryxHs-s z760E$!9~b{T3B2LlJMaf>+`;yKgii)xG9*i^v6bkEnrdNZ_hV=cH$?TNg7ADR77L literal 0 HcmV?d00001