Große Sprachmodelle haben viele Aufgaben erleichtert, wie z.B. die Erstellung von Chatbots, Sprachübersetzung, Textzusammenfassung usw. Früher mussten wir Modelle für die Zusammenfassung schreiben, und dann gab es immer das Problem der Leistung. Jetzt können wir dies einfach mit Hilfe großer Sprachmodelle (LLMs) tun. Zum Beispiel können modernste LLMs bereits ein ganzes Buch in ihrem Kontextfenster verarbeiten. Aber es gibt immer noch einige Einschränkungen bei der Zusammenfassung sehr großer Dokumente.
# Einschränkungen der Zusammenfassung großer Dokumente durch LLMs
Kontextgrenze oder Kontextlänge in einem LLM bezieht sich auf die Anzahl der Tokens, die ein Modell verarbeiten kann. Jedes Modell hat seine eigene Kontextlänge, auch als Maximalanzahl von Tokens oder Tokenlimit bekannt. Zum Beispiel hat ein Standard-GPT-4-Modell eine Kontextlänge von 128.000 Tokens. Es geht Informationen für Tokens, die darüber hinausgehen, verloren. Einige moderne LLMs haben eine Kontextgrenze von bis zu 1 Million Tokens. Allerdings leiden LLMs mit zunehmender Kontextgrenze unter Einschränkungen wie Rezency und Primacy. Wir können auch Möglichkeiten untersuchen, um diese Effekte zu mildern.
- Der Primacy-Effekt in LLMs bezieht sich darauf, dass das Modell mehr Bedeutung auf Informationen legt, die am Anfang einer Sequenz präsentiert werden.
- Der Rezency-Effekt bezieht sich darauf, dass das Modell die zuletzt verarbeiteten Informationen stärker betont.
Beide Effekte beeinflussen das Modell zugunsten bestimmter Teile der Eingabedaten. Das Modell kann wichtige Informationen in der Mitte der Sequenz übersehen.
Das zweite Problem ist Kosten. Wir können das erste Problem der Kontextgrenze lösen, indem wir den Text aufteilen, aber wir können das ganze Buch nicht direkt an das Modell weitergeben. Das würde viel kosten. Wenn wir zum Beispiel 1 Million Tokens eines Buches haben und es direkt an das GPT4-Modell weitergeben, würde uns das insgesamt etwa 90 US-Dollar kosten (Prompt- und Abschluss-Tokens). Wir müssen einen Mittelweg finden, um unseren Text zusammenzufassen und dabei den Preis, die Kontextgrenze und den vollständigen Kontext des Buches zu berücksichtigen.
In diesem Tutorial lernen Sie, ein ganzes Buch unter Berücksichtigung des Preises und der Kontextgrenze des Modells zusammenzufassen. Fangen wir an.
# Große Dokumente mit LangChain und OpenAI zusammenfassen
# Einrichten der Umgebung
Um dem Tutorial folgen zu können, müssen Sie Folgendes installiert haben:
- Python
- Eine IDE (VS Code würde funktionieren)
Um die Abhängigkeiten zu installieren, öffnen Sie Ihr Terminal und geben Sie den Befehl ein:
pip install langchain openai tiktoken fpdf2 pandas
Dieser Befehl installiert alle erforderlichen Abhängigkeiten.
# Das Buch laden
Sie werden das Buch "David Copperfield" von Charles Dickens verwenden, das öffentlich für dieses Projekt verfügbar ist. Laden wir das Buch mit dem von LangChain bereitgestellten Dienstprogramm PyPDFLoader
.
from langchain.document_loaders import PyPDFLoader
# Buch laden
loader = PyPDFLoader("David-Copperfield.pdf")
pages = loader.load_and_split()
Es wird das gesamte Buch geladen, aber wir sind nur am Inhalt interessiert. Wir können die Seiten wie das Vorwort und die Einleitung überspringen.
# Öffnungs- und Schlussteile ausschneiden
pages = pages[6:1308]
# Die Seiten kombinieren und Tabs durch Leerzeichen ersetzen
text = ' '.join([page.page_content.replace('\t', ' ') for page in pages])
Jetzt haben wir den Inhalt. Lassen Sie uns die ersten 200 Zeichen ausdrucken.
text[0:200]
# Vorverarbeitung
Lassen Sie uns den Text von unnötigem Inhalt wie nicht druckbaren Zeichen, zusätzlichen Leerzeichen usw. bereinigen.
import re
def clean_text(text):
# Entfernen der spezifischen Phrase 'Free eBooks at Planet eBook.com' und umgebender Leerzeichen
cleaned_text = re.sub(r'\s*Free eBooks at Planet eBook\.com\s*', '', text, flags=re.DOTALL)
# Entfernen von zusätzlichen Leerzeichen
cleaned_text = re.sub(r' +', ' ', cleaned_text)
# Entfernen von nicht druckbaren Zeichen, optional gefolgt von 'David Copperfield'
cleaned_text = re.sub(r'(David Copperfield )?[\x00-\x1F]', '', cleaned_text)
# Ersetzen von Zeilenumbrüchen durch Leerzeichen
cleaned_text = cleaned_text.replace('\n', ' ')
# Entfernen von Leerzeichen um Bindestriche
cleaned_text = re.sub(r'\s*-\s*', '', cleaned_text)
return cleaned_text
clean_text=clean_text(text)
Nachdem die Daten bereinigt wurden, sind wir bereit, uns mit dem Zusammenfassungsproblem zu befassen.
# Die OpenAI API laden
Bevor wir die OpenAI API verwenden können, müssen wir sie konfigurieren und hier Anmeldeinformationen angeben.
import os
os.environ["OPENAI_API_KEY"] = "Ihr-OpenAI-Schlüssel-hier"
Geben Sie dort Ihren API-Schlüssel ein und er wird als Umgebungsvariable festgelegt.
Lassen Sie uns sehen, wie viele Tokens wir im Buch haben:
from langchain import OpenAI
llm = OpenAI()
Tokens = llm.get_num_tokens(clean_text)
print (f"Wir haben {Tokens} Tokens im Buch")
Wir haben über 466.000 Tokens in diesem Buch, und wenn wir sie alle direkt an das LLM weitergeben, würde es uns viel kosten. Um die Kosten zu reduzieren, werden wir K-Means-Clustering implementieren, um die wichtigen Abschnitte aus dem Buch zu extrahieren.
Hinweis: Die Entscheidung, K-Means-Clustering zu verwenden, wurde von dem Datenexperten Greg Kamradts Tutorial (opens new window) inspiriert.
Um wichtige Teile des Buches zu erhalten, teilen wir das Buch zunächst in verschiedene Abschnitte auf.
# Den Inhalt in Dokumente aufteilen
Wir teilen den Buchinhalt in Dokumente auf, indem wir das Dienstprogramm SemanticChunker
von LangChain verwenden.
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai.embeddings import OpenAIEmbeddings
text_splitter = SemanticChunker(
OpenAIEmbeddings(), breakpoint_threshold_type="interquartile"
)
docs = text_splitter.create_documents([clean_text])
Der SemanticChunker
erhält zwei Argumente, das erste ist das Embeddings-Modell. Die von diesem Modell generierten Embeddings werden verwendet, um den Text basierend auf der Semantik aufzuteilen. Das zweite Argument ist der breakpoint_threshold_type
, der die Punkte bestimmt, an denen der Text basierend auf der semantischen Ähnlichkeit in verschiedene Abschnitte aufgeteilt werden soll.
Hinweis: Durch die Verarbeitung dieser kleineren, semantisch ähnlichen Abschnitte versuchen wir, die Rezency- und Primacy-Effekte in unserem LLM zu minimieren. Diese Strategie ermöglicht es unserem Modell, jeden kleinen Kontext effektiver zu verarbeiten und eine ausgewogenere Interpretation und Generierung von Antworten zu gewährleisten.
# Die Embeddings jedes Dokuments finden
Lassen Sie uns nun die Embeddings jedes generierten Dokuments erhalten. Sie erhalten die Embeddings mit der Standardmethode von OpenAI
.
import numpy as np
import openai
def get_embeddings(text):
response = openai.embeddings.create(
model="text-embedding-3-small",
input=text
)
return response.data
embeddings=get_embeddings([doc.page_content for doc in docs]
)
Die Methode get_embeddings
gibt uns die Embeddings aller Dokumente.
Hinweis: Die Methode text-embedding-3-small
wurde speziell von OpenAI veröffentlicht und gilt als kostengünstiger und schneller.
# Die Daten neu anordnen
Als nächstes werden wir Listen der Dokumentinhalte und ihrer Embeddings in ein pandas DataFrame umwandeln, um eine einfachere Datenverarbeitung und -analyse zu ermöglichen.
import pandas as pd
content_list = [doc.page_content for doc in docs]
df = pd.DataFrame(content_list, columns=['page_content'])
vectors = [embedding.embedding for embedding in embeddings]
array = np.array(vectors)
embeddings_series = pd.Series(list(array))
df['embeddings'] = embeddings_series
# Faiss für effizientes Clustering anwenden
Jetzt werden wir die Dokumentenvektoren in ein Format umwandeln, das mit Faiss (opens new window) kompatibel ist, sie in 50 Gruppen mit K-Means clusteren und dann einen Faiss-Index für effiziente Ähnlichkeitssuchen zwischen Dokumenten erstellen.
**<code>import numpy as np \
import faiss \
<em># Konvertieren Sie in float32, falls noch nicht erfolgt</em> \
array = array.astype('float32') \
num_clusters = 50 \
<em># Dimensionalität der Vektoren</em> \
dimension = array.shape[1] \
<em># KMeans mit Faiss trainieren</em> \
kmeans = faiss.Kmeans(dimension, num_clusters, niter=20, verbose=True) \
kmeans.train(array) \
<em># Zugriff auf die Zentroide</em> \
centroids = kmeans.centroids \
<em># Einen neuen Index für den ursprünglichen Datensatz erstellen</em> \
index = faiss.IndexFlatL2(dimension) \
<em># Den ursprünglichen Datensatz zum Index hinzufügen</em> \
index.add(array)</code></strong>
Dieses K-Means-Clustering gruppiert die Dokumente in 50 Gruppen.
Hinweis: Der Grund für die Wahl des K-Means-Clustering besteht darin, dass jeder Cluster einen ähnlichen Inhalt oder ähnlichen Kontext hat, da alle Dokumente innerhalb dieses Clusters verwandte Embeddings haben, und wir dasjenige auswählen werden, das dem Kern am nächsten liegt.
# Die wichtigen Dokumente auswählen
Jetzt wählen wir einfach das wichtigste Dokument aus jedem Cluster aus. Dazu wählen wir nur den ersten nächsten Vektor zum Zentroid aus.
D, I = index.search(centroids, 1)
Dieser Code verwendet die search
-Methode auf dem Index, um das am nächsten gelegene Dokument zu jedem Zentroid in der Liste der Zentroide zu finden. Er gibt zwei Arrays zurück: D
, das die Abstände der am nächsten gelegenen Dokumente zu ihren jeweiligen Zentroiden enthält, und I
, das die Indizes dieser am nächsten gelegenen Dokumente enthält. Der zweite Parameter 1
in der search
-Methode gibt an, dass nur das einzelne am nächsten gelegene Dokument für jeden Zentroid gefunden werden soll.
Jetzt müssen wir die ausgewählten Dokumentindizes sortieren, da die Dokumente in Bezug auf die Reihenfolge des Buches angeordnet sind.
sorted_array = np.sort(I, axis=0)
sorted_array=sorted_array.flatten()
extracted_docs = [docs[i] for i in sorted_array]
# Die Zusammenfassung jedes Dokuments erhalten
Der nächste Schritt besteht darin, die Zusammenfassung jedes Dokuments mit dem GPT-4-Modell zu erhalten, um Geld zu sparen. Um GPT-4 zu verwenden, definieren wir das Modell.
model = ChatOpenAI(temperature=0,model="gpt-4")
Definieren Sie den Prompt und erstellen Sie eine Prompt-Vorlage mit LangChain, um sie an das Modell zu übergeben.
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_template("""
Ihnen werden verschiedene Passagen aus einem Buch nacheinander gegeben. Geben Sie eine Zusammenfassung des folgenden Textes an. Ihr Ergebnis muss detailliert sein und mindestens 2 Absätze umfassen. Wenn Sie zusammenfassen, tauchen Sie direkt in die Erzählung oder Beschreibungen aus dem Text ein, ohne einleitende Phrasen wie 'In dieser Passage' zu verwenden. Gehen Sie direkt auf die Hauptereignisse, Charaktere und Themen ein und fassen Sie die Essenz und wichtige Details aus dem Text in einer fließenden Erzählung zusammen. Das Ziel ist es, eine einheitliche Sicht auf den Inhalt zu präsentieren und die Geschichte nahtlos fortzusetzen, als ob die Passage natürlich in die Zusammenfassung übergeht.
Passage:
```{text}```
ZUSAMMENFASSUNG:
"""
)
Diese Prompt-Vorlage hilft dem Modell, die Dokumente effektiver und effizienter zusammenzufassen.
Der nächste Schritt besteht darin, eine Kette von LangChain mit Hilfe der LangChain Expression Language (LCEL) zu definieren.
chain= (
prompt
| model
|StrOutputParser() )
Die Zusammenfassungskette verwendet den StrOutputParser (opens new window), um die Ausgabe zu analysieren. Es gibt auch andere Ausgabeparser (opens new window) zum Erkunden.
Sie können die definierte Kette schließlich auf jedes Dokument anwenden, um eine Zusammenfassung zu erhalten.
from tqdm import tqdm
final_summary = ""
for doc in tqdm(extracted_docs, desc="Verarbeite Dokumente"):
# Die neue Zusammenfassung erhalten.
new_summary = chain2.invoke({"text": doc.page_content})
# Die Liste der letzten beiden Zusammenfassungen aktualisieren: die erste entfernen und die neue am Ende hinzufügen.
final_summary+=new_summary
Der obige Code wendet die Kette nacheinander auf jedes Dokument an und fügt jede Zusammenfassung zur final_summary
hinzu.
# Die Zusammenfassung als PDF speichern
Der nächste Schritt besteht darin, die Zusammenfassung zu formatieren und im PDF-Format zu speichern.
from fpdf import FPDF
class PDF(FPDF):
def header(self):
# Arial fett 15 auswählen
self.set_font('Arial', 'B', 15)
# Nach rechts verschieben
self.cell(80)
# Rahmen um den Titel
self.cell(30, 10, 'Zusammenfassung', 1, 0, 'C')
# Zeilenumbruch
self.ln(20)
def footer(self):
# Gehe 1,5 cm vom unteren Rand
self.set_y(-15)
# Arial kursiv 8 auswählen
self.set_font('Arial', 'I', 8)
# Seitenzahl
self.cell(0, 10, 'Seite %s' % self.page_no(), 0, 0, 'C')
# Instanziieren Sie das PDF-Objekt und fügen Sie eine Seite hinzu
pdf = PDF()
pdf.add_page()
pdf.set_font("Arial", size=12)
# Stellen Sie sicher, dass der Text 'last_summary' als UTF-8 behandelt wird
# Ersetzen Sie 'last_summary' durch Ihre tatsächliche Textvariable, wenn sie anders lautet
# Stellen Sie sicher, dass Ihr Text eine UTF-8-codierte Zeichenkette ist
last_summary_utf8 = last_summary.encode('latin-1', 'replace').decode('latin-1')
pdf.multi_cell(0, 10, last_summary_utf8)
# Speichern Sie das PDF in einer Datei
pdf_output_path = "s_output1.pdf"
pdf.output(pdf_output_path)
So haben wir nun die vollständige Zusammenfassung des Buches im PDF-Format.
# Fazit
In diesem Tutorial haben wir uns mit den Komplexitäten der Zusammenfassung großer Texte wie ganzer Bücher unter Verwendung von LLMs befasst und dabei Herausforderungen in Bezug auf den Kontextumfang und die Kosten bewältigt. Wir haben die Schritte zur Vorverarbeitung des Textes gelernt und eine Strategie implementiert, die semantisches Chunking und K-Means-Clustering kombiniert, um die Kontextbeschränkungen des Modells effektiv zu bewältigen. Durch die Verwendung effizienten Clusterings haben wir wichtige Passagen effizient extrahiert und den Aufwand für die direkte Verarbeitung massiver Texte reduziert. Dieser Ansatz reduziert nicht nur die Kosten erheblich, indem die Anzahl der verarbeiteten Tokens minimiert wird, sondern mildert auch die Rezency- und Primacy-Effekte, die in LLMs inhärent sind, und gewährleistet eine ausgewogene Berücksichtigung aller Textsegmente.
Es gibt eine große Begeisterung für die Entwicklung von KI-Anwendungen über die APIs von LLMs, bei denen Vektordatenbanken eine wichtige Rolle spielen, indem sie eine effiziente Speicherung und Abfrage von kontextuellen Embeddings ermöglichen. MyScaleDB (opens new window) ist eine Vektordatenbank, die speziell für KI-Anwendungen entwickelt wurde und alle Faktoren wie Kosten, Genauigkeit und Geschwindigkeit berücksichtigt. Ihre SQL-freundliche Schnittstelle ermöglicht es Entwicklern, mit der Entwicklung ihrer KI-Anwendungen zu beginnen, ohne etwas Neues lernen zu müssen.
Wenn Sie mehr mit uns besprechen möchten, sind Sie herzlich eingeladen, dem MyScale Discord (opens new window) beizutreten, um Ihre Gedanken und Feedback zu teilen.
Dieser Artikel wurde ursprünglich auf The New Stack veröffentlicht. (opens new window)