DEV Community

InterSystems Developer for InterSystems

Posted on Originally published at community.intersystems.com

InterSystems for dummies – IRIS Vector Search (Part II)

After an extensive math lesson, we are going to put our new knowledge into practice and create one of the best context-related databases.

Get your magnifying glasses and hats ready, because Vector Holmes is back.

Vector Calculation

To calculate the vector associated with a text, image, or sound, we will use a Python library called sentence-transformers, which allows us to transform content into a vector.
To implement this, we should create the following function:

ClassMethod Embedding(Text) [ Language = python ]
{
    from sentence_transformers import SentenceTransformer

    model_name = 'sentence-transformers/all-MiniLM-L6-v2'

    # Cache in global variables of the embedded Python module (persistent per process).
    global _cached_embedding_model
    if '_cached_embedding_model' not in globals():
        _cached_embedding_model = SentenceTransformer(model_name)

    vector = _cached_embedding_model.encode(
        [Text],
        normalize_embeddings=True,
        convert_to_numpy=True,
        show_progress_bar=False
    )

    return str(vector[0].tolist())
}
Enter fullscreen mode Exit fullscreen mode

This method "vectorizes" the content of our text using a pre-trained library called "all-MiniLM-L6-v2". However, if you wish, you can modify it and utilize your own trained library.

First Steps


Note: To access the terminal of our Docker instance, use the following command:
Docker-compose exec iris iris session iris


Let's use an example of a vectorized search. For this, we will utilize a table called St.vectorsearch.Feeling that has the following fields:

Field Type Description
Text %String Text about how I feel
Value %Integer Identifier of my feeling (See attached table)
Vector %Vector Text vector value

You can create the data by running the following command:

Do ##class(St.vectorsearch.Data).Init()

Next, we are going to load the sentiment data from the /opt/irisbuild/data/training.csv directory, using the Populate command from the St.vectorsearch.Data class.

USER>do ##class(St.vectorsearch.Data).Populate()
Truncating table St_vectorsearch.Feeling
Preparing to load data from file training.csv
Loading data from file training.csv
Total records loaded: 2000
Calculating vectors for records...
Processing 496 of 2000
Enter fullscreen mode Exit fullscreen mode

Be patient, since it will create a vector for each record, and it might take quite a while.


Note: Populate will initialize a data model of 2000 records. There is another file with 5410 records if you want more information in the data model. If you wish to work with that one, use the PopulateFull() command instead.


The values of feelings are as follows:

Feeling Value
sadness 0
joy 1
love 2
anger 3
fear 4

For example:

“I left with my bouquet of red and yellow tulips under my arm, feeling slightly more optimistic than when I arrived.” It has a value of 1 (Joy).

“I can’t walk into a shop anywhere where I do not feel comfortable.” It has a value of 4 (Fear).

If we use this function to create vectors, the first text we pass will return the next vector:

.034666668623685836791, .012147962115705013276, .020678628236055374146, 
.043785430490970611572, .030321707949042320251, -.0081106657162308692932, 
-.028869708999991416931, -.059950094670057296752, .067945346236228942871, 
-.070874534547328948974, -.060159780085086822509, .016239311546087265014, 
-.034665744751691818237, -.011642633005976676941, .080176420509815216064, 
………
.0099751437082886695861, .0098187308758497238159, .00016082286310847848653, .013545278459787368774, -.0049553057178854942321, .054155148565769195556, .025806473568081855773, -.038503900170326232911, .039657127112150192261, 
-.073851920664310455322, -.070615962147712707519, .066068030893802642822, 
-.082378372550010681152, -.043505564332008361816, -.0054294602014124393463
Enter fullscreen mode Exit fullscreen mode

Note: The vector value has been reduced to keep the text from being too long; the vector actually has 384 values.


If we look for another cheerful text when creating the vector, it will give us the following value.

Example:

“Today, I’m very happy.”

[-0.007015716750174761, 0.05171322077512741, 0.0045058708637952805, -0.04008815810084343, 0.009171668440103531, -0.04867621883749962, 0.08268272876739502, 0.004975424613803625, -0.062008269131183624, 
……
0.036987580358982086, 0.027985544875264168, 0.05382953956723213, 0.031146947294473648, -0.011256292462348938, 0.005441852379590273]
Enter fullscreen mode Exit fullscreen mode

This data does not make sense right now because we are not going to make a value-by-value comparison. That means we are not going to look for a record with exactly the same value as the vector because each vector is different. For that, we will use the formulas explained in the first part.

We are going to use the following SQL commands to see which vectors are the closest to the vector we have calculated.

 

Dot Product Search

As we indicated in the first part, the dot product search tells us how aligned two different vectors are. That means the closer it is to 1, the closer, or rather more similar, two vectors are.

To perform our search, we will use the condition “VECTOR_DOT_PRODUCT” that compares two vectors to determine their alignment:

SELECT TOP 5 Text, Value
 FROM St_vectorsearch.Feeling
ORDER BY VECTOR_DOT_PRODUCT(Vector, TO_VECTOR('[-0.007015716750174761, 0.05171322077512741, 0.0045058708637952805, -0.04008815810084343, 0.009171668440103531, -0.04867621883749962, 
......
-0.036987580358982086, 0.027985544875264168, 0.05382953956723213, 0.031146947294473648, -0.011256292462348938, 0.005441852379590273]', float)) DESC
Enter fullscreen mode Exit fullscreen mode

This search gives us the following results:

So… “Today, I’m very happy” returns most results with a value of 1, joy.

But… what is the percentage of proximity our text has regarding the rest of the retrieved values?

We can find that out by comparing it with the current vector and displaying the value in a percentage format.

SELECT TOP 5 Text, Value, TO_CHAR(VECTOR_DOT_PRODUCT(Vector, TO_VECTOR('[-0.007015716750174761, 0.05171322077512741, 0.0045058708637952805, -0.04008815810084343, 0.009171668440103531, -0.04867621883749962, 
......
 -0.036987580358982086, 0.027985544875264168, 0.05382953956723213, 0.031146947294473648, -0.011256292462348938, 0.005441852379590273]', float)) * 100 , '990.99%') AS Percentage
 FROM St_vectorsearch.Feeling
ORDER BY VECTOR_DOT_PRODUCT(Vector, TO_VECTOR('[-0.007015716750174761, 0.05171322077512741, 0.0045058708637952805, -0.04008815810084343, 0.009171668440103531, -0.04867621883749962, 
......
-0.036987580358982086, 0.027985544875264168, 0.05382953956723213, 0.031146947294473648, -0.011256292462348938, 0.005441852379590273]', float)) DESC
Enter fullscreen mode Exit fullscreen mode

This search gives us the following results:

The percentage of similarity to the most matching text is 65%. Our mind knows that, among all the texts, the meaning of “Today, I’m very happy” is a feeling of joy, meaning it should be closer to 100% comparing to the text “I am feeling so happy”

Cosine Similarity Search

The cosine similarity search is performed using the cosine of the angle between the vectors being compared.

If we want to perform vector search with cosine similarity, we would have to utilize the condition “VECTOR_COSINE”, just as we have previously done it:

SELECT TOP 5 Text, Value, TO_CHAR(VECTOR_COSINE(Vector, TO_VECTOR('[-0.007015716750174761, 0.05171322077512741, 0.0045058708637952805, -0.04008815810084343, 0.009171668440103531, -0.04867621883749962, 
......
 -0.036987580358982086, 0.027985544875264168, 0.05382953956723213, 0.031146947294473648, -0.011256292462348938, 0.005441852379590273]', float)) * 100 , '990.99%') AS Percentage
 FROM St_vectorsearch.Feeling
ORDER BY VECTOR_COSINE(Vector, TO_VECTOR('[-0.007015716750174761, 0.05171322077512741, 0.0045058708637952805, -0.04008815810084343, 0.009171668440103531, -0.04867621883749962, 
......
-0.036987580358982086, 0.027985544875264168, 0.05382953956723213, 0.031146947294473648, -0.011256292462348938, 0.005441852379590273]', float)) DESC
Enter fullscreen mode Exit fullscreen mode

Oh… surprise!! It gave us back the same results:

As mentioned in the first part, this type of search is more advisable for comparing more than one parameter, such as movie genre, lead actor, etc. In other words, this is how the movie recommendation system of Netflix or HBO works.

Does it Support Multiple Languages?

What will happen if, instead of using the text “Today, I’m very happy,” we do it in Spanish?

“Hoy, estoy muy feliz.”

The created vector (reducing the text for obvious reasons) will resemble the following:

[-0.020053215324878693, 0.08766797184944153, 0.04118209332227707, 0.027171766385436058, -0.026769554242491722, -0.045022152364254,
…..
 0.038541924208402634, 0.018977241590619087, 0.03448185324668884, 0.10012426972389221, 0.08426760882139206, -0.09713190793991089]
Enter fullscreen mode Exit fullscreen mode

And if we use it to perform the search as we did previously, we will get the result below:

We got a combination of love (2), fear (4), and joy (1), when the meaning is identical to the English text.

This rather diverse result appeared because the word "feliz" is closer to "feel" than "happy".

As you have noticed, the creation of vectors is closer to the phonetics of the texts than to their meanings.

I recommend testing the same text in different languages.

Here you have the result of searching for it in French “Aujourd'hui, je suis très heureux”:

Therefore, if we want to use this system to search for text in a document that we have fully "tokenized", it can be solved in a very simple way.

In a nutshell, we are going to change the model we used to create the token to the model paraphrase-multilingual-MiniLM-L12-v2:

model_name = 'sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2'
Enter fullscreen mode Exit fullscreen mode

Therefore, if we modify this line in the Embedding method, we can use multilingual support in data retrieval.

This is the result of using this model with the search in Spanish:

Now, if you create a language-based token, why does it internally "translate" "Feliz" to "Happy"? Yes, I have used double quotation marks around "translate" because it is not a context-based translation like in any other online dictionary. It interprets the word by creating a token, which can cause some confusion when similar words are used.

For example, in Spanish, the word "tiempo" is the same word for the meaning of the word "time" or "weather", so the token for this word could be confusing. Fortunately, the token is not based on a single word, but on a set of words that make up the phrase. It means that the rest of the phrase's context helps us create a token much closer to the final one. Therefore, our vector seems to have translated the text we want to search for.

Case Study


Very important note: I encountered some difficulties developing these practical examples. When trying to create a vector from an IRIS production environment, the process would completely freeze.

The solution to using the St.Vectorsearch.Vector.Embedding method without any issues was to download the model locally instead of querying the online model for each request. When using the model locally, invoking this class from production did not require an Internet connection to download the model, making the vector creation process much faster.


I have created a small demo to show how a text, entered from a form, is "tokenized" and searched in the database to find the five closest results.

http://localhost:52773/csp/user/feeling.html


Note: Make sure production is running. To do this, log in to the Management Portal and start production if it was stopped.


The first time you ask about a feeling, it may take a little while, because it caches the model for future queries.

 

This website invokes an API running on IRIS that converts the text into a vector. Then it invokes the SQL search as I mentioned previously.

It performs a search for the 5 closest phrases to the calculated vector. Then the website displays those results and indicates which sentiment is repeated more often, showing the arithmetic mean of the most repeated results.

Can We Have More Than One Element to Create a Vector?

In the sentiment example, the phrase included information we used to create a search vector. However, what do we need if we have more than one column to perform the search?

If we want someone to recommend a similar movie to the one we have already seen, the result will depend on many factors. It could be related to the movie release year (because you like older films) or the genre (comedy, drama, science fiction), etc.

In this case, we will create a vector based on the text that includes the fields we want to index. This way, we will have all the fields we wish to use for our query:


Note: The order of the data is important because if we are interested in films from the same year and genre, those columns will have the most weight in the index.


If we are looking for recommendations based on the plot, genre, director, and actors, that would be the order:

“Overview: [Overview]. Genre: [Genre]. Directed by [Director] and starring by [Star1], [Star2], [Star3] and [Star4]. Movie year [Year]. Ranking: [Rating]."
Enter fullscreen mode Exit fullscreen mode

We created the St.Vectorsearch.Movie table with the following fields:
|Field|Type|Description|
|-|-|
|Link|%String|Link to the movie poster|
|Title|%String|Movie title|
|Year|%Integer|Year of the film release|
|Certificate|%String|Age classification (see attached table)|
|Runtime|%Integer|Film duration in minutes|
|Genre|%String|Film genre|
|Rating|%Decimal(3,1)|IMDb rating|
|Overview|%String|Movie description|
|Director|%String|Name of director|
|Star1|%String|Names of actors/actresses|
|Star2|%String|Names of actors/actresses|
|Star3|%String|Names of actors/actresses|
|Star4|%String|Names of actors/actresses|
|Vector|%Vector|Vector value of the film card|

To understand the age classifications better, I will show you the meaning of those values:

Certificate Description Comment
A A is Adults (equivalent to the USA R). Rating in the UK
UA UA is for ages 12 and up with parental supervision. Rating in the UK
U U is Universal (for everyone). Rating in the UK
PG-13 PG-13 (Parents Strongly Cautioned): Strong warning for parents. Some materials may be inappropriate for children under the age of 13. Current (MPA)
R R (Restricted): Those under 17 years of age must be accompanied by a parent or adult guardian because it contains adult material. Current (MPA)
PG PG (Parental Guidance Suggested): Parental guidance is suggested. Some content may not be suitable for young children. Current (MPA)
G G (General Audiences): For all audiences. Current (MPA)
PASSED The film complies with the strict moral standards of the time to be shown in cinemas. They were used between the 1930s and 1960s under the famous Hays Code.
TV-14 TV-14: Parents strongly advised. Contains material that many parents would consider inappropriate for children under the age of 14. TV Parental Guidelines
16 Not recommended for children under 16 years old. Typical numerical age classification of European or Latin American systems.
TV-MA It may be inappropriate for viewers under the age of 17  
due to graphic violence, explicit sexual activity, or crude language. TV Parental Guidelines  
UNRATED Not classified.  
GP GP: It was a temporary code used in the early 1970s. It was equivalent to what we know today as PG (Parental Guidance Suggestion).  
APPROVED The film complies with the strict moral standards of the time in order to be shown in cinemas. They were used between the 1930s and 1960s under the famous Hays Code.
TV-PG Recommended parental guidance. TV Parental Guidelines
U/A U is Universal (for everyone). Rating in the UK

In the same way that we initialized the table and loaded the data from the Feeling table, we employ the following commands:


Note: To access the terminal of our Docker instance, use the following command:
Docker-compose exec iris iris session iris


Do ##class(St.vectorsearch.Data).InitMovie()
Enter fullscreen mode Exit fullscreen mode

Next, we will load the movie data from the /opt/irisbuild/data/imdb_top_1000.csv directory, using the PopulateMovie command from the St.vectorsearch.Data class.

USER>do ##class(St.Vectorsearch.Data).PopulateMovie()
Truncating table St_Vectorsearch.Movie
Preparing to load data from file imdb_top_1000.csv
Loading data from file imdb_top_1000.csv
Total records loaded: 999
Calculating vectors for records...
Processing 116 of 999
Enter fullscreen mode Exit fullscreen mode

Note: During the demo initialization process in Docker, the test data loading operations are already performed.


In each movie, we have initialized the vector value using the phrase we have created with the different fields. I encourage everyone to make appropriate changes to the St.Vectorsearch.Data.PopulateMovie class to align with your priorities better.

As I mentioned in the first part, there is another vector approximation method (by cosine similarity), which allows us to search vectors that form the closest angle (those that are closest in the same direction).

That is why it is the best system to use if you wish to find data recommendations.

To use this, we will need the condition “VECTOR_COSINE”, which can tell us the closest angles to the indicated vector.

SELECT TOP 5 ID, Link, Title, Year
FROM St_Vectorsearch.Movie 
WHERE Title <> ? 
ORDER BY VECTOR_COSINE(Vector, TO_VECTOR(?, float)) DESC
Enter fullscreen mode Exit fullscreen mode

In this case, we are going to look for movies that approximate the angle of the vector associated with the film we have, without, of course, having it recommend itself.

You can access the demo via the link below:

http://localhost:52773/csp/user/movies.html

 

In this case, the recommended films are similar to 1988's "Akira," although I would personally put "Kôkaku Kidôtai," also known as "Ghost in the Shell," first. They are similar because they are both animated films. Mental note: I still need to watch Papurika...

So, that is all for now. You have everything you need to work with vector indices, including practical examples and an application you can use for your experiments.

Please leave any suggestions or ideas you have in the comments, including practical ways to implement this knowledge, etc...

See you at the next “InterSystems for Dummies”!

Top comments (0)