Este artículo proporciona una guía detallada sobre cómo construir un sistema RAG multimodal utilizando Milvus y cómo abrir varias posibilidades para los sistemas de IA.
Ya no basta con limitarse a un único formato de datos. A medida que las empresas dependen cada vez más de la información para tomar decisiones cruciales, necesitan la capacidad de comparar datos en distintos formatos. Afortunadamente, los sistemas de IA tradicionales limitados a un único tipo de datos han dado paso a sistemas multimodales que pueden comprender y procesar información compleja.
Los sistemas de búsqueda multimodal y recuperación multimodal-generación aumentada (RAG) han demostrado recientemente grandes avances en este campo. Estos sistemas procesan múltiples tipos de datos, incluidos texto, imágenes y audio, para proporcionar respuestas sensibles al contexto.
En esta publicación del blog, analizaremos cómo los desarrolladores pueden crear su propio sistema RAG multimodal con Milvus. También le explicaremos cómo crear un sistema que pueda manejar datos de texto e imágenes, en particular, realizar búsquedas de similitud y aprovechar un modelo de lenguaje para refinar el resultado. Comencemos.
Una base de datos vectorial es un tipo especial de base de datos que se utiliza para almacenar, indexar y recuperar incrustaciones vectoriales, que son representaciones matemáticas de datos que permiten comparar datos no solo para determinar su equivalencia sino también su similitud semántica.
Milvus ayuda a los desarrolladores a ofrecer una solución flexible para gestionar y consultar datos vectoriales a gran escala. Su eficiencia hace que Milvus sea una opción ideal para los desarrolladores que crean aplicaciones utilizando modelos de aprendizaje profundo, como recuperación de generación aumentada (RAG), búsqueda multimodal, motor de recomendaciones y detección de anomalías.
Milvus ofrece múltiples opciones de implementación para adaptarse a las necesidades de los desarrolladores.
Antes de construir el sistema, es importante comprender el RAG tradicional basado en texto y su evolución al RAG multimodal.
La generación aumentada de recuperación (RAG, por sus siglas en inglés) es un método para recuperar información contextual de fuentes externas y generar resultados más precisos a partir de modelos lingüísticos extensos (LLM, por sus siglas en inglés). La RAG tradicional es una estrategia muy eficaz para mejorar los resultados de los LLM, pero sigue limitada a los datos textuales. En muchas aplicaciones del mundo real, los datos se extienden más allá del texto: la incorporación de imágenes, gráficos y otras modalidades proporciona un contexto crítico.
El RAG multimodal aborda la limitación anterior al permitir el uso de diferentes tipos de datos, lo que proporciona un mejor contexto a los LLM.
En pocas palabras, en un sistema RAG multimodal, el componente de recuperación busca información relevante en diferentes modalidades de datos, y el componente de generación genera resultados más precisos basados en la información recuperada.
Las incrustaciones vectoriales y la búsqueda de similitud son dos conceptos fundamentales de RAG multimodal. Vamos a entenderlos.
Como se ha comentado, las incrustaciones vectoriales son representaciones matemáticas o numéricas de datos. Las máquinas utilizan esta representación para comprender el significado semántico de distintos tipos de datos, como texto, imágenes y audio.
Al utilizar el procesamiento del lenguaje natural (PLN), los fragmentos de documentos se transforman en vectores y las palabras semánticamente similares se asignan a puntos cercanos en el espacio vectorial. Lo mismo ocurre con las imágenes, donde las incrustaciones representan las características semánticas. Esto nos permite comprender métricas como el color, la textura y las formas de los objetos en un formato numérico.
El objetivo principal de utilizar incrustaciones vectoriales es ayudar a preservar las relaciones y similitudes entre diferentes piezas de datos.
La búsqueda por similitud se utiliza para buscar y localizar datos en un conjunto de datos determinado. En el contexto de las incrustaciones de vectores, la búsqueda por similitud encuentra vectores en el conjunto de datos determinado que sean más cercanos al vector de consulta.
Los siguientes son algunos métodos que se utilizan comúnmente para medir la similitud entre vectores:
La elección de la medida de similitud generalmente depende de los datos específicos de la aplicación y de cómo el desarrollador aborda el problema.
Al realizar búsquedas de similitud en conjuntos de datos a gran escala, la potencia de cálculo y los recursos necesarios son muy altos. Aquí es donde entran en juego los algoritmos de vecino más cercano aproximado (ANN). Los algoritmos ANN se utilizan para intercambiar un pequeño porcentaje o cantidad de precisión por una mejora significativa de la velocidad. Esto los convierte en una opción adecuada para aplicaciones a gran escala.
Milvus también utiliza algoritmos avanzados de ANN, incluidos HNSW y DiskANN, para realizar búsquedas de similitud eficientes en grandes conjuntos de datos de incrustación de vectores, lo que permite a los desarrolladores encontrar rápidamente puntos de datos relevantes. Además, Milvus admite otros algoritmos de indexación, como HSNW, IVF, CAGRA, etc., lo que lo convierte en una solución de búsqueda de vectores mucho más eficiente.
Ahora que hemos aprendido los conceptos, es momento de construir un sistema RAG multimodal utilizando Milvus. Para este ejemplo, utilizaremos Milvus Lite (la versión liviana de Milvus, ideal para experimentar y crear prototipos) para el almacenamiento y la recuperación de vectores, BGE para el procesamiento y la incrustación de imágenes precisas, y GPT-4o para la reclasificación avanzada de resultados.
En primer lugar, necesitará una instancia de Milvus para almacenar sus datos. Puede configurar Milvus Lite con pip, ejecutar una instancia local con Docker o registrarse para obtener una cuenta Milvus alojada gratuita a través de Zilliz Cloud.
En segundo lugar, necesitas un LLM para tu canal de RAG, así que dirígete a
A continuación, cree un nuevo directorio y un Python
Para este tutorial, también necesitarás instalar el
pip install -U pymilvus
pip install --upgrade pymilvus openai datasets opencv-python timm einops ftfy peft tqdm git clone https://github.com/FlagOpen/FlagEmbedding.git pip install -e FlagEmbedding
El siguiente comando descargará los datos de ejemplo y los extraerá a una carpeta local “./images_folder”, que incluye:
wget https://github.com/milvus-io/bootcamp/releases/download/data/amazon_reviews_2023_subset.tar.gz tar -xzf amazon_reviews_2023_subset.tar.gz
Utilizaremos el modelo BGE visualizado “bge-visualized-base-en-v1.5” para generar incrustaciones tanto de imágenes como de texto.
Ahora descarga el peso de HuggingFace.
wget https://huggingface.co/BAAI/bge-visualized/resolve/main/Visualized_base_en_v1.5.pth
Entonces, construyamos un codificador.
import torch from visual_bge.modeling import Visualized_BGE class Encoder: def __init__(self, model_name: str, model_path: str): self.model = Visualized_BGE(model_name_bge=model_name, model_weight=model_path) self.model.eval() def encode_query(self, image_path: str, text: str) -> list[float]: with torch.no_grad(): query_emb = self.model.encode(image=image_path, text=text) return query_emb.tolist()[0] def encode_image(self, image_path: str) -> list[float]: with torch.no_grad(): query_emb = self.model.encode(image=image_path) return query_emb.tolist()[0] model_name = "BAAI/bge-base-en-v1.5" model_path = "./Visualized_base_en_v1.5.pth" # Change to your own value if using a different model path encoder = Encoder(model_name, model_path)
Esta sección le guiará sobre cómo cargar imágenes de ejemplo en nuestra base de datos con sus incrustaciones correspondientes.
Generar incrustaciones
Primero, necesitamos crear incrustaciones para todas las imágenes en el conjunto de datos.
Cargar todas las imágenes del directorio de datos y convertirlas en incrustaciones.
import os from tqdm import tqdm from glob import glob data_dir = ( "./images_folder" # Change to your own value if using a different data directory ) image_list = glob( os.path.join(data_dir, "images", "*.jpg") ) # We will only use images ending with ".jpg" image_dict = {} for image_path in tqdm(image_list, desc="Generating image embeddings: "): try: image_dict[image_path] = encoder.encode_image(image_path) except Exception as e: print(f"Failed to generate embedding for {image_path}. Skipped.") continue print("Number of encoded images:", len(image_dict))
En esta sección, primero buscaremos imágenes relevantes utilizando una consulta multimodal y luego usaremos un servicio LLM para reclasificar los resultados recuperados y encontrar el mejor con una explicación.
Ejecutar búsqueda multimodal
Ahora estamos listos para realizar la búsqueda multimodal avanzada con la consulta compuesta por instrucciones de imagen y texto.
query_image = os.path.join( data_dir, "leopard.jpg" ) # Change to your own query image path query_text = "phone case with this image theme" query_vec = encoder.encode_query(image_path=query_image, text=query_text) search_results = milvus_client.search( collection_name=collection_name, data=[query_vec], output_fields=["image_path"], limit=9, # Max number of search results to return search_params={"metric_type": "COSINE", "params": {}}, # Search parameters )[0] retrieved_images = [hit.get("entity").get("image_path") for hit in search_results] print(retrieved_images)
El resultado se muestra a continuación:
['./images_folder/images/518Gj1WQ-RL._AC_.jpg', './images_folder/images/41n00AOfWhL._AC_.jpg'
Reordenar los resultados con GPT-4o
Ahora, utilizaremos GPT-4o para clasificar las imágenes recuperadas y encontrar los resultados que mejor se ajusten a las necesidades. El LLM también explicará por qué se clasifica de esa manera.
1. Crea una vista panorámica.
import numpy as np import cv2 img_height = 300 img_width = 300 row_count = 3 def create_panoramic_view(query_image_path: str, retrieved_images: list) -> np.ndarray: """ creates a 5x5 panoramic view image from a list of images args: images: list of images to be combined returns: np.ndarray: the panoramic view image """ panoramic_width = img_width * row_count panoramic_height = img_height * row_count panoramic_image = np.full( (panoramic_height, panoramic_width, 3), 255, dtype=np.uint8 ) # create and resize the query image with a blue border query_image_null = np.full((panoramic_height, img_width, 3), 255, dtype=np.uint8) query_image = Image.open(query_image_path).convert("RGB") query_array = np.array(query_image)[:, :, ::-1] resized_image = cv2.resize(query_array, (img_width, img_height)) border_size = 10 blue = (255, 0, 0) # blue color in BGR bordered_query_image = cv2.copyMakeBorder( resized_image, border_size, border_size, border_size, border_size, cv2.BORDER_CONSTANT, value=blue, ) query_image_null[img_height * 2 : img_height * 3, 0:img_width] = cv2.resize( bordered_query_image, (img_width, img_height) ) # add text "query" below the query image text = "query" font_scale = 1 font_thickness = 2 text_org = (10, img_height * 3 + 30) cv2.putText( query_image_null, text, text_org, cv2.FONT_HERSHEY_SIMPLEX, font_scale, blue, font_thickness, cv2.LINE_AA, ) # combine the rest of the images into the panoramic view retrieved_imgs = [ np.array(Image.open(img).convert("RGB"))[:, :, ::-1] for img in retrieved_images ] for i, image in enumerate(retrieved_imgs): image = cv2.resize(image, (img_width - 4, img_height - 4)) row = i // row_count col = i % row_count start_row = row * img_height start_col = col * img_width border_size = 2 bordered_image = cv2.copyMakeBorder( image, border_size, border_size, border_size, border_size, cv2.BORDER_CONSTANT, value=(0, 0, 0), ) panoramic_image[ start_row : start_row + img_height, start_col : start_col + img_width ] = bordered_image # add red index numbers to each image text = str(i) org = (start_col + 50, start_row + 30) (font_width, font_height), baseline = cv2.getTextSize( text, cv2.FONT_HERSHEY_SIMPLEX, 1, 2 ) top_left = (org[0] - 48, start_row + 2) bottom_right = (org[0] - 48 + font_width + 5, org[1] + baseline + 5) cv2.rectangle( panoramic_image, top_left, bottom_right, (255, 255, 255), cv2.FILLED ) cv2.putText( panoramic_image, text, (start_col + 10, start_row + 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2, cv2.LINE_AA, ) # combine the query image with the panoramic view panoramic_image = np.hstack([query_image_null, panoramic_image]) return panoramic_image
2. Combine la imagen de consulta y las imágenes recuperadas con índices en una vista panorámica.
from PIL import Image combined_image_path = os.path.join(data_dir, "combined_image.jpg") panoramic_image = create_panoramic_view(query_image, retrieved_images) cv2.imwrite(combined_image_path, panoramic_image) combined_image = Image.open(combined_image_path) show_combined_image = combined_image.resize((300, 300)) show_combined_image.show()
3. Reordene los resultados y dé una explicación.
Enviaremos todas las imágenes combinadas al servicio LLM multimodal junto con las indicaciones adecuadas para clasificar los resultados obtenidos con una explicación. Nota: Para habilitar GPT-4o como LLM, debe preparar su
import requests import base64 openai_api_key = "sk-***" # Change to your OpenAI API Key def generate_ranking_explanation( combined_image_path: str, caption: str, infos: dict = None ) -> tuple[list[int], str]: with open(combined_image_path, "rb") as image_file: base64_image = base64.b64encode(image_file.read()).decode("utf-8") information = ( "You are responsible for ranking results for a Composed Image Retrieval. " "The user retrieves an image with an 'instruction' indicating their retrieval intent. " "For example, if the user queries a red car with the instruction 'change this car to blue,' a similar type of car in blue would be ranked higher in the results. " "Now you would receive instruction and query image with blue border. Every item has its red index number in its top left. Do not misunderstand it. " f"User instruction: {caption} \n\n" ) # add additional information for each image if infos: for i, info in enumerate(infos["product"]): information += f"{i}. {info}\n" information += ( "Provide a new ranked list of indices from most suitable to least suitable, followed by an explanation for the top 1 most suitable item only. " "The format of the response has to be 'Ranked list: []' with the indices in brackets as integers, followed by 'Reasons:' plus the explanation why this most fit user's query intent." ) headers = { "Content-Type": "application/json", "Authorization": f"Bearer {openai_api_key}", } payload = { "model": "gpt-4o", "messages": [ { "role": "user", "content": [ {"type": "text", "text": information}, { "type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}, }, ], } ], "max_tokens": 300, } response = requests.post( "https://api.openai.com/v1/chat/completions", headers=headers, json=payload ) result = response.json()["choices"][0]["message"]["content"] # parse the ranked indices from the response start_idx = result.find("[") end_idx = result.find("]") ranked_indices_str = result[start_idx + 1 : end_idx].split(",") ranked_indices = [int(index.strip()) for index in ranked_indices_str] # extract explanation explanation = result[end_idx + 1 :].strip() return ranked_indices, explanation
Obtenga los índices de imagen después de la clasificación y el motivo del mejor resultado:
ranked_indices, explanation = generate_ranking_explanation( combined_image_path, query_text )
4. Muestra el mejor resultado con una explicación
print(explanation) best_index = ranked_indices[0] best_img = Image.open(retrieved_images[best_index]) best_img = best_img.resize((150, 150)) best_img.show()
Resultados:
Reasons: The most suitable item for the user's query intent is index 6 because the instruction specifies a phone case with the theme of the image, which is a leopard. The phone case with index 6 has a thematic design resembling the leopard pattern, making it the closest match to the user's request for a phone case with the image theme.
Consulta el código completo en este cuaderno . Para obtener más información sobre cómo iniciar una demostración en línea con este tutorial, consulta el
En esta publicación de blog, analizamos la construcción de un sistema RAG multimodal utilizando
Las soluciones RAG multimodales abren varias posibilidades para los sistemas de IA que pueden comprender y procesar fácilmente múltiples formas de datos. Algunas posibilidades comunes incluyen motores de búsqueda de imágenes mejorados, mejores resultados basados en el contexto y más.