mirror of
https://github.com/qdrant/landing_page.git
synced 2026-09-25 22:18:30 +02:00
articles
This commit is contained in:
@@ -32,7 +32,7 @@ keywords = "search engine, neural network, matching, filter, SaaS, approximate n
|
||||
quick_start = "https://github.com/qdrant/qdrant#usage"
|
||||
tutorial = "/404.html"
|
||||
|
||||
linkedin = "#"
|
||||
linkedin = "https://www.linkedin.com/in/andrey-vasnetsov-75268897/"
|
||||
telegram = "https://t.me/neural_network_engineering"
|
||||
|
||||
email = "info@qdrant.tech"
|
||||
@@ -76,5 +76,5 @@ keywords = "search engine, neural network, matching, filter, SaaS, approximate n
|
||||
identifier = "articles"
|
||||
name = "Articles"
|
||||
weight = -70
|
||||
url = "/#articles"
|
||||
url = "/articles"
|
||||
|
||||
|
||||
@@ -7,3 +7,218 @@ preview_image: /articles_data/metric-learning-tips/preview.png
|
||||
small_preview_image: /articles_data/metric-learning-tips/scatter-graph.svg
|
||||
weight: 20
|
||||
---
|
||||
|
||||
|
||||
## How to train object matching model with no labeled data and use it in production
|
||||
|
||||
|
||||
Currently, most machine-learning-related business cases are solved as a classification problems.
|
||||
Classification algorithms are so well studied in practice that even if the original problem is not directly a classification task, it is usually decomposed or approximately converted into one.
|
||||
|
||||
However, despite its simplicity, the classification task has requirements that could complicate its production integration and scaling.
|
||||
E.g. it requires a fixed number of classes, where each class should have a sufficient number of training samples.
|
||||
|
||||
In this article, I will describe how we overcome these limitations by switching to metric learning.
|
||||
By the example of matching job positions and candidates, I will show how to train metric learning model with no manually labeled data, how to estimate prediction confidence, and how to serve metric learning in production.
|
||||
|
||||
|
||||
## What is metric learning and why using it?
|
||||
|
||||
According to Wikipedia, metric learning is the task of learning a distance function over objects.
|
||||
In practice, it means that we can train a model that tells a number for any pair of given objects.
|
||||
And this number should represent a degree or score of similarity between those given objects.
|
||||
For example, objects with a score of 0.9 could be more similar than objects with a score of 0.5
|
||||
Actual scores and their direction could vary among different implementations.
|
||||
|
||||
In practice, there are two main approaches to metric learning and two corresponding types of NN architectures.
|
||||
The first is the interaction-based approach, which first builds local interactions (i.e., local matching signals) between two objects. Deep neural networks learn hierarchical interaction patterns for matching.
|
||||
Examples of neural network architectures include MV-LSTM, ARC-II, and MatchPyramid.
|
||||
|
||||

|
||||
> MV-LSTM, example of interaction-based model, [Shengxian Wan et al.
|
||||
](https://www.researchgate.net/figure/Illustration-of-MV-LSTM-S-X-and-S-Y-are-the-in_fig1_285271115) via Researchgate
|
||||
|
||||
The second is the representation-based approach.
|
||||
In this case distance function is composed of 2 components:
|
||||
the Encoder transforms an object into embedded representation - usually a large float point vector, and the Comparator takes embeddings of a pair of objects from the Encoder and calculates their similarity.
|
||||
The most well-known example of this embedding representation is Word2Vec.
|
||||
|
||||
Examples of neural network architectures also include DSSM, C-DSSM, and ARC-I.
|
||||
|
||||
The Comparator is usually a very simple function that could be calculated very quickly.
|
||||
It might be cosine similarity or even a dot production.
|
||||
Two-stage schema allows performing complex calculations only once per object.
|
||||
Once transformed, the Comparator can calculate object similarity independent of the Encoder much more quickly.
|
||||
For more convenience, embeddings can be placed into specialized storages or vector search engines.
|
||||
These search engines allow to manage embeddings using API, perform searches and other operations with vectors.
|
||||
|
||||

|
||||
> C-DSSM, example of representation-based model, [Xue Li et al.](https://arxiv.org/abs/1901.10710v2) via arXiv
|
||||
|
||||
Pre-trained NNs can also be used. The output of the second-to-last layer could work as an embedded representation.
|
||||
Further in this article, I would focus on the representation-based approach, as it proved to be more flexible and fast.
|
||||
|
||||
So what are the advantages of using metric learning comparing to classification?
|
||||
Object Encoder does not assume the number of classes.
|
||||
So if you can't split your object into classes,
|
||||
if the number of classes is too high, or you suspect that it could grow in the future - consider using metric learning.
|
||||
|
||||
In our case, business goal was to find suitable vacancies for candidates who specify the title of the desired position.
|
||||
To solve this, we used to apply a classifier to determine the job category of the vacancy and the candidate.
|
||||
But this solution was limited to only a few hundred categories.
|
||||
Candidates were complaining that they couldn't find the right category for them.
|
||||
Training the classifier for new categories would be too long and require new training data for each new category.
|
||||
Switching to metric learning allowed us to overcome these limitations, the resulting solution could compare any pair position descriptions, even if we don't have this category reference yet.
|
||||
|
||||

|
||||
> T-SNE with job samples, Image by Author. Play with [Embedding Projector](https://projector.tensorflow.org/?config=https://gist.githubusercontent.com/generall/7e712425e3b340c2c4dbc1a29f515d91/raw/b45b2b6f6c1d5ab3d3363c50805f3834a85c8879/config.json) yourself.
|
||||
|
||||
With metric learning, we learn not a concrete job type but how to match job descriptions from a candidate's CV and a vacancy.
|
||||
Secondly, with metric learning, it is easy to add more reference occupations without model retraining.
|
||||
We can then add the reference to a vector search engine.
|
||||
Next time we will match occupations - this new reference vector will be searchable.
|
||||
|
||||
|
||||
## Data for metric learning
|
||||
|
||||
Unlike classifiers, a metric learning training does not require specific class labels.
|
||||
All that is required are examples of similar and dissimilar objects.
|
||||
We would call them positive and negative samples.
|
||||
|
||||
At the same time, it could be a relative similarity between a pair of objects.
|
||||
For example, twins look more alike to each other than a pair of random people.
|
||||
And random people are more similar to each other than a man and a cat.
|
||||
A model can use such relative examples for learning.
|
||||
|
||||
The good news is that the division into classes is only a special case of determining similarity.
|
||||
To use such datasets, it is enough to declare samples from one class as positive and samples from another class as negative.
|
||||
In this way, it is possible to combine several datasets with mismatched classes into one generalized dataset for metric learning.
|
||||
|
||||
But not only datasets with division into classes are suitable for extracting positive and negative examples.
|
||||
If, for example, there are additional features in the description of the object, the value of these features can also be used as a similarity factor.
|
||||
It may not be as explicit as class membership, but the relative similarity is also suitable for learning.
|
||||
|
||||
In the case of job descriptions, there are many ontologies of occupations, which were able to be combined into a single dataset thanks to this approach.
|
||||
We even went a step further and used identical job titles to find similar descriptions.
|
||||
|
||||
As a result, we got a self-supervised universal dataset that did not require any manual labeling.
|
||||
|
||||
Unfortunately, universality does not allow some techniques to be applied in training.
|
||||
Next, I will describe how to overcome this disadvantage.
|
||||
|
||||
## Training the model
|
||||
|
||||
There are several ways to train a metric learning model.
|
||||
Among the most popular is the use of Triplet or Contrastive loss functions, but I will not go deep into them in this article.
|
||||
However, I will tell you about one interesting trick that helped us work with unified training examples.
|
||||
|
||||
One of the most important practices to efficiently train the metric learning model is hard negative mining.
|
||||
This technique aims to include negative samples on which model gave worse predictions during the last training epoch.
|
||||
Most articles that describe this technique assume that training data consists of many small classes (in most cases it is people's faces).
|
||||
With data like this, it is easy to find bad samples - if two samples from different classes have a high similarity score, we can use it as a negative sample.
|
||||
But we had no such classes in our data, the only thing we have is occupation pairs assumed to be similar in some way.
|
||||
We cannot guarantee that there is no better match for each job occupation among this pair.
|
||||
That is why we can't use hard negative mining for our model.
|
||||
|
||||
|
||||

|
||||
> [Alfonso Medela et al.](https://arxiv.org/abs/1905.10675) via arXiv
|
||||
|
||||
|
||||
To compensate for this limitation we can try to increase the number of random (weak) negative samples.
|
||||
One way to achieve this is to train the model longer, so it will see more samples by the end of the training.
|
||||
But we found a better solution in adjusting our loss function.
|
||||
In a regular implementation of Triplet or Contractive loss, each positive pair is compared with some or a few negative samples.
|
||||
What we did is we allow pair comparison amongst the whole batch.
|
||||
That means that loss-function penalizes all pairs of random objects if its score exceeds any of the positive scores in a batch.
|
||||
This extension gives `~ N * B^2` comparisons where `B` is a size of batch and `N` is a number of batches.
|
||||
Much bigger than `~ N * B` in regular triplet loss.
|
||||
This means that increasing the size of the batch significantly increases the number of negative comparisons, and therefore should improve the model performance.
|
||||
We were able to observe this dependence in our experiments.
|
||||
Similar idea we also found in the article [Supervised Contrastive Learning](https://arxiv.org/abs/2004.11362).
|
||||
|
||||
|
||||
## Model confidence
|
||||
|
||||
In real life it is often needed to know how confident the model was in the prediction.
|
||||
Whether manual adjustment or validation of the result is required.
|
||||
|
||||
With conventional classification, it is easy to understand by scores how confident the model is in the result.
|
||||
If the probability values of different classes are close to each other, the model is not confident.
|
||||
If, on the contrary, the most probable class differs greatly, then the model is confident.
|
||||
|
||||
At first glance, this cannot be applied to metric learning.
|
||||
Even if the predicted object similarity score is small it might only mean that the reference set has no proper objects to compare with.
|
||||
Conversely, the model can group garbage objects with a large score.
|
||||
|
||||
Fortunately, we found a small modification to the embedding generator, which allows us to define confidence in the same way as it is done in conventional classifiers with a Softmax activation function.
|
||||
The modification consists in building an embedding as a combination of feature groups.
|
||||
Each feature group is presented as a one-hot encoded sub-vector in the embedding.
|
||||
If the model can confidently predict the feature value - the corresponding sub-vector will have a high absolute value in some of its elements.
|
||||
For a more intuitive understanding, I recommend thinking about embeddings not as points in space, but as a set of binary features.
|
||||
|
||||
To implement this modification and form proper feature groups we would need to change a regular linear output layer to a concatenation of several Softmax layers.
|
||||
Each softmax component would represent an independent feature and force the neural network to learn them.
|
||||
|
||||
Let's take for example that we have 4 softmax components with 128 elements each.
|
||||
Every such component could be roughly imagined as a one-hot-encoded number in the range of 0 to 127.
|
||||
Thus, the resulting vector will represent one of `128^4` possible combinations.
|
||||
If the trained model is good enough, you can even try to interpret the values of singular features individually.
|
||||
|
||||
|
||||

|
||||
> Softmax feature embeddings, Image by Author.
|
||||
|
||||
|
||||
## Neural rules
|
||||
|
||||
Machine learning models rarely train to 100% accuracy.
|
||||
In a conventional classifier, errors can only be eliminated by modifying and repeating the training process.
|
||||
Metric training, however, is more flexible in this matter and allows you to introduce additional steps that allow you to correct the errors of an already trained model.
|
||||
|
||||
A common error of the metric learning model is erroneously declaring objects close although in reality they are not.
|
||||
To correct this kind of error, we introduce exclusion rules.
|
||||
|
||||
Rules consist of 2 object anchors encoded into vector space.
|
||||
If the target object falls into one of the anchors' effects area - it triggers the rule. It will exclude all objects in the second anchor area from the prediction result.
|
||||
|
||||

|
||||
> Neural exclusion rules, Image by Author.
|
||||
|
||||
The convenience of working with embeddings is that regardless of the number of rules,
|
||||
you only need to perform the encoding once per object.
|
||||
Then to find a suitable rule, it is enough to compare the target object's embedding and the pre-calculated embeddings of the rule's anchors.
|
||||
Which, when implemented, translates into just one additional query to the vector search engine.
|
||||
|
||||
|
||||
## Vector search in production
|
||||
|
||||
When implementing a metric learning model in production, the question arises about the storage and management of vectors.
|
||||
It should be easy to add new vectors if new job descriptions appear in the service.
|
||||
|
||||
In our case, we also needed to apply additional conditions to the search.
|
||||
We needed to filter, for example, the location of candidates and the level of language proficiency.
|
||||
|
||||
We did not find a ready-made tool for such vector management, so we created and open sourced our internal vector search engine called [Qdrant](https://github.com/qdrant/qdrant).
|
||||
|
||||
It allows you to add and delete vectors with a simple API, independent of a programming language you are using.
|
||||
You can also assign the payload to vectors.
|
||||
This payload allows additional filtering during the search request.
|
||||
|
||||
Qdrant has a pre-built docker image and start working with it is just as simple as running
|
||||
|
||||
```
|
||||
docker run -p 6333:6333 generall/qdrant
|
||||
```
|
||||
|
||||
Documentation with examples could be found [here](https://qdrant.github.io/qdrant/redoc/index.html).
|
||||
|
||||
|
||||
## Conclusion
|
||||
|
||||
In this article, I have shown how metric learning can be more scalable and flexible than the classification models.
|
||||
I suggest trying similar approaches in your tasks - it might be matching similar texts, images, or audio data.
|
||||
With the existing variety of pre-trained neural networks and a vector search engine, it is easy to build your metric learning-based application.
|
||||
|
||||
Subscribe to my [telegram channel](https://t.me/neural_network_engineering), where I talk about neural networks engineering, publish other examples of metric learning and neural search applications.
|
||||
|
||||
|
||||
@@ -7,3 +7,362 @@ preview_image: /articles_data/neural-search-tutorial/preview.png
|
||||
small_preview_image: /articles_data/neural-search-tutorial/tutorial.svg
|
||||
weight: 10
|
||||
---
|
||||
|
||||
## How to build a neural search service with BERT + Qdrant + FastAPI
|
||||
|
||||

|
||||
|
||||
Information retrieval technology is one of the main technologies that enabled the modern Internet to exist.
|
||||
These days, search technology is the heart of a variety of applications.
|
||||
From web-pages search to product recommendations.
|
||||
For many years, this technology didn't get much change until neural networks came into play.
|
||||
|
||||
In this tutorial we are going to find answers to these questions:
|
||||
|
||||
* What is the difference between regular and neural search?
|
||||
* What neural networks could be used for search?
|
||||
* In what tasks is neural network search useful?
|
||||
* How to build and deploy own neural search service step-by-step?
|
||||
|
||||
## What is neural search?
|
||||
|
||||
A regular full-text search, such as Google's, consists of searching for keywords inside a document.
|
||||
For this reason, the algorithm can not take into account the real meaning of the query and documents.
|
||||
Many documents that might be of interest to the user are not found because they use different wording.
|
||||
|
||||
Neural search tries to solve exactly this problem - it attempts to enable searches not by keywords but by meaning.
|
||||
To achieve this, the search works in 2 steps.
|
||||
In the first step, a specially trained neural network encoder converts the query and the searched objects into a vector representation called embeddings.
|
||||
The encoder must be trained so that similar objects, such as texts with the same meaning or alike pictures get a close vector representation.
|
||||
|
||||

|
||||
|
||||
Having this vector representation, it is easy to understand what the second step should be.
|
||||
To find documents similar to the query you now just need to find the nearest vectors.
|
||||
The most convenient way to determine the distance between two vectors is to calculate the cosine distance.
|
||||
The usual Euclidean distance can also be used, but in the case of vectors of high dimensions, it is not so efficient.
|
||||
|
||||
## Which model could be used?
|
||||
|
||||
It is ideal to use a model specially trained to determine the closeness of meanings.
|
||||
For example, models trained on Semantic Textual Similarity (STS) datasets.
|
||||
Current state-of-the-art models could be found on this [leaderboard](https://paperswithcode.com/sota/semantic-textual-similarity-on-sts-benchmark?p=roberta-a-robustly-optimized-bert-pretraining).
|
||||
|
||||
However, not only specially trained models can be used.
|
||||
If the model is trained on a large enough dataset, its internal features can work as embeddings too.
|
||||
So, for instance, you can take any pre-trained on ImageNet model and cut off the last layer from it.
|
||||
In the penultimate layer of the neural network, as a rule, the highest-level features are formed, which, however, do not correspond to specific classes.
|
||||
The output of this model can be used as an embedding.
|
||||
|
||||
## What tasks is neural search good for?
|
||||
|
||||
Neural search has the greatest advantage in areas where the query cannot be formulated precisely.
|
||||
Querying a table in a SQL database is not the best place for neural search.
|
||||
|
||||
On the contrary, if the query itself is fuzzy, or it cannot be formulated as a set of conditions - neural search can help you.
|
||||
If the search query is a picture, sound file or long text, neural network search is almost the only option.
|
||||
|
||||
If you want to build a recommendation system, the neural approach can also be useful.
|
||||
The user's actions can be encoded in vector space in the same way as a picture or text.
|
||||
And having those vectors, it is possible to find semantically similar users and determine the next probable user actions.
|
||||
|
||||
## Let's build our own
|
||||
|
||||
With all that said, let's make our neural network search.
|
||||
As an example, I decided to make a search for startups by their description.
|
||||
In this demo, we will see the cases when text search works better and the cases when neural network search works better.
|
||||
|
||||
|
||||
I will use data from [startups-list.com](https://www.startups-list.com/).
|
||||
Each record contains the name, a paragraph describing the company, the location and a picture.
|
||||
Raw parsed data can be found at [this link](https://storage.googleapis.com/generall-shared-data/startups_demo.json).
|
||||
|
||||
### Prepare data for neural search
|
||||
|
||||
To be able to search for our descriptions in vector space, we must get vectors first.
|
||||
We need to encode the descriptions into a vector representation.
|
||||
As the descriptions are textual data, we can use a pre-trained language model.
|
||||
As mentioned above, for the task of text search there is a whole set of pre-trained models specifically tuned for semantic similarity.
|
||||
|
||||
One of the easiest libraries to work with pre-trained language models, in my opinion, is the [sentence-transformers](https://github.com/UKPLab/sentence-transformers) by UKPLab.
|
||||
It provides a way to conveniently download and use many pre-trained models, mostly based on transformer architecture.
|
||||
Transformers is not the only architecture suitable for neural search, but for our task, it is quite enough.
|
||||
|
||||
We will use a model called `distilbert-base-nli-stsb-mean-tokens`.
|
||||
DistilBERT means that the size of this model has been reduced by a special technique compared to the original BERT.
|
||||
This is important for the speed of our service and its demand for resources.
|
||||
The word `stsb` in the name means that the model was trained for the Semantic Textual Similarity task.
|
||||
|
||||
The complete code for data preparation with detailed comments can be found and run in [Colab Notebook](https://colab.research.google.com/drive/1kPktoudAP8Tu8n8l-iVMOQhVmHkWV_L9?usp=sharing).
|
||||
|
||||
[](https://colab.research.google.com/drive/1kPktoudAP8Tu8n8l-iVMOQhVmHkWV_L9?usp=sharing)
|
||||
|
||||
### Vector search engine
|
||||
|
||||
Now as we have a vector representation for all our records, we need to store them somewhere.
|
||||
In addition to storing, we may also need to add or delete a vector, save additional information with the vector.
|
||||
And most importantly, we need a way to search for the nearest vectors.
|
||||
|
||||
The vector search engine can take care of all these tasks.
|
||||
It provides a convenient API for searching and managing vectors.
|
||||
In our tutorial we will use [Qdrant](https://github.com/qdrant/qdrant) vector search engine.
|
||||
It not only supports all necessary operations with vectors but also allows to store additional payload along with vectors and use it to perform filtering of the search result.
|
||||
Qdrant has a client for python and also defines the API schema if you need to use it from other languages.
|
||||
|
||||
The easiest way to use Qdrant is to run a pre-built image.
|
||||
So make sure you have Docker installed on your system.
|
||||
|
||||
To start Qdrant, use the instructions on its [homepage](https://github.com/qdrant/qdrant).
|
||||
|
||||
Download image from [DockerHub](https://hub.docker.com/r/generall/qdrant):
|
||||
|
||||
```
|
||||
docker pull generall/qdrant
|
||||
```
|
||||
|
||||
And run the service inside the docker:
|
||||
|
||||
```
|
||||
docker run -p 6333:6333 \
|
||||
-v $(pwd)/qdrant_storage:/qdrant/storage \
|
||||
generall/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
|
||||
```
|
||||
|
||||
This means that the service is successfully launched and listening port 6333.
|
||||
To make sure you can test [http://localhost:6333/](http://localhost:6333/) in your browser and get qdrant version info.
|
||||
|
||||
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
|
||||
|
||||
Now once we have the vectors prepared and the search engine running, we can start uploading the data.
|
||||
To interact with Qdrant from python, I recommend using an out-of-the-box client library.
|
||||
|
||||
To install it, use the following command
|
||||
|
||||
```
|
||||
pip install qdrant-client
|
||||
```
|
||||
|
||||
At this point, we should have startup records in file `startups.json`, encoded vectors in file `startup_vectors.npy`, and running Qdrant on a local machine.
|
||||
Let's write a script to upload all startup data and vectors into the search engine.
|
||||
|
||||
First, let's create a client object for Qdrant.
|
||||
|
||||
```python
|
||||
# Import client library
|
||||
from qdrant_client import QdrantClient
|
||||
|
||||
qdrant_client = QdrantClient(host='localhost', port=6333)
|
||||
```
|
||||
|
||||
Qdrant allows you to combine vectors of the same purpose into collections.
|
||||
Many independent vector collections can exist on one service at the same time.
|
||||
|
||||
Let's create a new collection for our startup vectors.
|
||||
|
||||
```python
|
||||
qdrant_client.recreate_collection(collection_name='startups', vector_size=768)
|
||||
```
|
||||
|
||||
The `recreate_collection` function first tries to remove an existing collection with the same name.
|
||||
This is useful if you are experimenting and running the script several times.
|
||||
|
||||
The `vector_size` parameter is very important.
|
||||
It tells the service the size of the vectors in that collection.
|
||||
All vectors in a collection must have the same size, otherwise, it is impossible to calculate the distance between them.
|
||||
`768` is the output dimensionality of the encoder we are using.
|
||||
|
||||
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.
|
||||
|
||||
Let's create an iterator over the startup data and vectors.
|
||||
|
||||
```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')
|
||||
```
|
||||
|
||||
And the final step - data uploading
|
||||
|
||||
```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?
|
||||
)
|
||||
```
|
||||
|
||||
Now we have vectors, uploaded to the vector search engine.
|
||||
On the next step we will learn how to actually search for closest vectors.
|
||||
|
||||
The full code for this step could be found [here](https://github.com/qdrant/qdrant_demo/blob/master/qdrant_demo/init_vector_search_index.py).
|
||||
|
||||
### Make a 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.
|
||||
A model to convert the query into a vector and Qdrant client, to perform a search queries.
|
||||
|
||||
```python
|
||||
# File: neural_searcher.py
|
||||
|
||||
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)
|
||||
```
|
||||
|
||||
The search function looks as simple as possible:
|
||||
|
||||
```python
|
||||
def search(self, text: str):
|
||||
# Convert text query into vector
|
||||
vector = self.model.encode(text)
|
||||
|
||||
# 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 = [payload for point, payload in search_result]
|
||||
return payloads
|
||||
```
|
||||
|
||||
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_openapi_client.models.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
|
||||
)
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
We now have a class for making neural search queries. Let's wrap it up into a service.
|
||||
|
||||
|
||||
### Deploy as a service
|
||||
|
||||
To build the service we will use the FastAPI framework.
|
||||
It is super easy to use and requires minimal code writing.
|
||||
|
||||
To install it, use the command
|
||||
|
||||
```
|
||||
pip install fastapi uvicorn
|
||||
```
|
||||
|
||||
Our service will have only one API endpoint and will look like this:
|
||||
|
||||
```python
|
||||
# File: service.py
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
# That is the file where NeuralSearcher is stored
|
||||
from neural_searcher import NeuralSearcher
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
# Create an instance of the neural searcher
|
||||
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)
|
||||
|
||||
```
|
||||
|
||||
Now, if you run the service with
|
||||
|
||||
```
|
||||
python service.py
|
||||
```
|
||||
|
||||
and open your browser at [http://localhost:8000/docs](http://localhost:8000/docs) , you should be able to see a debug interface for your service.
|
||||
|
||||

|
||||
|
||||
Feel free to play around with it, make queries and check out the results.
|
||||
This concludes the tutorial.
|
||||
|
||||
|
||||
### Online Demo
|
||||
|
||||
The described code is the core of this [online demo](https://demo.qdrant.tech).
|
||||
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 neural search on and off to compare the result with regular full-text search.
|
||||
Try to use startup description to find similar ones.
|
||||
|
||||
## Conclusion
|
||||
|
||||
In this tutorial, I have tried to give minimal information about neural search, but enough to start using it.
|
||||
Many potential applications are not mentioned here, this is a space to go further into the subject.
|
||||
|
||||
Subscribe to my [telegram channel](https://t.me/neural_network_engineering), where I talk about neural networks engineering, publish other examples of neural networks and neural search applications.
|
||||
|
||||
@@ -16,6 +16,7 @@ weight: 20
|
||||
short_description: |
|
||||
Find similar images, detect duplicates, or even find a picture by text description - all of that you can do with Qdrant.
|
||||
Mostly you won't even need to train a neural network for that. Pre-trained models are usually enough to begin with.
|
||||
Check out our [demo](https://food-discovery.qdrant.tech/)!
|
||||
---
|
||||
|
||||
Sometimes text search is not enough.
|
||||
|
||||
@@ -10,7 +10,8 @@ default_link_name: Demo
|
||||
weight: 10
|
||||
short_description: |
|
||||
The neural search uses **semantic embeddings** instead of keywords and works best with short texts.
|
||||
With Qdrant and a pre-trained neural network, you can build and deploy semantic neural search on your data in minutes!
|
||||
With Qdrant and a pre-trained neural network, you can build and deploy semantic neural search on your data in minutes.
|
||||
Check out our [demo](https://demo.qdrant.tech/)!
|
||||
---
|
||||
|
||||
In many cases, the usual full-text search does not provide the desired result.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: Advertising
|
||||
icon: ad-campaign
|
||||
custom_link_name: Article by Twitter
|
||||
custom_link: https://www.sciencedirect.com/science/article/abs/pii/S0925231217308445
|
||||
---
|
||||
|
||||
User interests cannot be described with rules, and that's where neural networks come in.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: Customer Support and Sales Optimization
|
||||
icon: customer-service
|
||||
custom_link_name: Sentence Embeddings for Customer Support
|
||||
custom_link: https://blog.floydhub.com/automate-customer-support-part-one/
|
||||
---
|
||||
|
||||
Current advances in NLP can reduce the retinue work of customer service by up to 80 percent.
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
title: E-Commerce Search
|
||||
icon: dairy-products
|
||||
weight: 30
|
||||
custom_link_name: Paper by The Home Depot
|
||||
custom_link: https://arxiv.org/abs/2104.07572
|
||||
---
|
||||
|
||||
Increase your online basket size and revenue with the AI-powered search.
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
---
|
||||
title: Face recognition
|
||||
title: Biometric identification
|
||||
icon: face-scan
|
||||
custom_link_name: Face Recognition Paper
|
||||
custom_link: https://arxiv.org/abs/1810.06951v1
|
||||
custom_link_name2: Speaker Recognition Paper
|
||||
custom_link2: https://arxiv.org/abs/2003.11982
|
||||
|
||||
---
|
||||
|
||||
Not only totalitarian states use facial recognition.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: Fashion Search
|
||||
icon: clothing
|
||||
custom_link_name: Article by Zalando
|
||||
custom_link: https://engineering.zalando.com/posts/2018/02/search-deep-neural-network.html
|
||||
---
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: Fintech
|
||||
icon: bank
|
||||
custom_link_name: Related Research
|
||||
custom_link: https://arxiv.org/abs/1808.05492
|
||||
---
|
||||
|
||||
Fraud detection is like recommendations in reverse.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: Food Discovery
|
||||
weight: 20
|
||||
icon: search
|
||||
custom_link_name: Demo
|
||||
custom_link_name: Our Demo
|
||||
custom_link: https://food-discovery.qdrant.tech
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: Law Case Search
|
||||
icon: hammer
|
||||
custom_link_name: Related Research
|
||||
custom_link: https://arxiv.org/abs/2004.12307
|
||||
---
|
||||
|
||||
The wording of court decisions can be difficult not only for ordinary people, but sometimes for the lawyers themselves.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
title: Media and Games
|
||||
icon: game-controller
|
||||
custom_link_name: Related Research
|
||||
custom_link: https://arxiv.org/abs/1803.00202
|
||||
---
|
||||
|
||||
Personalized recommendations for music, movies, games, and other entertainment content are also some sort of search.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{{- partial "header.html" . -}}
|
||||
|
||||
{{- partial "second_header.html" . -}}
|
||||
|
||||
<div id="content">
|
||||
{{ block "main" . }}{{ end }}
|
||||
</div>
|
||||
|
||||
{{- partial "footer.html" . -}}
|
||||
@@ -0,0 +1,50 @@
|
||||
{{ define "main" }}
|
||||
|
||||
<section>
|
||||
<div class="auto-container pt-5">
|
||||
<div class="row clearfix">
|
||||
|
||||
<div class="col-12 sec-title text-center">
|
||||
<h4>{{ .Params.description }}</h4>
|
||||
<div class="text">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ range .Pages }}
|
||||
|
||||
{{ $link := .Permalink }}
|
||||
{{ if .Params.external_link }}
|
||||
{{ $link = .Params.external_link }}
|
||||
{{ end }}
|
||||
|
||||
{{ if .Params.short_description }}
|
||||
|
||||
<div class="col-lg-4 col-md-6 col-sm-12">
|
||||
<!-- News block Two -->
|
||||
<div class="news-block-two">
|
||||
<div class="inner-box">
|
||||
<div class="image">
|
||||
<a href="{{ $link }}" data-caption="{{ .Title }}"><img src="{{ .Params.preview_image }}" alt="" /></a>
|
||||
</div>
|
||||
<div class="lower-box">
|
||||
<div class="clearfix">
|
||||
<h5>{{ .Title }}</h5>
|
||||
<p>{{ .Params.short_description | markdownify }}</p>
|
||||
<div class="pull-right">
|
||||
<a href="{{ $link }}" target="_blank" class="theme-btn btn-style-five btn-small">
|
||||
<span class="txt">Read</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{ end }}
|
||||
@@ -0,0 +1,10 @@
|
||||
{{ define "main" }}
|
||||
|
||||
<article>
|
||||
<div class="auto-container mb-5 mt-5">
|
||||
{{ .Content }}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
|
||||
{{ end }}
|
||||
@@ -215,7 +215,7 @@
|
||||
|
||||
|
||||
{{ with (.Site.GetPage "section" "articles") }}
|
||||
<!-- End Projects Section Two -->
|
||||
<!-- End Articles Section -->
|
||||
<section id="articles" class="projects-section-two" style="background-image: url(/images/background/pattern-9.png)">
|
||||
<div class="auto-container">
|
||||
<!-- Sec Title -->
|
||||
@@ -238,13 +238,13 @@
|
||||
<div class="inner-box wow fadeInLeft" data-wow-delay="0ms" data-wow-duration="1500ms">
|
||||
<div class="block-content">
|
||||
<div class="image">
|
||||
<a href="{{ $link }}"><img src="{{ .Params.preview_image }}" alt=""/></a>
|
||||
<a href="{{ $link }}" target="_blank"><img src="{{ .Params.preview_image }}" alt=""/></a>
|
||||
</div>
|
||||
<div class="lower-content">
|
||||
<h5><a href="{{ $link }}">{{ .Title }}</a></h5>
|
||||
<div class="text">{{ .Params.description }}
|
||||
</div>
|
||||
<a href="{{ $link }}" class="theme-btn learn-btn"><span class="txt">Learn More</span></a>
|
||||
<a href="{{ $link }}" target="_blank" class="theme-btn learn-btn"><span class="txt">Learn More</span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -254,7 +254,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- End Projects Section Two -->
|
||||
<!-- End Articles Section -->
|
||||
|
||||
{{ end }}
|
||||
|
||||
|
||||
@@ -141,11 +141,15 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Newsletter Form -->
|
||||
<p>Subscribe to our e-mail newsletter if you want to receive news about new features and applications of Qdrant.</p>
|
||||
<p>Subscribe to our e-mail newsletter if you want to be updated on new features and news regarding Qdrant.</p>
|
||||
<p>Like what we are doing? Consider giving us a ⭐ <a href="{{ .Site.Params.github }}">on Github</a>.</p>
|
||||
<div class="newsletter-form">
|
||||
<form method="post" action="contact.html">
|
||||
<form action="{{ .Site.Params.mailchimp_subscribe }}" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank" novalidate>
|
||||
<div class="form-group">
|
||||
<input type="email" name="email" value="" placeholder="Enter Your Email" required="">
|
||||
<div style="position: absolute; left: -5000px;" aria-hidden="true">
|
||||
<input type="text" name="{{ .Site.Params.mailchimp_subscribe_id }}" tabindex="-1" value="">
|
||||
</div>
|
||||
<input type="email" name="EMAIL" value="" placeholder="Enter Your Email" required="">
|
||||
<button type="submit" class="theme-btn btn-style-three"><span class="txt">Subscribe</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -29,12 +29,9 @@
|
||||
<div class="clearfix">
|
||||
<p>{{ .Params.short_description | markdownify }}</p>
|
||||
<div class="pull-right">
|
||||
{{ if .Params.default_link }}
|
||||
<a href="{{ .Params.default_link }}" class="theme-btn btn-style-five"><span
|
||||
class="txt">{{ .Params.default_link_name }}</span></a>
|
||||
{{ else }}
|
||||
<a href="#" class="theme-btn btn-style-five btn-small"><span class="txt">Learn More...</span></a>
|
||||
{{end}}
|
||||
<a href="{{ .Site.Params.mailchimp_contact_form }}" target="_blank" class="theme-btn btn-style-five btn-small">
|
||||
<span class="txt">Contact Us</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -25,10 +25,13 @@
|
||||
<div class="media-body">
|
||||
<h5 class="mt-0">{{ .Title }}</h5>
|
||||
<p>{{ .Content }}</p>
|
||||
<a target="_blank" href="#" class="float-right mr-3"><span class="txt">Learn more</span></a>
|
||||
<a target="_blank" href="{{ .Site.Params.mailchimp_contact_form }}" class="float-right mr-3"><span class="txt">Contact Us</span></a>
|
||||
{{ if .Params.custom_link }}
|
||||
<a target="_blank" href="{{ .Params.custom_link }}" class="float-right mr-3"><span class="txt">{{ .Params.custom_link_name }}</span></a>
|
||||
{{ end }}
|
||||
{{ if .Params.custom_link2 }}
|
||||
<a target="_blank" href="{{ .Params.custom_link2 }}" class="float-right mr-3"><span class="txt">{{ .Params.custom_link_name2 }}</span></a>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -84,4 +84,10 @@ QDRANT - RELATED
|
||||
|
||||
.news-icon {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
article img {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
Reference in New Issue
Block a user