paint-brush
Vous souhaitez rechercher quelque chose avec une image et une description textuelle ? Essayez un RAG multimodalpar@codingjaguar
Nouvelle histoire

Vous souhaitez rechercher quelque chose avec une image et une description textuelle ? Essayez un RAG multimodal

par Jiang Chen16m2024/11/27
Read on Terminal Reader

Trop long; Pour lire

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.
featured image - Vous souhaitez rechercher quelque chose avec une image et une description textuelle ? Essayez un RAG multimodal
Jiang Chen HackerNoon profile picture
0-item
1-item

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.

Qu'est-ce que Milvus ?

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 est une base de données vectorielles open source hautes performances conçue pour évoluer. Vous pouvez la trouver sur GitHub avec une licence Apache-2.0 et plus de 30 000 étoiles.


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. Milvus Lite est une version légère qui s'exécute dans une application Python et est parfaite pour le prototypage d'applications dans un environnement local. Milvus Standalone et Milvus Distributed sont des options évolutives et prêtes pour la production.

RAG multimodal : au-delà du texte

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.

Comprendre les plongements vectoriels et la recherche de similarité

L'intégration de vecteurs et la recherche de similarité sont deux concepts fondamentaux du RAG multimodal. Essayons de les comprendre tous les deux.

Incorporations vectorielles

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.

Recherche de similarité

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 :

  1. Distance euclidienne : mesure la distance en ligne droite entre deux points dans l'espace vectoriel.
  2. Similarité cosinus : mesure le cosinus de l'angle entre deux vecteurs (en mettant l'accent sur leur direction plutôt que sur leur grandeur).
  3. Produit scalaire : Une simple multiplication d'éléments correspondants additionnés.


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.


Construire un RAG multimodal avec Milvus

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.

Prérequis

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 OpenAI et obtenez une clé API. Le niveau gratuit est suffisant pour faire fonctionner ce code.


Ensuite, créez un nouveau répertoire et un Python environnement virtuel (ou prenez les mesures que vous utilisez pour gérer Python).


Pour ce tutoriel, vous devrez également installer le Pymilvus bibliothèque, qui est le SDK Python officiel de Milvus, et une poignée d'outils courants.

Configurer Milvus Lite

 pip install -U pymilvus

Installer les dépendances

 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

Télécharger les données

La commande suivante téléchargera les données d’exemple et les extraira dans un dossier local « ./images_folder », qui comprend :


  • Images : Un sous-ensemble de Avis sur Amazon 2023 contenant environ 900 images des catégories « Appareils électroménagers », « Téléphones portables et accessoires » et « Électronique ».
  • Un exemple d'image de requête : leopard.jpg


 wget https://github.com/milvus-io/bootcamp/releases/download/data/amazon_reviews_2023_subset.tar.gz tar -xzf amazon_reviews_2023_subset.tar.gz

Charger le modèle d'intégration

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)

Générer des intégrations et charger des données dans Milvus

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))

Effectuer une recherche multimodale et reclasser les résultats

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() 


Résultats de recherche multimodale

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 Clé API OpenAI à l'avance.


 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. 



Coque de téléphone à imprimé léopard – Meilleur résultat


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 exemple d'application .

Conclusion

Dans cet article de blog, nous avons discuté de la construction d'un système RAG multimodal utilisant Milvus (une base de données vectorielle open source). Nous avons expliqué comment les développeurs peuvent configurer Milvus, charger des données d'image, effectuer des recherches de similarité et utiliser un LLM pour reclasser les résultats récupérés afin d'obtenir des réponses plus précises.


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.