Voyager 3 von Voyage AI ist ein neues, hochmodernes Modell, mit dem Sie Text und Bilder in denselben Raum einbetten können. In diesem Beitrag erkläre ich, wie man diese multimodalen Einbettungen aus Zeitschriften extrahiert, sie in einer Vektordatenbank (Weaviate) speichert und Ähnlichkeitssuchen für Text und Bilder mit denselben Einbettungsvektoren durchführt.
Die Einbettung von Bildern und Text in denselben Raum wird es uns ermöglichen, hochpräzise Suchvorgänge in multimodalen Inhalten wie Webseiten, PDF-Dateien, Zeitschriften, Büchern, Broschüren und verschiedenen Dokumenten durchzuführen. Warum ist diese Technik so interessant? Der interessanteste Aspekt der Einbettung von Text und Bildern in denselben Raum besteht darin, dass man einen mit einem bestimmten Bild verbundenen Text suchen und abrufen kann und umgekehrt. Wenn Sie z. B. nach Katzen suchen, finden Sie Bilder, auf denen Katzen zu sehen sind, aber auch Text, der sich auf diese Bilder bezieht, selbst wenn in dem Text nicht ausdrücklich das Wort "Katze" vorkommt.
Ich möchte den Unterschied zwischen der traditionellen Ähnlichkeitssuche bei der Texteinbettung und dem multimodalen Einbettungsraum aufzeigen:
BEISPIELFRAGE: Was steht in der Zeitschrift über Katzen?
Regelmäßige Antworten auf die Ähnlichkeitssuche
Die angebotenen Suchergebnisse enthalten keine spezifischen Informationen über Katzen. Sie erwähnen zwar Tierporträts und Fototechniken, aber nicht ausdrücklich Katzen oder Details zu ihnen.
Wie oben dargestellt, wird das Wort "Katze" nicht erwähnt; es gibt nur ein Bild und eine Erklärung, wie man das Tier fotografiert. Da das Wort "Katze" nicht erwähnt wurde, ergab eine normale Ähnlichkeitssuche keine Ergebnisse.
Multimodale Suche nach Antworten
Dieses Magazin zeigt das Porträt einer Katze und hebt die feine Erfassung ihrer Gesichtszüge und ihres Charakters hervor. Der Text unterstreicht, wie gut gemachte Tierporträts die Seele des Porträtierten erreichen und eine emotionale Verbindung mit dem Betrachter durch überzeugenden Augenkontakt herstellen.
Mithilfe der multimodalen Suche werden wir ein Bild einer Katze finden und dann den entsprechenden Text damit verknüpfen. Wenn diese Daten dem Modell zur Verfügung gestellt werden, kann es besser antworten und den Kontext verstehen.
Wie man eine multimodale Einbettungs- und Abrufpipeline aufbaut
Ich werde nun in einigen Schritten beschreiben, wie eine solche Pipeline funktioniert:
- Wir werden die Unstrukturiert(eine leistungsstarke Python-Bibliothek zur Datenextraktion) Extrahiert Text und Bilder aus PDF-Dateien.
- Wir werden die Voyager Multimodal 3 Das Modell erzeugt multimodale Vektoren für Text und Bilder im gleichen Vektorraum.
- Wir fügen sie in den Vektorspeicher ein (Weaviate) in.
- Schließlich führen wir eine Ähnlichkeitssuche durch und vergleichen die Ergebnisse von Text und Bild.
Schritt 1: Einrichten der Vektorspeicherung und Extraktion von Bildern und Text aus dem Dokument (PDF)
Hier müssen wir etwas manuelle Arbeit leisten. Normalerweise ist Weaviate ein sehr einfach zu bedienender Vektorspeicher, der die Daten automatisch umwandelt und Einbettungen beim Einfügen hinzufügt. Für Voyager Multimodal v3 gibt es jedoch kein Plugin, so dass wir die Einbettungen manuell berechnen müssen.In diesem Fall müssen wir eine Sammlung erstellen, ohne ein Vektormodul zu definieren.
weaviate importieren
from weaviate.classes.config importieren Konfigurieren
client = weaviate.connect_to_local()
sammlung_name = "multimodal_demo"
client.collections.delete(sammlung_name)
try.
client.collections.create(
name=sammlung_name,
vectorizer_config=Configure.Vectorizer.none() # Vektorisierer für diese Sammlung nicht einstellen
)
Sammlung = client.collections.get(sammlung_name)
außer Exception.
collection = client.collections.get(collection_name)pyt
Hier führe ich eine lokale Weaviate-Instanz in einem Docker-Container aus.
Schritt 2:Extrahieren von Dokumenten und Bildern aus PDF
Dies ist ein wichtiger Schritt in der Arbeitsweise des Prozesses. Hier erhalten wir eine PDF-Datei, die Text und Bilder enthält. Dann extrahieren wir den Inhalt (Bilder und Text) und speichern ihn in entsprechenden Blöcken. Jeder Block wird also eine PDF-Datei sein, die Zeichenketten (eigentlichen Text) und Python PIL-Bilder Die Liste der Elemente der Datei
Wir werden die Unstrukturiert Bibliothek, um einen Teil der schweren Arbeit zu erledigen, aber wir müssen noch etwas Logik schreiben und Bibliotheksparameter konfigurieren.
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=["Bild", "Tabelle"],
extract_image_block_to_payload=True)
chunks = chunk_by_title(elements)
Hier müssen wir die hi_res Strategie und verwenden die extract_image_block_to_payload Exportieren Sie das Bild als Nutzdaten, da wir diese Informationen später für die eigentliche Einbettung benötigen. Sobald wir alle Elemente extrahiert haben, gruppieren wir sie in Blöcke, die auf den Titeln im Dokument basieren.
Weitere Informationen finden Sie unter Unstrukturierte Dokumentation über Chunking.
Im folgenden Skript werden wir diese Blöcke verwenden, um zwei Listen auszugeben:
- Eine Liste mit den Objekten, die wir an Voyager 3 senden werden, um den Vektor zu erstellen
- Eine Liste mit den von Unstructured extrahierten Metadaten. Diese Metadaten sind erforderlich, weil wir sie dem Vektorspeicher hinzufügen müssen. Sie liefern uns zusätzliche Attribute, nach denen wir filtern können, und sagen uns etwas über die abgerufenen Daten.
from unstructured.staging.base import elements_from_base64_gzipped_json
importieren PIL.
importieren io
importiere base64
einbettende_Objekte = []
einbetten_metadaten = []
for chunks in chunks:
embedding_object = []
metedata_dict = {
"text": chunk.to_dict()["text"],
"Dateiname": chunk.to_dict()["metadata"]["Dateiname"],
"Seitennummer": chunk.to_dict()["Metadaten"]["Seitennummer"],
"last_modified": chunk.to_dict()["metadata"]["last_modified"],
"filetype": chunk.to_dict()["metadata"]["filetype"]
}
embedding_object.append(chunk.to_dict()["text"])
# Hinzufügen eines Bildes zu einem Einbettungsobjekt
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"] == "Bild".
base64_image = ele.to_dict()["metadata"]["image_base64"]
image_data.append(base64_image)
pil_image = PIL.Image.open(io.BytesIO(base64.b64decode(base64_image)))
# Wenn das Bild größer als 1000x1000 ist, ändern Sie die Größe des Bildes unter Beibehaltung des Seitenverhältnisses
if pil_image.size[0] > 1000 oder pil_image.size[1] > 1000.
verhältnis = 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)
einbetten_objekt.anhängen(pil_image)
metedata_dict["image_data"] = image_data
embedding_objects.append(embedding_object)
embedding_metadatas.append(metedata_dict)
Das Ergebnis dieses Skripts ist eine Liste von Listen, deren Inhalt im Folgenden dargestellt wird:
[['Standort von \n\n Island KIRKJUFELL'.
,
], [<PIL.
[Dieser ikonische Berg war unsere erste Wahl für einen Standort in Island, und wir hatten schon viele Fotos von dem nahe gelegenen Wasserfall gesehen, bevor wir dorthin fuhren. Also war dies der erste Ort, den wir bei Sonnenaufgang ansteuerten - und wir wurden nicht enttäuscht. Die Wasserfälle bieten die perfekte Nahaufnahme für dieses Foto (oben) und der Kirkjufell ist aus diesem Blickwinkel ein perfekter spitzer Hügel. Wir verbrachten ein oder zwei Stunden damit, die Wasserfälle zu erkunden und verschiedene Blickwinkel zu finden.]
Schritt 3: Vektorisierung der extrahierten Daten
In diesem Schritt verwenden wir den im vorherigen Schritt erstellten Block mit der Voyager-Python-Pakete Senden Sie sie an Voyager, der uns eine Liste aller eingebetteten Objekte zurückgibt. Dieses Ergebnis können wir dann verwenden und schließlich in Weaviate speichern.
von dotenv importieren load_dotenv
importieren voyageai
load_dotenv()
vo = voyageai.Client()
# Dadurch wird automatisch die Umgebungsvariable VOYAGE_API_KEY verwendet.
# Alternativ können Sie auch vo = voyageai.Client(api_key="") verwenden.
# Beispieleingaben mit Textstrings und PIL-Bildobjekten
Eingaben = einbetten_Objekte
# vektorisierte Eingaben
Ergebnis = vo.multimodal_embed(
Eingaben, model="voyage-multimodal_embed(
model="voyage-multimodal-3",
truncation=False
)
Wenn wir auf result.embeddings zugreifen, erhalten wir eine Liste mit allen berechneten Einbettungsvektoren:
[[-0.052734375, -0.0164794921875, 0.050048828125, 0.01348876953125, -0.048095703125, ...]]
Wir können nun die batch.add_object
Methode speichert diese eingebetteten Daten in Weaviate als einen einzigen Stapel. Beachten Sie, dass wir dem Parameter properties auch Metadaten hinzugefügt haben.
mit collection.batch.dynamic() as batch:
for i, data_row in enumerate(embedding_objects): batch.add_object(): batch.add_object(): batch.
batch.add_object(
properties=einbettung_metadaten[i], vector=Ergebnis.einbettungen[i], vector=Ergebnis.
vector=Ergebnis.Einbettungen[i]
)
Schritt 4: Abfrage der Daten
Wir können nun eine Ähnlichkeitssuche durchführen und die Daten abfragen. Das ist einfach, denn der Prozess ähnelt einer normalen Ähnlichkeitssuche, die für eine Texteinbettung durchgeführt wird. Da Weaviate nicht über ein Modul für Voyager Multimodal verfügt, müssen wir den Suchanfragevektor selbst berechnen, bevor wir ihn an Weaviate zur Durchführung einer Ähnlichkeitssuche weitergeben.
from weaviate.classes.query import MetadataQuery
question = "Was stand in der Zeitschrift über Wasserfälle?"
vector = vo.multimodal_embed([[Frage]], model="voyage-multimodal-3")
vector.embeddings[0]
response = collection.query.near_vector(
near_vector=vector.embeddings[0], # Ihr Abfragevektor hier
limit=2,
return_metadata=MetadataQuery(distance=True)
)
# Anzeige der Ergebnisse
for o in response.objects.
print(o.properties['text'])
for image_data in o.properties['image_data']:
# Ein Bild mit PIL anzeigen
img = PIL.Image.open(io.BytesIO(base64.b64decode(image_data)))
Breite, Höhe = img.size
wenn Breite > 500 oder Höhe > 500.
ratio = min(500/Breite, 500/Höhe)
new_size = (int(width * ratio), int(height * ratio))
img = img.resize(neue_Größe)
display(img)
print(o.metadata.distance)
Die folgende Abbildung zeigt, dass eine Suche nach Wasserfällen Text und Bilder zurückgibt, die für diese Suchanfrage relevant sind. Wie Sie sehen können, spiegeln die Fotos Wasserfälle wider, aber der Text selbst erwähnt sie nicht. Der Text bezieht sich auf ein Bild, auf dem ein Wasserfall zu sehen ist, weshalb er auch abgerufen wurde. Dies ist bei einer normalen Suche mit Texteinbettung nicht möglich.
Schritt 5: Hinzufügen zur gesamten Suchpipeline
Nachdem wir nun den Text und die Bilder aus der Zeitschrift extrahiert, Einbettungen für sie erstellt, sie zu Weaviate hinzugefügt und unsere Ähnlichkeitssuche eingerichtet haben, füge ich sie der gesamten Retrieval-Pipeline hinzu. In diesem Beispiel verwende ich LangGraph. Der Benutzer stellt eine Frage zu dieser Zeitschrift, und die Pipeline beantwortet diese Frage. Nun, da die ganze Arbeit getan ist, ist dieser Teil so einfach wie das Einrichten einer typischen Retrieval-Pipeline mit normalem Text.
Ich habe einen Teil der Logik, die wir im vorherigen Abschnitt besprochen haben, in andere Module abstrahiert. Hier ist ein Beispiel dafür, wie ich sie in die LangGraph-Pipeline integriert habe.
class MultiModalRetrievalState(TypedDict).
messages: Annotated[Sequence[BaseMessage], add_messages]
results: List[Document]
base_64_images: Liste[str]
class RAGNodes(BaseNodes).
def __init__(self, logger, mode="online", document_handler=None): super().
__init__(logger, mode="online", document_handler=None). __init__(logger, mode): super().
self.weaviate = Weaviate()
self.mode = mode
async def multi_modal_retrieval(self, state: MultiModalRetrievalState, config): collection_name = config.
collection_name = config.get("konfigurierbar", {}).get("collection_name")
self.weaviate.set_collection(sammlung_name)
print("Multimodale Suche läuft")
print(f "Suche nach {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("Answering question")
llm = self.llm_factory.create_llm(mode=self.mode, model_type="default")
include_images = config.get("konfigurierbar", {}).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}
# Definieren der Konfiguration
class GraphConfig(TypedDict).
Modus: 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("antwort_frage", END)
multi_modal_graph = graph.compile()
__all__ = ["multi_modal_graph"]
Der obige Code erzeugt das folgende Diagramm
In dieser Spur wird dieSie können die Inhalte und Bilder sehen, die an OpenAI gesendet werden, um Fragen zu beantworten.
ein Urteil fällen
Die multimodale Einbettung eröffnet die Möglichkeit, Informationen aus verschiedenen Datentypen (z. B. Text und Bilder) innerhalb desselben Einbettungsraums zu integrieren und abzurufen. Durch die Kombination modernster Tools wie dem Voyager Multimodal 3 Modell, Weaviate und LangGraph zeigen wir, wie eine robuste Retrieval-Pipeline aufgebaut werden kann, die Inhalte intuitiver versteht und verknüpft als traditionelle reine Textansätze.
Dieser Ansatz verbessert die Such- und Abrufgenauigkeit für eine Vielzahl von Datenquellen wie Zeitschriften, Broschüren und PDFs erheblich. Außerdem wird gezeigt, wie die multimodale Einbettung umfassendere, kontextbezogene Erkenntnisse liefern kann, die Bilder mit beschreibendem Text verknüpfen, selbst wenn keine expliziten Schlüsselwörter vorhanden sind. In diesem Tutorial können Sie diese Techniken erkunden und auf Ihre Projekte anwenden.
Beispiel Notebook: https://github.com/vectrix-ai/vectrix-graphs/blob/main/examples/multi-model-embeddings.ipynb