Prompt engineering

Optymalizuj LLM z DSPy: Przewodnik krok po kroku, aby zbudować, zoptymalizować i ocenić systemy AI

mm
Dodaj Unite.AI do preferowanych źródeł w Google
DSPy is a framework for algorithmically optimizing LM prompts and weights

Ponieważ możliwości dużych modeli językowych (LLM) ciągle się rozwijają, tworzenie solidnych systemów AI, które wykorzystują ich potencjał, stało się coraz bardziej złożone. Konwencjonalne podejścia często obejmują skomplikowane techniki promptowania, generowanie danych do dokształcania oraz ręczne sterowanie, aby zapewnić przestrzeganie domenowych ograniczeń. Jednak ten proces może być uciążliwy, podatny na błędy i silnie zależny od interwencji człowieka.

Wkraczają DSPy, rewolucyjna platforma zaprojektowana do usprawnienia tworzenia systemów AI opartych na LLM. DSPy wprowadza systematyczne podejście do optymalizacji promptów LM i wag, umożliwiając deweloperom tworzenie zaawansowanych aplikacji z minimalnym wysiłkiem ręcznym.

W tym kompleksowym przewodniku, będziemy eksplorować podstawowe zasady DSPy, jego modułową architekturę oraz gamę potężnych funkcji, które oferuje. Będziemy również zagłębiać się w praktyczne przykłady, demonstrując, jak DSPy może przekształcić sposób, w jaki tworzysz systemy AI z LLM.

Czym jest DSPy, i dlaczego go potrzebujesz?

DSPy jest platformą, która oddziela przepływ Twojego programu (moduły) od parametrów (prompty LM i wagi) każdego kroku. To oddzielenie pozwala na systematyczną optymalizację prompty LM i wag, umożliwiając Ci tworzenie złożonych systemów AI z większą niezawodnością, przewidywalnością i przestrzeganiem domenowych ograniczeń.

Tradycyjnie, tworzenie systemów AI z LLM wymagało uciążliwego procesu rozkładania problemu na kroki, tworzenia skomplikowanych prompty dla każdego kroku, generowania syntetycznych przykładów do dokształcania oraz ręcznego sterowania, aby LLM przestrzegały określonych ograniczeń. To podejście nie tylko było czasochłonne, ale także podatne na błędy, ponieważ nawet niewielkie zmiany w potoku, LM lub danych mogły wymagać obszernego przeprojektowania prompty i kroków dokształcania.

DSPy rozwiązuje te wyzwania, wprowadzając nowy paradygmat: optymalizatory. Te algorytmy napędzane przez LM mogą dostosowywać prompty i wagi Twoich wywołań LM, biorąc pod uwagę metrykę, którą chcesz maksymalizować. Automatyzując proces optymalizacji, DSPy upoważnia deweloperów do tworzenia solidnych systemów AI z minimalnym wysiłkiem ręcznym, zwiększając niezawodność i przewidywalność wyjść LM.

Modułowa architektura DSPy

W sercu DSPy leży modułowa architektura, która ułatwia kompozycję złożonych systemów AI. Platforma dostarcza zestaw wbudowanych modułów, które abstrahują różne techniki promptowania, takie jak dspy.ChainOfThought i dspy.ReAct. Te moduły mogą być łączone i komponowane w większe programy, pozwalając deweloperom tworzyć skomplikowane potoki dostosowane do ich konkretnych wymagań.

Każdy moduł zawiera parametry do nauki, w tym instrukcje, przykłady few-shot i wagi LM. Gdy moduł jest wywołany, optymalizatory DSPy mogą dostosowywać te parametry, aby maksymalizować pożądaną metrykę, zapewniając, że wyjścia LM przestrzegają określonych ograniczeń i wymagań.

Optymalizacja z DSPy

DSPy wprowadza gamę potężnych optymalizatorów zaprojektowanych do poprawy wydajności i niezawodności Twoich systemów AI. Te optymalizatory wykorzystują algorytmy napędzane przez LM do dostosowywania prompty i wag Twoich wywołań LM, maksymalizując określoną metrykę i przestrzegając domenowych ograniczeń.

Niektóre z kluczowych optymalizatorów dostępnych w DSPy to:

  1. BootstrapFewShot: Ten optymalizator rozszerza sygnaturę, automatycznie generując i włączając zoptymalizowane przykłady wewnątrz promptu wysłanego do modelu, implementując few-shot learning.
  2. BootstrapFewShotWithRandomSearch: Stosuje BootstrapFewShot kilka razy z losową wyszukiwarką nad wygenerowanymi demonstracjami, wybierając najlepszy program podczas optymalizacji.
  3. MIPRO: Generuje instrukcje i przykłady few-shot w każdym kroku, z generacją instrukcji, która jest świadoma danych i demonstracji. Wykorzystuje optymalizację bayesowską do efektywnego wyszukiwania przestrzeni generowania instrukcji i demonstracji w Twoich modułach.
  4. BootstrapFinetune: Destyluje program DSPy oparty na prompcie do aktualizacji wag dla mniejszych LM, pozwalając Ci dokształcać podstawowe LLM(y) w celu zwiększenia wydajności.

Wykorzystując te optymalizatory, deweloperzy mogą systematycznie optymalizować swoje systemy AI, zapewniając wysokiej jakości wyjścia i przestrzeganie domenowych ograniczeń i wymagań.

Początek z DSPy

Aby zilustrować potęgę DSPy, przejdźmy przez praktyczny przykład budowy systemu generacji z wykorzystaniem odzyskiwania (RAG) do odpowiedzi na pytania.

Krok 1: Konfigurowanie modelu językowego i modelu odzyskiwania

Pierwszym krokiem jest konfigurowanie modelu językowego (LM) i modelu odzyskiwania (RM) wewnątrz DSPy.

Aby zainstalować DSPy, uruchom:


pip install dspy-ai

DSPy obsługuje wiele interfejsów API LM i RM, a także lokalne hostowanie modeli, co ułatwia integrację preferowanych modeli.


import dspy

<p># Konfigurowanie LM i RM
turbo = dspy.OpenAI(model='gpt-3.5-turbo')
colbertv2_wiki17_abstracts = dspy.ColBERTv2(url='http://20.102.90.50:2017/wiki17_abstracts')</p>

<p>dspy.settings.configure(lm=turbo, rm=colbertv2_wiki17_abstracts)</p>

Krok 2: Ładowanie zbioru danych

Następnie załadujmy zbiór danych HotPotQA, który zawiera kolekcję złożonych par pytań i odpowiedzi, zwykle odpowiedzianych w sposób wielokrokowy.


from dspy.datasets import HotPotQA

<p># Ładowanie zbioru danych
dataset = HotPotQA(train_seed=1, train_size=20, eval_seed=2023, dev_size=50, test_size=0)</p>

<p># Określenie pola 'question' jako wejście
trainset = [x.with_inputs('question') for x in dataset.train]
devset = [x.with_inputs('question') for x in dataset.dev]</p>

Krok 3: Tworzenie sygnatur

DSPy wykorzystuje sygnatury do definiowania zachowania modułów. W tym przykładzie zdefiniujemy sygnaturę dla zadania generowania odpowiedzi, określając pola wejściowe (kontekst i pytanie) oraz pole wyjściowe (odpowiedź).


<p>class GenerateAnswer(dspy.Signature):
"""Odpowiadaj na pytania z krótkimi odpowiedziami faktoidowymi."""

context = dspy.InputField(desc="może zawierać istotne fakty")
question = dspy.InputField()
answer = dspy.OutputField(desc="zwykle między 1 a 5 słowami")</p>

Krok 4: Tworzenie potoku

Zbudujemy nasz potok RAG jako moduł DSPy, który składa się z metody inicjalizacji (__init__) do deklarowania podmodułów (dspy.Retrieve i dspy.ChainOfThought) oraz metody forward do opisu przepływu sterowania odpowiedzią na pytanie przy użyciu tych modułów.


<p>class RAG(dspy.Module):
def __init__(self, num_passages=3):
super().__init__()

self.retrieve = dspy.Retrieve(k=num_passages)
self.generate_answer = dspy.ChainOfThought(GenerateAnswer)

def forward(self, question):
context = self.retrieve(question).passages
prediction = self.generate_answer(context=context, question=question)
return dspy.Prediction(context=context, answer=prediction.answer)</p>

Krok 5: Optymalizacja potoku

Z potokiem zdefiniowanym, możemy teraz zoptymalizować go przy użyciu optymalizatorów DSPy. W tym przykładzie wykorzystamy optymalizator BootstrapFewShot, który generuje i wybiera skuteczne prompty dla naszych modułów na podstawie zbioru treningowego i metryki walidacji.


<p>from dspy.teleprompt import BootstrapFewShot</p>

<p># Metryka walidacji
def validate_context_and_answer(example, pred, trace=None):
answer_EM = dspy.evaluate.answer_exact_match(example, pred)
answer_PM = dspy.evaluate.answer_passage_match(example, pred)
return answer_EM and answer_PM</p>

<p># Konfigurowanie optymalizatora
teleprompter = BootstrapFewShot(metric=validate_context_and_answer)</p>

<p># Kompilacja programu
compiled_rag = teleprompter.compile(RAG(), trainset=trainset)</p>

Krok 6: Ocena potoku

Po skompilowaniu programu, istotne jest ocenienie jego wydajności na zbiorze deweloperskim, aby upewnić się, że spełnia wymagane dokładność i niezawodność.


from dspy.evaluate import Evaluate

<p># Konfigurowanie ewaluatora
evaluate = Evaluate(devset=devset, metric=validate_context_and_answer, num_threads=4, display_progress=True, display_table=0)</p>

<p># Ocena skompilowanego programu RAG
evaluation_result = evaluate(compiled_rag)</p>

<p>print(f"Ewaluacja wyniku: {evaluation_result}")</p>

Krok 7: Inspekcja historii modelu

Aby uzyskać głębsze zrozumienie interakcji modelu, można przejrzeć najnowsze generacje, inspekcjonując historię modelu.


<p># Inspekcja historii modelu
turbo.inspect_history(n=1)</p>

Krok 8: Dokonywanie predykcji

Z potokiem zoptymalizowanym i ocenionym, możesz teraz go wykorzystać do dokonywania predykcji na nowych pytaniach.


<p># Przykładowe pytanie
question = "Którą nagrodę otrzymała pierwsza książka Gary'ego Zukava?"</p>

<p># Dokonywanie predykcji przy użyciu skompilowanego programu RAG
prediction = compiled_rag(question)</p>

<p>print(f"Pytanie: {question}")
print(f"Odpowiedź: {prediction.answer}")
print(f"Kontekst odzyskany: {prediction.context}")</p>

Minimalny przykład działania z DSPy

Teraz, przejdźmy przez kolejny minimalny przykład działania, wykorzystując zbiór danych GSM8K i model OpenAI GPT-3.5-turbo, aby symulować zadania promptowania wewnątrz DSPy.

Ustawienia

Upewnij się, że Twoje środowisko jest właściwie skonfigurowane:


<p>import dspy
from dspy.datasets.gsm8k import GSM8K, gsm8k_metric</p>

<p># Konfigurowanie LM
turbo = dspy.OpenAI(model='gpt-3.5-turbo-instruct', max_tokens=250)
dspy.settings.configure(lm=turbo)</p>

<p># Ładowanie pytań matematycznych z zestawu danych GSM8K
gsm8k = GSM8K()
gsm8k_trainset, gsm8k_devset = gsm8k.train[:10], gsm8k.dev[:10]</p>

print(gsm8k_trainset)

Zbiór danych gsm8k_trainset i gsm8k_devset zawiera listę przykładów z każdym przykładem mającym pole pytania i odpowiedzi.

Definiowanie modułu

Następnie, zdefiniuj niestandardowy program, wykorzystując moduł ChainOfThought do rozumnego rozumowania:


<p>class CoT(dspy.Module):
def __init__(self):
super().__init__()
self.prog = dspy.ChainOfThought("question -&gt; answer")

def forward(self, question):
return self.prog(question=question)</p>

Kompilacja i ocena modelu

Teraz, skompiluj go z optymalizatorem BootstrapFewShot:


<p>from dspy.teleprompt import BootstrapFewShot</p>

<p># Konfigurowanie optymalizatora
config = dict(max_bootstrapped_demos=4, max_labeled_demos=4)</p>

<p># Optymalizacja z użyciem metryki gsm8k_metric
teleprompter = BootstrapFewShot(metric=gsm8k_metric, **config)
optimized_cot = teleprompter.compile(CoT(), trainset=gsm8k_trainset)</p>

<p># Konfigurowanie ewaluatora
from dspy.evaluate import Evaluate</p>

<p>evaluate = Evaluate(devset=gsm8k_devset, metric=gsm8k_metric, num_threads=4, display_progress=True, display_table=0)
evaluate(optimized_cot)</p>

<p># Inspekcja historii modelu
turbo.inspect_history(n=1)</p>

Ten przykład demonstruje, jak skonfigurować środowisko, zdefiniować niestandardowy moduł, skompilować model i rygorystycznie ocenić jego wydajność, wykorzystując dostarczony zbiór danych i konfigurację optymalizatora.

Zarządzanie danymi w DSPy

DSPy działa z zestawami treningowymi, deweloperskimi i testowymi. Dla każdego przykładu w Twoich danych, zwykle masz trzy typy wartości: wejścia, etykiety pośrednie i etykiety końcowe. Chociaż etykiety pośrednie lub końcowe są opcjonalne, posiadanie kilku przykładów wejściowych jest niezbędne.

Tworzenie obiektów przykładu

Obiekty przykładu w DSPy są podobne do słowników Pythona, ale posiadają przydatne narzędzia:


<p>qa_pair = dspy.Example(question="To jest pytanie?", answer="To jest odpowiedź.")</p>

<p>print(qa_pair)
print(qa_pair.question)
print(qa_pair.answer)</p>

Wynik:


<p>Example({'question': 'To jest pytanie?', 'answer': 'To jest odpowiedź.'}) (input_keys=None)
To jest pytanie?
To jest odpowiedź.</p>

Określanie kluczy wejściowych

W DSPy, obiekty Example mają metodę with_inputs(), aby oznaczyć określone pola jako wejścia:


<p>print(qa_pair.with_inputs("question"))
print(qa_pair.with_inputs("question", "answer"))</p>

Wartości mogą być dostępne za pomocą operatora kropki, a metody takie jak inputs() i labels() zwracają nowe obiekty Example, zawierające tylko klucze wejściowe lub nie-wejściowe, odpowiednio.

Optymalizatory w DSPy

Optymalizator DSPy dostosowuje parametry programu DSPy (tj. prompty i/lub wagi LM), aby maksymalizować określone metryki. DSPy oferuje różne wbudowane optymalizatory, z których każdy wykorzystuje różne strategie.

Dostępne optymalizatory

  • BootstrapFewShot: Generuje przykłady few-shot, wykorzystując dostarczone dane wejściowe i wyjściowe.
  • BootstrapFewShotWithRandomSearch: Stosuje BootstrapFewShot kilka razy z losową wyszukiwarką nad wygenerowanymi demonstracjami.
  • COPRO: Generuje i doskonali nowe instrukcje dla każdego kroku, optymalizując je z wykorzystaniem wspomagania koordynatów.
  • MIPRO: Optymalizuje instrukcje i przykłady few-shot, wykorzystując optymalizację bayesowską.

Wybór optymalizatora

Jeśli nie wiesz, od czego zacząć, wykorzystaj BootstrapFewShotWithRandomSearch:

Dla bardzo małych zbiorów danych (10 przykładów), wykorzystaj BootstrapFewShot.
Dla nieco większych zbiorów danych (50 przykładów), wykorzystaj BootstrapFewShotWithRandomSearch.
Dla większych zbiorów danych (300+ przykładów), wykorzystaj MIPRO.

Oto, jak wykorzystać BootstrapFewShotWithRandomSearch:


<p>from dspy.teleprompt import BootstrapFewShotWithRandomSearch</p>

<p>config = dict(max_bootstrapped_demos=4, max_labeled_demos=4, num_candidate_programs=10, num_threads=4)
teleprompter = BootstrapFewShotWithRandomSearch(metric=YOUR_METRIC_HERE, **config)
optimized_program = teleprompter.compile(YOUR_PROGRAM_HERE, trainset=YOUR_TRAINSET_HERE)</p>

Zapisywanie i ładowanie zoptymalizowanych programów

Po uruchomieniu programu przez optymalizator, zapisz go do przyszłego użycia:

optimized_program.save(YOUR_SAVE_PATH)

Załaduj zapisany program:


<p>loaded_program = YOUR_PROGRAM_CLASS()
loaded_program.load(path=YOUR_SAVE_PATH)</p>

Zaawansowane funkcje: Aserty DSPy

Aserty DSPy automatyzują egzekwowanie ograniczeń obliczeniowych na LLM, zwiększając niezawodność, przewidywalność i poprawność wyjść LM.

Wykorzystywanie asertów

Zdefiniuj funkcje walidacji i zadeklaruj aserty po generacji modelu. Na przykład:


<p>dspy.Suggest(
len(query) &lt;= 100,
&quot;Zapytanie powinno być krótkie i nie dłuższe niż 100 znaków&quot;,
)</p>

<p>dspy.Suggest(
validate_query_distinction_local(prev_queries, query),
"Zapytanie powinno być odrębne od: " + "; ".join(f"{i+1}) {q}" for i, q in enumerate(prev_queries)),
)</p>

Przekształcanie programów z asertami


<p>from dspy.primitives.assertions import assert_transform_module, backtrack_handler</p>

<p>baleen_with_assertions = assert_transform_module(SimplifiedBaleenAssertions(), backtrack_handler)</p>

Alternatywnie, aktywuj aserty bezpośrednio w programie:


<p>baleen_with_assertions = SimplifiedBaleenAssertions().activate_assertions()</p>

Optymalizacje sterowane asertami

Aserty DSPy współpracują z optymalizacjami DSPy, szczególnie z BootstrapFewShotWithRandomSearch, w tym ustawieniami takimi jak:

  • Kompilacja z asertami
  • Kompilacja + inferencja z asertami

Podsumowanie

DSPy oferuje potężne i systematyczne podejście do optymalizacji modeli językowych i ich prompty. Postępując zgodnie z krokami przedstawionymi w tych przykładach, możesz tworzyć, optymalizować i oceniać złożone systemy AI z łatwością. Modułowa konstrukcja DSPy i zaawansowane optymalizatory pozwalają na efektywną i skuteczną integrację różnych modeli językowych, czyniąc DSPy cennym narzędziem dla każdego, kto pracuje w dziedzinie NLP i AI.

Niezależnie od tego, czy budujesz prosty system odpowiedzi na pytania, czy bardziej złożony potok, DSPy zapewnia elastyczność i solidność niezbędną do osiągnięcia wysokiej wydajności i niezawodności.

Przez ostatnie pięć lat zanurzałem się w fascynującym świecie Machine Learning i Deep Learning. Moja pasja i ekspertyza doprowadziły mnie do udziału w ponad 50 różnorodnych projektach inżynierii oprogramowania, ze szczególnym uwzględnieniem AI/ML. Moja nieustanna ciekawość również skierowała mnie w stronę Natural Language Processing, dziedziny, którą chcę dalej eksplorować.