Aufbau eines kleinen Vektorshops von Grund auf

王林
Freigeben: 2024-08-27 06:34:02
Original
468 Leute haben es durchsucht

Angesichts der sich weiterentwickelnden Landschaft der generativen KI spielen Vektordatenbanken eine entscheidende Rolle bei der Unterstützung generativer KI-Anwendungen. Derzeit sind so viele Vektordatenbanken verfügbar, die Open Source sind, wie Chroma, Milvus, sowie andere beliebte proprietäre Vektordatenbanken wie Pinecone, SingleStore. Den ausführlichen Vergleich verschiedener Vektordatenbanken können Sie auf dieser Seite lesen.

Aber haben Sie sich jemals gefragt, wie diese Vektordatenbanken hinter den Kulissen funktionieren?

Eine gute Möglichkeit, etwas zu lernen, besteht darin, zu verstehen, wie die Dinge unter der Haube funktionieren. In diesem Artikel werden wir einen winzigen In-Memory-Vektorspeicher „Pixie“ von Grund auf mit Python erstellen, mit nur NumPy als Abhängigkeit.

Building a tiny vector store from scratch


Bevor wir uns mit dem Code befassen, wollen wir kurz besprechen, was ein Vektorspeicher ist.

Was ist ein Vektorspeicher?

Ein Vektorspeicher ist eine Datenbank zum effizienten Speichern und Abrufen von Vektoreinbettungen. Bei diesen Einbettungen handelt es sich um numerische Darstellungen von Daten (häufig Text, aber auch Bilder, Audio usw.), die semantische Bedeutung in einem hochdimensionalen Raum erfassen. Das Hauptmerkmal eines Vektorspeichers ist seine Fähigkeit, effiziente Ähnlichkeitssuchen durchzuführen und die relevantesten Datenpunkte basierend auf ihren Vektordarstellungen zu finden. Vektorspeicher können für viele Aufgaben verwendet werden, wie zum Beispiel:

  1. Semantische Suche
  2. Retrieval Augmented Generation (RAG)
  3. Empfehlungssystem

Lasst uns programmieren

In diesem Artikel erstellen wir einen kleinen In-Memory-Vektorspeicher namens „Pixie“. Es verfügt zwar nicht über alle Optimierungen eines produktionstauglichen Systems, demonstriert jedoch die Kernkonzepte. Pixie wird zwei Hauptfunktionen haben:

  1. Dokumenteinbettungen speichern
  2. Ähnlichkeitssuchen durchführen

Einrichten des Vektorspeichers

Zuerst erstellen wir eine Klasse namens Pixie:

import numpy as np from sentence_transformers import SentenceTransformer from helpers import cosine_similarity class Pixie: def __init__(self, embedder) -> None: self.store: np.ndarray = None self.embedder: SentenceTransformer = embedder
Nach dem Login kopieren
  1. Zuerst importieren wir Numpy für effiziente numerische Operationen und die Speicherung mehrdimensionaler Arrays.
  2. Wir werden auch SentenceTransformer aus der Satztransformers-Bibliothek importieren. Wir verwenden SentenceTransformer zur Generierung von Einbettungen, Sie können jedoch jedes beliebige Einbettungsmodell verwenden, das Text in Vektoren umwandelt. In diesem Artikel liegt unser Hauptaugenmerk auf dem Vektorspeicher selbst und nicht auf der Generierung von Einbettungen.
  3. Als nächstes initialisieren wir die Pixie-Klasse mit einem Embedder. Der Embedder kann außerhalb des Hauptvektorspeichers verschoben werden, der Einfachheit halber initialisieren wir ihn jedoch innerhalb der Vektorspeicherklasse.
  4. self.store speichert unsere Dokumenteinbettungen als NumPy-Array.
  5. self.embedder enthält das Einbettungsmodell, das wir zum Konvertieren von Dokumenten und Abfragen in Vektoren verwenden.

Dokumente aufnehmen

Um Dokumente/Daten in unseren Vektorspeicher aufzunehmen, implementieren wir die from_docs-Methode:

def from_docs(self, docs): self.docs = np.array(docs) self.store = self.embedder.encode(self.docs) return f"Ingested {len(docs)} documents"
Nach dem Login kopieren

Diese Methode bewirkt einige wichtige Dinge:

  1. Es nimmt eine Liste von Dokumenten und speichert sie als NumPy-Array in self.docs.
  2. Es verwendet das Embedder-Modell, um jedes Dokument in eine Vektoreinbettung umzuwandeln. Diese Einbettungen werden in self.store.
  3. gespeichert
  4. Es wird eine Meldung zurückgegeben, die bestätigt, wie viele Dokumente erfasst wurden. Die Kodierungsmethode unseres Embedders übernimmt hier die Hauptarbeit und wandelt jedes Textdokument in eine hochdimensionale Vektordarstellung um.

Ähnlichkeitssuche durchführen

Das Herzstück unseres Vektorshops ist die Ähnlichkeitssuchfunktion:

def similarity_search(self, query, top_k=3): matches = list() q_embedding = self.embedder.encode(query) top_k_indices = cosine_similarity(self.store, q_embedding, top_k) for i in top_k_indices: matches.append(self.docs[i]) return matches
Nach dem Login kopieren

Lassen Sie uns das aufschlüsseln:

  1. Wir beginnen mit der Erstellung einer leeren Liste namens Übereinstimmungen, um unsere Übereinstimmungen zu speichern.
  2. Wir kodieren die Benutzerabfrage mit demselben Embedder-Modell, das wir für die Aufnahme der Dokumente verwendet haben. Dadurch wird sichergestellt, dass sich der Abfragevektor im selben Bereich wie unsere Dokumentvektoren befindet.
  3. Wir rufen eine cosine_similarity-Funktion auf (die wir als Nächstes definieren), um die ähnlichsten Dokumente zu finden.
  4. Wir verwenden die zurückgegebenen Indizes, um die tatsächlichen Dokumente aus self.docs abzurufen.
  5. Abschließend erhalten Sie die Liste der passenden Dokumente zurück.

Implementierung der Kosinusähnlichkeit

import numpy as np def cosine_similarity(store_embeddings, query_embedding, top_k): dot_product = np.dot(store_embeddings, query_embedding) magnitude_a = np.linalg.norm(store_embeddings, axis=1) magnitude_b = np.linalg.norm(query_embedding) similarity = dot_product / (magnitude_a * magnitude_b) sim = np.argsort(similarity) top_k_indices = sim[::-1][:top_k] return top_k_indices
Nach dem Login kopieren

Diese Funktion erledigt mehrere wichtige Dinge:

  1. It calculates the cosine similarity using the formula: cos(θ) = (A · B) / (||A|| * ||B||)
  2. First, we calculate the dot product between the query embeddings and all document embeddings in the store.
  3. Then, we compute the magnitudes (Euclidean norms) of all vectors.
  4. Lastly, we sort the found similarities and return the indices of the top-k most similar documents. We are using cosine similarity because it measures the angle between vectors, ignoring their magnitudes. This means it can find semantically similar documents regardless of their length.
  5. There are other similarity metrics that you can explore such as:
    1. Euclidean distance
    2. Dot product similarity

You can read more about cosine similarity here.

Piecing everything together

Now that we have built all the pieces, let's understand how they work together:

  1. When we create a Pixie instance, we provide it with an embedding model.
  2. When we ingest documents, we create vector embeddings for each document and store them in self.store.
  3. For a similarity search:
    1. We create an embedding for the query.
    2. We calculate cosine similarity between the query embeddings and all document embeddings.
    3. We return the most similar documents. All the magic happens inside the cosine similarity calculation. By comparing the angle between vectors rather than their magnitude, we can find semantically similar documents even if they use different words or phrasing.

Seeing it in action

Now let's implement a simple RAG system using ourPixievector store. We'll ingest a story document of a "space battle & alien invasion" and then ask questions about it to see how it generates an answer.

import os import sys import warnings warnings.filterwarnings("ignore") import ollama import numpy as np from sentence_transformers import SentenceTransformer current_dir = os.path.dirname(os.path.abspath(__file__)) root_dir = os.path.abspath(os.path.join(current_dir, "..")) sys.path.append(root_dir) from pixie import Pixie # creating an instance of a pre-trained embedder model embedder = SentenceTransformer("all-MiniLM-L6-v2") # creating an instance of Pixie vector store pixie = Pixie(embedder) # generate an answer using llama3 and context docs def generate_answer(prompt): response = ollama.chat( model="llama3", options={"temperature": 0.7}, messages=[ { "role": "user", "content": prompt, }, ], ) return response["message"]["content"] with open("example/spacebattle.txt") as f: content = f.read() # ingesting the data into vector store ingested = pixie.from_docs(docs=content.split("\n\n")) print(ingested) # system prompt PROMPT = """ User has asked you following question and you need to answer it based on the below provided context. If you don't find any answer in the given context then just say 'I don't have answer for that'. In the final answer, do not add "according to the context or as per the context". You can be creative while using the context to generate the final answer. DO NOT just share the context as it is. CONTEXT: {0} QUESTION: {1} ANSWER HERE: """ while True: query = input("\nAsk anything: ") if len(query) == 0: print("Ask a question to continue...") quit() if query == "/bye": quit() # search similar matches for query in the embedding store similarities = pixie.similarity_search(query, top_k=5) print(f"query: {query}, top {len(similarities)} matched results:\n") print("-" * 5, "Matched Documents Start", "-" * 5) for match in similarities: print(f"{match}\n") print("-" * 5, "Matched Documents End", "-" * 5) context = ",".join(similarities) answer = generate_answer(prompt=PROMPT.format(context, query)) print("\n\nQuestion: {0}\nAnswer: {1}".format(query, answer)) continue
Nach dem Login kopieren

Here is the output:

Ingested 8 documents Ask anything: What was the invasion about? query: What was the invasion about?, top 5 matched results: ----- Matched Documents Start ----- Epilogue: A New Dawn Years passed, and the alliance between humans and Zorani flourished. Together, they rebuilt what had been lost, creating a new era of exploration and cooperation. The memory of the Krell invasion served as a stark reminder of the dangers that lurked in the cosmos, but also of the strength that came from unity. Admiral Selene Cortez retired, her name etched in the annals of history. Her legacy lived on in the new generation of leaders who continued to protect and explore the stars. And so, under the twin banners of Earth and Zorani, the galaxy knew peace—a fragile peace, hard-won and deeply cherished. Chapter 3: The Invasion Kael's warning proved true. The Krell arrived in a wave of bio-mechanical ships, each one bristling with organic weaponry and shields that regenerated like living tissue. Their tactics were brutal and efficient. The Titan Fleet, caught off guard, scrambled to mount a defense. Admiral Cortez's voice echoed through the corridors of the Prometheus. "All hands to battle stations! Prepare to engage!" The first clash was catastrophic. The Krell ships, with their organic hulls and adaptive technology, sliced through human defenses like a knife through butter. The outer rim colonies fell one by one, each defeat sending a shockwave of despair through the fleet. Onboard the Prometheus, Kael offered to assist, sharing Zorani technology and knowledge. Reluctantly, Cortez agreed, integrating Kael’s insights into their strategy. New energy weapons were developed, capable of piercing Krell defenses, and adaptive shields were installed to withstand their relentless attacks. Chapter 5: The Final Battle Victory on Helios IV was a much-needed morale boost, but the war was far from over. The Krell regrouped, launching a counter-offensive aimed directly at Earth. Every available ship was called back to defend humanity’s homeworld. As the Krell armada approached, Earth’s skies filled with the largest fleet ever assembled. The Prometheus led the charge, flanked by newly built warships and the remaining Zorani vessels that had joined the fight. "This is it," Cortez addressed her crew. "The fate of our species depends on this battle. We hold the line here, or we perish." The space above Earth turned into a maelstrom of fire and metal. Ships collided, energy beams sliced through the void, and explosions lit up the darkness. The Krell, relentless and numerous, seemed unbeatable. In the midst of chaos, Kael revealed a hidden aspect of Zorani technology—a weapon capable of creating a singularity, a black hole that could consume the Krell fleet. It was a desperate measure, one that could destroy both fleets. Admiral Cortez faced an impossible choice. To use the weapon would mean sacrificing the Titan Fleet and potentially Earth itself. But to do nothing would mean certain destruction at the hands of the Krell. "Activate the weapon," she ordered, her voice heavy with resolve. The Prometheus moved into position, its hull battered and scorched. As the singularity weapon charged, the Krell ships converged, sensing the threat. In a blinding burst of light, the weapon fired, tearing the fabric of space and creating a black hole that began to devour everything in its path. Chapter 1: The Warning It began with a whisper—a distant signal intercepted by the outermost listening posts of the Titan Fleet. The signal was alien, unlike anything the human race had ever encountered. For centuries, humanity had expanded its reach into the cosmos, colonizing distant planets and establishing trade routes across the galaxy. The Titan Fleet, the pride of Earth's military might, stood as the guardian of these far-flung colonies.Admiral Selene Cortez, a seasoned commander with a reputation for her sharp tactical mind, was the first to analyze the signal. As she sat in her command center aboard the flagship Prometheus, the eerie transmission played on a loop. It was a distress call, but its origin was unknown. The message, when decoded, revealed coordinates on the edge of the Andromeda Sector. "Set a course," Cortez ordered. The fleet moved with precision, a testament to years of training and discipline. Chapter 4: Turning the Tide The next battle, over the resource-rich planet of Helios IV, was a turning point. Utilizing the new technology, the Titan Fleet managed to hold their ground. The energy weapons seared through Krell ships, and the adaptive shields absorbed their retaliatory strikes. "Focus fire on the lead ship," Cortez commanded. "We break their formation, we break their spirit." The flagship of the Krell fleet, a massive dreadnought known as Voreth, was targeted. As the Prometheus and its escorts unleashed a barrage, the Krell ship's organic armor struggled to regenerate. In a final, desperate maneuver, Cortez ordered a concentrated strike on Voreth's core. With a blinding flash, the dreadnought exploded, sending a ripple of confusion through the Krell ranks. The humans pressed their advantage, driving the Krell back. ----- Matched Documents End ----- Question: What was the invasion about? Answer: The Krell invasion was about the Krell arriving in bio-mechanical ships with organic weaponry and shields that regenerated like living tissue, seeking to conquer and destroy humanity.
Nach dem Login kopieren

Building a tiny vector store from scratch

We have successfully built a tiny in-memory vector store from scratch by using Python and NumPy. While it is very basic, it demonstrates the core concepts such as vector storage, and similarity search. Production grade vector stores are much more optimized and feature-rich.

Github repo: Pixie

Happy coding, and may your vectors always point in the right direction!

Das obige ist der detaillierte Inhalt vonAufbau eines kleinen Vektorshops von Grund auf. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!