Acerca de la búsqueda híbrida

La Búsqueda vectorial admite la búsqueda híbrida, un patrón de arquitectura popular en la recuperación de información (IR) que combina la búsqueda semántica y la búsqueda de palabras clave (también llamada búsqueda basada en tokens). Con la búsqueda híbrida, los desarrolladores pueden aprovechar lo mejor de los dos enfoques, lo que proporciona una mejor calidad de búsqueda.

En esta página, se explican los conceptos de búsqueda híbrida, búsqueda semántica y búsqueda basada en tokens, y se incluyen ejemplos de cómo configurar la búsqueda híbrida y la búsqueda basada en tokens:

¿Por qué es importante la búsqueda híbrida?

Como se describe en la Descripción general de la Búsqueda de vectores, la búsqueda semántica con la Búsqueda de vectores puede encontrar elementos con similitudes semánticas mediante consultas.

Los modelos de incorporación, como las incorporaciones de Vertex AI, crean un espacio vectorial como un mapa de significados del contenido. Cada incorporación multimodal o de texto es una ubicación en el mapa que representa el significado de algún contenido. Como ejemplo simplificado, cuando un modelo de incorporación toma un texto que analiza películas en un 10%, música en un 2% y actores en un 30%, podría representar este texto con una incorporación [0.1, 0.02, 0.3]. Con la Búsqueda vectorial, puedes encontrar rápidamente otras incorporaciones en su vecindario. Esta búsqueda por significado del contenido se denomina búsqueda semántica.

Ilustración de términos de búsqueda semántica en relación con otros en un mapa de significado.

La búsqueda semántica con incorporaciones y la búsqueda vectorial pueden ayudar a que los sistemas de TI sean tan inteligentes como los bibliotecarios experimentados o el personal de la tienda. Las incorporaciones se pueden usar para vincular diferentes datos comerciales con sus significados, por ejemplo, consultas y resultados de la búsqueda, textos e imágenes, actividades de los usuarios y productos recomendados, textos en inglés y en japonés, o datos de sensores y condiciones de alerta. Con esta función, existe una amplia variedad de casos de uso para las incorporaciones.

La búsqueda semántica no cubre todos los requisitos posibles para las aplicaciones de recuperación de información, como la generación de aumento de recuperación (RAG). La búsqueda semántica solo puede encontrar datos que el modelo de incorporación pueda interpretar. Por ejemplo, las consultas o los conjuntos de datos con números de productos o SKUs arbitrarios, los nombres de productos nuevos que se agregaron recientemente y los nombres en clave propietarios de la empresa no funcionan con la búsqueda semántica porque no se incluyen en el conjunto de datos de entrenamiento del modelo de incorporación. Esto se denomina datos "fuera de dominio".

En esos casos, deberás combinar la búsqueda semántica con la búsqueda basada en palabras clave (también llamada búsqueda basada en tokens) para formar una búsqueda híbrida. Con la búsqueda híbrida, puedes aprovechar la búsqueda semántica y la basada en tokens para lograr una mejor calidad de la búsqueda.

Uno de los sistemas de búsqueda híbrida más populares es la Búsqueda de Google. El servicio incorporó la búsqueda semántica en 2015 con el modelo de RankBrain, además de su algoritmo de búsqueda de palabras clave basado en tokens. Con la introducción de la búsqueda híbrida, la Búsqueda de Google pudo mejorar significativamente la calidad de la búsqueda abordando los dos requisitos: búsqueda por significado y búsqueda por palabra clave.

En el pasado, crear un motor de búsqueda híbrido era una tarea compleja. Al igual que con la Búsqueda de Google, debes compilar y operar dos tipos diferentes de motores de búsqueda (búsqueda semántica y búsqueda basada en tokens) y combinar y clasificar los resultados. Con la compatibilidad con la búsqueda híbrida en la Búsqueda vectorial, puedes compilar tu propio sistema de búsqueda híbrida con un solo índice de Búsqueda vectorial personalizado según las necesidades de tu empresa.

¿Cómo funciona la búsqueda basada en tokens en la Búsqueda de vectores? Después de dividir el texto en tokens (como palabras o subpalabras), puedes usar algoritmos populares de incorporación dispersa, como TF-IDF, BM25 o SPLADE, para generar una incorporación dispersa para el texto.

Una explicación simplificada de las incorporaciones dispersas es que son vectores que representan cuántas veces aparece cada palabra o subpalabra en el texto. Las incorporaciones dispersas típicas no tienen en cuenta la semántica del texto.

Incorporaciones dispersas

Podría haber miles de palabras diferentes en los textos. Por lo tanto, esta incorporación suele tener decenas de miles de dimensiones, con solo algunas de ellas que tienen valores distintos de cero. Por eso se llaman incorporaciones “dispersas”. La mayoría de sus valores son ceros. Este espacio de incorporación disperso funciona como un mapa de palabras clave, similar a un índice de libros.

En este espacio de incorporación disperso, puedes encontrar incorporaciones similares si observas el vecindario de una incorporación de consulta. Estas incorporaciones son similares en términos de la distribución de las palabras clave que se usan en sus textos.

Ilustración de palabras clave similares que se encuentran cerca en un mapa de significado.

Este es el mecanismo básico de la búsqueda basada en tokens con incorporaciones dispersas. Con la búsqueda híbrida en la Búsqueda de vectores, puedes combinar embeddings densos y dispersos en un solo índice de vectores y ejecutar consultas con embeddings densos, dispersos o ambos. El resultado es una combinación de la búsqueda semántica y los resultados de la búsqueda basados en tokens.

La búsqueda híbrida también proporciona una latencia de consulta más baja en comparación con un motor de búsqueda basado en tokens con un diseño de índice invertido. Al igual que la búsqueda vectorial para la búsqueda semántica, cada consulta con incorporaciones densas o dispersas finaliza en milisegundos, incluso con millones o miles de millones de elementos.

Para explicar cómo usar la búsqueda basada en tokens, las siguientes secciones incluyen ejemplos de código que generan incorporaciones dispersas y compilan un índice con ellas en la Búsqueda de vectores.

Para probar este código de muestra, usa el notebook: Combinación de búsqueda semántica y de palabras clave: un instructivo de búsqueda híbrida con la Búsqueda de vectores de Vertex AI

El primer paso es preparar un archivo de datos para crear un índice de incorporaciones escasas, según el formato de datos que se describe en Estructura y formato de los datos de entrada.

En JSON, el archivo de datos se ve de la siguiente manera:

{"id": "3", "sparse_embedding": {"values": [0.1, 0.2], "dimensions": [1, 4]}}
{"id": "4", "sparse_embedding": {"values": [-0.4, 0.2, -1.3], "dimensions": [10, 20, 30]}}

Cada elemento debe tener una propiedad sparse_embedding que tenga propiedades values y dimensions. Los embeddings dispersos tienen miles de dimensiones con algunos valores distintos de cero. Este formato de datos funciona de manera eficiente porque contiene los valores distintos de cero solo con sus posiciones en el espacio.

Prepara un conjunto de datos de muestra

Como conjunto de datos de muestra, usaremos el conjunto de datos de Google Merchandise Store, que tiene alrededor de 200 filas de productos de la marca Google.

0                          Google Sticker
1                    Google Cloud Sticker
2                       Android Black Pen
3                   Google Ombre Lime Pen
4                    For Everyone Eco Pen
                      ...
197        Google Recycled Black Backpack
198    Google Cascades Unisex Zip Sweater
199    Google Cascades Womens Zip Sweater
200         Google Cloud Skyline Backpack
201       Google City Black Tote Backpack

Prepara un vectorizador de TF-IDF

Con este conjunto de datos, entrenaremos un vectorizador, un modelo que genera incorporaciones escasas a partir de un texto. En este ejemplo, se usa TfidfVectorizer en scikit-learn, que es un vectorizador básico que usa el algoritmo TF-IDF.

from sklearn.feature_extraction.text import TfidfVectorizer

# Make a list of the item titles
corpus = df.title.tolist()

# Initialize TfidfVectorizer
vectorizer = TfidfVectorizer()

# Fit and Transform
vectorizer.fit_transform(corpus)

La variable corpus contiene una lista de los 200 nombres de elementos, como "Calcomanía de Google" o "Pin de Dino de Chrome". Luego, el código los pasa al vectorizador llamando a la función fit_transform(). Con eso, el vectorizador se prepara para generar incorporaciones dispersas.

El vectorizador de TF-IDF intenta dar mayor importancia a las palabras características del conjunto de datos (como “Camisas” o “Dino”) en comparación con las palabras triviales (como “El”, “a” o “de”) y cuenta cuántas veces se usan esas palabras características en el documento especificado. Cada valor de una incorporación dispersa representa una frecuencia de cada palabra según los recuentos. Para obtener más información sobre TF-IDF, consulta ¿Cómo funcionan TF-IDF y TfidfVectorizer?.

En este ejemplo, usamos la tokenización básica a nivel de la palabra y la vectorización de TF-IDF para simplificar. En el desarrollo de producción, puedes elegir cualquier otra opción de tokenización y vectorización para generar incorporaciones escasas según tus requisitos. En el caso de los generadores de tokens, en muchos casos, los generadores de tokens de subpalabras tienen un buen rendimiento en comparación con la tokenización a nivel de la palabra y son opciones populares. En el caso de los vectorizadores, BM25 es popular como una versión mejorada de TF-IDF. SPLADE es otro algoritmo de vectorización popular que toma algunos semánticas para la incorporación dispersa.

Obtén una incorporación dispersa

Para que el vectorizador sea más fácil de usar con Vector Search, definiremos una función wrapper, get_sparse_embedding():

def get_sparse_embedding(text):

  # Transform Text into TF-IDF Sparse Vector
  tfidf_vector = vectorizer.transform([text])

  # Create Sparse Embedding for the New Text
  values = []
  dims = []
  for i, tfidf_value in enumerate(tfidf_vector.data):
    values.append(float(tfidf_value))
    dims.append(int(tfidf_vector.indices[i]))
  return {"values": values, "dimensions": dims}

Esta función pasa el parámetro "text" al vectorizador para generar una incorporación escasa. Luego, conviértelo al formato {"values": ...., "dimensions": ...} que se mencionó antes para compilar un índice disperso de la Búsqueda de vectores.

Puedes probar esta función:

text_text = "Chrome Dino Pin"
get_sparse_embedding(text_text)

Esto debería mostrar la siguiente incorporación dispersa:

{'values': [0.6756557405747007, 0.5212913389979028, 0.5212913389979028],
 'dimensions': [157, 48, 33]}

Crea un archivo de datos de entrada

En este ejemplo, generaremos incorporaciones dispersas para los 200 elementos.

items = []
for i in range(len(df)):
  id = i
  title = df.title[i]
  sparse_embedding = get_sparse_embedding(title)
  items.append({"id": id, "title": title, "sparse_embedding": sparse_embedding})

Este código genera la siguiente línea para cada elemento:

{
  'id': 0,
  'title': 'Google Sticker',
  'sparse_embedding': {
    'values': [0.933008728540452, 0.359853737603667],
    'dimensions': [191, 78]
  }
}

Luego, guárdalos como un archivo JSONL "items.json" y súbelos a un bucket de Cloud Storage.

# output as a JSONL file and save to bucket
with open("items.json", "w") as f:
  for item in items:
    f.write(f"{item}\n")
! gsutil cp items.json $BUCKET_URI

Crea un índice de incorporación dispersa en la Búsqueda de vectores

A continuación, compilaremos e implementaremos un índice de incorporación dispersa en Vector Search. Este es el mismo procedimiento que se documenta en la Guía de inicio rápido de la Búsqueda de vectores.

# create Index
my_index = aiplatform.MatchingEngineIndex.create_tree_ah_index(
  display_name = f"vs-hybridsearch-index-{UID}",
  contents_delta_uri = BUCKET_URI,
  dimensions = 768,
  approximate_neighbors_count = 10,
)

Para usar el índice, debes crear un extremo de índice. Funciona como una instancia de servidor que acepta solicitudes de consulta para tu índice.

# create IndexEndpoint
my_index_endpoint = aiplatform.MatchingEngineIndexEndpoint.create(
  display_name = f"vs-quickstart-index-endpoint-{UID}",
  public_endpoint_enabled = True
)

Con el extremo del índice, especifica el ID del índice implementado único para implementarlo.

DEPLOYED_INDEX_ID = f"vs_quickstart_deployed_{UID}"
# deploy the Index to the Index Endpoint
my_index_endpoint.deploy_index(
    index = my_index, deployed_index_id = DEPLOYED_INDEX_ID
)

Después de esperar a que se complete la implementación, ya podemos ejecutar una consulta de prueba.

Ejecuta una consulta con un índice de incorporación disperso

Para ejecutar una consulta con un índice de incorporación dispersa, debes crear un objeto HybridQuery para encapsular la incorporación dispersa del texto de la consulta, como en el siguiente ejemplo:

from google.cloud.aiplatform.matching_engine.matching_engine_index_endpoint import HybridQuery

# create HybridQuery
query_text = "Kids"
query_emb = get_sparse_embedding(query_text)
query = HybridQuery(
  sparse_embedding_dimensions=query_emb['dimensions'],
  sparse_embedding_values=query_emb['values'],
)

En este código de ejemplo, se usa el texto "Kids" para la consulta. Ahora, ejecuta una consulta con el objeto HybridQuery.

# build a query request
response = my_index_endpoint.find_neighbors(
  deployed_index_id=DEPLOYED_INDEX_ID,
  queries=[query],
  num_neighbors=5,
)

# print results
for idx, neighbor in enumerate(response[0]):
  title = df.title[int(neighbor.id)]
  print(f"{title:<40}")

Esto debería proporcionar un resultado como el siguiente:

Google Blue Kids Sunglasses
Google Red Kids Sunglasses
YouTube Kids Coloring Pencils
YouTube Kids Character Sticker Sheet

De los 200 artículos, el resultado contiene los nombres de los artículos que tienen la palabra clave "Kids".

En este ejemplo, se combina la búsqueda basada en tokens con la búsqueda semántica para crear una búsqueda híbrida en la Búsqueda vectorial.

Cómo crear un índice híbrido

Para compilar un índice híbrido, cada elemento debe tener "embedding" (para la incorporación densa) y "sparse_embedding":

items = []
for i in range(len(df)):
  id = i
  title = df.title[i]
  dense_embedding = get_dense_embedding(title)
  sparse_embedding = get_sparse_embedding(title)
  items.append(
    {"id": id, "title": title,
      "embedding": dense_embedding,
      "sparse_embedding": sparse_embedding,}
  )
items[0]

La función get_dense_embedding() usa la API de incorporaciones de Vertex AI para generar incorporaciones de texto con 768 dimensiones. Esto genera incorporaciones densas y dispersas en el siguiente formato:

{
  "id": 0,
  "title": "Google Sticker",
  "embedding":
    [0.022880317643284798,
    -0.03315234184265137,
    ...
    -0.03309667482972145,
    0.04621824622154236],
  "sparse_embedding": {
    "values": [0.933008728540452, 0.359853737603667],
    "dimensions": [191, 78]
  }
}

El resto del proceso es el mismo que en el ejemplo: Cómo usar la búsqueda basada en tokens: sube el archivo JSONL al bucket de Cloud Storage, crea un índice de búsqueda vectorial con el archivo y, luego, implementa el índice en el extremo del índice.

Ejecuta una consulta híbrida

Después de implementar el índice híbrido, puedes ejecutar una consulta híbrida:

# create HybridQuery
query_text = "Kids"
query_dense_emb = get_dense_embedding(query_text)
query_sparse_emb = get_sparse_embedding(query_text)
query = HybridQuery(
  dense_embedding=query_dense_emb,
  sparse_embedding_dimensions=query_sparse_emb['dimensions'],
  sparse_embedding_values=query_sparse_emb['values'],
  rrf_ranking_alpha=0.5,
)

Para el texto de la consulta “Niños”, genera incorporaciones densas y dispersas para la palabra y encapsulalas en el objeto HybridQuery. La diferencia con el HybridQuery anterior son dos parámetros adicionales: dense_embedding y rrf_ranking_alpha.

Esta vez, imprimiremos las distancias de cada elemento:

# print results
for idx, neighbor in enumerate(response[0]):
  title = df.title[int(neighbor.id)]
  dense_dist = neighbor.distance if neighbor.distance else 0.0
  sparse_dist = neighbor.sparse_distance if neighbor.sparse_distance else 0.0
  print(f"{title:<40}: dense_dist: {dense_dist:.3f}, sparse_dist: {sparse_dist:.3f}")

En cada objeto neighbor, hay una propiedad distance que tiene la distancia entre la consulta y el elemento con la incorporación densa, y una propiedad sparse_distance que tiene la distancia con la incorporación dispersa. Estos valores son distancias invertidas, por lo que un valor más alto significa una distancia más corta.

Si ejecutas una consulta con HybridQuery, obtendrás el siguiente resultado:

Google Blue Kids Sunglasses             : dense_dist: 0.677, sparse_dist: 0.606
Google Red Kids Sunglasses              : dense_dist: 0.665, sparse_dist: 0.572
YouTube Kids Coloring Pencils           : dense_dist: 0.655, sparse_dist: 0.478
YouTube Kids Character Sticker Sheet    : dense_dist: 0.644, sparse_dist: 0.468
Google White Classic Youth Tee          : dense_dist: 0.645, sparse_dist: 0.000
Google Doogler Youth Tee                : dense_dist: 0.639, sparse_dist: 0.000
Google Indigo Youth Tee                 : dense_dist: 0.637, sparse_dist: 0.000
Google Black Classic Youth Tee          : dense_dist: 0.632, sparse_dist: 0.000
Chrome Dino Glow-in-the-Dark Youth Tee  : dense_dist: 0.632, sparse_dist: 0.000
Google Bike Youth Tee                   : dense_dist: 0.629, sparse_dist: 0.000

Además de los resultados de la búsqueda basados en tokens que tienen la palabra clave "Kids", también se incluyen resultados de la búsqueda semántica. Por ejemplo, se incluye “Google White Classic Youth Tee” porque el modelo de incorporación sabe que “Youth” y “Kids” son semánticamente similares.

Para combinar los resultados de la búsqueda semántica y basada en tokens, la búsqueda híbrida usa la fusión de clasificación recíproca (RRF). Para obtener más información sobre la RRF y cómo especificar el parámetro rrf_ranking_alpha, consulta ¿Qué es la fusión de rango recíproco?.

Reclasificación

El RRF proporciona una forma de combinar la clasificación de los resultados de la búsqueda semántica y basada en tokens. En muchos sistemas de recuperación de información o de recomendación de producción, los resultados pasarán por más algoritmos de clasificación de precisión, lo que se denomina reclasificación. Con la combinación de la recuperación rápida a nivel de milisegundos con la búsqueda vectorial y el restablecimiento del ranking de precisión en los resultados, puedes compilar sistemas de varias etapas que proporcionan una mayor calidad de búsqueda o un mejor rendimiento de las recomendaciones.

Canalización para evaluar millones de artículos y, luego, volver a clasificar cientos de artículos para producir una cantidad menor de recomendaciones.

La API de clasificación de Vertex AI proporciona una forma de implementar la clasificación en función de la relevancia genérica entre el texto de la consulta y los textos de los resultados de la búsqueda con el modelo previamente entrenado. TensorFlow Ranking también proporciona una introducción sobre cómo diseñar y entrenar modelos de aprendizaje de clasificación (LTR) para la clasificación avanzada que se puede personalizar para varios requisitos empresariales.

Cómo comenzar a usar la búsqueda híbrida

Los siguientes recursos pueden ayudarte a comenzar a usar la búsqueda híbrida en la Búsqueda de vectores.

Recursos de búsqueda híbrida

Recursos de Búsqueda de vectores

Conceptos adicionales

En las siguientes secciones, se describen TF-IDF y TfidVectorizer, la fusión de clasificaciones recíprocas y el parámetro alfa con más detalle.

¿Cómo funcionan TF-IDF y TfidfVectorizer?

La función fit_transform() ejecuta dos procesos importantes del algoritmo TF-IDF:

  • Ajuste: El vectorizador calcula la frecuencia de documento inversa (IDF) para cada término del vocabulario. El IDF refleja la importancia que tiene un término en todo el corpus. Los términos poco comunes obtienen puntuaciones de IDF más altas:

    IDF(t) = log_e(Total number of documents / Number of documents containing term t)

  • Transformación:

    • Asignación de tokens: Divide los documentos en términos individuales (palabras o frases).
    • Cálculo de la frecuencia de término (TF): Cuenta la frecuencia con la que cada término aparece en cada documento con lo siguiente:

      TF(t, d) = (Number of times term t appears in document d) / (Total number of terms in document d)

    • Cálculo de TF-IDF: Combina la TF de cada término con el IDF calculado previamente para crear una puntuación de TF-IDF. Esta puntuación representa la importancia de un término en un documento en particular en relación con todo el corpus.

      TF-IDF(t, d) = TF(t, d) * IDF(t)

      El vectorizador de TF-IDF intenta dar más importancia a las palabras características del conjunto de datos, como “Camisas” o “Dino”, en comparación con las palabras triviales, como “El”, “a” o “de”, y cuenta cuántas veces se usan esas palabras características en el documento especificado. Cada valor de una incorporación dispersa representa una frecuencia de cada palabra según los recuentos.

¿Qué es la fusión de clasificación recíproca?

Para combinar los resultados de la búsqueda semántica y basada en tokens, la búsqueda híbrida usa la fusión de clasificación recíproca (RRF). El RRF es un algoritmo para combinar varias listas de elementos clasificados en una sola clasificación unificada. Es una técnica popular para combinar resultados de la búsqueda de diferentes fuentes o métodos de recuperación, especialmente en sistemas de búsqueda híbridos y modelos de lenguaje extensos.

En el caso de la búsqueda híbrida de la Búsqueda vectorial, la distancia densa y la distancia dispersa se miden en diferentes espacios y no se pueden comparar directamente entre sí. Por lo tanto, el RRF funciona de manera eficaz para combinar y clasificar los resultados de los dos espacios diferentes.

A continuación, te mostramos cómo funciona el RRF:

  1. Rango recíproco: Para cada elemento de una lista clasificada, calcula su rango recíproco. Esto significa tomar la inversa de la posición (clasificación) del elemento en la lista. Por ejemplo, el elemento que ocupa el primer lugar obtiene una clasificación recíproca de 1/1 = 1, y el elemento que ocupa el segundo lugar obtiene 1/2 = 0.5.
  2. Suma las clasificaciones recíprocas: Suma las clasificaciones recíprocas de cada elemento en todas las listas clasificadas. Esto genera una puntuación final para cada elemento.
  3. Ordenar por puntuación final: Ordena los elementos por su puntuación final de forma descendente. Los elementos con las puntuaciones más altas se consideran los más relevantes o importantes.

En resumen, los elementos con clasificaciones más altas en los resultados densos y dispersos se mostrarán en la parte superior de la lista. Por lo tanto, el artículo "Gafas de sol para niños azules de Google" se encuentra en la parte superior, ya que tiene clasificaciones más altas en los resultados de la búsqueda densa y dispersa. Los elementos como "Google White Classic Youth Tee" tienen una clasificación baja, ya que solo tienen clasificaciones en el resultado de la búsqueda densa.

Cómo se comporta el parámetro alfa

En el ejemplo de cómo usar la búsqueda híbrida, se establece el parámetro rrf_ranking_alpha como 0.5 cuando se crea el objeto HybridQuery. Puedes especificar un peso para ordenar los resultados de la búsqueda densa y dispersa con los siguientes valores para rrf_ranking_alpha:

  • 1 o no especificado: La búsqueda híbrida solo usa resultados de búsqueda densa y omite los resultados de búsqueda dispersos.
  • 0: La búsqueda híbrida solo usa resultados de búsqueda dispersos y omite los resultados de búsqueda densos.
  • 0 a 1: La búsqueda híbrida combina los resultados de las incorporaciones densas y dispersas con el peso que especifica el valor. 0.5 significa que se combinarán con el mismo peso.