🚀 Prova Zilliz Cloud, la versione completamente gestita di Milvus, gratuitamente—sperimenta prestazioni 10 volte più veloci! Prova Ora>>

milvus-logo
LFAI

HomeBlogsCome iniziare con la ricerca semantica ibrida / full text con Milvus 2.5

Come iniziare con la ricerca semantica ibrida / full text con Milvus 2.5

  • Engineering
December 17, 2024
Stefan Webb

In questo articolo vi mostreremo come utilizzare rapidamente la nuova funzione di ricerca full-text e come combinarla con la ricerca semantica convenzionale basata su embeddings vettoriali.

Requisiti

Per prima cosa, assicuratevi di aver installato Milvus 2.5:

pip install -U pymilvus[model]

e avere un'istanza funzionante di Milvus Standalone (ad esempio, sul computer locale) utilizzando le istruzioni di installazione contenute nei documenti di Milvus.

Creare lo schema dei dati e gli indici di ricerca

Importiamo le classi e le funzioni necessarie:

from pymilvus import MilvusClient, DataType, Function, FunctionType, model

Avrete notato due nuove voci per Milvus 2.5, Function e FunctionType, che spiegheremo tra poco.

Quindi apriamo il database con Milvus Standalone, cioè in locale, e creiamo lo schema dei dati. Lo schema comprende una chiave primaria intera, una stringa di testo, un vettore denso di dimensione 384 e un vettore sparso (di dimensione illimitata). Si noti che Milvus Lite non supporta attualmente la ricerca full-text, ma solo Milvus Standalone e 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}

Avrete notato il parametro enable_analyzer=True. Questo parametro indica a Milvus 2.5 di abilitare il parser lessicale su questo campo e di creare un elenco di token e di frequenze di token, necessari per la ricerca full-text. Il campo sparse conterrà una rappresentazione vettoriale della documentazione come bag-of-words prodotta dal parsing text.

Ma come possiamo collegare i campi text e sparse e dire a Milvus come sparse deve essere calcolato da text? È qui che dobbiamo richiamare l'oggetto Function e aggiungerlo allo schema:

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'astrazione dell'oggetto Function è più generale rispetto all'applicazione della ricerca full-text. In futuro, potrà essere utilizzato per altri casi in cui un campo deve essere una funzione di un altro campo. Nel nostro caso, specifichiamo che sparse è una funzione di text tramite la funzione FunctionType.BM25. BM25 si riferisce a una metrica comune nel reperimento di informazioni, utilizzata per calcolare la somiglianza di una query con un documento (rispetto a un insieme di documenti).

Utilizziamo il modello di incorporamento predefinito di Milvus, che è parafrasi-alberto-piccolo-v2:

embedding_fn = model.DefaultEmbeddingFunction()

Il passo successivo è aggiungere i nostri indici di ricerca. Ne abbiamo uno per il vettore denso e uno separato per il vettore rado. Il tipo di indice è SPARSE_INVERTED_INDEX con BM25, poiché la ricerca full-text richiede un metodo di ricerca diverso da quello dei vettori densi 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"
)

Infine, creiamo la nostra collezione:

client.drop_collection('demo')
client.list_collections()
[]
client.create_collection(
    collection_name='demo', 
    schema=schema, 
    index_params=index_params
)

client.list_collections()
['demo']

E con ciò, abbiamo un database vuoto impostato per accettare documenti di testo ed eseguire ricerche semantiche e full-text!

L'inserimento dei dati non è diverso dalle versioni precedenti di 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}

Illustriamo innanzitutto una ricerca full-text prima di passare alla ricerca ibrida:

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
)

Il parametro di ricerca drop_ratio_search si riferisce alla percentuale di documenti con punteggio inferiore da eliminare durante l'algoritmo di ricerca.

Vediamo i risultati:

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.'}}

Combiniamo ora quanto appreso per eseguire una ricerca ibrida che combina ricerche semantiche e full-text separate con 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.'}}

Come avrete notato, non è diversa da una ricerca ibrida con due campi semantici separati (disponibile da Milvus 2.4). I risultati sono identici a quelli della ricerca full-text in questo semplice esempio, ma per database più grandi e ricerche specifiche per parole chiave la ricerca ibrida ha in genere un richiamo più elevato.

Riassunto

Ora avete tutte le conoscenze necessarie per eseguire ricerche full-text e ibride semantiche/full-text con Milvus 2.5. Per ulteriori informazioni su come funziona la ricerca full-text e perché è complementare alla ricerca semantica, consultare i seguenti articoli:

Like the article? Spread the word

Continua a Leggere