Strange results for cosine similarity / semantic search
I am trying to use the model in a semantic search scenario. In my experiments where embeddings are indexed into Elasticsearch and searched with a knn
query, the results are mostly not relevant.
I have created an isolated demonstration which computes cosine distance between a query and a document:
import sentence_transformers
model = sentence_transformers.SentenceTransformer("Seznam/simcse-dist-mpnet-paracrawl-cs-en")
embeddings = [
model.encode("Čím se zabývá fyzika?"),
model.encode("Fyzika (z řeckého φυσικός (fysikos): přírodní, ze základu φύσις (fysis): příroda, přirozenost, archaicky též silozpyt) je exaktní vědní obor, který zkoumá zákonitosti přírodních jevů. Popisuje vlastnosti a projevy hmoty, antihmoty, vakua, přírodních sil, světla i neviditelného záření, tepla, zvuku atd. Vztahy mezi těmito objekty fyzika obvykle vyjadřuje matematickými prostředky. Mnoho poznatků fyziky je úspěšně aplikováno v praxi, což významně přispívá k rozvoji civilizace."),
]
sentence_transformers.util.cos_sim(embeddings[0], embeddings[1]).tolist()
# => [[0.12196816504001617]]
The first input is the query, the second input is a small passage from Wikipedia. The result is a really low similarity, 0.12.
When I try to compute similarity across a larger dataset, like here: karmiq/wikipedia-embeddings-cs-seznam-mpnet (expand "Use sentence_transformers.util.semantic_search"), the results are also mostly irrelevant. For the query Čím se zabývá fyzika?, I'm getting results like this:
# [0.58] Dynamika Fyzikální zákony [Newtonovy pohybové zákony]
# [0.53] Teorie množin [Ordinální číslo]
# [0.52] Fyzika částic Fyzikální jevy [Rozpad částice]
# [0.52] Druhy vlaků [Násled]
# [0.51] Zkratky [PKP]
# ...
Note that for eg. the intfloat/multilingual-e5-small I'm getting much better results for a corresponding dataset: karmiq/wikipedia-embeddings-cs-e5-small:
# [0.90] Fyzika částic ( též částicová fyzika ) je oblast fyziky, která se zabývá částicemi. V širším smyslu… [Fyzika částic]
# [0.89] Fyzika ( z řeckého φυσικός ( fysikos ): přírodní, ze základu φύσις ( fysis ): příroda, archaicky… [Fyzika]
# [0.89] Molekulová fyzika ( též molekulární fyzika ) je část fyziky, která zkoumá látky na úrovni atomů a… [Molekulová fyzika]
# [0.88] Jaderná fyzika ( též fyzika atomového jádra nebo nukleonika ) je část fyziky, která se zabývá… [Jaderná fyzika]
# [0.88] Atomová, molekulová a optická fyzika ( AMO ) se zabývá studiem interakcí mezi hmotou a hmotou,… [Atomová, molekulová a optická fyzika]
# ...
Do you have any idea what I might be doing wrong here? Do you have any pointers how to use the model for semantic search?
Hi
@karmiq
,
I am not very familiar with sentence_transformers
library, but when I run the code using standard transformers
library as in the How to use Section
in https://huggingface.co./Seznam/dist-mpnet-paracrawl-cs-en such as:
import torch
from transformers import AutoModel, AutoTokenizer
model_name = "Seznam/simcse-dist-mpnet-paracrawl-cs-en" # Hugging Face link
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModel.from_pretrained(model_name)
def compute_similarity(text1, text2):
# Tokenize the input texts
batch_dict = tokenizer([text1, text2], max_length=512, padding=True, truncation=True, return_tensors='pt')
outputs = model(**batch_dict)
embeddings = outputs.last_hidden_state[:, 0] # Extract CLS token embeddings
similarity = torch.nn.functional.cosine_similarity(embeddings[0], embeddings[1], dim=0)
return similarity.item()
query = "Čím se zabývá fyzika?"
candidates = [
"Fyzika (z řeckého φυσικός (fysikos): přírodní, ze základu φύσις (fysis): příroda, přirozenost, archaicky též silozpyt) je exaktní vědní obor, který zkoumá zákonitosti přírodních jevů. Popisuje vlastnosti a projevy hmoty, antihmoty, vakua, přírodních sil, světla i neviditelného záření, tepla, zvuku atd. Vztahy mezi těmito objekty fyzika obvykle vyjadřuje matematickými prostředky. Mnoho poznatků fyziky je úspěšně aplikováno v praxi, což významně přispívá k rozvoji civilizace.",
"Dynamika Fyzikální zákony [Newtonovy pohybové zákony]",
"Teorie množin [Ordinální číslo]",
"Zkratky [PKP]"
]
for candidate in candidates:
print(candidate)
print(compute_similarity(query, candidate))
I get more reasonable results:
Fyzika (z řeckého φυσικός (fysikos): přírodní, ze základu φύσις (fysis): příroda, přirozenost, archaicky též silozpyt) je exaktní vědní obor, který zkoumá zákonitosti přírodních jevů. Popisuje vlastnosti a projevy hmoty, antihmoty, vakua, přírodních sil, světla i neviditelného záření, tepla, zvuku atd. Vztahy mezi těmito objekty fyzika obvykle vyjadřuje matematickými prostředky. Mnoho poznatků fyziky je úspěšně aplikováno v praxi, což významně přispívá k rozvoji civilizace.
0.6310943365097046
Dynamika Fyzikální zákony [Newtonovy pohybové zákony]
0.5915541052818298
Teorie množin [Ordinální číslo]
0.2819131910800934
Zkratky [PKP]
0.06636099517345428
One of the issues of using sentence_transformers
might be that it loads tokenizer badly. Could you please check what is the output of the tokenizer that sentence_transformers use
for:
text1 = "Čím se zabývá fyzika?"
text2 = "Fyzika (z řeckého φυσικός (fysikos): přírodní, ze základu φύσις (fysis): příroda, přirozenost, archaicky též silozpyt) je exaktní vědní obor, který zkoumá zákonitosti přírodních jevů. Popisuje vlastnosti a projevy hmoty, antihmoty, vakua, přírodních sil, světla i neviditelného záření, tepla, zvuku atd. Vztahy mezi těmito objekty fyzika obvykle vyjadřuje matematickými prostředky. Mnoho poznatků fyziky je úspěšně aplikováno v praxi, což významně přispívá k rozvoji civilizace."
batch_dict = tokenizer([text1, text2], max_length=512, padding=True, truncation=True, return_tensors='pt')
print(batch_dict)
I get:
{'input_ids': tensor([[ 101, 32693, 7367, 38819, 52191, 1029, 102, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0],
[ 101, 52191, 1006, 1062, 56142, 6806, 100, 1006, 32156, 5332,
15710, 1007, 1024, 33750, 1010, 27838, 43953, 100, 1006, 32156,
6190, 1007, 1024, 38851, 1010, 34709, 30668, 1010, 19261, 4801,
34610, 31327, 18153, 7685, 2102, 1007, 15333, 4654, 4817, 30626,
40107, 2078, 30553, 35854, 1010, 30720, 53612, 55693, 31895, 40157,
51460, 1012, 38936, 33927, 1037, 43187, 40597, 1010, 3424, 54689,
3723, 1010, 42287, 2050, 1010, 40157, 31327, 1010, 34445, 1045,
45819, 39346, 40510, 1010, 36955, 1010, 41493, 32582, 1012, 35065,
30826, 38256, 37260, 52191, 34135, 45606, 35707, 33354, 33633, 1012,
31962, 48899, 51842, 15333, 37805, 31745, 44512, 1058, 34958, 1010,
31434, 42682, 43056, 1047, 41032, 48519, 1012, 102]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])}
@karmiq
hey - direct loading using the SentenceTransformer class does not work because the sentence model is created with the MEAN pooling by default. Our models work with CLS pooling.
You can load it like this:
import sentence_transformers
from sentence_transformers.models import Transformer, Pooling
embedding_model = Transformer("Seznam/simcse-dist-mpnet-paracrawl-cs-en")
pooling = Pooling(word_embedding_dimension=embedding_model.get_word_embedding_dimension(), pooling_mode="cls")
model = sentence_transformers.SentenceTransformer(modules=[embedding_model, pooling])
Thanks for the replies, @arahusky and @nekoboost !
The tokenizer issue is a good hunch, this is the output I'm seeing:
texts = [
"Čím se zabývá fyzika?",
"Fyzika (z řeckého φυσικός (fysikos): přírodní, ze základu φύσις (fysis): příroda, přirozenost, archaicky též silozpyt) je exaktní vědní obor, který zkoumá zákonitosti přírodních jevů. Popisuje vlastnosti a projevy hmoty, antihmoty, vakua, přírodních sil, světla i neviditelného záření, tepla, zvuku atd. Vztahy mezi těmito objekty fyzika obvykle vyjadřuje matematickými prostředky. Mnoho poznatků fyziky je úspěšně aplikováno v praxi, což významně přispívá k rozvoji civilizace.",
]
for text in texts:
print(model.tokenizer(text, padding=True, truncation=True, max_length=512, return_tensors='pt'))
print('-'*80)
# {'input_ids': tensor([[ 101, 32693, 7367, 38819, 52191, 1029, 102]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1]])}
# --------------------------------------------------------------------------------
# {'input_ids': tensor([[ 101, 52191, 1006, 1062, 56142, 6806, 100, 1006, 32156, 5332,
# 15710, 1007, 1024, 33750, 1010, 27838, 43953, 100, 1006, 32156,
# 6190, 1007, 1024, 38851, 1010, 34709, 30668, 1010, 19261, 4801,
# 34610, 31327, 18153, 7685, 2102, 1007, 15333, 4654, 4817, 30626,
# 40107, 2078, 30553, 35854, 1010, 30720, 53612, 55693, 31895, 40157,
# 51460, 1012, 38936, 33927, 1037, 43187, 40597, 1010, 3424, 54689,
# 3723, 1010, 42287, 2050, 1010, 40157, 31327, 1010, 34445, 1045,
# 45819, 39346, 40510, 1010, 36955, 1010, 41493, 32582, 1012, 35065,
# 30826, 38256, 37260, 52191, 34135, 45606, 35707, 33354, 33633, 1012,
# 31962, 48899, 51842, 15333, 37805, 31745, 44512, 1058, 34958, 1010,
# 31434, 42682, 43056, 1047, 41032, 48519, 1012, 102]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
# 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
# 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
# 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
# 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
# 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
# 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
# 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
# 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]])}
# --------------------------------------------------------------------------------
At least in the first item, it doesn't look like it's returning the padding tokens, otherwise it returns the same tokens, at least in my limited understanding.
@nekoboost , thanks for the clarification! When I adjust the isolated example to use the code you've provided, the score does improve significantly:
texts = [
"Čím se zabývá fyzika?",
"Fyzika (z řeckého φυσικός (fysikos): přírodní, ze základu φύσις (fysis): příroda, přirozenost, archaicky též silozpyt) je exaktní vědní obor, který zkoumá zákonitosti přírodních jevů. Popisuje vlastnosti a projevy hmoty, antihmoty, vakua, přírodních sil, světla i neviditelného záření, tepla, zvuku atd. Vztahy mezi těmito objekty fyzika obvykle vyjadřuje matematickými prostředky. Mnoho poznatků fyziky je úspěšně aplikováno v praxi, což významně přispívá k rozvoji civilizace.",
]
import sentence_transformers
from sentence_transformers.models import Transformer, Pooling
embedding_model = Transformer("Seznam/simcse-dist-mpnet-paracrawl-cs-en")
pooling = Pooling(word_embedding_dimension=embedding_model.get_word_embedding_dimension(), pooling_mode="cls")
model = sentence_transformers.SentenceTransformer(modules=[embedding_model, pooling])
embeddings = [ model.encode(text) for text in texts]
sentence_transformers.util.cos_sim(embeddings[0], embeddings[1]).tolist()
# [[0.6310944557189941]]
When I run the example I have in the README, on the whole Wikipedia dataset, I'm still getting irrelevant results — that's expected, though, due to the incorrect initialization of the model when computing the corpus embeddings. I'll regenerate the embeddings with the new code and will report the results.
I've regenerated the embeddings using the new code from
@nekoboost
(649bf8f
), and the example from the README now works much better:
# ...
query = "Čím se zabývá fyzika?"
# ...
[0.72] Molekulová fyzika ( též molekulární fyzika ) je část fyziky, která zkoumá látky na úrovni atomů a… [Molekulová fyzika]
[0.70] Fyzika ( z řeckého φυσικός ( fysikos ): přírodní, ze základu φύσις ( fysis ): příroda, archaicky… [Fyzika]
[0.69] Experimentální fyzika je spolu s teoretickou fyzikou jednou ze dvou hlavních metod zkoumání fyziky.… [Experimentální fyzika]
[0.69] Mechanika je obor fyziky, který se zabývá mechanickým pohybem, tedy přemísťováním těles v prostoru… [Mechanika]
[0.69] Matematická fyzika je vědecká disciplína zabývající se aplikací matematiky k řešení fyzikálních… [Matematická fyzika]
[0.68] Fyzika částic Fyzikální jevy [Rozpad částice]
[0.68] Teoretická fyzika se snaží racionálně, často pomocí matematických vztahů, vysvětlit fyzikální jevy… [Teoretická fyzika]
[0.68] Fyzika částic ( též částicová fyzika ) je oblast fyziky, která se zabývá částicemi. V širším smyslu… [Fyzika částic]
[0.66] Fyzika pevných látek je část fyziky, která studuje makroskopické vlastnosti pevných látek, přičemž… [Fyzika pevných látek]
[0.66] Klasická fyzika je označení pro starší fyzikální teorie, zejména ty popsané mezi koncem 17. a… [Klasická fyzika]
I'll try the model in a demo application I have.
I've plugged the model into a demo application I have (semantic search with Elasticsearch, code here, demo here), and the results are indeed much better than previously, where they have been almost 100% not relevant.
For a query Co je to staroměstský orloj?
, I'm getting these results with size=10:
* Oldřišov
* Označování ulic a veřejných prostranství
* Spálov (okres Nový Jičín)
* Bedřichov (Oskava)
* Osiky
* Nesuchyně
* Zvole (okres Šumperk)
* Oldřiš
* Pohořelice
* Lukovany
It seems to return geographically related concepts, but almost all results are unrelated to the specific query, and it's not able to match the best hit (page Staroměstský orloj).
With the intfloat/multilingual-e5-small
model, I'm getting these results:
* Staroměstský orloj
* Orloj
* Pokojový orloj Jana Maška
* Pohádkový orloj v Ostravě
* Jakub Čech (hodinář)
* Slovenský orloj
* Mistr Hanuš
* Olomoucký orloj
* Chmelový orloj
* Hvězdný čas
If I try a query with "vocabulary mismatch" problem, velké staré hodiny pro turisty v Praze
, I'm getting results like this:
* Buquoyský palác (Staré Město)
* Seznam představitelů Starého Města pražského
* Pomník mistra Jana Husa
* Palác pánů z Kunštátu a Poděbrad
* Krocínova kašna
* Dům U Vejvodů
* Komořany (zámek)
* Lidový dům (Hybernská)
* Královská cesta
* Dům U Voříkovských
The e5-small
model struggles with this query as well, where eg. the e5-large
model seems to get it quite right:
* Veřejné hodiny v Praze
* Hodiny na České
* Staroměstský orloj
* Patrik Pařízek
* Gnómón
* Brněnský orloj
* Pražský poledník
* Přesýpací hodiny
* Kinského zahrada
* Pražský metronom
The good news is that it seems like the huggingface/text-embeddings-inference
project produces the same results, even though it loads the model with the Rust-based Candle library. The only important thing is to pass the cls
pooling setting, as you have suggested:
text-embeddings-router --model-id Seznam/simcse-dist-mpnet-paracrawl-cs-en --pooling cls
Hi, iam getting different scores - make sure you do NOT use any prefixes (e.g. "query {query}"), also check that the cosine sim is actually calculated, not the Euclidean or dot product
Co je to staroměstský orloj?
Oldřišov similarity: 0.5156916975975037
Staroměstský orloj similarity: 0.855506181716919
@nekoboost , thanks! Could you please share the code you've used?
I've double-checked in the code that the prefixes are not used for this model (they should apply only to the E5 models), and will run some manual testing (fresh embedding, than similarity matches) on my end.
Your code above produces the same results c :
texts = [
"Co je to staroměstský orloj?",
"Staroměstský orloj",
]
import sentence_transformers
from sentence_transformers.models import Transformer, Pooling
embedding_model = Transformer("Seznam/simcse-dist-mpnet-paracrawl-cs-en")
pooling = Pooling(word_embedding_dimension=embedding_model.get_word_embedding_dimension(), pooling_mode="cls")
model = sentence_transformers.SentenceTransformer(modules=[embedding_model, pooling])
embeddings = [ model.encode(text) for text in texts]
sentence_transformers.util.cos_sim(embeddings[0], embeddings[1]).tolist()
[[0.855506181716919]]