• Acerca de Milvus
  • Comenzar
  • Conceptos
  • Guía del usuario
    • Colecciones
    • Esquema y campos de datos
    • Insertar y eliminar
    • Índices
    • Buscar en
    • Función e inferencia de modelos
    • Optimización del almacenamiento
    • Instantáneas
  • Importación de datos
  • Herramientas de IA
  • Guía de administración
  • Herramientas
  • Integraciones
  • Tutoriales
  • Preguntas frecuentes
  • API Reference

Función BM25

La función BM 25 permite realizar búsquedas de texto completo transformando el texto en bruto en vectores dispersos y puntuando los documentos en función de su relevancia léxica. Aplica la concordancia basada en términos y la ponderación en función de la frecuencia para apoyar la recuperación eficaz de documentos de texto que coinciden estrechamente con los términos de la consulta.

Como función de texto local, la función BM25 se ejecuta dentro de Milvus y no requiere la inferencia de modelos ni integraciones externas. Proporciona un mecanismo de recuperación determinista y transparente para escenarios de búsqueda basados en texto.

Funcionamiento de BM25

El algoritmo BM25 es un algoritmo de puntuación de relevancia basado en términos ampliamente utilizado en la recuperación de texto completo. En Milvus, BM25 se implementa como una cadena de recuperación dispersa que convierte el texto en representaciones de términos ponderados y recupera los K documentos más importantes utilizando índices dispersos distribuidos.

El flujo de trabajo global consta de dos rutas simétricas: la ingesta de documentos y el procesamiento del texto de consulta, que comparten la misma lógica de análisis de texto.

Ingesta de documentos: Del texto a la representación dispersa

Cuando se inserta un documento, su texto bruto es procesado en primer lugar por un analizador, que lo tokeniza en términos individuales.

Por ejemplo, el documento

"We are loving Milvus!"

puede analizarse en los siguientes términos:

["we", "love", "milvus"]

A continuación, cada documento se representa como una frecuencia de términos (TF), que registra cuántas veces aparece cada término en el documento. Por ejemplo:

{
  "we": 1,
  "love": 1,
  "milvus": 1
}

Al mismo tiempo, Milvus actualiza las estadísticas a nivel de corpus, incluyendo:

  • la frecuencia documental (FD) de cada término

  • la longitud media del documento

  • listas de contabilización que asignan cada término a los documentos que lo contienen

La representación TF del documento se inserta en incrustaciones dispersas, en las que las publicaciones de términos se dividen en nodos para una recuperación escalable.

Proceso de consulta de texto: Aplicación de la ponderación IDF

Cuando se emite una consulta basada en texto, se procesa mediante el mismo analizador utilizado durante la ingesta de documentos, lo que garantiza una segmentación de términos coherente.

Por ejemplo, la consulta

"who loves Milvus?"

puede analizarse en:

["who", "love", "milvus"]

Para cada término de la consulta, Milvus busca su frecuencia inversa de documentos (FID ) en las estadísticas del corpus. La IDF refleja el grado de información de un término en todo el conjunto de datos: los términos menos frecuentes reciben una ponderación mayor, mientras que los términos más comunes reciben una ponderación menor.

Conceptualmente, esto produce un conjunto de términos de consulta ponderados por IDF, como:

{
  "who": 0.1,
  "love": 0.5,
  "milvus": 1.2
}

Puntuación BM25 y recuperación top K

BM25 clasifica los documentos calculando una puntuación de pertinencia basada en los términos de la consulta que coinciden. La puntuación se realiza a nivel de término y se agrega a nivel de documento.

Puntuación por términos

Para cada término de la consulta que aparece en un documento, BM25 calcula una puntuación a nivel de término:

term_score =
  IDF(term) ×
  TF_boost(term, document, k1) ×
  length_normalization(document, b)

Donde

  • IDF(término) refleja la rareza del término en la colección

  • TF_boost(..., k1) aumenta con la frecuencia del término, pero se satura a medida que aumenta la frecuencia.

  • length_normalization(..., b) ajusta la puntuación en función de la longitud del documento.

Puntuación a nivel de documento y recuperación Top-K

La puntuación final del documento es la suma de las puntuaciones a nivel de término para todos los términos de consulta coincidentes:

document_score =
  sum of term_score over all matched query terms

Los documentos se clasifican según su puntuación final y se devuelven los K documentos con mayor puntuación.

Antes de empezar

Antes de utilizar la función BM25, planifique el esquema de su colección para asegurarse de que admite la búsqueda léxica de texto completo:

  • Un campo de texto para el contenido bruto

    Su colección debe incluir un campo VARCHAR para almacenar el texto en bruto. Este campo es la fuente del texto que se procesará para la búsqueda de texto completo.

  • Un analizador para el campo de texto

    El campo de texto debe tener un analizador activado. El analizador define el modo en que el texto se tokeniza y normaliza antes de que la función BM25 calcule la relevancia léxica.

    Por defecto, Milvus proporciona un analizador integrado que tokeniza el texto basándose en los espacios en blanco y la puntuación. Si su aplicación requiere un comportamiento personalizado de tokenización o normalización, puede definir un analizador personalizado. Para más detalles, consulte Elegir el analizador adecuado para su caso de uso.

  • Un vector disperso para la salida BM25

    Su colección debe incluir un campo SPARSE_FLOAT_VECTOR para almacenar las representaciones dispersas generadas por la función BM25. Este campo se utiliza para la indexación y la recuperación durante la búsqueda de texto completo.

Una vez resueltas estas consideraciones a nivel de esquema, proceda a crear la colección y a utilizar la función BM25.

Etapa 1: Crear una colección con una función BM25

Para utilizar la función BM25, debe definirla al crear la colección. La función pasa a formar parte del esquema de la colección y se aplica automáticamente durante la inserción y la búsqueda de datos.

Definir los campos del esquema

El esquema de su colección debe incluir al menos tres campos obligatorios:

  • Campo primario: Identifica de forma única cada entidad de la colección.

  • Campo de texto (VARCHAR): Almacena documentos de texto sin procesar. Debe configurar enable_analyzer=True para que Milvus pueda procesar el texto para la clasificación de relevancia BM25. Por defecto, Milvus utiliza el standard para el análisis de texto. Para configurar un analizador diferente, consulte Visión general del analizador.

  • Campo vectorial disperso (SPARSE_FLOAT_VECTOR): Almacena las incrustaciones dispersas generadas automáticamente por la función BM25.

from pymilvus import MilvusClient, DataType, Function, FunctionType

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

schema = client.create_schema()

schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True, auto_id=True) # Primary field
schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=1000, enable_analyzer=True) # Text field
schema.add_field(field_name="sparse", datatype=DataType.SPARSE_FLOAT_VECTOR) # Sparse vector field; no dim required for sparse vectors
import io.milvus.v2.common.DataType;
import io.milvus.v2.service.collection.request.AddFieldReq;
import io.milvus.v2.service.collection.request.CreateCollectionReq;

CreateCollectionReq.CollectionSchema schema = CreateCollectionReq.CollectionSchema.builder()
        .build();
schema.addField(AddFieldReq.builder()
        .fieldName("id")
        .dataType(DataType.Int64)
        .isPrimaryKey(true)
        .autoID(true)
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("text")
        .dataType(DataType.VarChar)
        .maxLength(1000)
        .enableAnalyzer(true)
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("sparse")
        .dataType(DataType.SparseFloatVector)
        .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 := "http://localhost:19530"
token := "root:Milvus"

client, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
    Address: milvusAddr,
    APIKey: token
})
if err != nil {
    fmt.Println(err.Error())
    // handle error
}
defer client.Close(ctx)

schema := entity.NewSchema()
schema.WithField(entity.NewField().
    WithName("id").
    WithDataType(entity.FieldTypeInt64).
    WithIsPrimaryKey(true).
    WithIsAutoID(true),
).WithField(entity.NewField().
    WithName("text").
    WithDataType(entity.FieldTypeVarChar).
    WithEnableAnalyzer(true).
    WithMaxLength(1000),
).WithField(entity.NewField().
    WithName("sparse").
    WithDataType(entity.FieldTypeSparseVector),
)
import { MilvusClient, DataType } from "@zilliz/milvus2-sdk-node";

const address = "http://localhost:19530";
const token = "root:Milvus";
const client = new MilvusClient({address, token});
const schema = [
  {
    name: "id",
    data_type: DataType.Int64,
    is_primary_key: true,
  },
  {
    name: "text",
    data_type: "VarChar",
    enable_analyzer: true,
    enable_match: true,
    max_length: 1000,
  },
  {
    name: "sparse",
    data_type: DataType.SparseFloatVector,
  },
];

console.log(res.results)
export schema='{
        "autoId": true,
        "enabledDynamicField": false,
        "fields": [
            {
                "fieldName": "id",
                "dataType": "Int64",
                "isPrimary": true
            },
            {
                "fieldName": "text",
                "dataType": "VarChar",
                "elementTypeParams": {
                    "max_length": 1000,
                    "enable_analyzer": true
                }
            },
            {
                "fieldName": "sparse",
                "dataType": "SparseFloatVector"
            }
        ]
    }'

Definición de la función BM25

La función BM25 convierte el texto tokenizado en vectores dispersos compatibles con la puntuación BM25.

Defina la función y añádala a su esquema:

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, # Set to `BM25`
)

schema.add_function(bm25_function)
import io.milvus.common.clientenum.FunctionType;
import io.milvus.v2.service.collection.request.CreateCollectionReq.Function;

import java.util.*;

schema.addFunction(Function.builder()
        .functionType(FunctionType.BM25)
        .name("text_bm25_emb")
        .inputFieldNames(Collections.singletonList("text"))
        .outputFieldNames(Collections.singletonList("sparse"))
        .build());
function := entity.NewFunction().
    WithName("text_bm25_emb").
    WithInputFields("text").
    WithOutputFields("sparse").
    WithType(entity.FunctionTypeBM25)
schema.WithFunction(function)
const functions = [
    {
      name: 'text_bm25_emb',
      description: 'bm25 function',
      type: FunctionType.BM25,
      input_field_names: ['text'],
      output_field_names: ['sparse'],
      params: {},
    },
];
export schema='{
        "autoId": true,
        "enabledDynamicField": false,
        "fields": [
            {
                "fieldName": "id",
                "dataType": "Int64",
                "isPrimary": true
            },
            {
                "fieldName": "text",
                "dataType": "VarChar",
                "elementTypeParams": {
                    "max_length": 1000,
                    "enable_analyzer": true
                }
            },
            {
                "fieldName": "sparse",
                "dataType": "SparseFloatVector"
            }
        ],
        "functions": [
            {
                "name": "text_bm25_emb",
                "type": "BM25",
                "inputFieldNames": ["text"],
                "outputFieldNames": ["sparse"],
                "params": {}
            }
        ]
    }'

Configurar el índice

Después de definir el esquema con los campos necesarios y la función incorporada, configure el índice para su colección.

index_params = client.prepare_index_params()

index_params.add_index(
    field_name="sparse",

    index_type="SPARSE_INVERTED_INDEX",
    metric_type="BM25",
    params={
        "inverted_index_algo": "DAAT_MAXSCORE",
        "bm25_k1": 1.2,
        "bm25_b": 0.75
    }

)
import io.milvus.v2.common.IndexParam;

Map<String,Object> params = new HashMap<>();
params.put("inverted_index_algo", "DAAT_MAXSCORE");
params.put("bm25_k1", 1.2);
params.put("bm25_b", 0.75);

List<IndexParam> indexes = new ArrayList<>();
indexes.add(IndexParam.builder()
        .fieldName("sparse")
        .indexType(IndexParam.IndexType.AUTOINDEX)
        .metricType(IndexParam.MetricType.BM25)
        .extraParams(params)
        .build());
indexOption := milvusclient.NewCreateIndexOption("my_collection", "sparse",
    index.NewAutoIndex(entity.MetricType(entity.BM25)))
    .WithExtraParam("inverted_index_algo", "DAAT_MAXSCORE")
    .WithExtraParam("bm25_k1", 1.2)
    .WithExtraParam("bm25_b", 0.75)
const index_params = [
  {
    field_name: "sparse",
    metric_type: "BM25",
    index_type: "SPARSE_INVERTED_INDEX",
    params: {
        "inverted_index_algo": "DAAT_MAXSCORE",
        "bm25_k1": 1.2,
        "bm25_b": 0.75
    }
  },
];
export indexParams='[
        {
            "fieldName": "sparse",
            "metricType": "BM25",
            "indexType": "AUTOINDEX",
            "params":{
               "inverted_index_algo": "DAAT_MAXSCORE",
               "bm25_k1": 1.2,
               "bm25_b": 0.75
            }
        }
    ]'

Crear la colección

Ahora cree la colección utilizando el esquema y los parámetros de índice definidos:

client.create_collection(
    collection_name='my_collection',
    schema=schema,
    index_params=index_params
)
import io.milvus.v2.service.collection.request.CreateCollectionReq;

CreateCollectionReq requestCreate = CreateCollectionReq.builder()
        .collectionName("my_collection")
        .collectionSchema(schema)
        .indexParams(indexes)
        .build();
client.createCollection(requestCreate);
err = client.CreateCollection(ctx,
    milvusclient.NewCreateCollectionOption("my_collection", schema).
        WithIndexOptions(indexOption))
if err != nil {
    fmt.Println(err.Error())
    // handle error
}
await client.create_collection(
    collection_name: 'my_collection',
    schema: schema,
    index_params: index_params,
    functions: functions
);
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" \
--header "Request-Timeout: 10" \
-d "{
    \"collectionName\": \"my_collection\",
    \"schema\": $schema,
    \"indexParams\": $indexParams
}"

Una vez creada la colección con una función BM25, podrá insertar texto y realizar búsquedas léxicas basadas en la consulta de texto.

Etapa 2: Insertar datos de texto en la colección

Una vez configurada la colección y el índice, está listo para insertar datos de texto. En este proceso, sólo tiene que proporcionar el texto en bruto. La función BM25 que definimos anteriormente genera automáticamente el vector disperso para cada entrada de texto.

client.insert('my_collection', [
    {'text': 'information retrieval is a field of study.'},
    {'text': 'information retrieval focuses on finding relevant information in large datasets.'},
    {'text': 'data mining and information retrieval overlap in research.'},
])
import com.google.gson.Gson;
import com.google.gson.JsonObject;

import io.milvus.v2.service.vector.request.InsertReq;

Gson gson = new Gson();
List<JsonObject> rows = Arrays.asList(
        gson.fromJson("{\"text\": \"information retrieval is a field of study.\"}", JsonObject.class),
        gson.fromJson("{\"text\": \"information retrieval focuses on finding relevant information in large datasets.\"}", JsonObject.class),
        gson.fromJson("{\"text\": \"data mining and information retrieval overlap in research.\"}", JsonObject.class)
);

client.insert(InsertReq.builder()
        .collectionName("my_collection")
        .data(rows)
        .build());
// go
await client.insert({
collection_name: 'my_collection',
data: [
    {'text': 'information retrieval is a field of study.'},
    {'text': 'information retrieval focuses on finding relevant information in large datasets.'},
    {'text': 'data mining and information retrieval overlap in research.'},
]);
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/insert" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d '{
    "data": [
        {"text": "information retrieval is a field of study."},
        {"text": "information retrieval focuses on finding relevant information in large datasets."},
        {"text": "data mining and information retrieval overlap in research."}
    ],
    "collectionName": "my_collection"
}'

Paso 3: Búsqueda con consulta de texto

Una vez que haya insertado datos en su colección, puede realizar búsquedas de texto completo utilizando consultas de texto sin procesar. Milvus convierte automáticamente su consulta en un vector disperso y clasifica los resultados de búsqueda coincidentes utilizando el algoritmo BM25, y luego devuelve los resultados topK (limit).

search_params = {

}

res = client.search(
    collection_name='my_collection',
    data=['whats the focus of information retrieval?'],
    anns_field='sparse',
    output_fields=['text'], # Fields to return in search results; sparse field cannot be output
    limit=3,
    search_params=search_params
)

print(res)
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.request.data.EmbeddedText;
import io.milvus.v2.service.vector.response.SearchResp;

Map<String,Object> searchParams = new HashMap<>();

SearchResp searchResp = client.search(SearchReq.builder()
        .collectionName("my_collection")
        .data(Collections.singletonList(new EmbeddedText("whats the focus of information retrieval?")))
        .annsField("sparse")
        .topK(3)
        .searchParams(searchParams)
        .outputFields(Collections.singletonList("text"))
        .build());
annSearchParams := index.NewCustomAnnParam()
resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
    "my_collection", // collectionName
    3,               // limit
    []entity.Vector{entity.Text("whats the focus of information retrieval?")},
).WithConsistencyLevel(entity.ClStrong).
    WithANNSField("sparse").
    WithAnnParam(annSearchParams).
    WithOutputFields("text"))
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)
    fmt.Println("text: ", resultSet.GetColumn("text").FieldData().GetScalars())
}
await client.search(
    collection_name: 'my_collection',
    data: ['whats the focus of information retrieval?'],
    anns_field: 'sparse',
    output_fields: ['text'],
    limit: 3,

)
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
--data-raw '{
    "collectionName": "my_collection",
    "data": [
        "whats the focus of information retrieval?"
    ],
    "annsField": "sparse",
    "limit": 3,
    "outputFields": [
        "text"
    ],
    "searchParams":{
        "params":{}
    }
}'