Vektordatenbanken
15.01.2024, 15:23 Uhr
So werden aus LLMs und Vektordatenbanken hocheffiziente NLP-Suchmaschinen
Bislang wurden Vektordatenbanken fast ausschließlich im eCommerce-Bereich eingesetzt. Dann kamen Large Language Models wie GPT. Damit erlangen die Datenbanken einen ganz neuen Einsatzbereich.
Wir alle kennen ChatGPT und ähnliche KI-basierte Anwendungen. Die Medien haben den Hype um künstliche Intelligenz angeheizt – und zwar völlig zu Recht, denn was generative KI heutzutage zu leisten vermag, stößt das Tor in ganz neue Welten auf. Chatbots sind sogar mittlerweile in der Lage, den Turing-Test zu bestehen, und sind für die Gesprächspartner somit von echten Menschen nicht mehr zu unterscheiden. Das ist zwar eine herausragende technologische Errungenschaft, viel spannender ist aber, was „unter der Haube“ oder – vielleicht angemessener – hinter der Stirn der künstlichen Intelligenz vor sich geht. Dort arbeiten sogenannte Large Language Models (LLMs), genauer „Generative pre-trained transformers“ – oder kurz: GPTs. Diese künstlichen neuronalen Netzwerke werden mit einer großen Anzahl von Daten (vorrangig Texten) trainiert, bis sie in der Lage sind, neue Inhalte zu generieren.
Ein Vektor, sie zu berechnen
In diesem Zusammenhang spielen Vektoren eine besondere Rolle, mit denen mathematisch Distanzen berechnet werden. Auf Sprachmodelle angewandt steht natürlich zunächst die Frage im Raum, wie Entwickler Sprache mathematisch greifbar machen können. Das ist die Grundvoraussetzung, denn ein Computer kann sie nur so überhaupt erfassen.
Geht es um den Vergleich einzelner Worte, ist der Vorgang noch relativ simpel: Theoretisch genügt es, deren Buchstaben in Zahlen umzuwandeln, die Länge zu erfassen und dann die Nähe zwischen ihnen zu berechnen. Was der Computer allerdings nicht so einfach erfassen kann, ist die Bedeutung der Worte. Das stellt ein gewaltiges Problem dar, denn kann die Maschine die Bedeutung nicht erfassen, ist sie auch nicht in der Lage, Zusammenhänge zu erkennen. Das ist aber im Kontext von künstlicher Intelligenz absolut unabdingbar.
Glücklicherweise gibt es mittlerweile bestimmte Algorithmen, die nicht nur Worte, sondern ganze Sätze und größere Textabschnitte in sogenannte Vektorrepräsentationen (Embeddings) umrechnen können. Diese Werte stellen sozusagen Koordinaten in einem vieldimensionalen Koordinatensystem dar, anhand derer sich inhaltliche Distanzen zwischen Sätzen, Worten und Texten berechnen lassen.
In der Geometrie sind Koordinatensysteme zwei- oder dreidimensional. Bei der Vektorrechnung sind den Dimensionen keine Grenzen gesetzt und je höher ihre Anzahl ist, desto feingranularer lassen sich die jeweiligen Embeddings in Beziehung zueinander stellen. Um ein Beispiel zu geben: Um eine Koordinate in einem Raum zu berechnen, benötigt man drei Punkte – jeweils einen auf der X-, Y- und Z-Achse. Mehrere Koordinaten-Trios ermöglichen es, Distanzen zwischen Punkten im Raum zu berechnen.
Beim Large Language Model GPT-3, das nicht einmal die aktuellste Version des Modells ist, kommen je nach Engine bis zu 12288 Dimensionen zum Einsatz.
In diesem multidimensionalen Raum repräsentieren Vektoren also die Koordinaten, eine Strecke und eine Richtung. Was die Maschine allerdings sieht ist lediglich eine Dezimalzahl. Anhand dieser Vektorrepräsentationen kann der Computer schließlich berechnen, wie die Worte, Sätze und Texte miteinander in Beziehung stehen.
Die genauere Berechnung findet in Sprachmodellen über die Berechnung der Kosinus-Ähnlichkeit statt, bei der zunächst der Kosinus den Winkel zwischen zwei Vektoren bestimmt und dann abgeglichen wird. Wo die mathematische Distanz zwischen den Vektorrepräsentationen am geringsten ist, dort passt die Bedeutung der Texte am besten zueinander. Der Computer kann auf diese Weise die Zusammenhänge erfassen und Sprache verstehen – und ist plötzlich zum Natural Language Processing (NLP) in der Lage.
Vektordatenbanken – das KI-Gedächtnis
Um dieses Verständnis in der Praxis nutzen zu können, brauchen Large Language Models aber nicht nur die Fähigkeit, Sprache zu verstehen, sondern auch ein Gedächtnis, aus dem es Wissen schöpfen kann. Kombiniert man Large Language Models mit Vektordatenbanken, gibt man der KI ein solches und eröffnet noch dazu den Weg zu einem sehr interessanten Einsatzgebiet: Retrieval Augmented Generation (RAG).
Bei dieser Technik werden relevante Informationen aus einer externen Quelle abgerufen und via Algorithmus in Vektoren umgewandelt, sodass ein LLM sie verarbeiten kann. Anschließend werden die Embeddings in einer Vektordatenbank abgelegt. Indem Entwickler dem Sprachmodell Zugriff auf diese Daten geben und sie in das LLM einspeisen, wird es mit neuem Wissen angereichert: Die KI kann so spezifische Fragen beantworten, für die es ursprünglich nicht trainiert wurde. Das Ergebnis ist eine extrem genaue und hocheffiziente Suchmachine.
Ein sehr positiver Nebeneffekt der Nutzung von Vektorrepräsentationen ist, dass die grundlegenden Konzepte, Prinzipien und Operationen der Vektorrechnung umfassend erforscht und formuliert sind, sodass sich die Funktionalitäten von Vektordatenbankanbietern gut vergleichen lassen. Eine Open-Source-Datenbank ist daher genauso geeignet für die Berechnung der Kosinus-Ähnlichkeit und das Speichern von Embeddings wie eine proprietäre. Die quelloffene, weit verbreitete und leistungsstarke Datenbank PostgreSQL ist per se zwar keine Vektordatenbank. Allerdings gibt es mit pgvector mittlerweile eine passende Open-Source-Erweiterung, um sie entsprechend nachzurüsten. Auf diese Weise können Nutzer ihre Vorteile wie die Skalierbarkeit und Robustheit auch als Basis für ihre RAG-Anwendung nutzen, ohne sich mit einer neuen Datenbank vertraut machen zu müssen. Die Verwaltung der Vektordatenbank wird natürlich für Unternehmen noch einfacher, wenn sie sie als Teil einer Managed-Platform-Lösung verwenden.
Praxisbeispiel: Eine intelligente Suche für die Instaclustr-Doku
Eine sinnvolle RAG-Anwendung ist zum Beispiel eine Suchanwendung. Zu Demonstrationszwecken bauen wir daher eine solche für die Instaclustr-Dokumentation, sodass Nutzer auf die Frage „Wie erstelle ich einen Redis-Cluster unter Verwendung von Terraform?“ eine passende Antwort direkt aus dem Informationsmaterial bekommen. Für die praktische Umsetzung eignet sich das Python-Framework LangChain. Da die Instaclustr-Dokumentation aus verschiedenen Webseiten besteht, müssen wir die Inhalte mit einem sogenannten Document Loader, den das Framework bereitstellt, extrahieren. In diesem Fall eignet sich dafür der HTML-Loader, Lang-Chain kommt aber mit deutlich mehr Formaten und Quellen zurecht.
Da Large Language Models ein begrenztes „Context Window“ haben, sollten die Informationen in leichter zu verarbeitende Teile aufgespalten werden – würde man versuchen, ein ganzes Buch als Embedding auszulesen, würde sich das sehr negativ auf die Performanz der Anwendung auswirken, sowohl was die Genauigkeit als auch was die Geschwindigkeit angeht.
LangChain bietet daher ein nützliches Tooling für die Segmentierung (Document Transformation). Daraufhin erstellen wir die Vektorrepräsentationen, also die Embeddings, mit dem Embedding-Modell „text-embedding-ada-002“ von OpenAI. Und zum Speichern der Embeddings und der Originalinhalte kommt PostgreSQL in Verbindung mit pgvector zum Einsatz. Sind all diese Schritte durchgeführt, ergibt sich daraus folgender Nutzer-Workflow:
1. Der User gibt in die Suchmaske die Frage „Wie erstelle ich einen Redis-Cluster unter Verwendung von Terraform?“ ein.
2. Die Suchmaschine sendet die Frage via API-Call an das Embedding-Modell von OpenAI, das aus ihr eine Vektorrepräsentation erstellt.
3. Ein weiterer API-Call zu PostgreSQL findet statt, der eine Suche nach der größten Kosinus-Ähnlichkeit zwischen dem Embedding der Frage und den in der Datenbank vorliegenden Embeddings der Dokumentation auslöst.
4. Ist der gewünschte Inhalt gefunden, erhält der User eine entsprechende Antwort auf seine Frage.
Dieser Workflow muss nun noch in Code gegossen werden.
Zeit zu Coden!
Zunächst muss PostgreSQL mit pgvector erweitert werden. Auch eine Tabelle müssen wir anlegen, in der alle Dokumente mit ihren Embeddings später abliegen:
CREATE EXTENSION vector;
CREATE TABLE insta_documentation
(id bigserial PRIMARY KEY,
title,
content,
url,
embedding
vector(3));
Der folgende Python-Code erlaubt es der RAG-Anwendung, die Instaclustr-Dokumentation auszulesen, die wichtigsten Textteile wie Titel und Inhalt mit Beautiful Soup zu extrahieren und sie zusammen mit der entsprechenden URL in der PostgreSQL-Tabelle zu speichern:
urls = [...]
def init_connection():
return psycopg2.connect(**st.secrets["postgres"])
def extract_info(url):
hdr = {'User-Agent': 'Mozilla/5.0'}
req = Request(url,headers=hdr)
response = urlopen(req)
soup = BeautifulSoup(response, 'html.parser')
title = soup.find('title').text
middle_section =
soup.find('div', class_='documentation-middle').contents
# middle section consists of header, content and
# instaclustr banner and back and forth links -
# we want only the first two
content = str(middle_section[0]) + str(middle_section[1])
return title, content, url
conn = init_connection()
cursor = conn.cursor()
for url in urls:
page_content = extract_info(url)
postgres_insert_query =
""" INSERT INTO insta_documentation (title, content, url)
VALUES (%s, %s, %s)"""
cursor.execute(postgres_insert_query, page_content)
conn.commit()
if conn:
cursor.close()
conn.close()
Danach ist es Zeit, die Dokumentationsseiten aus der Datenbank zu laden, sie aufzusplitten und die Embeddings zu erstellen, die dann ebenfalls in der Datenbank gespeichert werden:
def init_connection():
return psycopg2.connect(**st.secrets["postgres"])
conn = init_connection()
cursor = conn.cursor()
# Define and execute query to the insta_documentation table,
# limiting to 10 results for testing (creating embeddings through
# the OpenAI API can get costly when dealing with a huge amount of data)
postgres_query = """ SELECT title, content, url FROM
insta_documentation LIMIT 10"""
cursor.execute(postgres_query)
results = cursor.fetchall()
conn.commit()
# Load results into pandas DataFrame for easier manipulation
df = pd.DataFrame(results, columns=['title', 'content', 'url'])
# Break down content text which exceed max input token
# limit into smaller chunk documents
# Define text splitter
html_splitter = RecursiveCharacterTextSplitter.from_language(
language=Language.HTML, chunk_size=1000, chunk_overlap=100)
# We need to initialize our embeddings model
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
docs = []
for i in range(len(df.index)):
# Create document with metadata for each content chunk
docs = docs + html_splitter.create_documents(
[df['content'][i]], metadatas=[{"title": df['title'][i],
"url": df['url'][i]}])
# Create pgvector dataset
db = PGVector.from_documents(
embedding=embeddings,
documents=docs,
collection_name=COLLECTION_NAME,
connection_string=CONNECTION_STRING,
distance_strategy=DistanceStrategy.COSINE,
)
Zu guter Letzt erstellen wir noch den Retriever, der für das Finden des Inhalts zuständig ist:
query = st.text_input('Your question',
placeholder='How can I sign up for an Instaclustr console account?')
retriever = store.as_retriever(search_kwargs={"k": 3})
qa = RetrievalQA.from_chain_type(
llm=OpenAI(),
chain_type="stuff",
retriever=retriever,
return_source_documents=True,
verbose=True,
)
result = qa({"query": query})
source_documents = result["source_documents"]
document_page_content =
[document.page_content for document in source_documents]
document_metadata = [document.metadata for document in source_documents]
Für das Erstellen der Benutzeroberfläche eignet sich das leistungsstarke Tool Streamlit, mit dem sich interaktive Python-Interfaces erstellen lassen.
Sobald die Suchmaske aufgesetzt ist, kann es mit der Suche losgehen. Durch Large Language Models und Vektordatenbanken wie PostgreSQL und pgvector werden Suchen in natürlicher Sprache nicht nur möglich, sondern zudem auch sehr effizient.