7 Schritte zum Aufbau eines einfachen RAG-Systems von Grund auf7 Schritte zum Aufbau eines einfachen RAG-Systems von Grund auf
Bild vom Autor

# Einführung

Heutzutage verwendet quick jeder ChatGPT, Gemini oder ein anderes großes Sprachmodell (LLM). Sie machen das Leben einfacher, können aber trotzdem etwas falsch machen. Ich erinnere mich zum Beispiel daran, dass ich ein generatives Modell gefragt habe, wer die letzten US-Präsidentschaftswahlen gewonnen habe, und den Namen des vorherigen Präsidenten zurückbekommen habe. Es klang zuversichtlich, aber das Modell stützte sich lediglich auf Trainingsdaten vor der Wahl. Hier hilft die Retrieval-Augmented Technology (RAG) LLMs dabei, genauere und aktuellere Antworten zu geben. Anstatt sich nur auf das interne Wissen des Modells zu verlassen, bezieht es Informationen aus externen Quellen – wie PDFs, Dokumenten oder APIs – und nutzt diese, um eine kontextbezogenere und zuverlässigere Antwort zu erstellen. In diesem Leitfaden führe ich Sie durch sieben praktische Schritte zum Aufbau eines einfachen RAG-Methods von Grund auf.

# Den Workflow der Retrieval-Augmented Technology verstehen

Bevor wir mit dem Codieren fortfahren, hier die Idee im Klartext. Ein RAG-System besteht aus zwei Kernstücken: dem Retriever und die Generator. Der Retriever durchsucht Ihre Wissensdatenbank und ruft die relevantesten Textabschnitte heraus. Der Generator ist das Sprachmodell, das diese Schnipsel in eine natürliche, nützliche Antwort umwandelt. Der Prozess ist wie folgt unkompliziert:

  1. Ein Benutzer stellt eine Frage.
  2. Der Retriever durchsucht Ihre indizierten Dokumente oder Ihre Datenbank und gibt die am besten passenden Passagen zurück.
  3. Diese Passagen werden dem LLM als Kontext übergeben.
  4. Das LLM generiert dann eine Antwort, die auf diesem abgerufenen Kontext basiert.

Jetzt werden wir diesen Ablauf in sieben einfache Schritte unterteilen und ihn durchgängig aufbauen.

# Schritt 1: Vorverarbeitung der Daten

Auch wenn große Sprachmodelle bereits viel aus Lehrbüchern und Webdaten wissen, haben sie keinen Zugriff auf Ihre privaten oder neu generierten Informationen wie Forschungsnotizen, Firmendokumente oder Projektdateien. RAG hilft Ihnen, das Modell mit Ihren eigenen Daten zu versorgen und so die Reduzierung zu reduzieren Halluzinationen und die Antworten präziser und aktueller zu gestalten. In diesem Artikel halten wir die Dinge einfach und verwenden ein paar kurze Textdateien zu Konzepten des maschinellen Lernens.

information/
 ├── supervised_learning.txt
 └── unsupervised_learning.txt
supervised_learning.txt:
In this sort of machine studying (supervised), the mannequin is skilled on labeled information. 
In easy phrases, each coaching instance has an enter and an related output label. 
The target is to construct a mannequin that generalizes properly on unseen information. 
Widespread algorithms embody:
- Linear Regression
- Determination Timber
- Random Forests
- Help Vector Machines

Classification and regression duties are carried out in supervised machine studying.
For instance: spam detection (classification) and home value prediction (regression).
They are often evaluated utilizing accuracy, F1-score, precision, recall, or imply squared error.
unsupervised_learning.txt:
In this sort of machine studying (unsupervised), the mannequin is skilled on unlabeled information. 
Standard algorithms embody:
- Okay-Means
- Principal Part Evaluation (PCA)
- Autoencoders

There aren't any predefined output labels; the algorithm robotically detects 
underlying patterns or constructions inside the information.
Typical use circumstances embody anomaly detection, buyer clustering, 
and dimensionality discount.
Efficiency will be measured qualitatively or with metrics akin to silhouette rating 
and reconstruction error.

Die nächste Aufgabe besteht darin, diese Daten zu laden. Dazu erstellen wir eine Python-Datei, load_data.py:

import os

def load_documents(folder_path):
    docs = ()
    for file in os.listdir(folder_path):
        if file.endswith(".txt"):
            with open(os.path.be a part of(folder_path, file), 'r', encoding='utf-8') as f:
                docs.append(f.learn())
    return docs

Bevor wir die Daten verwenden, bereinigen wir sie. Wenn der Textual content chaotisch ist, kann das Modell irrelevante oder falsche Passagen abrufen, was zu mehr Halluzinationen führt. Lassen Sie uns nun eine weitere Python-Datei erstellen. clean_data.py:

import re

def clean_text(textual content: str) -> str:
    textual content = re.sub(r's+', ' ', textual content)
    textual content = re.sub(r'(^x00-x7F)+', ' ', textual content)
    return textual content.strip()

Zum Schluss kombinieren Sie alles in einer neuen Datei mit dem Namen prepare_data.py So laden und bereinigen Sie Ihre Dokumente gemeinsam:

from load_data import load_documents
from clean_data import clean_text

def prepare_docs(folder_path="information/"):
    """
    Hundreds and cleans all textual content paperwork from the given folder.
    """
    # Load Paperwork
    raw_docs = load_documents(folder_path)

    # Clear Paperwork
    cleaned_docs = (clean_text(doc) for doc in raw_docs)

    print(f"Ready {len(cleaned_docs)} paperwork.")
    return cleaned_docs

# Schritt 2: Textual content in Blöcke umwandeln

LLMs besitzen eine kleine Kontextfenster — Beispielsweise können sie nur eine begrenzte Textmenge gleichzeitig verarbeiten. Wir lösen dieses Downside, indem wir lange Dokumente in kurze, überlappende Teile unterteilen (die Anzahl der Wörter in einem Teil beträgt normalerweise 300 bis 500 Wörter). Wir werden verwenden LangChain‚S RecursiveCharacterTextSplitterwodurch Textual content an natürlichen Stellen wie Sätzen oder Absätzen geteilt wird. Jedes Teil ergibt einen Sinn und das Modell kann beim Beantworten schnell das relevante Teil finden.

split_text.py

from langchain.text_splitter import RecursiveCharacterTextSplitter

def split_docs(paperwork, chunk_size=500, chunk_overlap=100):
 
   # outline the splitter
   splitter = RecursiveCharacterTextSplitter(
       chunk_size=chunk_size,
       chunk_overlap=chunk_overlap
   )

   # use the splitter to separate docs into chunks
   chunks = splitter.create_documents(paperwork)
   print(f"Whole chunks created: {len(chunks)}")

   return chunks

Chunking hilft dem Modell, den Textual content zu verstehen, ohne seine Bedeutung zu verlieren. Wenn wir keine kleine Überlappung zwischen den Teilen hinzufügen, kann das Modell an den Rändern verwirrt werden und die Antwort ergibt möglicherweise keinen Sinn.

# Schritt 3: Vektoreinbettungen erstellen und speichern

Ein Laptop versteht keine Textinformationen; es versteht nur Zahlen. Wir müssen additionally unsere Textblöcke in Zahlen umwandeln. Diese Zahlen werden als Vektoreinbettungen bezeichnet und helfen dem Laptop, die Bedeutung des Textes zu verstehen. Wir können Instruments wie verwenden OpenAI, SatzTransformersoder Umarmendes Gesicht dafür. Erstellen wir eine neue Datei mit dem Namen create_embeddings.py und verwenden Sie SentenceTransformers, um Einbettungen zu generieren.

from sentence_transformers import SentenceTransformer
import numpy as np

def get_embeddings(text_chunks):
  
   # Load embedding mannequin
   mannequin = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
  
   print(f"Creating embeddings for {len(text_chunks)} chunks:")
   embeddings = mannequin.encode(text_chunks, show_progress_bar=True)
  
   print(f"Embeddings form: {embeddings.form}")
   return np.array(embeddings)

Jede Vektoreinbettung erfasst ihre semantische Bedeutung. Ähnliche Textblöcke weisen Einbettungen auf, die im Vektorraum nahe beieinander liegen. Jetzt speichern wir Einbettungen in einer Vektordatenbank wie FAISS (Fb AI Similarity Search), Chroma oder Pinecone. Dies hilft bei der schnellen Ähnlichkeitssuche. Verwenden wir zum Beispiel FAISS (eine einfache, lokale Choice). Sie können es installieren mit:

Als nächstes erstellen wir eine Datei mit dem Namen store_faiss.py. Zuerst führen wir die notwendigen Importe durch:

import faiss
import numpy as np
import pickle

Jetzt erstellen wir mithilfe der Funktion einen FAISS-Index aus unseren Einbettungen build_faiss_index().

def build_faiss_index(embeddings, save_path="faiss_index"):
   """
   Builds FAISS index and saves it.
   """
   dim = embeddings.form(1)
   print(f"Constructing FAISS index with dimension: {dim}")

   # Use a easy flat L2 index
   index = faiss.IndexFlatL2(dim)
   index.add(embeddings.astype('float32'))

   # Save FAISS index
   faiss.write_index(index, f"{save_path}.index")
   print(f"Saved FAISS index to {save_path}.index")

   return index

Jede Einbettung stellt einen Textblock dar, und FAISS hilft dabei, in Zukunft die nächsten abzurufen, wenn ein Benutzer eine Frage stellt. Schließlich müssen wir alle Textblöcke (ihre Metadaten) in einem speichern pickle Datei gespeichert, sodass sie später zum Abrufen problemlos erneut geladen werden kann.

def save_metadata(text_chunks, path="faiss_metadata.pkl"):
   """
   Saves the mapping of vector positions to textual content chunks.
   """
   with open(path, "wb") as f:
       pickle.dump(text_chunks, f)
   print(f"Saved textual content metadata to {path}")

# Schritt 4: Relevante Informationen abrufen

In diesem Schritt wird die Frage des Benutzers zunächst in eine numerische Kind umgewandelt, genau wie wir es zuvor mit allen Textblöcken gemacht haben. Der Laptop vergleicht dann die numerischen Werte der Blöcke mit dem Vektor der Frage, um die nächstliegenden zu finden. Dieser Vorgang wird aufgerufen Ähnlichkeitssuche.
Erstellen wir eine neue Datei mit dem Namen retrieve_faiss.py und führen Sie die Importe nach Bedarf durch:

import faiss
import pickle
import numpy as np
from sentence_transformers import SentenceTransformer

Erstellen Sie nun eine Funktion, um den zuvor gespeicherten FAISS-Index von der Festplatte zu laden, damit er durchsucht werden kann.

def load_faiss_index(index_path="faiss_index.index"):
    """
    Hundreds the saved FAISS index from disk.
    """
    print("Loading FAISS index.")
    return faiss.read_index(index_path)

Wir benötigen außerdem eine weitere Funktion, die die Metadaten lädt, die die zuvor gespeicherten Textblöcke enthalten.

def load_metadata(metadata_path="faiss_metadata.pkl"):
    """
    Hundreds textual content chunk metadata (the precise textual content items).
    """
    print("Loading textual content metadata.")
    with open(metadata_path, "rb") as f:
        return pickle.load(f)

Die ursprünglichen Textblöcke werden in einer Metadatendatei gespeichert (faiss_metadata.pkl) und werden verwendet, um FAISS-Ergebnisse wieder in lesbaren Textual content abzubilden. An dieser Stelle erstellen wir eine weitere Funktion, die die Anfrage eines Benutzers entgegennimmt, sie einbettet und die am besten passenden Blöcke aus dem FAISS-Index findet. Hier findet die semantische Suche statt.

def retrieve_similar_chunks(question, index, text_chunks, top_k=3):
    """
    Retrieves top_k most related chunks for a given question.
  
    Parameters:
        question (str): The consumer's enter query.
        index (faiss.Index): FAISS index object.
        text_chunks (listing): Authentic textual content chunks.
        top_k (int): Variety of prime outcomes to return.
  
    Returns:
        listing: High matching textual content chunks.
    """
  
    # Embed the question
    mannequin = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
    # Guarantee question vector is float32 as required by FAISS
    query_vector = mannequin.encode((question)).astype('float32')
  
    # Search FAISS for nearest vectors
    distances, indices = index.search(query_vector, top_k)
  
    print(f"Retrieved prime {top_k} comparable chunks.")
    return (text_chunks(i) for i in indices(0))

Dadurch erhalten Sie die drei wichtigsten Textabschnitte, die Sie als Kontext verwenden können.

# Schritt 5: Kombinieren des abgerufenen Kontexts

Sobald wir die relevantesten Blöcke haben, besteht der nächste Schritt darin, sie zu einem einzigen Kontextblock zusammenzufassen. Dieser Kontext wird dann an die Anfrage des Benutzers angehängt, bevor er an das LLM übergeben wird. Dieser Schritt stellt sicher, dass das Modell über alle notwendigen Informationen verfügt, um genaue und fundierte Antworten zu generieren. Sie können die Stücke wie folgt kombinieren:

context_chunks = retrieve_similar_chunks(question, index, text_chunks, top_k=3)
context = "nn".be a part of(context_chunks)

Dieser zusammengeführte Kontext wird später beim Erstellen der endgültigen Eingabeaufforderung für das LLM verwendet.

# Schritt 6: Verwenden eines großen Sprachmodells zum Generieren der Antwort

Jetzt kombinieren wir den abgerufenen Kontext mit der Benutzeranfrage und geben ihn an ein LLM weiter, um die endgültige Antwort zu generieren. Hier verwenden wir ein frei verfügbares Open-Supply-Modell von Hugging Face, Sie können jedoch jedes beliebige Modell verwenden, das Sie bevorzugen.

Erstellen wir eine neue Datei mit dem Namen generate_answer.py und fügen Sie die Importe hinzu:

from transformers import AutoTokenizer, AutoModelForCausalLM
import torch
from retrieve_faiss import load_faiss_index, load_metadata, retrieve_similar_chunks

Definieren Sie nun eine Funktion generate_answer() das den kompletten Prozess durchführt:

def generate_answer(question, top_k=3):
    """
    Retrieves related chunks and generates a last reply.
    """
    # Load FAISS index and metadata
    index = load_faiss_index()
    text_chunks = load_metadata()

    # Retrieve prime related chunks
    context_chunks = retrieve_similar_chunks(question, index, text_chunks, top_k=top_k)
    context = "nn".be a part of(context_chunks)

    # Load open-source LLM
    print("Loading LLM...")
    model_name = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
    # Load tokenizer and mannequin, utilizing a tool map for environment friendly loading
    tokenizer = AutoTokenizer.from_pretrained(model_name)
    mannequin = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.float16, device_map="auto")

    # Construct the immediate
    immediate = f"""
    Context:
    {context}
    Query:
    {question}
    Reply:
    """

    # Generate output
    inputs = tokenizer(immediate, return_tensors="pt").to(mannequin.system)
    # Use the right enter for mannequin era
    with torch.no_grad():
        outputs = mannequin.generate(**inputs, max_new_tokens=200, pad_token_id=tokenizer.eos_token_id)
    
    # Decode and clear up the reply, eradicating the unique immediate
    full_text = tokenizer.decode(outputs(0), skip_special_tokens=True)
    
    # Easy technique to take away the immediate half from the output
    reply = full_text.break up("Reply:")(1).strip() if "Reply:" in full_text else full_text.strip()
    
    print("nFinal Reply:")
    print(reply)

# Schritt 7: Ausführen der Full Retrieval-Augmented Technology Pipeline

Dieser letzte Schritt bringt alles zusammen. Wir erstellen eine principal.py Datei, die den gesamten Arbeitsablauf vom Laden der Daten bis zur Generierung der endgültigen Antwort automatisiert.

# Information preparation
from prepare_data import prepare_docs
from split_text import split_docs

# Embedding and storage
from create_embeddings import get_embeddings
from store_faiss import build_faiss_index, save_metadata

# Retrieval and reply era
from generate_answer import generate_answer

Definieren Sie nun die Hauptfunktion:

def run_pipeline():
    """
    Runs the total end-to-end RAG workflow.
    """
    print("nLoad and Clear Information:")
    paperwork = prepare_docs("information/")
    print(f"Loaded {len(paperwork)} clear paperwork.n")

    print("Cut up Textual content into Chunks:")
    # paperwork is an inventory of strings, however split_docs expects an inventory of paperwork
    # For this easy instance the place paperwork are small, we move them as strings
    chunks_as_text = split_docs(paperwork, chunk_size=500, chunk_overlap=100)
    # On this case, chunks_as_text is an inventory of LangChain Doc objects

    # Extract textual content content material from LangChain Doc objects
    texts = (c.page_content for c in chunks_as_text)
    print(f"Created {len(texts)} textual content chunks.n")

    print("Generate Embeddings:")
    embeddings = get_embeddings(texts)
  
    print("Retailer Embeddings in FAISS:")
    index = build_faiss_index(embeddings)
    save_metadata(texts)
    print("Saved embeddings and metadata efficiently.n")

    print("Retrieve & Generate Reply:")
    question = "Does unsupervised ML cowl regression duties?"
    generate_answer(question)

Führen Sie abschließend die Pipeline aus:

if __name__ == "__main__":
    run_pipeline()

Ausgabe:

Screenshot der AusgabeScreenshot der Ausgabe
Screenshot der Ausgabe | Bild vom Autor

# Zusammenfassung

RAG schließt die Lücke zwischen dem, was ein LLM „bereits weiß“ und den sich ständig ändernden Informationen in der Welt. Ich habe eine sehr einfache Pipeline implementiert, damit Sie verstehen, wie RAG funktioniert. Auf Unternehmensebene kommen viele fortgeschrittene Konzepte wie das Hinzufügen von Leitplanken, Hybridsuche, Streaming und Kontextoptimierungstechniken zum Einsatz. Wenn Sie daran interessiert sind, fortgeschrittenere Konzepte zu erkunden, finden Sie hier einige meiner persönlichen Favoriten:

Kanwal Mehreen ist ein Ingenieur für maschinelles Lernen und ein technischer Redakteur mit einer großen Leidenschaft für Datenwissenschaft und die Schnittstelle zwischen KI und Medizin. Sie ist Mitautorin des E-Books „Maximizing Productiveness with ChatGPT“. Als Google Technology Scholar 2022 für APAC setzt sie sich für Vielfalt und akademische Exzellenz ein. Sie ist außerdem als Teradata Variety in Tech Scholar, Mitacs Globalink Analysis Scholar und Harvard WeCode Scholar anerkannt. Kanwal ist ein leidenschaftlicher Verfechter von Veränderungen und hat FEMCodes gegründet, um Frauen in MINT-Bereichen zu stärken.

Von admin

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert