Cet article fournit un guide détaillé sur la façon de construire un système RAG multimodal à l'aide de Milvus et comment ouvrir diverses possibilités pour les systèmes d'IA.
Le recours à un format de données unique ne suffit plus. Les entreprises s'appuyant de plus en plus sur les informations pour prendre des décisions cruciales, elles doivent pouvoir comparer des données dans des formats disparates. Heureusement, les systèmes d'IA traditionnels limités à un seul type de données ont cédé la place à des systèmes multimodaux capables de comprendre et de traiter des informations complexes.
Les systèmes de recherche multimodale et de génération augmentée de données multimodales (RAG) ont récemment fait de grands progrès dans ce domaine. Ces systèmes traitent plusieurs types de données, notamment du texte, des images et de l'audio, pour fournir des réponses contextuelles.
Dans cet article de blog, nous verrons comment les développeurs peuvent créer leur propre système RAG multimodal à l'aide de Milvus. Nous vous expliquerons également comment créer un tel système capable de gérer des données textuelles et d'images, en particulier, d'effectuer des recherches de similarité et d'exploiter un modèle de langage pour affiner la sortie. Alors, commençons.
Une base de données vectorielle est un type spécial de base de données utilisé pour stocker, indexer et récupérer des plongements vectoriels, qui sont des représentations mathématiques de données qui vous permettent de comparer les données non seulement pour l'équivalence mais aussi pour la similarité sémantique.
Milvus aide les développeurs à fournir une solution flexible pour la gestion et l'interrogation de données vectorielles à grande échelle. Son efficacité fait de Milvus un choix idéal pour les développeurs qui créent des applications utilisant des modèles d'apprentissage profond, tels que la génération augmentée de récupération (RAG), la recherche multimodale, le moteur de recommandation et la détection d'anomalies.
Milvus propose plusieurs options de déploiement pour répondre aux besoins des développeurs.
Avant de construire le système, il est important de comprendre le RAG traditionnel basé sur du texte et son évolution vers le RAG multimodal.
La génération augmentée de récupération (RAG) est une méthode permettant de récupérer des informations contextuelles à partir de sources externes et de générer des résultats plus précis à partir de modèles linguistiques volumineux (LLM). La génération augmentée de récupération traditionnelle est une stratégie très efficace pour améliorer les résultats des LLM, mais elle reste limitée aux données textuelles. Dans de nombreuses applications du monde réel, les données s'étendent au-delà du texte : l'intégration d'images, de graphiques et d'autres modalités fournit un contexte essentiel.
Le RAG multimodal répond à la limitation ci-dessus en permettant l'utilisation de différents types de données, offrant ainsi un meilleur contexte aux LLM.
En termes simples, dans un système RAG multimodal, le composant de récupération recherche des informations pertinentes dans différentes modalités de données, et le composant de génération génère des résultats plus précis en fonction des informations récupérées.
L'intégration de vecteurs et la recherche de similarité sont deux concepts fondamentaux du RAG multimodal. Essayons de les comprendre tous les deux.
Comme nous l'avons vu, les intégrations vectorielles sont des représentations mathématiques/numériques de données. Les machines utilisent cette représentation pour comprendre la signification sémantique de différents types de données, tels que le texte, les images et l'audio.
Lors de l'utilisation du traitement du langage naturel (NLP), les fragments de documents sont transformés en vecteurs et les mots sémantiquement similaires sont mappés sur des points proches dans l'espace vectoriel. Il en va de même pour les images, où les intégrations représentent les caractéristiques sémantiques. Cela nous permet de comprendre des paramètres tels que la couleur, la texture et les formes des objets dans un format numérique.
L’objectif principal de l’utilisation d’intégrations vectorielles est d’aider à préserver les relations et les similitudes entre différents éléments de données.
La recherche de similarité est utilisée pour rechercher et localiser des données dans un ensemble de données donné. Dans le contexte des plongements vectoriels, la recherche de similarité trouve les vecteurs dans l'ensemble de données donné qui sont les plus proches du vecteur de requête.
Voici quelques méthodes couramment utilisées pour mesurer la similarité entre les vecteurs :
Le choix de la mesure de similarité dépend généralement des données spécifiques à l’application et de la manière dont le développeur aborde le problème.
Lors de la réalisation d'une recherche de similarité sur des ensembles de données à grande échelle, la puissance de calcul et les ressources requises sont très élevées. C'est là qu'interviennent les algorithmes de voisinage le plus proche (ANN). Les algorithmes ANN sont utilisés pour échanger un petit pourcentage ou une certaine quantité de précision contre une amélioration significative de la vitesse. Cela en fait un choix approprié pour les applications à grande échelle.
Milvus utilise également des algorithmes ANN avancés, notamment HNSW et DiskANN, pour effectuer des recherches de similarité efficaces sur de grands ensembles de données vectorielles intégrées, permettant aux développeurs de trouver rapidement des points de données pertinents. De plus, Milvus prend en charge d'autres algorithmes d'indexation, tels que HSNW, IVF, CAGRA, etc., ce qui en fait une solution de recherche vectorielle beaucoup plus efficace.
Maintenant que nous avons appris les concepts, il est temps de construire un système RAG multimodal à l'aide de Milvus. Pour cet exemple, nous utiliserons Milvus Lite (la version allégée de Milvus, idéale pour l'expérimentation et le prototypage) pour le stockage et la récupération des vecteurs, BGE pour le traitement et l'intégration précis des images, et GPT-4o pour le reclassement avancé des résultats.
Tout d'abord, vous aurez besoin d'une instance Milvus pour stocker vos données. Vous pouvez configurer Milvus Lite à l'aide de pip, exécuter une instance locale à l'aide de Docker ou créer un compte Milvus hébergé gratuit via Zilliz Cloud.
Deuxièmement, vous avez besoin d'un LLM pour votre pipeline RAG, alors rendez-vous sur
Ensuite, créez un nouveau répertoire et un Python
Pour ce tutoriel, vous devrez également installer le
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
La commande suivante téléchargera les données d’exemple et les extraira dans un dossier local « ./images_folder », qui comprend :
wget https://github.com/milvus-io/bootcamp/releases/download/data/amazon_reviews_2023_subset.tar.gz tar -xzf amazon_reviews_2023_subset.tar.gz
Nous utiliserons le modèle BGE visualisé « bge-visualized-base-en-v1.5 » pour générer des intégrations pour les images et le texte.
Téléchargez maintenant le poids depuis HuggingFace.
wget https://huggingface.co/BAAI/bge-visualized/resolve/main/Visualized_base_en_v1.5.pth
Alors, construisons un encodeur.
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)
Cette section vous guidera sur la manière de charger des exemples d'images dans notre base de données avec leurs intégrations correspondantes.
Générer des intégrations
Tout d’abord, nous devons créer des intégrations pour toutes les images de l’ensemble de données.
Chargez toutes les images du répertoire de données et convertissez-les en intégrations.
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))
Dans cette section, nous allons d’abord rechercher des images pertinentes à l’aide d’une requête multimodale, puis utiliser un service LLM pour reclasser les résultats récupérés et trouver le meilleur avec une explication.
Exécuter une recherche multimodale
Nous sommes maintenant prêts à effectuer la recherche multimodale avancée avec la requête composée d'instructions d'image et de texte.
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)
Le résultat est présenté ci-dessous :
['./images_folder/images/518Gj1WQ-RL._AC_.jpg', './images_folder/images/41n00AOfWhL._AC_.jpg'
Résultats de reclassement avec GPT-4o
Nous allons maintenant utiliser GPT-4o pour classer les images récupérées et trouver les résultats les mieux adaptés. Le LLM expliquera également pourquoi il est classé ainsi.
1. Créez une vue panoramique.
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. Combinez l'image de requête et les images récupérées avec des index dans une vue panoramique.
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. Réorganisez les résultats et donnez une explication
Nous enverrons toutes les images combinées au service LLM multimodal avec les invites appropriées pour classer les résultats récupérés avec une explication. Remarque : pour activer GPT-4o comme LLM, vous devez préparer votre
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
Obtenez les indices d'image après le classement et la raison du meilleur résultat :
ranked_indices, explanation = generate_ranking_explanation( combined_image_path, query_text )
4. Affichez le meilleur résultat avec explication
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()
Résultats:
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.
Consultez le code complet dans ce bloc-notes . Pour en savoir plus sur la façon de démarrer une démonstration en ligne avec ce didacticiel, veuillez vous référer au
Dans cet article de blog, nous avons discuté de la construction d'un système RAG multimodal utilisant
Les solutions RAG multimodales ouvrent de nombreuses possibilités aux systèmes d'IA capables de comprendre et de traiter facilement plusieurs formes de données. Parmi les possibilités courantes, on peut citer des moteurs de recherche d'images améliorés, de meilleurs résultats axés sur le contexte, etc.