# Búsqueda híbrida en MyScale

Esta guía presenta los beneficios de utilizar la búsqueda híbrida para mejorar su experiencia de búsqueda de texto y proporciona instrucciones sobre cómo implementarla con MyScale.

# ¿Por qué necesita la búsqueda híbrida?

La búsqueda vectorial puede capturar las relaciones semánticas entre las palabras, manejar expresiones complejas y ambiguas en lenguaje natural y admitir búsquedas multimodales y cruzadas. Si bien es potente y eficiente para muchas tareas, la búsqueda vectorial puede necesitar ayuda para comprender correctamente la semántica en búsquedas de texto corto.

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

Las consultas de texto corto a menudo necesitan más información y contexto para producir resultados precisos y altamente relevantes; en su lugar, devuelven resultados de baja precisión. Por ejemplo, en casos en los que los usuarios necesitan realizar coincidencias completas y precisas para frases como nombres de productos, etiquetas de productos y tallas de ropa, las tecnologías de búsqueda tradicionales (no de búsqueda vectorial) basadas en coincidencia de términos, como "BM25" y "TF-IDF", son una opción más adecuada.

La búsqueda híbrida, una combinación de búsqueda semántica y coincidencia de términos tradicional, supera los desafíos de la cobertura semántica insuficiente en documentos vectorizados. Por ejemplo, si está buscando un destornillador en la base de datos de una ferretería utilizando la búsqueda vectorial, el conjunto de resultados incluirá todas las opciones almacenadas en la base de datos. Sin embargo, la coincidencia de términos es más útil cuando se busca un destornillador específico que coincida exactamente con el modelo, la longitud y el material.

# Uso de la búsqueda híbrida en MyScale

Este tutorial le mostrará cómo utilizar la búsqueda híbrida en MyScale. Para completar este tutorial, solo necesita una cuenta de MyScale y un entorno Python3 en su máquina local. Inicie sesión en MyScale y cree un clúster, que utilizará en todo este tutorial.

TIP

Las instrucciones para crear un clúster se encuentran en nuestra documentación de inicio rápido (opens new window).

Una vez que haya creado un clúster, los siguientes pasos son los siguientes:

# Crear una tabla en MyScale

Ejecute la siguiente declaración SQL para crear la tabla rd0 en el Espacio de Trabajo SQL de MyScale:

CREATE TABLE default.rd0
(
    `id` UInt64,
    `body` String,
    `title` String,
    `url` String,
    `body_vector` Array(Float32),
    CONSTRAINT check_length CHECK length(body_vector) = 384
)
ENGINE = MergeTree
ORDER BY id;

Puede utilizar la siguiente declaración SQL para comprobar si se ha creado la tabla:

SHOW tables;

Si se creó la tabla, esta declaración SQL devolverá el siguiente conjunto de resultados:

name
rd0

# Importar datos desde Amazon S3

Hemos mejorado el conjunto de datos de resúmenes de Wikipedia (opens new window) alojado por RedisSearch al incluir datos vectoriales. Utilizamos sentence-transformers/all-MiniLM-L6-v2 para convertir el texto en la columna body en vectores de 384 dimensiones. Estos vectores se almacenan en la columna body_vector, y se utiliza el coseno para calcular la distancia entre ellos.

TIP

Para obtener más información sobre cómo utilizar all-MiniLM-L6-v2, consulte la documentación de HuggingFace (opens new window).

El conjunto de datos final, wiki_abstract_with_vector.parquet (opens new window), tiene un tamaño de 8,2 GB y contiene 5.622.309 entradas. Puede obtener una vista previa del contenido de este conjunto de datos a continuación. No es necesario descargarlo en su máquina local, ya que podemos importarlo directamente en MyScale a través de S3.

id body title url body_vector
... ... ... ... ...
77 Jake Rodkin is an American .... and Puzzle Agent. Jake Rodkin https://en.wikipedia.org/wiki/Jake_Rodkin (opens new window) [-0.081793934,....,-0.01105572]
78 Friedlandpreis der Heimkehrer is ... of Germany. Friedlandpreis der Heimkehrer https://en.wikipedia.org/wiki/Friedlandpreis_der_Heimkehrer (opens new window) [0.018285718,...,0.03049711]
... ... ... ... ...

Ejecute el siguiente comando SQL en el Espacio de Trabajo SQL para importar estos datos.

INSERT INTO default.rd0 SELECT * FROM s3('https://myscale-datasets.s3.ap-southeast-1.amazonaws.com/wiki_abstract_with_vector.parquet','Parquet');

Note

El tiempo estimado para la importación de datos es de aproximadamente 10 minutos.

Ejecute la siguiente declaración SQL para comprobar cuándo los datos importados han alcanzado las 5.622.309 filas.

SELECT COUNT(*) FROM default.rd0;

TIP

Puede ejecutar esta declaración SQL más de una vez hasta que los datos hayan terminado de importarse.

# Crear un índice vectorial

La primera parte de la creación de un índice vectorial es mejorar el rendimiento de la búsqueda vectorial mediante la fusión de las partes de datos de la tabla en una sola parte antes de agregar el índice vectorial a esta tabla.

# Mejorar el rendimiento de la búsqueda vectorial

Para optimizar la tabla (mejorar el rendimiento de la búsqueda vectorial), ejecute el siguiente comando SQL en su Espacio de Trabajo SQL:

OPTIMIZE TABLE default.rd0 FINAL;

Este comando puede tardar un tiempo en ejecutarse.

Ejecute la siguiente declaración SQL para comprobar si las partes de datos de esta tabla se han condensado en 1.

SELECT COUNT(*) FROM system.parts WHERE table='rd0' AND active=1;

Si las partes de datos se comprimieron en 1, esta declaración SQL devolverá el siguiente conjunto de resultados:

count()
1

# Crear el índice vectorial

Ejecute la siguiente declaración para crear un índice vectorial:

Note

MSTG es un índice vectorial desarrollado por MyScale.

ALTER TABLE default.rd0 ADD VECTOR INDEX RD0_MSTG body_vector
TYPE MSTG('metric_type=Cosine');

La creación de un índice requiere tiempo. Ejecute la siguiente declaración SQL para verificar el progreso de la creación del índice. Si la columna de estado devuelve Built, el índice se ha creado correctamente. Mientras el índice aún se está construyendo, la columna de estado debería devolver InProgress.

SELECT * FROM system.vector_indices;

# Realizar búsqueda vectorial y búsqueda híbrida

Esta guía describe tanto la búsqueda vectorial como la búsqueda híbrida.

Sin embargo, antes de continuar, debemos realizar algunos trabajos preliminares.

# Trabajo preliminar

Utilice el siguiente código Python en su aplicación para lograr lo siguiente:

  • Modifique el host, el nombre de usuario y la contraseña para conectarse a su clúster de MyScale.
  • Importe el modelo all-MiniLM-L6-v2 del transformador para convertir el texto en vectores.
  • Cree una función de salida simple para ver los resultados de la ejecución de SQL.
import clickhouse_connect
from prettytable import PrettyTable
from sentence_transformers import SentenceTransformer
# Utilice el modelo all-MiniLM-L6-v2 del transformador
model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
# Información de MyScale
host = "your_endpoint"
port = 443
username = "your_username"
password = "your_password"
database = "default"
table = "rd0"
# Inicializar el cliente de MyScale
client = clickhouse_connect.get_client(host=host, port=port,
                                       username=username, password=password)
# Utilice una tabla para mostrar su contenido
def print_results(result_rows, field_names):
    x = PrettyTable()
    x.field_names = field_names
    for row in result_rows:
        x.add_row(row)
    print(x)

# Búsqueda Vectorial

Como se describe en el siguiente fragmento de código de Python, el proceso de búsqueda vectorial, también conocido como búsqueda neural, es el siguiente:

  • Utilice el modelo all-MiniLM-L6-v2 para convertir el texto "historia sobre el servicio de noticias de televisión" en un vector de incrustación.
  • Utilice una búsqueda vectorial para devolver las 5 páginas de Wikipedia más similares del conjunto de datos.
# probando la búsqueda vectorial
sentence = "history about television news service"
sentence_embedding = model.encode([sentence])[0]
sentence_result = client.query(query=f"SELECT id, title, body, distance('alpha=1')"
                                     f"(body_vector, {list(sentence_embedding)}) AS distance "
                                     f"FROM {database}.{table} ORDER BY distance ASC LIMIT 5")
print_results(sentence_result.result_rows, ["ID", "Title", "Body", "Distance"])

La siguiente tabla describe los resultados de la búsqueda.

ID Title Body Distance
2341540 Television news in the United States Television news in the United States has evolved over many years. It has gone from a simple 10- to 15-minute format in the evenings, to a variety of programs and channels. 0.3019871711730957
4741891 United States cable news Cable news channels are television channels devoted to television news broadcasts, with the name deriving from the proliferation of such networks during the 1980s with the advent of cable television. In the United States, early networks included CNN in 1980, Financial News Network (FNN) in 1981 and CNN2 (now HLN) in 1982. 0.3059382438659668
4555265 News and Views (TV series) News and Views was an early American evening news program. Broadcast on ABC from 1948 to 1951, it was ABC's first evening news program and one of the first such programs on any television network; Both CBS and NBC also initiated their evening news programs (respectively CBS Television News and Camel News Caravan, called Camel Newsreel Theatre at first) that same year, both debuting a few months before the first broadcast of News and Views on August 11, 1948. 0.3165452480316162
185179 MediaTelevision Media Television was a Canadian television newsmagazine series, which aired weekly on Citytv from 1991 to 2004. It was also syndicated internationally, airing in over 100 countries around the world at some point during its run. 0.32938069105148315
1426832 News service News service may refer to: 0.3431185483932495

# Limitaciones de la Búsqueda Vectorial

A partir de la descripción anterior, está claro que utilizar una búsqueda vectorial pura para frases cortas tiene limitaciones.

Por ejemplo, convirtamos la frase "Isla BGLE" en un vector, realicemos una búsqueda vectorial y veamos los resultados.

terms = "BGLE Island"
terms_embedding = model.encode([terms])[0]
stage1 = f"SELECT id, title, body, distance('alpha=1')" \
         f"(body_vector,{list(terms_embedding)}) AS distance FROM {database}.{table} " \
         f"ORDER BY distance ASC LIMIT 5"
sentence_result = client.query(query=stage1)
print_results(client.query(query=stage1).result_rows, ["ID", "Title", "Body", "Distance"])

Aquí están los cinco principales resultados de búsqueda:

ID Title Body Distance
2625112 Bligh Island (Alaska) Bligh Island}} 0.227422833442688
2625120 Bligh Island (Canada) Bligh Island}} 0.227422833442688
4894492 Hedley (band) Island 0.3269183039665222
4708096 Blueberry Island Blueberry Island may refer to: 0.3446136713027954
5519217 Brown Island (Antarctica) Brown Island}} 0.35350120067596436

Note

Al observar estos resultados, es evidente que los 5 primeros resultados no incluyen la palabra "BGLE".

# Búsqueda Híbrida

Utilicemos una búsqueda híbrida para mejorar la precisión de los resultados en lugar de depender únicamente de la búsqueda vectorial para frases cortas o palabras individuales. Por ejemplo, para el término "Isla BGLE", seguiremos un enfoque de dos etapas:

  • Utilice una búsqueda vectorial para identificar los 200 candidatos principales.
  • Utilice las funciones incorporadas de MyScale y un método TF-IDF (frecuencia de término-inverso de frecuencia de documento) simplificado para reorganizar y refinar estos resultados.

# Utilizar una Búsqueda Vectorial

El siguiente fragmento de código describe cómo realizar una búsqueda vectorial para identificar los 200 mejores resultados:

# Etapa 1. Recuperación Vectorial
terms = "BGLE Island"
terms_embedding = model.encode([terms])[0]
terms_pattern = [f'(?i){x}' for x in terms.split(' ')]
stage1 = f"SELECT id, title, body, distance('alpha=1')" \
         f"(body_vector,{list(terms_embedding)}) AS distance FROM {database}.{table} " \
         f"ORDER BY distance ASC LIMIT 200"

# Funciones Auxiliares

Antes de realizar una búsqueda híbrida, debemos comprender las siguientes dos funciones proporcionadas por MyScale:

multiMatchAllIndices(): Esta función devuelve el índice de inicio de todas las subcadenas en una cadena que coinciden con las expresiones regulares especificadas. Toma dos parámetros, la cadena de origen y una lista de expresiones regulares.

TIP

Este índice comienza desde 1 y no desde 0.

Note

Para obtener más información, consulte la documentación de ClickHouse sobre multiMatchAllIndices (opens new window).

Por ejemplo:

SELECT multiMatchAllIndices(
        'He likes to eat tomatoes.',
        ['(?i)\\blike\\b', '(?i)likes', '(?i)Tomatoes']) AS result

Cuando se ejecuta, esta instrucción SQL devolverá el siguiente resultado:

result
[2, 3]

countMatches(): Esta función cuenta el número de subcadenas especificadas en una cadena. Toma dos parámetros, la cadena de origen y una expresión regular utilizando la sintaxis re2.

Note

Para obtener más información, consulte la documentación de ClickHouse sobre countMatches (opens new window).

Por ejemplo:

SELECT countMatches('He likes to eat tomatoes', '(?i)Tomatoes') AS result

Cuando se ejecuta, esta instrucción SQL devolverá el siguiente resultado:

result
1

# Ordenar los Resultados de la Búsqueda

Como se resalta en el siguiente fragmento de código de Python, esta siguiente etapa ordena estos resultados de búsqueda dos veces (reordenamiento de términos):

  • Ordena estos resultados en función de su popularidad. Cuantas más coincidencias de búsqueda tenga un resultado, mayor será su clasificación.
  • Ordena estos resultados nuevamente en función del número de coincidencias de búsqueda (frecuencia de término). Cuanto mayor sea la frecuencia de término, mayor será la clasificación.

TIP

Utilizamos un TF-IDF simplificado para ordenar estos resultados por segunda vez.

# Etapa 2. Reordenamiento de Términos
stage2 = f"SELECT tempt.id, tempt.title,tempt.body, distance1, distance2 FROM ({stage1}) tempt " \
         f"ORDER BY length(multiMatchAllIndices(arrayStringConcat([body, title], ' '), {terms_pattern})) " \
         f"AS distance1 DESC, " \
         f"log(1 + countMatches(arrayStringConcat([title, body], ' '), '(?i)({terms.replace(' ', '|')})')) " \
         f"AS distance2 DESC limit 10"
sentence1_result = client.query(query=stage2)
print_results(sentence1_result.result_rows, ["ID", "Title", "Body", "distance1", "distance2"])

Los resultados de la búsqueda son los siguientes:

ID Title Body distance1 distance2
4426976 Symington Islands Symington Islands () is a group of small islands lying west-northwest of Lahille Island, in the Biscoe Islands. Charted by the British Graham Land Expedition (BGLE) under Rymill, 1934-37. 2 1.945910148700207
4425283 Saffery Islands Saffery Islands () is a group of islands extending west from Black Head, off the west coast of Graham Land. Charted by the British Graham Land Expedition (BGLE) under Rymill, 1934–37. 2 1.6094379132876024
466090 The Narrows (Antarctica) The Narrows () is a narrow channel between Pourquoi Pas Island and Blaiklock Island, connecting Bigourdan Fjord and Bourgeois Fjord off the west coast of Graham Land. It was discovered and given this descriptive name by the British Graham Land Expedition (BGLE), 1934–37, under Rymill. 2 1.3862943611198906
79253 Boaz Island, Bermuda Boaz Island, formerly known as Gate's Island or Yates Island, is one of the six main islands of Bermuda. It is part of a chain of islands in the west of the country that make up Sandys Parish, lying between the larger Ireland Island and Somerset Island, and is connected to both by bridges. 1 2.1972245771389134
3886596 Moresby Island (Gulf Islands) Moresby Island is one of the Gulf Islands of British Columbia, located on the west side of Swanson Channel and east of the southern end of Saltspring Island. It is not to be confused with Moresby Island, the second largest of the Queen Charlotte Islands off the north coast of BC. 1 2.0794415416798357
5026601 Bazett Island Bazett Island is a small island close south of the west end of Krogh Island, in the Biscoe Islands. It was mapped from air photos by the Falkland Islands and Dependencies Aerial Survey Expedition (1956–57), and named by the UK Antarctic Place-Names Committee for Henry C. 1 1.945910148700207
5026603 Bazzano Island Bazzano Island () is a small island lying off the south end of Petermann Island, between Lisboa Island and Boudet Island in the Wilhelm Archipelago. It was discovered and named by the French Antarctic Expedition, 1908–10, under Jean-Baptiste Charcot. 1 1.945910148700207
5451889 Baudisson Island Baudisson Island is an island of Papua New Guinea, located south of New Hanover Island and west of the northern part of New Ireland. It is located between Selapiu Island and Manne Island. 1 1.945910148700207
4176021 Bluck's Island, Bermuda Bluck's Island (formerly Denslow['s] Island, Dyer['s] Island) is an island of Bermuda. It lies in the harbor of Hamilton in Warwick Parish. 1 1.7917594699409376
202822 Sorge Island Sorge Island () is an island lying just south of The Gullet in Barlas Channel, close east of Adelaide Island. Mapped by Falkland Islands Dependencies Survey (FIDS) from surveys and air photos, 1948-59. 1 1.7917594699409376

Las dos operaciones de ordenamiento se muestran a continuación:

rerank

# Explicación de TF-IDF

TF-IDF es una medida estadística utilizada para evaluar la relevancia de una palabra en una colección de documentos. Logra esto multiplicando dos métricas: el número de veces que una palabra aparece en un documento específico y la frecuencia inversa (opuesta) de la palabra en el conjunto de todos los documentos.

Por ejemplo:

En este ejemplo, es el conjunto de palabras, y ​ es la frecuencia de todas las palabras/términos que aparecen en el documento . El cálculo estándar de TF-IDF calcula la frecuencia de término de cada palabra individualmente para medir la relevancia:

El siguiente paso es calcular una frecuencia inversa de documento diferente para cada palabra. Si todas las palabras se consideran como una clase, el cálculo de TF-IDF se puede simplificar de la siguiente manera:

Donde

Cuando utilizamos el cálculo simplificado de TF-IDF, utilizamos la misma frecuencia inversa de documento (IDF) como denominador para calcular la relevancia de todas las frecuencias de término. Por lo tanto, estos denominadores IDF que no afectan los resultados de clasificación se pueden eliminar, por lo que el TF-IDF simplificado final se convierte en una forma de TF: