Démarrer avec la recherche hybride sémantique / plein texte avec Milvus 2.5
Dans cet article, nous vous montrerons comment utiliser rapidement la nouvelle fonction de recherche en texte intégral et la combiner avec la recherche sémantique conventionnelle basée sur les vector embeddings.
Conditions requises
Tout d'abord, assurez-vous d'avoir installé Milvus 2.5 :
pip install -U pymilvus[model]
et que vous disposez d'une instance de Milvus Standalone en cours d'exécution (par exemple, sur votre machine locale) à l'aide des instructions d'installation figurant dans la documentation de Milvus.
Construction du schéma de données et des indices de recherche
Nous importons les classes et les fonctions nécessaires :
from pymilvus import MilvusClient, DataType, Function, FunctionType, model
Vous avez peut-être remarqué deux nouvelles entrées pour Milvus 2.5, Function
et FunctionType
, que nous expliquerons bientôt.
Ensuite, nous ouvrons la base de données avec Milvus Standalone, c'est-à-dire localement, et nous créons le schéma de données. Le schéma comprend une clé primaire entière, une chaîne de texte, un vecteur dense de dimension 384 et un vecteur clairsemé (de dimensionnalité illimitée). Notez que Milvus Lite ne prend pas actuellement en charge la recherche plein texte, mais seulement Milvus Standalone et Milvus Distributed.
client = MilvusClient(uri="http://localhost:19530")
schema = client.create_schema()
schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True, auto_id=True)
schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=1000, enable_analyzer=True)
schema.add_field(field_name="dense", datatype=DataType.FLOAT_VECTOR, dim=768),
schema.add_field(field_name="sparse", datatype=DataType.SPARSE_FLOAT_VECTOR)
{'auto_id': False, 'description': '', 'fields': [{'name': 'id', 'description': '', 'type': <DataType.INT64: 5>, 'is_primary': True, 'auto_id': True}, {'name': 'text', 'description': '', 'type': <DataType.VARCHAR: 21>, 'params': {'max_length': 1000, 'enable_analyzer': True}}, {'name': 'dense', 'description': '', 'type': <DataType.FLOAT_VECTOR: 101>, 'params': {'dim': 768}}, {'name': 'sparse', 'description': '', 'type': <DataType.SPARSE_FLOAT_VECTOR: 104>}], 'enable_dynamic_field': False}
Vous avez peut-être remarqué le paramètre enable_analyzer=True
. Il indique à Milvus 2.5 d'activer l'analyseur lexical sur ce champ et de construire une liste de tokens et de fréquences de tokens, qui sont nécessaires pour la recherche en texte intégral. Le champ sparse
contiendra une représentation vectorielle de la documentation sous la forme d'un sac de mots produit à partir de l'analyse text
.
Mais comment relier les champs text
et sparse
et indiquer à Milvus comment sparse
doit être calculé à partir de text
? C'est ici que nous devons invoquer l'objet Function
et l'ajouter au schéma :
bm25_function = Function(
name="text_bm25_emb", # Function name
input_field_names=["text"], # Name of the VARCHAR field containing raw text data
output_field_names=["sparse"], # Name of the SPARSE_FLOAT_VECTOR field reserved to store generated embeddings
function_type=FunctionType.BM25,
)
schema.add_function(bm25_function)
{'auto_id': False, 'description': '', 'fields': [{'name': 'id', 'description': '', 'type': <DataType.INT64: 5>, 'is_primary': True, 'auto_id': True}, {'name': 'text', 'description': '', 'type': <DataType.VARCHAR: 21>, 'params': {'max_length': 1000, 'enable_analyzer': True}}, {'name': 'dense', 'description': '', 'type': <DataType.FLOAT_VECTOR: 101>, 'params': {'dim': 768}}, {'name': 'sparse', 'description': '', 'type': <DataType.SPARSE_FLOAT_VECTOR: 104>, 'is_function_output': True}], 'enable_dynamic_field': False, 'functions': [{'name': 'text_bm25_emb', 'description': '', 'type': <FunctionType.BM25: 1>, 'input_field_names': ['text'], 'output_field_names': ['sparse'], 'params': {}}]}
L'abstraction de l'objet Function
est plus générale que celle de l'application de la recherche en texte intégral. À l'avenir, il pourra être utilisé dans d'autres cas où un champ doit être une fonction d'un autre champ. Dans notre cas, nous spécifions que sparse
est une fonction de text
via la fonction FunctionType.BM25
. BM25
fait référence à une métrique courante dans la recherche d'informations, utilisée pour calculer la similarité d'une requête avec un document (par rapport à une collection de documents).
Nous utilisons le modèle d'intégration par défaut dans Milvus, qui est paraphrase-albert-small-v2:
embedding_fn = model.DefaultEmbeddingFunction()
L'étape suivante consiste à ajouter nos index de recherche. Nous en avons un pour le vecteur dense et un autre pour le vecteur clairsemé. Le type d'index est SPARSE_INVERTED_INDEX
avec BM25
car la recherche en texte intégral nécessite une méthode de recherche différente de celle des vecteurs denses standard.
index_params = client.prepare_index_params()
index_params.add_index(
field_name="dense",
index_type="AUTOINDEX",
metric_type="COSINE"
)
index_params.add_index(
field_name="sparse",
index_type="SPARSE_INVERTED_INDEX",
metric_type="BM25"
)
Enfin, nous créons notre collection :
client.drop_collection('demo')
client.list_collections()
[]
client.create_collection(
collection_name='demo',
schema=schema,
index_params=index_params
)
client.list_collections()
['demo']
Nous disposons ainsi d'une base de données vide configurée pour accepter des documents textuels et effectuer des recherches sémantiques et en texte intégral !
Insérer des données et effectuer une recherche plein texte
L'insertion de données ne diffère pas des versions précédentes de Milvus :
docs = [
'information retrieval is a field of study.',
'information retrieval focuses on finding relevant information in large datasets.',
'data mining and information retrieval overlap in research.'
]
embeddings = embedding_fn(docs)
client.insert('demo', [
{'text': doc, 'dense': vec} for doc, vec in zip(docs, embeddings)
])
{'insert_count': 3, 'ids': [454387371651630485, 454387371651630486, 454387371651630487], 'cost': 0}
Illustrons d'abord une recherche en texte intégral avant de passer à la recherche hybride :
search_params = {
'params': {'drop_ratio_search': 0.2},
}
results = client.search(
collection_name='demo',
data=['whats the focus of information retrieval?'],
output_fields=['text'],
anns_field='sparse',
limit=3,
search_params=search_params
)
Le paramètre de recherche drop_ratio_search
fait référence à la proportion de documents moins bien notés à abandonner au cours de l'algorithme de recherche.
Voyons les résultats :
for hit in results[0]:
print(hit)
{'id': 454387371651630485, 'distance': 1.3352930545806885, 'entity': {'text': 'information retrieval is a field of study.'}}
{'id': 454387371651630486, 'distance': 0.29726022481918335, 'entity': {'text': 'information retrieval focuses on finding relevant information in large datasets.'}}
{'id': 454387371651630487, 'distance': 0.2715056240558624, 'entity': {'text': 'data mining and information retrieval overlap in research.'}}
Recherche hybride sémantique et en texte intégral
Combinons maintenant ce que nous avons appris pour effectuer une recherche hybride qui combine des recherches sémantiques et en texte intégral distinctes avec un reranker :
from pymilvus import AnnSearchRequest, RRFRanker
query = 'whats the focus of information retrieval?'
query_dense_vector = embedding_fn([query])
search_param_1 = {
"data": query_dense_vector,
"anns_field": "dense",
"param": {
"metric_type": "COSINE",
},
"limit": 3
}
request_1 = AnnSearchRequest(**search_param_1)
search_param_2 = {
"data": [query],
"anns_field": "sparse",
"param": {
"metric_type": "BM25",
"params": {"drop_ratio_build": 0.0}
},
"limit": 3
}
request_2 = AnnSearchRequest(**search_param_2)
reqs = [request_1, request_2]
ranker = RRFRanker()
res = client.hybrid_search(
collection_name="demo",
output_fields=['text'],
reqs=reqs,
ranker=ranker,
limit=3
)
for hit in res[0]:
print(hit)
{'id': 454387371651630485, 'distance': 0.032786883413791656, 'entity': {'text': 'information retrieval is a field of study.'}}
{'id': 454387371651630486, 'distance': 0.032258063554763794, 'entity': {'text': 'information retrieval focuses on finding relevant information in large datasets.'}}
{'id': 454387371651630487, 'distance': 0.0317460335791111, 'entity': {'text': 'data mining and information retrieval overlap in research.'}}
Comme vous l'avez peut-être remarqué, cela ne diffère pas d'une recherche hybride avec deux champs sémantiques distincts (disponible depuis Milvus 2.4). Les résultats sont identiques à ceux de la recherche en texte intégral dans cet exemple simple, mais pour les bases de données plus importantes et les recherches par mot-clé, la recherche hybride a généralement un taux de rappel plus élevé.
Résumé
Vous disposez désormais de toutes les connaissances nécessaires pour effectuer des recherches en texte intégral et des recherches hybrides sémantique/texte intégral avec Milvus 2.5. Voir les articles suivants pour plus de détails sur le fonctionnement de la recherche en texte intégral et la raison pour laquelle elle est complémentaire de la recherche sémantique :
- Conditions requises
- Construction du schéma de données et des indices de recherche
- Insérer des données et effectuer une recherche plein texte
- Recherche hybride sémantique et en texte intégral
- Résumé
On This Page
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word