En la mayoría de los casos, las empresas permanecen en un nivel muy básico de búsqueda simplemente consultando directamente las bases de datos OLTP. Apache Lucene, Apache Solr, Elasticsearch, Sphinx, MeiliSearch, Typesense, etc. tienden a ser comparativamente más rápidos y mucho mejores para manejar consultas complejas y trabajar con filtros.
Las aplicaciones modernas a menudo incorporan soluciones de búsqueda para permitir a los usuarios acceder rápidamente al contenido existente bajo demanda. Es difícil imaginar cualquier otra funcionalidad que pueda recuperar esta información de manera eficiente, lo que hace que la búsqueda sea una función esencial en la mayoría de las aplicaciones.
Opciones de solución de búsqueda personalizadas
Simultáneamente, incluso si la necesidad de consultar y buscar es muy común, diferentes aplicaciones adoptan enfoques drásticamente diferentes.
En la mayoría de los casos, las empresas permanecen en un nivel muy básico de búsqueda simplemente consultando directamente las bases de datos OLTP. Las solicitudes pueden tener este aspecto: SELECT id, title FROM, entities WHERE, description LIKE '%bow% .
Sin embargo, con más frecuencia, se representan en estructuras complejas de combinación de tablas de múltiples niveles que son imposibles de leer, lentas y primitivas. No pueden comprender el contexto, requieren numerosas personalizaciones y son muy difíciles de implementar correctamente.
Si bien es posible mejorar el tiempo de ejecución de consultas a través de vistas materializadas, almacenamiento en caché de consultas y otras técnicas, la complejidad adicional genera un retraso considerable entre las actualizaciones primarias y los resultados de búsqueda consistentes.
Las alternativas más eficientes a las soluciones de búsqueda primitivas basadas en bases de datos pueden constituir motores de búsqueda de código abierto como Apache Lucene, Apache Solr, Elasticsearch, Sphinx, MeiliSearch, Typesense, etc.
Estos tienden a ser comparativamente más rápidos y mucho mejores para manejar consultas complejas y trabajar con filtros. Pero una vez que estos motores de búsqueda se comparan con sus contrapartes como Google Search o DuckDuckGo, queda claro que las soluciones de código abierto no logran crear un contexto de búsqueda adecuado y modalidades de consulta: son incapaces de comprender una consulta si el usuario proporciona una solicitud de búsqueda vaga.
Extrayendo el significado de la consulta
¡Imagina que simplemente no puedes recordar cómo se llaman esos cítricos amarillos con un sabor agrio! Pero desea buscar en la aplicación un artículo sobre cómo cultivar esta fruta misteriosa. ¿Cómo haces esa búsqueda?
Su consulta puede ser, “Cómo cultivar cítricos amarillos amargos en interiores”. Cualquiera de los motores de búsqueda de código abierto mencionados anteriormente puede tener dificultades significativas para devolver resultados relevantes para esta consulta, incluso si la base de datos contiene artículos sobre el cultivo de "limones".
Esto se debe a que la extracción del significado de una consulta es una tarea de lenguaje natural y es poco probable que se resuelva sin componentes de IA. GPT-3 es bueno en esta tarea.
Ofertas Open AI basado en GPT-3 que convierte texto en lenguaje natural en un vector de números de punto flotante. Una incrustación es esencialmente un espacio de baja dimensión en el que se pueden traducir vectores de alta dimensión. En este caso, el vector de alta dimensión es texto y el de baja dimensión es un vector de salida de tamaño fijo. La distancia entre vectores representa la fuerza con la que se correlacionan. Cuanto menor sea la distancia, mayor será la correlación. Al redefinir el texto como una forma basada en vectores, la tarea se reduce a un problema de búsqueda clásico de ML.
Elegir el modelo correcto
La conversión del texto del documento en un vector representativo puede ocurrir en segundo plano, mientras que la vectorización de la consulta de búsqueda debe ocurrir durante el tiempo de ejecución. Hay varias familias de modelos GPT-3 que ofrece OpenAI:
Las dimensiones de vector más altas conducen a más información incrustada y, por lo tanto, también a costos más altos y búsquedas más lentas.
Los documentos suelen ser largos y las consultas suelen ser cortas e incompletas. Por lo tanto, la vectorización de cualquier documento difiere significativamente de la vectorización de cualquier consulta considerando la densidad y el tamaño del contenido. OpenAI lo sabe, por lo que ofrece dos modelos emparejados, -doc y -query :
Es importante tener en cuenta que la consulta y el documento deben utilizar la misma familia de modelos y tener la misma longitud del vector de salida.
Conjunto de datos de ejemplo
Puede ser más fácil observar y comprender el poder de esta solución de búsqueda a través de ejemplos. Para este ejemplo, recurramos a la que contiene metadatos sobre aproximadamente 5000 películas de TMDb. Crearemos una solución de búsqueda basada únicamente en documentos de películas, no en reseñas.
El conjunto de datos contiene muchas columnas, pero nuestro proceso de vectorización se basará solo en las columnas de título y descripción general.
Ejemplo del registro:
Title: Harry Potter and the Half-Blood Prince Overview: As Harry begins his sixth year at Hogwarts, he discovers an old book marked as 'Property of the Half-Blood Prince', and begins to learn more about Lord Voldemort's dark past.
Mapeemos el conjunto de datos en texto listo para indexar:
Este bloque de código genera una lista cuyo tamaño es igual a los parámetros con los que opera el modelo, que en el caso de text-search-babbage-doc-001 es 2048.
Se debe aplicar un proceso de incrustación similar a todos los documentos en los que nos gustaría buscar:
La columna combined_info_search contendrá una representación vectorial del texto_combinado.
Y, sorprendentemente, ¡eso ya es todo! Finalmente, estamos listos para realizar una consulta de búsqueda de muestra:
from openai.embeddings_utils import get_embedding, cosine_similarity def search_movies(df, query, n=3, pprint=True): embedding = get_embedding( query, engine="text-search-babbage-query-001" ) df["similarities"] = df.combined_info_search.apply(lambda x: cosine_similarity(x, embedding)) res = ( df.sort_values("similarities", ascending=False) .head(n) .combined_info ) if pprint: for r in res: print(r[:200]) print() return res res = search_movies(df, "movie about the wizardry school", n=3)
Estos son los resultados que obtenemos:
Title: Harry Potter and the Philosopher's StoneOverview: Harry Potter has lived under the stairs at his aunt and uncle's house his whole life. But on his 11th birthday, he learns he's a powerful wizard — with a place waiting for him at the Hogwarts School of Witchcraft and Wizardry. As he learns to harness his newfound powers with the help of the school's kindly headmaster, Harry uncovers the truth about his parents' deaths — and about the villain who's to blame. Title: Harry Potter and the Goblet of FireOverview: Harry starts his fourth year at Hogwarts, competes in the treacherous Triwizard Tournament and faces the evil Lord Voldemort. Ron and Hermione help Harry manage the pressure — but Voldemort lurks, awaiting his chance to destroy Harry and all that he stands for. Title: Harry Potter and the Prisoner of AzkabanOverview: Harry, Ron and Hermione return to Hogwarts for another magic-filled year. Harry comes face to face with danger yet again, this time in the form of an escaped convict, Sirius Black — and turns to sympathetic Professor Lupin for help.
La descripción general de ' ' contiene las palabras 'wizardry' y 'school' y aparece primero en el resultado de la búsqueda. El segundo resultado ya no contiene la palabra 'escuela', pero aún conserva palabras cercanas a 'hechicería', 'Triwizard'. El tercer resultado contiene sólo un sinónimo de 'hechicería': magia.
Hay, por supuesto, una multitud de otras películas dentro de esta base de datos que presentan escuelas o magos (o ambos), pero las anteriores fueron las únicas que nos fueron devueltas. Esta es una prueba clara de que la solución de búsqueda funciona y realmente entendió el contexto de nuestra consulta.
Usamos el modelo de Babbage con solo 2048 parámetros. Davinci tiene seis veces más (12 288) parámetros y, por lo tanto, puede funcionar significativamente mejor con respecto a consultas altamente complejas.
Es posible que, en ocasiones, la solución de búsqueda no produzca resultados relevantes para algunas consultas. Por ejemplo, la consulta 'películas sobre magos en la escuela' produce:
Title: Harry Potter and the Philosopher's StoneOverview: Harry Potter has lived under the stairs at his aunt and uncle's house his whole life. But on his 11th birthday, he learns he's a powerful wizard — with a place waiting for him at the Hogwarts School of Witchcraft and Wizardry. As he learns to harness his newfound powers with the help of the school's kindly headmaster, Harry uncovers the truth about his parents' deaths — and about the villain who's to blame. Title: Dumb and Dumberer: When Harry Met LloydOverview: This wacky prequel to the 1994 blockbuster goes back to the lame-brained Harry and Lloyd's days as classmates at a Rhode Island high school, where the unprincipled principal puts the pair in remedial courses as part of a scheme to fleece the school. Title: Harry Potter and the Prisoner of AzkabanOverview: Harry, Ron and Hermione return to Hogwarts for another magic-filled year. Harry comes face to face with danger yet again, this time in the form of an escaped convict, Sirius Black — and turns to sympathetic Professor Lupin for help.
¿Qué está haciendo 'Dumb and Dumberer: When Harry Met Lloyd' aquí? Afortunadamente, este problema no se reprodujo en parámetros con más parámetros.
Cálculo de la distancia entre la consulta y los documentos
El resultado de la búsqueda debe consistir en documentos clasificados en orden descendente por relevancia. Para lograr esto, debemos ser conscientes de la distancia entre la consulta actual y cada documento. Cuanto más corta sea la longitud, comparativamente más relevante será el resultado. Luego, después de un alcance máximo definido, deberíamos dejar de considerar la relevancia de los documentos restantes.
En el ejemplo anterior, utilizamos para calcular la distancia debido a la alta dimensionalidad del espacio vectorial. Con el modelo Babbage, tenemos 2048 parámetros.
Los algoritmos de cálculo de distancias tienden a representar esta similitud (diferencia) entre una consulta y un documento con un solo número. Sin embargo, no podemos confiar en la debido a la — las distancias serán demasiado similares. Esto se debe a que la distancia euclidiana se vuelve muy poco práctica más allá de las siete dimensiones; en este punto, las distancias caen en los mismos cubos y se vuelven casi idénticas.
Si lo desea, puede consultar el repositorio resultante aquí: Alternativamente, puedes jugar con él en Google Colab .
Complejidad del tiempo
Utilizamos el enfoque de fuerza bruta para ordenar los documentos. Vamos a determinar: ● n: número de puntos en el conjunto de datos de entrenamiento ● d: dimensionalidad de los datos
La complejidad del tiempo de búsqueda para soluciones de fuerza bruta es O(n * d * n * log(n)) . El parámetro d depende del modelo (en el caso de Babbage, es igual a 2048) mientras que tenemos el bloque O(nlog(n)) debido al paso de clasificación.
Es fundamental recordarnos en esta etapa que los modelos más pequeños son más rápidos y económicos. Por ejemplo, en el paso de cálculo de similitud de casos de búsqueda, el modelo Ada es dos veces más rápido, mientras que el modelo Davinci es seis veces más lento.
Los cálculos de similitud de coseno entre la consulta y 4803 documentos de 2048 dimensiones tomaron 1260 ms en mi M1 Pro. En la implementación actual, el tiempo requerido para calcular crecería linealmente al número total de documentos. Simultáneamente, este enfoque admite la paralelización del cálculo.
Alternativas a la solución de fuerza bruta
En las soluciones de búsqueda, las consultas deben completarse lo más rápido posible. Y este precio generalmente se paga en el lado del entrenamiento y el tiempo de almacenamiento en caché. Podemos usar estructuras de datos como un árbol kd, un árbol r o un árbol de bolas. Considere el artículo de sobre el análisis de complejidad computacional de estos métodos: todos conducen a una complejidad computacional cercana a O(k * log(n)) , donde k es la cantidad de elementos que nos gustaría devolver dentro de un solo lote.
Los árboles Kd, los árboles de bolas y los árboles r constituyen estructuras de datos que se utilizan para almacenar y buscar de manera eficiente puntos en el espacio N-dimensional, como nuestros vectores de significado.
Los árboles Kd y ball son estructuras de datos basadas en árboles que utilizan un esquema de partición binario iterativo para dividir el espacio en regiones, donde cada nodo del árbol representa una subregión. Los árboles Kd son particularmente eficientes para buscar puntos dentro de un rango específico o encontrar el vecino más cercano a un punto dado.
De manera similar, los árboles r también se utilizan para almacenar puntos en un espacio N-dimensional; sin embargo, son mucho más eficientes para buscar puntos dentro de una región específica o encontrar todos los puntos dentro de una cierta distancia de un punto dado. Es importante destacar que los árboles r usan un esquema de partición diferente a los árboles kd y los árboles de bolas; dividen el espacio en 'rectángulos' en lugar de particiones binarias.
Las implementaciones de árboles quedan fuera del alcance de este artículo, y diferentes implementaciones conducirán a diferentes resultados de búsqueda.
Tiempo de consulta
Quizás, la desventaja más significativa de la solución de búsqueda actual es que debemos llamar a una API OpenAI externa para recuperar el vector de incrustación de consulta. No importa qué tan rápido nuestro algoritmo pueda encontrar los vecinos más cercanos, se requerirá un paso de bloqueo secuencial.
Text-search-babbage-query-001 Number of dimensions: 2048 Number of queries: 100 Average duration: 225ms Median duration: 207ms Max duration: 1301ms Min duration: 176ms
Text-search-ada-query-002 Number of dimensions: 1536 Number of queries: 100 Average duration: 264ms Median duration: 250ms Max duration: 859ms Min duration: 215ms
Text-search-davinci-query-001 Number of dimensions: 12288 Number of queries: 100 Average duration: 379ms Median duration: 364ms Max duration: 1161ms Min duration: 271ms
Si tomamos la mediana como punto de referencia, podemos ver que ada-002 es un +28% más lento y davinci-001 un +76% más lento.
Limitaciones de la incrustación de búsqueda GPT-3
Refiriéndose a , podemos concluir que GPT-3 no proporciona un rendimiento o una calidad de salida excepcionales y requiere la dependencia de una API externa que es bastante lenta. GPT-3 tiene la capacidad de admitir entradas de hasta 4096 tokens (aproximadamente 3072 palabras), sin embargo, no hay un servicio de truncamiento disponible a través de la API e intentar codificar texto de más de 4096 tokens generará un error. Por lo tanto, es responsabilidad del usuario (usted) determinar cuánto texto se puede codificar realmente.
Además, los costos de capacitación son relativamente altos con OpenAI.
Alternativamente, puede considerar probar o .
Búsqueda aproximada del vecino más cercano en Elasticsearch
Elasticsearch 8.0 es compatible con la búsqueda eficiente del vecino más cercano (ANN) que se puede usar para resolver las magnitudes de nuestro problema más rápido que cualquier KNN lineal. Elasticsearch 8.0 utiliza un algoritmo ANN llamado Hierarchical Navigable Small World graphs (HNSW) para organizar vectores en gráficos en función de la similitud. Probado en un conjunto de datos de 10 millones de vectores integrados, logramos un rendimiento impresionante de 200 consultas por segundo con ANN en una sola máquina enfocada en computación, mientras que solo 2 consultas por segundo con KNN. Ambos fueron proporcionados por Elasticsearch.
Según ElasticSearch y el , el costo de esta actuación es el 5% de la recuperación. Esto significa que alrededor de 1 de cada 10 de los vectores más cercanos encontrados no son genuinamente relativos. Aplicamos un enfoque híbrido para mejorar este resultado: solicite k+l en lugar de k y aplique filtrado para eliminar los valores atípicos. Lamentablemente, no hay capacidades de paginación. Por lo tanto, este enfoque solo funcionará en algunos casos de uso.
Como habrás visto, GPT-3 Embeddings no es la solución perfecta para todos y cada uno de los problemas de búsqueda debido a su complejidad de indexación, costo, así como la alta complejidad computacional de la operación de búsqueda, incluso de aproximaciones. Sin embargo, GPT-3 Embeddings sigue siendo una excelente opción para cualquiera que busque una columna vertebral poderosa para una solución de búsqueda con consultas poco frecuentes y requisitos de indexación modestos.
Además, también vale la pena agregar que Microsoft __ recientemente __ que el motor de búsqueda Bing ahora usa la nueva versión mejorada de GPT 3.5, llamada 'Prometheus' y desarrollada inicialmente para la búsqueda. Según el anuncio, el nuevo modelo de lenguaje Prometheus permite a Bing aumentar la relevancia, anotar fragmentos con mayor precisión, proporcionar resultados más actualizados, comprender la geolocalización y mejorar la seguridad. Esto puede abrir nuevas posibilidades para usar el modelo de lenguaje autorregresivo para soluciones de búsqueda, que definitivamente estaremos atentos en el futuro.