Travaux pratiques : Construire des recherches multimodales puissantes avec Voyager-3 et LangGraph

Voyager 3 de Voyage AI est un nouveau modèle de pointe qui vous permet d'intégrer du texte et des images dans le même espace. Dans ce billet, je vais expliquer comment extraire ces encastrements multimodaux de magazines, les stocker dans une base de données vectorielle (Weaviate), et effectuer des recherches de similarité sur du texte et des images en utilisant les mêmes vecteurs d'encastrement.

 

L'intégration d'images et de textes dans le même espace nous permettra d'effectuer des recherches très précises sur des contenus multimodaux tels que des pages web, des fichiers PDF, des magazines, des livres, des brochures et divers documents. Pourquoi cette technique est-elle si intéressante ? Le principal intérêt de l'intégration de textes et d'images dans le même espace est qu'il est possible de rechercher et d'extraire un texte lié à une image spécifique et vice versa. Par exemple, si vous recherchez des chats, vous trouverez des images montrant des chats, mais vous obtiendrez également du texte faisant référence à ces images, même si le texte ne contient pas explicitement le mot "chat".

Permettez-moi de montrer la différence entre la recherche de similarité d'intégration de texte traditionnelle et l'espace d'intégration multimodal :

EXEMPLE DE QUESTION : Que dit le magazine à propos des chats ?

实践:使用 Voyager-3 和 LangGraph 构建强大的多模态搜索

Capture d'écran d'un magazine photo - OUTDOOR

 

Réponses à la recherche régulière de similitudes

Les résultats de la recherche ne contiennent pas d'informations spécifiques sur les chats. Ils mentionnent des portraits d'animaux et des techniques de photographie, mais ne mentionnent pas explicitement les chats ou des détails les concernant.

Comme indiqué ci-dessus, le mot "chat" n'est pas mentionné ; il n'y a qu'une image et une explication sur la manière de prendre des photos de l'animal. Le mot "chat" n'étant pas mentionné, une recherche par similarité n'a donné aucun résultat.

Recherche multimodale de réponses

Ce magazine présente le portrait d'un chat, en mettant l'accent sur la finesse des traits de son visage et de son caractère. Le texte souligne que les portraits d'animaux bien réalisés atteignent l'âme de leurs sujets et créent un lien émotionnel avec le spectateur grâce à un contact visuel convaincant.

Grâce à la recherche multimodale, nous trouverons la photo d'un chat et y associerons un texte pertinent. En fournissant ces données au modèle, nous lui permettons de mieux répondre et de mieux comprendre le contexte.

 

Comment construire un pipeline d'intégration et d'extraction multimodale ?

Je vais maintenant décrire le fonctionnement d'un tel pipeline en quelques étapes :

  1. Nous utiliserons le Non structuré(une puissante bibliothèque Python pour l'extraction de données) Extrait le texte et les images des fichiers PDF.
  2. Nous utiliserons le Voyager Multimodal 3 Le modèle crée des vecteurs multimodaux pour le texte et les images dans le même espace vectoriel.
  3. Nous l'insérerons dans le magasin de vecteurs (Weaviate) dans.
  4. Enfin, nous effectuerons une recherche de similarité et comparerons les résultats du texte et des images.

Étape 1 : Configurer le stockage vectoriel et extraire les images et le texte du document (PDF)

Ici, nous devons effectuer un travail manuel. Normalement, Weaviate est un magasin de vecteurs très facile à utiliser qui transforme automatiquement les données et ajoute des embeddings lors de l'insertion. Cependant, il n'y a pas de plugin pour Voyager Multimodal v3, nous devons donc calculer les embeddings manuellement.Dans ce cas, nous devons créer une collection sans définir de module de vectorisation.

import weaviate
from weaviate.classes.config import Configure
client = weaviate.connect_to_local()
collection_name = "multimodal_demo"
client.collections.delete(collection_name)
try:
client.collections.create(
name=collection_name,
vectorizer_config=Configure.Vectorizer.none() # 不为此集合设置向量化器
)
collection = client.collections.get(collection_name)
except Exception:
collection = client.collections.get(collection_name)pyt

Ici, je fais tourner une instance locale de Weaviate dans un conteneur Docker.

Étape 2 :Extraction de documents et d'images à partir de PDF

Il s'agit d'une étape clé dans le fonctionnement du processus. Ici, nous obtenons un PDF contenant du texte et des images. Ensuite, nous extrayons le contenu (images et texte) et le stockons dans des blocs pertinents. Ainsi, chaque bloc sera un PDF contenant des chaînes de caractères (texte réel) et des images. Images Python PIL La liste des éléments du

Nous utiliserons le Non structuré pour faire le gros du travail, mais nous devons encore écrire un peu de logique et configurer les paramètres de la bibliothèque.

from unstructured.partition.auto import partition
from unstructured.chunking.title import chunk_by_title
elements = partition(
filename="./files/magazine_sample.pdf",
strategy="hi_res",
extract_image_block_types=["Image", "Table"],
extract_image_block_to_payload=True)
chunks = chunk_by_title(elements)

Ici, nous devons utiliser la fonction hi_res et d'utiliser la stratégie de extract_image_block_to_payload Exportez l'image vers la charge utile, car nous aurons besoin de ces informations plus tard pour l'intégration proprement dite. Une fois tous les éléments extraits, nous les regrouperons en blocs en fonction des titres du document.

Pour plus d'informations, consultez Documentation non structurée sur le découpage en morceaux.

Dans le script suivant, nous utiliserons ces blocs pour produire deux listes :

  1. Une liste contenant les objets que nous enverrons à Voyager 3 pour créer le vecteur
  2. Une liste contenant les métadonnées extraites par Unstructured. Ces métadonnées sont nécessaires car nous devons les ajouter au magasin de vecteurs. Elles nous fourniront des attributs supplémentaires sur lesquels filtrer et nous donneront des informations sur les données extraites.
from unstructured.staging.base import elements_from_base64_gzipped_json
import PIL.Image
import io
import base64
embedding_objects = []
embedding_metadatas = []
for chunk in chunks:
embedding_object = []
metedata_dict = {
"text": chunk.to_dict()["text"],
"filename": chunk.to_dict()["metadata"]["filename"],
"page_number": chunk.to_dict()["metadata"]["page_number"],
"last_modified": chunk.to_dict()["metadata"]["last_modified"],
"languages": chunk.to_dict()["metadata"]["languages"],
"filetype": chunk.to_dict()["metadata"]["filetype"]
}
embedding_object.append(chunk.to_dict()["text"])
# 将图像添加到嵌入对象
if "orig_elements" in chunk.to_dict()["metadata"]:
base64_elements_str = chunk.to_dict()["metadata"]["orig_elements"]
eles = elements_from_base64_gzipped_json(base64_elements_str)
image_data = []
for ele in eles:
if ele.to_dict()["type"] == "Image":
base64_image = ele.to_dict()["metadata"]["image_base64"]
image_data.append(base64_image)
pil_image = PIL.Image.open(io.BytesIO(base64.b64decode(base64_image)))
# 如果图像大于 1000x1000,则在保持纵横比的同时调整图像大小
if pil_image.size[0] > 1000 or pil_image.size[1] > 1000:
ratio = min(1000/pil_image.size[0], 1000/pil_image.size[1])
new_size = (int(pil_image.size[0] * ratio), int(pil_image.size[1] * ratio))
pil_image = pil_image.resize(new_size, PIL.Image.Resampling.LANCZOS)
embedding_object.append(pil_image)
metedata_dict["image_data"] = image_data
embedding_objects.append(embedding_object)
embedding_metadatas.append(metedata_dict)

Le résultat de ce script sera une liste de listes dont le contenu est indiqué ci-dessous :

[['来自\n\n冰岛 KIRKJUFELL 的位置',
<PIL.Image.Image image mode=RGB size=1000x381>,
<PIL.Image.Image image mode=RGB size=526x1000>],
['这座标志性的山峰是我们冰岛拍摄地点的首选,而且在我们去那里之前,我们就看过许多从附近瀑布拍摄的照片。因此,这是我们在日出时前往的第一个地方 - 我们没有失望。这些瀑布为这张照片(顶部)提供了完美的近景趣味,而从这个角度来看,Kirkjufell 是一座完美的尖山。我们花了一两个小时简单地探索这些瀑布,找到了几个不同的角度。']]

Étape 3 : Vectorisation des données extraites

Dans cette étape, nous utiliserons le bloc créé à l'étape précédente avec l'option Paquets Python pour Voyager Nous les envoyons à Voyager, qui nous renvoie une liste de tous les objets intégrés. Nous pouvons alors utiliser ce résultat et éventuellement le stocker dans Weaviate.

from dotenv import load_dotenv
import voyageai
load_dotenv()
vo = voyageai.Client()
# 这将自动使用环境变量 VOYAGE_API_KEY。
# 或者,您可以使用 vo = voyageai.Client(api_key="<您的密钥>")
# 包含文本字符串和 PIL 图像对象的示例输入
inputs = embedding_objects
# 向量化输入
result = vo.multimodal_embed(
inputs,
model="voyage-multimodal-3",
truncation=False
)

Si nous accédons à result.embeddings, nous obtiendrons une liste contenant tous les vecteurs d'intégration calculés :

[[-0.052734375, -0.0164794921875, 0.050048828125, 0.01348876953125, -0.048095703125, ...]]

Nous pouvons maintenant utiliser la fonction batch.add_object stocke ces données intégrées dans Weaviate en un seul lot. Notez que nous avons également ajouté des métadonnées au paramètre properties.

with collection.batch.dynamic() as batch:
for i, data_row in enumerate(embedding_objects):
batch.add_object(
properties=embedding_metadatas[i],
vector=result.embeddings[i]
)

Étape 4 : Interrogation des données

Nous pouvons maintenant effectuer une recherche de similarité et interroger les données. C'est facile parce que le processus est similaire à une recherche de similarité ordinaire effectuée sur un texte intégré. Comme Weaviate n'a pas de module pour Voyager Multimodal, nous devons calculer nous-mêmes le vecteur de la requête avant de le transmettre à Weaviate pour qu'il effectue une recherche de similarité.

from weaviate.classes.query import MetadataQuery
question = "杂志上关于瀑布说了什么?"
vector = vo.multimodal_embed([[question]], model="voyage-multimodal-3")
vector.embeddings[0]
response = collection.query.near_vector(
near_vector=vector.embeddings[0], # 您的查询向量在此处
limit=2,
return_metadata=MetadataQuery(distance=True)
)
# 显示结果
for o in response.objects:
print(o.properties['text'])
for image_data in o.properties['image_data']:
# 使用 PIL 显示图像
img = PIL.Image.open(io.BytesIO(base64.b64decode(image_data)))
width, height = img.size
if width > 500 or height > 500:
ratio = min(500/width, 500/height)
new_size = (int(width * ratio), int(height * ratio))
img = img.resize(new_size)
display(img)
print(o.metadata.distance)

L'image ci-dessous montre qu'une recherche sur les chutes d'eau renvoie du texte et des images correspondant à cette requête. Comme vous pouvez le constater, les photos représentent des chutes d'eau, mais le texte lui-même n'en fait pas mention. Le texte parle d'une image avec une chute d'eau, c'est pourquoi il a également été récupéré. Cela n'est pas possible dans le cas d'une recherche par incorporation de texte.

实践:使用 Voyager-3 和 LangGraph 构建强大的多模态搜索

Une image montrant les résultats d'une recherche par similarité

 

Étape 5 : L'ajouter à l'ensemble du pipeline de recherche

Maintenant que nous avons extrait le texte et les images du magazine, que nous avons créé des embeddings pour eux, que nous les avons ajoutés à Weaviate et que nous avons mis en place notre recherche de similarité, je vais l'ajouter à l'ensemble du pipeline de recherche. Dans cet exemple, je vais utiliser LangGraph. L'utilisateur posera une question sur ce magazine et le pipeline y répondra. Maintenant que tout le travail est fait, cette partie est aussi simple que la mise en place d'un pipeline de recherche typique utilisant du texte normal.

J'ai intégré une partie de la logique dont nous avons parlé dans la section précédente dans d'autres modules. Voici comment je l'ai intégrée dans le module LangGraph Exemples en cours d'élaboration.

class MultiModalRetrievalState(TypedDict):
messages: Annotated[Sequence[BaseMessage], add_messages]
results: List[Document]
base_64_images: List[str]
class RAGNodes(BaseNodes):
def __init__(self, logger, mode="online", document_handler=None):
super().__init__(logger, mode)
self.weaviate = Weaviate()
self.mode = mode
async def multi_modal_retrieval(self, state: MultiModalRetrievalState, config):
collection_name = config.get("configurable", {}).get("collection_name")
self.weaviate.set_collection(collection_name)
print("正在运行多模态检索")
print(f"正在搜索 {state['messages'][-1].content}")
results = self.weaviate.similarity_search(
query=state["messages"][-1].content, k=3, type="multimodal"
)
return {"results": results}
async def answer_question(self, state: MultiModalRetrievalState, config):
print("正在回答问题")
llm = self.llm_factory.create_llm(mode=self.mode, model_type="default")
include_images = config.get("configurable", {}).get("include_images", False)
chain = self.chain_factory.create_multi_modal_chain(
llm,
state["messages"][-1].content,
state["results"],
include_images=include_images,
)
response = await chain.ainvoke({})
message = AIMessage(content=response)
return {"messages": message}
# 定义配置
class GraphConfig(TypedDict):
mode: str = "online"
collection_name: str
include_images: bool = False
graph_nodes = RAGNodes(logger)
graph = StateGraph(MultiModalRetrievalState, config_schema=GraphConfig)
graph.add_node("multi_modal_retrieval", graph_nodes.multi_modal_retrieval)
graph.add_node("answer_question", graph_nodes.answer_question)
graph.add_edge(START, "multi_modal_retrieval")
graph.add_edge("multi_modal_retrieval", "answer_question")
graph.add_edge("answer_question", END)
multi_modal_graph = graph.compile()
__all__ = ["multi_modal_graph"]

Le code ci-dessus génère le graphique suivant

实践:使用 Voyager-3 和 LangGraph 构建强大的多模态搜索

Représentation visuelle des graphiques créés

 

实践:使用 Voyager-3 和 LangGraph 构建强大的多模态搜索

Dans cette trace, leVous pouvez voir le contenu et les images envoyés à OpenAI pour répondre aux questions.

 

rendre un verdict

L'intégration multimodale offre la possibilité d'intégrer et d'extraire des informations provenant de différents types de données (par exemple, du texte et des images) dans le même espace d'intégration. En combinant des outils de pointe tels que le modèle Voyager Multimodal 3, Weaviate et LangGraph, nous montrons comment construire un pipeline d'extraction robuste qui comprend et relie le contenu de manière plus intuitive que les approches traditionnelles basées uniquement sur le texte.

Cette approche améliore considérablement la précision de la recherche et de l'extraction pour une variété de sources de données telles que les magazines, les brochures et les PDF. Elle démontre également comment l'intégration multimodale peut fournir des informations plus riches et contextuelles qui relient les images à un texte descriptif, même en l'absence de mots-clés explicites. Ce tutoriel vous permet d'explorer et d'appliquer ces techniques à vos projets.

Exemple de carnet de notes : https://github.com/vectrix-ai/vectrix-graphs/blob/main/examples/multi-model-embeddings.ipynb

© déclaration de droits d'auteur

Articles connexes

Pas de commentaires

Vous devez être connecté pour participer aux commentaires !
S'inscrire maintenant
aucun
Pas de commentaires...