Uma Das Melhores Manipulas De Ganhar 60 90k Por Ano Em Casa Uma Das Melhores Manipulas De Ganhar 60 90k Por Ano Em Casa

Prompt Engineering ist tot: DSPy ist das neue Paradigma für Prompting

Ich erinnere mich noch sehr gut, dass bis vor einigen Monaten Prompt Engineering der letzte Schrei war. Der gesamte Arbeitsmarkt war mit der Rolle von Prompt Engineers gefüllt, aber das ist jetzt nicht mehr der Fall. Prompt Engineering war keine Kunst oder Wissenschaft, es war nur ein cleverer Hans-Phänomen, bei dem Menschen den notwendigen Kontext für das System bereitstellten, um eine bessere Antwort zu geben. Die Leute schrieben sogar Bücher/Blogs wie die Top 50 Prompts, um das Beste aus GPT herauszuholen, und so weiter und so fort. Aber groß angelegte Experimente haben gezeigt, dass es keine einzige Aufforderung oder Strategie gibt, die für alle Arten von Problemen funktioniert, einige Aufforderungen scheinen zwar in der Isolation besser zu sein, sind aber ein Volltreffer und ein Fehlschlag, wenn sie umfassend analysiert werden. Heute werden wir über DSPY: COMPILING DECLARATIVE LANGUAGE MODEL CALLS INTO SELF-IMPROVING PIPELINES sprechen, einen von Stanford entwickelten Rahmen für Selbstverbesserungs-Pipelines, bei dem LLMs als Modul behandelt werden, das von einem Compiler optimiert wird, ähnlich wie die Abstraktionen, die in PyTorch zu finden sind.

Einführung

Wie ich oben erwähnt habe, ist das Internet voll von Werbebüchern und -blogs. Und die meisten von ihnen verkaufen dir einen Haufen Mist. Jetzt, wie ich sagte, einige von ihnen mögen tatsächlich funktionieren, aber das ist keine sehr gute Art, unsere Apps aufzubauen. Nicht zu wissen, wann etwas nicht funktioniert, ist wichtig, wir müssen einen sicheren Hypothesenraum definieren, in dem das System funktioniert und nicht funktioniert.

Auto Draft

Die ersten Suchergebnisse auf Google für Bücher über Prompt Engineering. Es gibt tatsächlich Aufforderungen in diesen Büchern geschrieben, nicht die Verwendung von Techniken wie CoT oder ReAct

Es gibt sogar Paper, in denen gezeigt wurde, dass die Leistung von LLMs mit bestimmten emotionalen Aufforderungen zunimmt. Für mich habe ich immer noch meine Bedenken hinsichtlich der Authentizität eines solchen Papiers. Wie lange hält es an? Gilt es für jedes Thema? Gibt es Themen, bei denen diese Art von emotionaler Aufforderung zu schlechteren Ergebnissen führen könnte? Es gibt so viele Paper wie dieses, die unbeabsichtigt halbfertige Forschung verbreiten. Ein weiteres Paper wie dieses war die Embers of Autoregression, bei dem viele Dinge später widerlegt wurden.

Auto Draft

https://arxiv.org/pdf/2307.11760

Aber die größere Frage ist, was für eine wissenschaftliche/systematische Methode es ist, bei der ich einem System sagen muss, dass „Ich könnte gefeuert werden, wenn du mir nicht sofort die Antwort gibst oder meine Oma ist krank, und so weiter und so fort“. Das sind einfach Leute, die versuchen, sich in das Verhalten von LLMs zu hacken.

Das Problem mit der Aufforderung verstehen

Beispielsweise, wenn ich sage „Füge 5-Shot CoT mit RAG hinzu, mit harten negativen Beispielen“, ist es zwar konzeptionell klar, aber in der Praxis sehr schwer umzusetzen. LLMs sind sehr empfindlich gegenüber Aufforderungen, so dass das Hinzufügen dieser Art von Struktur in einer Aufforderung nicht funktioniert die meiste Zeit. Das Verhalten von LLMs ist sehr empfindlich gegenüber der Art und Weise, wie eine Aufforderung geschrieben wird, was es schwierig macht, sie zu steuern.

Daher, wenn wir eine Pipeline aufbauen, geht es nicht nur darum, ein LLM davon zu überzeugen, eine Ausgabe in einer bestimmten Weise zu geben, sondern die Ausgabe sollte so eingeschränkt sein, dass sie als Eingabe für andere Module in der größeren Pipeline funktionieren kann.

Um dieses Problem zu lösen, gibt es bereits viel Forschung, aber sie sind in vielerlei Hinsicht begrenzt. Die meisten von ihnen basieren auf String-Vorlagen, die zerbrechlich und nicht skalierbar sind. Das Sprachmodell ändert sich im Laufe der Zeit und die Aufforderung bricht. Wenn wir unser Modul in eine andere Pipeline einfügen wollen, funktioniert es nicht. Wir wollen, dass es mit neuen Tools, einer neuen Datenbank oder einem Abrufsystem interagiert, es funktioniert nicht.

Genau dieses Problem versucht DSPy zu lösen, indem LLM als Modul behandelt wird, dessen Verhalten automatisch angepasst wird, basierend auf der Art und Weise, wie es mit anderen Komponenten in der Pipeline interagiert.

DSPy Paradigma: Lass uns programmieren – nicht prompten – LMs

Das Ziel von DSPy ist es, den Fokus von der Feinabstimmung der LLMs auf ein gutes übergeordnetes Systemdesign zu verlagern.

Aber wie macht man das?

Um darüber auf einer mentalen Ebene nachzudenken, können wir die LLMs als Geräte betrachten: die Anweisungen ausführen und über eine Abstraktion operieren, die DNN ähnelt.

Beispielsweise definieren wir eine Schicht der Konvolution in PyTorch und sie kann auf einer Reihe von Eingaben betrieben werden, die von anderen Schichten kommen. Konzeptionell können wir diese Schichten stapeln und eine gewünschte Abstraktionsebene unserer ursprünglichen Eingaben erreichen, wir müssen keine CUDA-Kerne und viele andere Anweisungen definieren. Alles das ist bereits in der Definition der Konvolutionsschicht abstrahiert. Das wollen wir mit LLMs machen, bei denen LLMs abstrahierte Module sind, die in verschiedenen Kombinationen gestapelt werden, um ein bestimmtes Verhalten zu erreichen, sei es CoT, ReAct oder etwas anderes.

Um das gewünschte Verhalten zu erreichen, müssen wir einige Dinge ändern:

Auto Draft

NLP-Signatur

Dies sind einfach die Deklarationen des Verhaltens, das wir von unseren LLMs wollen. Dies definiert nur, was erreicht werden soll, nicht die Spezifikationen, wie es erreicht werden soll. Eine Spezifikation, die DSPy sagt, was eine Transformation tut, anstatt wie man das LLM auffordert, es zu tun.

None

Beispiel für Signaturen

  • Die Signatur behandelt das strukturierte Formatieren und Parsen der Logik.
  • Signaturen können in selbstverbessernde und pipeline-adaptive Aufforderungen oder Feinabstimmungen kompiliert werden.

DSPY leitet die Rolle der Felder ab, indem es:

  • Ihre Namen verwendet, z.B. verwendet DSPy in-context learning, um Fragen anders zu interpretieren als Antworten.
  • Ihre Spuren (Eingabe/Ausgabe-Beispiele) verwendet

Hinweis: All dies wird nicht hart codiert, sondern das System ermittelt es während der Kompilierung

Module

Hier verwenden wir die Signaturen, um unsere Module aufzubauen, z.B. wenn wir ein CoT-Modul aufbauen wollen, verwenden wir diese Signaturen, um es aufzubauen. Dies erzeugt automatisch hochwertige Aufforderungen, um das Verhalten bestimmter Aufforderungstechniken zu erreichen.

Eine technischere Definition: Ein Modul ist eine parameterisierte Schicht, die eine Signatur durch Abstraktion einer Aufforderungstechnik ausdrückt.

None

Arten von Modulen

Nachdem es deklariert wurde, verhält sich ein Modul wie eine aufrufbare Funktion.

Parameter: Um eine bestimmte Signatur auszudrücken, muss jeder LLM-Aufruf spezifizieren:

  • Das spezifische LLM zum Aufrufen
  • Die Aufforderungsanweisungen
  • Das String-Präfix für jedes Signaturfeld
  • Die Demonstrationen, die als Few-Shot-Aufforderungen und/oder als Feinabstimmungsdaten verwendet werden

Optimierer

Um dieses System zum Laufen zu bringen, nimmt der Optimierer die gesamte Pipeline und optimiert sie anhand einer bestimmten Metrik und erzeugt im Prozess automatisch die besten Aufforderungen und aktualisiert sogar die Gewichte des Sprachmodells.

Die Idee auf hoher Ebene ist, dass wir einen Optimierer verwenden werden, um unseren Code zu kompilieren, der Sprachmodellaufrufe enthält, so dass jeder Modul in unserer Pipeline in eine Aufforderung kompiliert wird, die automatisch für uns generiert wird, oder in ein neues Satz von Gewichten für unser Sprachmodell, das an die Aufgabe angepasst ist, die wir versuchen zu lösen.

Praktisches Beispiel

Eine einzelne Suchanfrage ist oft nicht ausreichend für komplexe QA-Aufgaben. Beispielsweise enthält ein Beispiel innerhalb von HotPotQA eine Frage über die Geburtsstadt des Autors von „Right Back At It Again“. Eine Suchanfrage identifiziert den Autor oft korrekt als „Jeremy McKinnon“, aber fehlt die Fähigkeit, die beabsichtigte Antwort zu komponieren, um herauszufinden, wann er geboren wurde.

Der Standardansatz für diese Herausforderung in der Literatur zur retrieval-gestützten NLP besteht darin, Multi-Hop-Suchsysteme aufzubauen, wie GoldEn (Qi et al., 2019) und Baleen (Khattab et al., 2021). Diese Systeme lesen die abgerufenen Ergebnisse und generieren dann zusätzliche Abfragen, um zusätzliche Informationen zu sammeln, wenn dies erforderlich ist, bevor sie zu einer endgültigen Antwort gelangen. Mit DSPy können wir solche Systeme in wenigen Zeilen Code simulieren.

Derzeit müssen wir sehr komplexe Aufforderungen schreiben und sie auf eine sehr unordentliche Weise strukturieren. Aber der schlechte Teil ist, dass sobald ich die Art der Fragen ändere, ich möglicherweise das gesamte Systemdesign ändern muss, aber nicht mit DSPy.

Konfiguration des Sprachmodells und des Abrufmodells

import dspy
turbo = dspy.OpenAI(model='gpt-3.5-turbo')
colbertv2_wiki17_abstracts = dspy.ColBERTv2(url='http://20.102.90.50:2017/wiki17_abstracts')
dspy.settings.configure(lm=turbo, rm=colbertv2_wiki17_abstracts)

Wenn Sie mehr über Abrufmodelle erfahren möchten:

Laden des Datasets

Wir verwenden das erwähnte HotPotQA-Dataset, eine Sammlung von komplexen Frage-Antwort-Paaren, die normalerweise in einem Multi-Hop-Verfahren beantwortet werden. Wir können dieses Dataset, das von DSPy bereitgestellt wird, über die HotPotQA-Klasse laden:

from dspy.datasets import HotPotQA
# Lade das Dataset.
dataset = HotPotQA(train_seed=1, train_size=20, eval_seed=2023, dev_size=50, test_size=0)
# Sag DSPy, dass das 'question'-Feld die Eingabe ist. Alle anderen Felder sind Labels und/oder Metadaten.
trainset = [x.with_inputs('question') for x in dataset.train]
devset = [x.with_inputs('question') for x in dataset.dev]
len(trainset), len(devset)
#Output
(20, 50)

Erstellen der Signatur

Nun, da wir die Daten geladen haben, lassen Sie uns mit dem Definieren der Signaturen für die Unteraufgaben unserer Baleen-Pipeline beginnen.

Wir beginnen damit, die GenerateAnswer-Signatur zu erstellen, die context und question als Eingabe nimmt und answer als Ausgabe gibt.

class GenerateAnswer(dspy.Signature):
    """Beantworte Fragen mit kurzen Fakten-Antworten.""""
    context = dspy.InputField(desc="kann relevante Fakten enthalten")
    question = dspy.InputField()
    answer = dspy.OutputField(desc="oft zwischen 1 und 5 Wörtern")
class GenerateSearchQuery(dspy.Signature):
    """Schreibe eine einfache Suchanfrage, die bei der Beantwortung einer komplexen Frage hilft.""""
    context = dspy.InputField(desc="kann relevante Fakten enthalten")
    question = dspy.InputField()
    query = dspy.OutputField()

Erstellen der Pipeline

Lassen Sie uns also die eigentliche Programm SimplifiedBaleen definieren. Es gibt viele mögliche Möglichkeiten, dies zu tun, aber wir halten uns in dieser Version an die wesentlichen Elemente.

from dsp.utils import deduplicate
class SimplifiedBaleen(dspy.Module):
    def __init__(self, passages_per_hop=3, max_hops=2):
        super().__init__()
        self.generate_query = [dspy.ChainOfThought(GenerateSearchQuery) for _ in range(max_hops)]
        self.retrieve = dspy.Retrieve(k=passages_per_hop)
        self.generate_answer = dspy.ChainOfThought(GenerateAnswer)
        self.max_hops = max_hops
    def forward(self, question):
        context = []
        for hop in range(self.max_hops):
            query = self.generate_query[hop](context=context, question=question).query
            passages = self.retrieve(query).passages
            context = deduplicate(context + passages)
        pred = self.generate_answer(context=context, question=question)
        return dspy.Prediction(context=context, answer=pred.answer)

Wie man sehen kann, definiert die __init__-Methode einige wichtige Submodule:

  • generate_query: Für jeden Hop haben wir einen dspy.ChainOfThought-Vorhersager mit der GenerateSearchQuery-Signatur.
  • retrieve: Dieses Modul führt die Suche mit den generierten Abfragen über unseren definierten ColBERT RM-Suchindex durch, indem es das dspy.Retrieve-Modul verwendet.
  • generate_answer: Dieses dspy.Predict-Modul wird mit der GenerateAnswer-Signatur verwendet, um die endgültige Antwort zu produzieren.

Die forward-Methode verwendet diese Submodule in einer einfachen Steuerungslogik.

  1. Zunächst führen wir eine Schleife bis zu self.max_hops mal aus.
  2. In jeder Iteration generieren wir eine Suchanfrage mit dem Vorhersager an self.generate_query[hop].
  3. Wir rufen die Top-k-Passagen mit dieser Abfrage ab.
  4. Wir fügen die (deduplizierten) Passagen zu unserem context-Akkumulator hinzu.
  5. Nach der Schleife verwenden wir self.generate_answer, um eine Antwort zu produzieren.
  6. Wir geben eine Vorhersage mit dem abgerufenen context und der vorhergesagten answer zurück.

Ausführen der Pipeline

Lassen Sie uns dieses Programm in seinem unkompilierten (Null-Shot) Setting ausführen.

Dies bedeutet nicht unbedingt, dass die Leistung schlecht sein wird, sondern vielmehr, dass wir durch die Zuverlässigkeit des zugrunde liegenden LM eingeschränkt sind, um unsere Subaufgaben aus minimalen Anweisungen zu verstehen. Oftmals ist dies völlig in Ordnung, wenn man die leistungsstärksten/teuersten Modelle (z.B. GPT-4) für die einfachsten und gängigsten Aufgaben (z.B. das Beantworten einfacher Fragen zu beliebten Entitäten) verwendet.

# Stelle eine beliebige Frage an dieses einfache RAG-Programm.
meine_frage = "Wie viele Stockwerke hat das Schloss, das David Gregory geerbt hat?"
# Hole die Vorhersage ab. Dies enthält `pred.context` und `pred.answer`.
uncompiled_baleen = SimplifiedBaleen()  # unkompiliertes (d.h. Null-Shot) Programm
pred = uncompiled_baleen(meine_frage)
# Gib den Kontext und die Antwort aus.
print(f"Frage: {meine_frage}")
print(f"Vorhergesagte Antwort: {pred.answer}")
print(f"Abgerufene Kontexte (gekürzt): {[c[:200] + '...' for c in pred.context]}")
#Output
Frage: Wie viele Stockwerke hat das Schloss, das David Gregory geerbt hat?
Vorhergesagte Antwort: fünf
Abgerufene Kontexte (gekürzt): ['David Gregory (Arzt) | David Gregory (20. Dezember 1625 – 1720) war ein schottischer Arzt und Erfinder. Sein Nachname wird manchmal als Gregorie geschrieben, die ursprüngliche schottische Schreibweise. Er erbte Kinn...', 'The Boleyn Inheritance | The Boleyn Inheritance ist ein Roman der britischen Autorin Philippa Gregory, der 2006 zum ersten Mal veröffentlicht wurde. Es ist eine direkte Fortsetzung ihres früheren Romans "The Other Boleyn Girl," a...', 'Gregory of Gaeta | Gregory war von 963 bis zu seinem Tod Herzog von Gaeta. Er war der zweite Sohn von Docibilis II von Gaeta und seiner Frau Orania. Er folgte seinem Bruder John II, der nur Töchter hinterließ...', 'Kinnairdy Castle | Kinnairdy Castle ist ein Tower House mit fünf Stockwerken und einem Dachboden, zwei Meilen südlich von Aberchirder, Aberdeenshire, Schottland. Der alternative Name ist Old Kinnairdy....', 'Kinnaird Head | Kinnaird Head (Schottisch-Gälisch: "An Ceann Àrd" , "hoher Kopfland") ist ein Vorgebirge, das in die Nordsee ragt, innerhalb der Stadt Fraserburgh, Aberdeenshire an der Ostküste von Schottla...', 'Kinnaird Castle, Brechin | Kinnaird Castle ist eine Burg aus dem 15. Jahrhundert in Angus, Schottland. Die Burg ist seit über 600 Jahren die Heimat der Familie Carnegie, dem Earl of Southesk....']

Optimierung der Pipeline

Ein Null-Shot-Ansatz versagt jedoch schnell bei spezialisierteren Aufgaben, neuen Domänen/Einstellungen und effizienteren (oder offeneren) Modellen.

Um dies zu beheben, bietet DSPy die Kompilierung an. Lassen Sie uns unser Multi-Hop (SimplifiedBaleen)-Programm kompilieren.

Lassen Sie uns zunächst unsere Validierungslogik für die Kompilierung definieren:

  • Die vorhergesagte Antwort stimmt mit der Gold-Antwort überein.
  • Der abgerufene Kontext enthält die Gold-Antwort.
  • Keine der generierten Abfragen ist zu ausführlich (d.h. keine überschreitet 100 Zeichen in der Länge).
  • Keine der generierten Abfragen wird grob wiederholt (d.h. keine ist innerhalb von 0,8 oder höher F1-Score von früheren Abfragen).
def validate_context_and_answer_and_hops(example, pred, trace=None):
    if not dspy.evaluate.answer_exact_match(example, pred): return False
    if not dspy.evaluate.answer_passage_match(example, pred): return False
    hops = [example.question] + [outputs.query for *_, outputs in trace if 'query' in outputs]
    if max([len(h) for h in hops]) > 100: return False
    if any(dspy.evaluate.answer_exact_match_str(hops[idx], hops[:idx], frac=0.8) for idx in range(2, len(hops))): return False
    return True

Wir werden einen der grundlegendsten Teleprompter in DSPy verwenden, nämlich BootstrapFewShot, um die Vorhersager in der Pipeline mit Few-Shot-Beispielen zu optimieren.

from dspy.teleprompt import BootstrapFewShot
teleprompter = BootstrapFewShot(metric=validate_context_and_answer_and_hops)
compiled_baleen = teleprompter.compile(SimplifiedBaleen(), teacher=SimplifiedBaleen(passages_per_hop=2), trainset=trainset)

Bewertung der Pipeline

Lassen Sie uns nun unsere Bewertungsfunktion definieren und die Leistung der unkompilierten und kompilierten Baleen-Pipelines vergleichen. Obwohl dieses Devset nicht als zuverlässige Benchmark dient, ist es für dieses Tutorial anschaulich.

from dspy.evaluate.evaluate import Evaluate
# Definiere eine Metrik, um zu überprüfen, ob wir die richtigen Dokumente abgerufen haben
def gold_passages_retrieved(example, pred, trace=None):
    gold_titles = set(map(dspy.evaluate.normalize_text, example["gold_titles"]))
    found_titles = set(
        map(dspy.evaluate.normalize_text, [c.split(" | ")[0] for c in pred.context])
    )
    return gold_titles.issubset(found_titles)
# Richte die `evaluate_on_hotpotqa`-Funktion ein. Wir werden diese Funktion mehrmals unten verwenden.
evaluate_on_hotpotqa = Evaluate(devset=devset, num_threads=1, display_progress=True, display_table=5)
uncompiled_baleen_retrieval_score = evaluate_on_hotpotqa(uncompiled_baleen, metric=gold_passages_retrieved, display=False)
compiled_baleen_retrieval_score = evaluate_on_hotpotqa(compiled_baleen, metric=gold_passages_retrieved)
print(f"## Retrieval-Score für unkompilierte Baleen: {uncompiled_baleen_retrieval_score}")
print(f"## Retrieval-Score für kompilierte Baleen: {compiled_baleen_retrieval_score}

Fazit

Auto Draft

Diese Ergebnisse zeigen, dass die Kombination eines Multi-Hop-Settings in DSPy sogar das menschliche Feedback übertreffen kann. Sie haben sogar gezeigt, dass ein viel kleineres Modell, wie T5, mit einem DSPy-Setting mit GPT verglichen wurde. DSPy ist eines der coolsten Dinge, auf die ich gestoßen bin, nachdem lang chain veröffentlicht wurde, es verspricht, ein viel besseres und systematisch entworfenes System zu schaffen, anstatt wilde Stücke in eine große LLM-Pipeline zu werfen.

 

Schreibe einen Kommentar

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