Búsqueda híbrida multivectorial
En muchas aplicaciones, un objeto puede buscarse mediante un rico conjunto de información, como el título y la descripción, o con múltiples modalidades, como texto, imágenes y audio. Por ejemplo, un tuit con un fragmento de texto y una imagen se buscará si el texto o la imagen coinciden con la semántica de la consulta de búsqueda. La búsqueda híbrida mejora la experiencia de búsqueda combinando búsquedas en estos diversos campos. Milvus lo soporta permitiendo la búsqueda en múltiples campos vectoriales, realizando simultáneamente varias búsquedas de Vecino más Cercano Aproximado (RNA). La búsqueda híbrida multivectorial es especialmente útil si desea buscar tanto texto como imágenes, varios campos de texto que describan el mismo objeto o vectores densos y dispersos para mejorar la calidad de la búsqueda.
Flujo de trabajo de la búsqueda híbrida
La búsqueda híbrida multivectorial integra diferentes métodos de búsqueda o abarca incrustaciones de varias modalidades:
Búsqueda devectores dispersos-densos: Los vectores densos son excelentes para captar las relaciones semánticas, mientras que los vectores dispersos son muy eficaces para la concordancia precisa de palabras clave. La búsqueda híbrida combina estos enfoques para proporcionar tanto una amplia comprensión conceptual como la relevancia exacta de los términos, mejorando así los resultados de la búsqueda. Al aprovechar los puntos fuertes de cada método, la búsqueda híbrida supera las limitaciones de los enfoques individuales y ofrece un mejor rendimiento para consultas complejas. Aquí encontrará una guía más detallada sobre la recuperación híbrida que combina la búsqueda semántica con la búsqueda de texto completo.
Búsqueda vectorial multimodal: La búsqueda vectorial multimodal es una potente técnica que permite buscar en varios tipos de datos, como texto, imágenes, audio y otros. La principal ventaja de este enfoque es su capacidad para unificar diferentes modalidades en una experiencia de búsqueda fluida y cohesionada. Por ejemplo, en la búsqueda de productos, un usuario puede introducir una consulta de texto para encontrar productos descritos con texto e imágenes. Al combinar estas modalidades mediante un método de búsqueda híbrido, se puede mejorar la precisión de la búsqueda o enriquecer los resultados de la misma.
Ejemplo
Consideremos un caso de uso real en el que cada producto incluye una descripción de texto y una imagen. A partir de los datos disponibles, podemos realizar tres tipos de búsqueda:
Búsqueda semántica de texto: Se trata de consultar la descripción textual del producto utilizando vectores densos. Los vectores de texto pueden generarse utilizando modelos como BERT y Transformers o servicios como OpenAI.
Búsqueda de texto completo: En este caso, consultamos la descripción textual del producto utilizando una coincidencia de palabras clave con vectores dispersos. Para ello se pueden utilizar algoritmos como BM25 o modelos de incrustación dispersa como BGE-M3 o SPLADE.
Búsqueda multimodal de imágenes: Este método consulta la imagen utilizando una consulta de texto con vectores densos. Las incrustaciones de imágenes pueden generarse con modelos como CLIP.
Esta guía le guiará a través de un ejemplo de búsqueda híbrida multimodal que combina los métodos de búsqueda anteriores, dada la descripción de texto sin procesar y las incrustaciones de imagen de los productos. Demostraremos cómo almacenar datos multivectoriales y realizar búsquedas híbridas con una estrategia de reordenación.
Crear una colección con múltiples campos vectoriales
El proceso de creación de una colección implica tres pasos clave: definir el esquema de la colección, configurar los parámetros del índice y crear la colección.
Definir el esquema
Para la búsqueda híbrida multivectorial, debemos definir múltiples campos vectoriales dentro de un esquema de colección. Por defecto, cada colección puede alojar hasta 4 campos vectoriales. Sin embargo, si es necesario, puede ajustar el proxy.maxVectorFieldNum para incluir hasta 10 campos vectoriales en una colección según sea necesario.
Este ejemplo incorpora los siguientes campos al esquema:
id: Sirve como clave primaria para almacenar los ID de texto. Este campo es del tipo de datosINT64.text: Sirve para almacenar contenido textual. Este campo es del tipo de datosVARCHARcon una longitud máxima de 1000 bytes. La opciónenable_analyzerse establece enTruepara facilitar la búsqueda de texto completo.text_dense: Se utiliza para almacenar vectores densos de los textos. Este campo es del tipo de datosFLOAT_VECTORcon una dimensión vectorial de 768.text_sparse: Se utiliza para almacenar vectores dispersos de los textos. Este campo es del tipoSPARSE_FLOAT_VECTOR.image_dense: Se utiliza para almacenar vectores densos de imágenes de productos. Este campo es del tipoFLOAT_VETORcon una dimensión vectorial de 512.
Dado que utilizaremos el algoritmo integrado BM25 para realizar una búsqueda de texto completo en el campo de texto, es necesario añadir el Milvus Function al esquema. Para más detalles, consulte Búsqueda de texto completo.
from pymilvus import (
MilvusClient, DataType, Function, FunctionType
)
client = MilvusClient(
uri="http://localhost:19530",
token="root:Milvus"
)
# Init schema with auto_id disabled
schema = MilvusClient.create_schema(auto_id=False)
# Add fields to schema
schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True, description="product id")
schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=1000, enable_analyzer=True, description="raw text of product description")
schema.add_field(field_name="text_dense", datatype=DataType.FLOAT_VECTOR, dim=768, description="text dense embedding")
schema.add_field(field_name="text_sparse", datatype=DataType.SPARSE_FLOAT_VECTOR, description="text sparse embedding auto-generated by the built-in BM25 function")
schema.add_field(field_name="image_dense", datatype=DataType.FLOAT_VECTOR, dim=512, description="image dense embedding")
# Add function to schema
bm25_function = Function(
name="text_bm25_emb",
input_field_names=["text"],
output_field_names=["text_sparse"],
function_type=FunctionType.BM25,
)
schema.add_function(bm25_function)
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.common.DataType;
import io.milvus.common.clientenum.FunctionType;
import io.milvus.v2.service.collection.request.AddFieldReq;
import io.milvus.v2.service.collection.request.CreateCollectionReq;
import io.milvus.v2.service.collection.request.CreateCollectionReq.Function;
import java.util.*;
MilvusClientV2 client = new MilvusClientV2(ConnectConfig.builder()
.uri("http://localhost:19530")
.token("root:Milvus")
.build());
CreateCollectionReq.CollectionSchema schema = client.createSchema();
schema.addField(AddFieldReq.builder()
.fieldName("id")
.dataType(DataType.Int64)
.isPrimaryKey(true)
.autoID(false)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("text")
.dataType(DataType.VarChar)
.maxLength(1000)
.enableAnalyzer(true)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("text_dense")
.dataType(DataType.FloatVector)
.dimension(768)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("text_sparse")
.dataType(DataType.SparseFloatVector)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("image_dense")
.dataType(DataType.FloatVector)
.dimension(512)
.build());
schema.addFunction(Function.builder()
.functionType(FunctionType.BM25)
.name("text_bm25_emb")
.inputFieldNames(Collections.singletonList("text"))
.outputFieldNames(Collections.singletonList("text_sparse"))
.build());
import (
"context"
"fmt"
"github.com/milvus-io/milvus/client/v2/column"
"github.com/milvus-io/milvus/client/v2/entity"
"github.com/milvus-io/milvus/client/v2/index"
"github.com/milvus-io/milvus/client/v2/milvusclient"
)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
milvusAddr := "localhost:19530"
client, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
Address: milvusAddr,
})
if err != nil {
fmt.Println(err.Error())
// handle error
}
defer client.Close(ctx)
function := entity.NewFunction().
WithName("text_bm25_emb").
WithInputFields("text").
WithOutputFields("text_sparse").
WithType(entity.FunctionTypeBM25)
schema := entity.NewSchema()
schema.WithField(entity.NewField().
WithName("id").
WithDataType(entity.FieldTypeInt64).
WithIsPrimaryKey(true),
).WithField(entity.NewField().
WithName("text").
WithDataType(entity.FieldTypeVarChar).
WithEnableAnalyzer(true).
WithMaxLength(1000),
).WithField(entity.NewField().
WithName("text_dense").
WithDataType(entity.FieldTypeFloatVector).
WithDim(768),
).WithField(entity.NewField().
WithName("text_sparse").
WithDataType(entity.FieldTypeSparseVector),
).WithField(entity.NewField().
WithName("image_dense").
WithDataType(entity.FieldTypeFloatVector).
WithDim(512),
).WithFunction(function)
import { MilvusClient, DataType } from "@zilliz/milvus2-sdk-node";
const address = "http://localhost:19530";
const token = "root:Milvus";
const client = new MilvusClient({address, token});
// Define fields
const fields = [
{
name: "id",
data_type: DataType.Int64,
is_primary_key: true,
auto_id: false
},
{
name: "text",
data_type: DataType.VarChar,
max_length: 1000,
enable_match: true
},
{
name: "text_dense",
data_type: DataType.FloatVector,
dim: 768
},
{
name: "text_sparse",
data_type: DataType.SPARSE_FLOAT_VECTOR
},
{
name: "image_dense",
data_type: DataType.FloatVector,
dim: 512
}
];
// define function
const functions = [
{
name: "text_bm25_emb",
description: "text bm25 function",
type: FunctionType.BM25,
input_field_names: ["text"],
output_field_names: ["text_sparse"],
params: {},
},
];
export bm25Function='{
"name": "text_bm25_emb",
"type": "BM25",
"inputFieldNames": ["text"],
"outputFieldNames": ["text_sparse"],
"params": {}
}'
export schema='{
"autoId": false,
"functions": [$bm25Function],
"fields": [
{
"fieldName": "id",
"dataType": "Int64",
"isPrimary": true
},
{
"fieldName": "text",
"dataType": "VarChar",
"elementTypeParams": {
"max_length": 1000,
"enable_analyzer": true
}
},
{
"fieldName": "text_dense",
"dataType": "FloatVector",
"elementTypeParams": {
"dim": "768"
}
},
{
"fieldName": "text_sparse",
"dataType": "SparseFloatVector"
},
{
"fieldName": "image_dense",
"dataType": "FloatVector",
"elementTypeParams": {
"dim": "512"
}
}
]
}'
Crear índice
from pymilvus import MilvusClient
# Prepare index parameters
index_params = client.prepare_index_params()
# Add indexes
index_params.add_index(
field_name="text_dense",
index_name="text_dense_index",
index_type="AUTOINDEX",
metric_type="IP"
)
index_params.add_index(
field_name="text_sparse",
index_name="text_sparse_index",
index_type="SPARSE_INVERTED_INDEX",
metric_type="BM25",
params={"inverted_index_algo": "DAAT_MAXSCORE"}, # or "DAAT_WAND" or "TAAT_NAIVE"
)
index_params.add_index(
field_name="image_dense",
index_name="image_dense_index",
index_type="AUTOINDEX",
metric_type="IP"
)
import io.milvus.v2.common.IndexParam;
import java.util.*;
Map<String, Object> denseParams = new HashMap<>();
IndexParam indexParamForTextDense = IndexParam.builder()
.fieldName("text_dense")
.indexName("text_dense_index")
.indexType(IndexParam.IndexType.AUTOINDEX)
.metricType(IndexParam.MetricType.IP)
.build();
Map<String, Object> sparseParams = new HashMap<>();
sparseParams.put("inverted_index_algo": "DAAT_MAXSCORE");
IndexParam indexParamForTextSparse = IndexParam.builder()
.fieldName("text_sparse")
.indexName("text_sparse_index")
.indexType(IndexParam.IndexType.SPARSE_INVERTED_INDEX)
.metricType(IndexParam.MetricType.BM25)
.extraParams(sparseParams)
.build();
IndexParam indexParamForImageDense = IndexParam.builder()
.fieldName("image_dense")
.indexName("image_dense_index")
.indexType(IndexParam.IndexType.AUTOINDEX)
.metricType(IndexParam.MetricType.IP)
.build();
List<IndexParam> indexParams = new ArrayList<>();
indexParams.add(indexParamForTextDense);
indexParams.add(indexParamForTextSparse);
indexParams.add(indexParamForImageDense);
indexOption1 := milvusclient.NewCreateIndexOption("my_collection", "text_dense",
index.NewAutoIndex(index.MetricType(entity.IP)))
indexOption2 := milvusclient.NewCreateIndexOption("my_collection", "text_sparse",
index.NewSparseInvertedIndex(entity.BM25, 0.2))
indexOption3 := milvusclient.NewCreateIndexOption("my_collection", "image_dense",
index.NewAutoIndex(index.MetricType(entity.IP)))
)
const index_params = [{
field_name: "text_dense",
index_name: "text_dense_index",
index_type: "AUTOINDEX",
metric_type: "IP"
},{
field_name: "text_sparse",
index_name: "text_sparse_index",
index_type: "IndexType.SPARSE_INVERTED_INDEX",
metric_type: "BM25",
params: {
inverted_index_algo: "DAAT_MAXSCORE",
}
},{
field_name: "image_dense",
index_name: "image_dense_index",
index_type: "AUTOINDEX",
metric_type: "IP"
}]
export indexParams='[
{
"fieldName": "text_dense",
"metricType": "IP",
"indexName": "text_dense_index",
"indexType":"AUTOINDEX"
},
{
"fieldName": "text_sparse",
"metricType": "BM25",
"indexName": "text_sparse_index",
"indexType": "SPARSE_INVERTED_INDEX",
"params":{"inverted_index_algo": "DAAT_MAXSCORE"}
},
{
"fieldName": "image_dense",
"metricType": "IP",
"indexName": "image_dense_index",
"indexType":"AUTOINDEX"
}
]'
Crear colección
Crea una colección llamada demo con el esquema de colección y los índices configurados en los dos pasos anteriores.
from pymilvus import MilvusClient
client.create_collection(
collection_name="my_collection",
schema=schema,
index_params=index_params
)
CreateCollectionReq createCollectionReq = CreateCollectionReq.builder()
.collectionName("my_collection")
.collectionSchema(schema)
.indexParams(indexParams)
.build();
client.createCollection(createCollectionReq);
err = client.CreateCollection(ctx,
milvusclient.NewCreateCollectionOption("my_collection", schema).
WithIndexOptions(indexOption1, indexOption2))
if err != nil {
fmt.Println(err.Error())
// handle error
}
res = await client.createCollection({
collection_name: "my_collection",
fields: fields,
index_params: index_params,
})
export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/collections/create" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d "{
\"collectionName\": \"my_collection\",
\"schema\": $schema,
\"indexParams\": $indexParams
}"
Insertar datos
Esta sección inserta datos en la colección my_collection basándose en el esquema definido anteriormente. Durante la inserción, asegúrate de que todos los campos, excepto aquellos con valores autogenerados, reciben datos en el formato correcto. En este ejemplo
idun número entero que representa el ID del productotextuna cadena que contiene la descripción del productotext_denseuna lista de 768 valores de coma flotante que representan la incrustación densa de la descripción del textoimage_denseuna lista de 512 valores en coma flotante que representan la incrustación densa de la imagen del producto.
Puede utilizar el mismo modelo o modelos diferentes para generar incrustaciones densas para cada campo. En este ejemplo, las dos incrustaciones densas tienen dimensiones diferentes, lo que sugiere que fueron generadas por modelos diferentes. Cuando defina cada búsqueda más adelante, asegúrese de utilizar el modelo correspondiente para generar la incrustación de consulta adecuada.
Dado que este ejemplo utiliza la función BM25 incorporada para generar incrustaciones dispersas a partir del campo de texto, no es necesario que proporcione vectores dispersos manualmente. Sin embargo, si opta por no utilizar BM25, deberá precalcular y proporcionar los vectores dispersos usted mismo.
from pymilvus import MilvusClient
data=[
{
"id": 0,
"text": "Red cotton t-shirt with round neck",
"text_dense": [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, ...],
"image_dense": [0.6366019600530924, -0.09323198122475052, ...]
},
{
"id": 1,
"text": "Wireless noise-cancelling over-ear headphones",
"text_dense": [0.19886812562848388, 0.06023560599112088, 0.6976963061752597, ...],
"image_dense": [0.6414180010301553, 0.8976979978567611, ...]
},
{
"id": 2,
"text": "Stainless steel water bottle, 500ml",
"dense": [0.43742130801983836, -0.5597502546264526, 0.6457887650909682, ...],
"image_dense": [-0.6901259768402174, 0.6100500332193755, ...]
}
]
res = client.insert(
collection_name="my_collection",
data=data
)
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import io.milvus.v2.service.vector.request.InsertReq;
Gson gson = new Gson();
JsonObject row1 = new JsonObject();
row1.addProperty("id", 0);
row1.addProperty("text", "Red cotton t-shirt with round neck");
row1.add("text_dense", gson.toJsonTree(text_dense1));
row1.add("image_dense", gson.toJsonTree(image_dense));
JsonObject row2 = new JsonObject();
row2.addProperty("id", 1);
row2.addProperty("text", "Wireless noise-cancelling over-ear headphones");
row2.add("text_dense", gson.toJsonTree(text_dense2));
row2.add("image_dense", gson.toJsonTree(image_dense2));
JsonObject row3 = new JsonObject();
row3.addProperty("id", 2);
row3.addProperty("text", "Stainless steel water bottle, 500ml");
row3.add("text_dense", gson.toJsonTree(dense3));
row3.add("image_dense", gson.toJsonTree(sparse3));
List<JsonObject> data = Arrays.asList(row1, row2, row3);
InsertReq insertReq = InsertReq.builder()
.collectionName("my_collection")
.data(data)
.build();
InsertResp insertResp = client.insert(insertReq);
_, err = client.Insert(ctx, milvusclient.NewColumnBasedInsertOption("my_collection").
WithInt64Column("id", []int64{0, 1, 2}).
WithVarcharColumn("text", []string{
"Red cotton t-shirt with round neck",
"Wireless noise-cancelling over-ear headphones",
"Stainless steel water bottle, 500ml",
}).
WithFloatVectorColumn("text_dense", 768, [][]float32{
{0.3580376395471989, -0.6023495712049978, 0.18414012509913835, ...},
{0.19886812562848388, 0.06023560599112088, 0.6976963061752597, ...},
{0.43742130801983836, -0.5597502546264526, 0.6457887650909682, ...},
}).
WithFloatVectorColumn("image_dense", 512, [][]float32{
{0.6366019600530924, -0.09323198122475052, ...},
{0.6414180010301553, 0.8976979978567611, ...},
{-0.6901259768402174, 0.6100500332193755, ...},
}).
if err != nil {
fmt.Println(err.Error())
// handle err
}
const { MilvusClient, DataType } = require("@zilliz/milvus2-sdk-node")
var data = [
{id: 0, text: "Red cotton t-shirt with round neck" , text_dense: [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, ...], image_dense: [0.6366019600530924, -0.09323198122475052, ...]},
{id: 1, text: "Wireless noise-cancelling over-ear headphones" , text_dense: [0.19886812562848388, 0.06023560599112088, 0.6976963061752597, ...], image_dense: [0.6414180010301553, 0.8976979978567611, ...]},
{id: 2, text: "Stainless steel water bottle, 500ml" , text_dense: [0.43742130801983836, -0.5597502546264526, 0.6457887650909682, ...], image_dense: [-0.6901259768402174, 0.6100500332193755, ...]}
]
var res = await client.insert({
collection_name: "my_collection",
data: data,
})
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/insert" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d '{
"data": [
{"id": 0, "text": "Red cotton t-shirt with round neck" , "text_dense": [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, ...], "image_dense": [0.6366019600530924, -0.09323198122475052, ...]},
{"id": 1, "text": "Wireless noise-cancelling over-ear headphones" , "text_dense": [0.19886812562848388, 0.06023560599112088, 0.6976963061752597, ...], "image_dense": [0.6414180010301553, 0.8976979978567611, ...]},
{"id": 2, "text": "Stainless steel water bottle, 500ml" , "text_dense": [0.43742130801983836, -0.5597502546264526, 0.6457887650909682, ...], "image_dense": [-0.6901259768402174, 0.6100500332193755, ...]}
],
"collectionName": "my_collection"
}'
Realizar una búsqueda híbrida
Crear varias instancias de AnnSearchRequest
La Búsqueda Híbrida se implementa creando múltiples AnnSearchRequest en la función hybrid_search(), donde cada AnnSearchRequest representa una petición de búsqueda ANN básica para un campo vectorial específico. Por lo tanto, antes de realizar una Búsqueda Híbrida, es necesario crear un AnnSearchRequest para cada campo vectorial.
Además, configurando el parámetro expr en un AnnSearchRequest, puede establecer las condiciones de filtrado para su búsqueda híbrida. Consulte Búsqueda filtrada y filtrado.
En la búsqueda híbrida, cada AnnSearchRequest sólo admite un dato de consulta.
Para demostrar las capacidades de varios campos vectoriales de búsqueda, construiremos tres peticiones de búsqueda AnnSearchRequest utilizando una consulta de ejemplo. También utilizaremos sus vectores densos precalculados para este proceso. Las peticiones de búsqueda se centrarán en los siguientes campos vectoriales
text_densepara la búsqueda semántica de texto, que permite la comprensión contextual y la recuperación basada en el significado más que en la coincidencia directa de palabras clave.text_sparseBúsqueda de texto completo o por palabras clave, centrada en coincidencias exactas de palabras o frases dentro del texto.image_densepara la búsqueda multimodal texto-imagen, para recuperar imágenes de productos relevantes basadas en el contenido semántico de la consulta.
from pymilvus import AnnSearchRequest
query_text = "white headphones, quiet and comfortable"
query_dense_vector = [0.3580376395471989, -0.6023495712049978, 0.5142999509918703, ...]
query_multimodal_vector = [0.015829865178701663, 0.5264158340734488, ...]
# text semantic search (dense)
search_param_1 = {
"data": [query_dense_vector],
"anns_field": "text_dense",
"param": {"nprobe": 10},
"limit": 2
}
request_1 = AnnSearchRequest(**search_param_1)
# full-text search (sparse)
search_param_2 = {
"data": [query_text],
"anns_field": "text_sparse",
"limit": 2
}
request_2 = AnnSearchRequest(**search_param_2)
# text-to-image search (multimodal)
search_param_3 = {
"data": [query_multimodal_vector],
"anns_field": "image_dense",
"param": {"nprobe": 10},
"limit": 2
}
request_3 = AnnSearchRequest(**search_param_3)
reqs = [request_1, request_2, request_3]
import io.milvus.v2.service.vector.request.AnnSearchReq;
import io.milvus.v2.service.vector.request.data.BaseVector;
import io.milvus.v2.service.vector.request.data.FloatVec;
import io.milvus.v2.service.vector.request.data.SparseFloatVec;
import io.milvus.v2.service.vector.request.data.EmbeddedText;
float[] queryDense = new float[]{-0.0475336798f, 0.0521207601f, 0.0904406682f, ...};
float[] queryMultimodal = new float[]{0.0158298651f, 0.5264158340f, ...}
List<BaseVector> queryTexts = Collections.singletonList(new EmbeddedText("white headphones, quiet and comfortable");)
List<BaseVector> queryDenseVectors = Collections.singletonList(new FloatVec(queryDense));
List<BaseVector> queryMultimodalVectors = Collections.singletonList(new FloatVec(queryMultimodal));
List<AnnSearchReq> searchRequests = new ArrayList<>();
searchRequests.add(AnnSearchReq.builder()
.vectorFieldName("text_dense")
.vectors(queryDenseVectors)
.params("{\"nprobe\": 10}")
.topK(2)
.build());
searchRequests.add(AnnSearchReq.builder()
.vectorFieldName("text_sparse")
.vectors(queryTexts)
.topK(2)
.build());
searchRequests.add(AnnSearchReq.builder()
.vectorFieldName("image_dense")
.vectors(queryMultimodalVectors)
.params("{\"nprobe\": 10}")
.topK(2)
.build());
queryText := entity.Text({"white headphones, quiet and comfortable"})
queryVector := []float32{0.3580376395471989, -0.6023495712049978, 0.18414012509913835, ...}
queryMultimodalVector := []float32{0.015829865178701663, 0.5264158340734488, ...}
request1 := milvusclient.NewAnnRequest("text_dense", 2, entity.FloatVector(queryVector)).
WithAnnParam(index.NewIvfAnnParam(10))
annParam := index.NewSparseAnnParam()
annParam.WithDropRatio(0.2)
request2 := milvusclient.NewAnnRequest("text_sparse", 2, queryText).
WithAnnParam(annParam)
request3 := milvusclient.NewAnnRequest("image_dense", 2, entity.FloatVector(queryMultimodalVector)).
WithAnnParam(index.NewIvfAnnParam(10))
const query_text = "white headphones, quiet and comfortable"
const query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, ...]
const query_multimodal_vector = [0.015829865178701663, 0.5264158340734488, ...]
const search_param_1 = {
"data": query_vector,
"anns_field": "text_dense",
"param": {"nprobe": 10},
"limit": 2
}
const search_param_2 = {
"data": query_text,
"anns_field": "text_sparse",
"limit": 2
}
const search_param_3 = {
"data": query_multimodal_vector,
"anns_field": "image_dense",
"param": {"nprobe": 10},
"limit": 2
}
export req='[
{
"data": [[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, ...]],
"annsField": "text_dense",
"params": {"nprobe": 10},
"limit": 2
},
{
"data": ["white headphones, quiet and comfortable"],
"annsField": "text_sparse",
"limit": 2
},
{
"data": [[0.015829865178701663, 0.5264158340734488, ...]],
"annsField": "image_dense",
"params": {"nprobe": 10},
"limit": 2
}
]'
Dado que el parámetro limit se establece en 2, cada AnnSearchRequest devuelve 2 resultados de búsqueda. En este ejemplo, se crean 3 instancias de AnnSearchRequest, lo que da un total de 6 resultados de búsqueda.
Configurar una estrategia de reordenación
Para fusionar y reordenar los conjuntos de resultados de búsqueda de RNA, es esencial seleccionar una estrategia de reordenación adecuada. Milvus ofrece dos tipos de estrategias de reordenación:
WeightedRanker: Utilice esta estrategia si los resultados deben hacer hincapié en un campo vectorial concreto. WeightedRanker le permite asignar mayor peso a determinados campos vectoriales, resaltándolos más.
RRFRanker (Reciprocal Rank Fusion Ranker): Elija esta estrategia cuando no se requiera un énfasis específico. RRFRanker equilibra eficazmente la importancia de cada campo vectorial.
Para obtener más información sobre estos mecanismos de reordenación, consulte Reordenación.
En este ejemplo, dado que no hay un énfasis particular en consultas de búsqueda específicas, procederemos con la estrategia RRFRanker.
from pymilvus import RRFRanker
ranker = RRFRanker(100)
import io.milvus.v2.service.vector.request.ranker.BaseRanker;
import io.milvus.v2.service.vector.request.ranker.RRFRanker;
BaseRanker reranker = new RRFRanker(100);
reranker := milvusclient.NewRRFReranker().WithK(100)
import { MilvusClient, DataType } from "@zilliz/milvus2-sdk-node";
const rerank = RRFRanker("100");
export rerank='{
"strategy": "rrf",
"params": { "k": 100}
}'
Realizar una búsqueda híbrida
Antes de iniciar una búsqueda híbrida, asegúrese de que la colección está cargada. Si algún campo vectorial de la colección carece de índice o no está cargado en memoria, se producirá un error al ejecutar el método de Búsqueda Híbrida.
from pymilvus import MilvusClient
res = client.hybrid_search(
collection_name="my_collection",
reqs=reqs,
ranker=ranker,
limit=2
)
for hits in res:
print("TopK results:")
for hit in hits:
print(hit)
import io.milvus.v2.common.ConsistencyLevel;
import io.milvus.v2.service.vector.request.HybridSearchReq;
import io.milvus.v2.service.vector.response.SearchResp;
HybridSearchReq hybridSearchReq = HybridSearchReq.builder()
.collectionName("my_collection")
.searchRequests(searchRequests)
.ranker(reranker)
.topK(2)
.build();
SearchResp searchResp = client.hybridSearch(hybridSearchReq);
resultSets, err := client.HybridSearch(ctx, milvusclient.NewHybridSearchOption(
"my_collection",
2,
request1,
request2,
request3,
).WithReranker(reranker))
if err != nil {
fmt.Println(err.Error())
// handle error
}
for _, resultSet := range resultSets {
fmt.Println("IDs: ", resultSet.IDs.FieldData().GetScalars())
fmt.Println("Scores: ", resultSet.Scores)
}
const { MilvusClient, DataType } = require("@zilliz/milvus2-sdk-node")
res = await client.loadCollection({
collection_name: "my_collection"
})
import { MilvusClient, RRFRanker, WeightedRanker } from '@zilliz/milvus2-sdk-node';
const search = await client.search({
collection_name: "my_collection",
data: [search_param_1, search_param_2, search_param_3],
limit: 2,
rerank: rerank
});
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/advanced_search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d "{
\"collectionName\": \"my_collection\",
\"search\": ${req},
\"rerank\": {
\"strategy\":\"rrf\",
\"params\": ${rerank}
},
\"limit\": 2
}"
A continuación se muestra el resultado:
["['id: 1, distance: 0.006047376897186041, entity: {}', 'id: 2, distance: 0.006422005593776703, entity: {}']"]
Con el parámetro limit=2 especificado para la Búsqueda Híbrida, Milvus ordenará los seis resultados obtenidos de las tres búsquedas. Finalmente, devolverá sólo los dos resultados más similares.