Understanding Vector-Based Recommendation Systems
One of the best ways that we have to personalize content is by using vector embeddings. When we have a bunch of data that we capture about the user, but aren’t able to match the data with a direct text search, we can embed the meaning of the data into a vector of dimensions.
What does this mean? We can use soemthing called an embedding model to represent text as a series of numbers. Each entity that is in our datastore will have a similar series of numbers. The number of numbers that we use to represent the meaning of the text is called the dimensionality of the embedding.
There is a trade off when it comes to dimensionality. The more dimensions we use, the more accurate our embedding will be. However, the more dimensions we use, the more computationally expensive it will be to search for similar entities.
Once we represent our data this way, we can use a technique called vector search to find entries that are similar in meaning to a given query.
How Vector Embeddings Work
Here’s an example of how an event might be represented as a vector:
const event =
'A scenic park offering walking trails, a playground, and a picnic area.';
const embedding = await getEmbedding(event); // returns a vector of 300 dimensions
console.log(embedding); // [0.2, 0.8, -0.3, ...]
This vector representation captures the semantic nature of the event. A query that describes “outdoor family activity” should align closely with this vector if the embedding model understands the concepts.
The position of these vectors in a high-dimensional space shows us semantic relationships - similar items will be closer together. We use something called cosine similarity to measure the similarity between two vectors in most cases. There are other ways to measure similarity, but cosine similarity is a good default.
Demonstrating Vector Similarity
I’ll share a basic demonstration of how vector similarity works by embedding a list of events into a vector space. We’ll use these embeddings to find similar events to a given query.
The description of each event is embedded into a vector space. This is occurring within your browser behind the scenes, using transformers.js
and rxdb
.
Basic Search vs. Weighted Search
Below, you will find a sample search component that will demonstrate how we can use vector similarity to find events that are a good suggestion for us.
In addition to a string, you can simulate some preferences that we might store about the user in question. These preferences might be inferred from collected data, provided by the user, or obtained via various other means.
There are two search modes you’ll find:
- Basic Search: Find events with a similar meaning to the user’s query.
- Weighted Search: Find events with a similar meaning to the user’s query, but also take into account the user’s preference data.
How Weighted Search Works
For each of the attirbutes that we collect about the user, we embed a small description of the attribute in question into the vector space. For example, for “noisy,” we embed “loud noisy bustling sound music not-family-friendly” into the vector space.
When we search for events, we use cosine similarity to find events that are similar to the user’s query. Then, we consider the selected preferences, and determine how close the preference is to the event.
For each preference, the weighted similarity score is applied to the event’s original similarity score. The final score is the sum of the original similarity score and the weighted similarity score.
Here’s what the math looks like:
Some weights are positive - for example, the default behavior is that we’re looking for events that are similar to a chosen preference. So, the similarity score is added to the final score.
But, some weights are negative - for example, when we choose one of the “avoid” preferences, we actually subtract the similarity score from the final score.
It’s all about the data
While the above example is simple, it shows how powerful this technique can be. Vector databases like Milvus are a good option for handling billions or vectors, and even filter vectors with metadata.
Ultimately though, this technique will only get you as far as the embeddings you use. The better the embeddings, the better the results. That’s why an extremely perfomant data pipeline is essential to high-performance recommendation systems.