Kontakt z nami

Szybka inżynieria

Optymalizuj LLM za pomocą DSPy: przewodnik krok po kroku dotyczący budowania, optymalizacji i oceny systemów AI

mm

Opublikowany

 on

DSPy to framework do algorytmicznej optymalizacji podpowiedzi i wag LM

W miarę ciągłego rozszerzania się możliwości dużych modeli językowych (LLM), opracowywanie solidnych systemów sztucznej inteligencji wykorzystujących ich potencjał staje się coraz bardziej złożone. Konwencjonalne podejścia często obejmują skomplikowane techniki podpowiedzi, generowanie danych w celu dostrojenia i ręczne wskazówki zapewniające zgodność z ograniczeniami specyficznymi dla domeny. Jednak proces ten może być żmudny, podatny na błędy i w dużym stopniu zależny od interwencji człowieka.

Wchodzę DSPy, rewolucyjną platformę zaprojektowaną w celu usprawnienia rozwoju systemów sztucznej inteligencji opartych na LLM. DSPy wprowadza systematyczne podejście do optymalizacji podpowiedzi i wag LM, umożliwiając programistom tworzenie zaawansowanych aplikacji przy minimalnym wysiłku ręcznym.

W tym obszernym przewodniku omówimy podstawowe zasady DSPy, jego modułową architekturę i szereg zaawansowanych funkcji, jakie oferuje. Zajmiemy się także praktycznymi przykładami, pokazując, jak DSPy może zmienić sposób tworzenia systemów AI za pomocą LLM.

Co to jest DSPy i dlaczego go potrzebujesz?

DSPy to framework oddzielający przepływ programu (modules) z parametrów (podpowiedzi LM i wagi) każdego kroku. To oddzielenie pozwala na systematyczną optymalizację podpowiedzi i wag LM, umożliwiając budowanie złożonych systemów AI o większej niezawodności, przewidywalności i zgodności z ograniczeniami specyficznymi dla domeny.

Tradycyjnie opracowywanie systemów AI za pomocą LLM obejmowało pracochłonny proces dzielenia problemu na etapy, tworzenie skomplikowanych podpowiedzi dla każdego kroku, generowanie syntetycznych przykładów do dostrajania i ręczne kierowanie LM w celu przestrzegania określonych ograniczeń. Takie podejście było nie tylko czasochłonne, ale także podatne na błędy, ponieważ nawet drobne zmiany w potoku, LM lub danych mogły wymagać rozległych przeróbek podpowiedzi i etapów dostrajania.

DSPy odpowiada na te wyzwania, wprowadzając nowy paradygmat: optymalizatory. Te algorytmy oparte na LM mogą dostroić monity i wagi wywołań LM, biorąc pod uwagę metrykę, którą chcesz zmaksymalizować. Automatyzując proces optymalizacji, DSPy umożliwia programistom budowanie solidnych systemów AI przy minimalnej interwencji ręcznej, zwiększając niezawodność i przewidywalność wyników LM.

Architektura modułowa DSPy

W sercu DSPy leży modułowa architektura, która ułatwia tworzenie złożonych systemów AI. Framework udostępnia zestaw wbudowanych modułów, które streszczają różne techniki podpowiedzi, takie jak dspy.ChainOfThought i dspy.ReAct. Moduły te można łączyć i składać w większe programy, umożliwiając programistom budowanie skomplikowanych potoków dostosowanych do ich specyficznych wymagań.

Każdy moduł zawiera parametry, których można się nauczyć, w tym instrukcje, przykłady kilku strzałów i wagi LM. Po wywołaniu modułu optymalizatory DSPy mogą dostroić te parametry, aby zmaksymalizować żądaną metrykę, zapewniając, że wyjścia LM spełniają określone ograniczenia i wymagania.

Optymalizacja za pomocą DSPy

DSPy wprowadza szereg potężnych optymalizatorów zaprojektowanych w celu zwiększenia wydajności i niezawodności systemów AI. Optymalizatory te wykorzystują algorytmy oparte na LM do dostrajania podpowiedzi i wag wywołań LM, maksymalizując określoną metrykę przy jednoczesnym przestrzeganiu ograniczeń specyficznych dla domeny.

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

  1. BootstrapFewShot: Ten optymalizator rozszerza sygnaturę, automatycznie generując i dołączając zoptymalizowane przykłady do zachęty wysyłanej do modelu, wdrażając uczenie się w kilku krokach.
  2. BootstrapFewShotWithRandomSearch: Dotyczy BootstrapFewShot kilkukrotnie losowo przeszukiwać wygenerowane demonstracje, wybierając najlepszy program zamiast optymalizacji.
  3. MIPRO: Generuje instrukcje i kilka przykładów w każdym kroku, przy czym generowanie instrukcji uwzględnia dane i demonstracje. Wykorzystuje optymalizację Bayesa do efektywnego przeszukiwania przestrzeni instrukcji generowania i demonstracji w modułach.
  4. BootstrapFinetune: Destyluje oparty na podpowiedziach program DSPy do aktualizacji masy dla mniejszych LM, umożliwiając precyzyjne dostrojenie bazowych LLM w celu zwiększenia wydajności.

Wykorzystując te optymalizatory, programiści mogą systematycznie optymalizować swoje systemy AI, zapewniając wysoką jakość wyników, przy jednoczesnym przestrzeganiu ograniczeń i wymagań specyficznych dla domeny.

Pierwsze kroki z DSPy

Aby zilustrować możliwości DSPy, przeanalizujmy praktyczny przykład budowania systemu generacji wspomaganej wyszukiwaniem (RAG) do odpowiadania na pytania.

Krok 1: Konfigurowanie modelu języka i modelu wyszukiwania

Pierwszy krok polega na skonfigurowaniu modelu języka (LM) i modelu wyszukiwania (RM) w DSPy.

Aby zainstalować uruchomienie DSPy:

pip install dspy-ai

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

import dspy
# Configure the LM and RM
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)

Krok 2: Ładowanie zbioru danych

Następnie załadujemy zbiór danych HotPotQA, który zawiera zbiór złożonych par pytanie-odpowiedź, na które zwykle udzielane są odpowiedzi w trybie wielu przeskoków.

from dspy.datasets import HotPotQA
# Load the dataset
dataset = HotPotQA(train_seed=1, train_size=20, eval_seed=2023, dev_size=50, test_size=0)
# Specify the 'question' field as the input
trainset = [x.with_inputs('question') for x in dataset.train]
devset = [x.with_inputs('question') for x in dataset.dev]

Krok 3: Budowanie podpisów

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

class GenerateAnswer(dspy.Signature):
"""Answer questions with short factoid answers."""
context = dspy.InputField(desc="may contain relevant facts")
question = dspy.InputField()
answer = dspy.OutputField(desc="often between 1 and 5 words")

Krok 4: Budowa rurociągu

Zbudujemy nasz potok RAG jako moduł DSPy, który składa się z metody inicjalizacji (__init__) służącej do zadeklarowania podmodułów (dspy.Retrieve i dspy.ChainOfThought) oraz metody forward (forward) opisującej przepływ sterowania odpowiadaniem pytanie za pomocą tych modułów.

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)

Krok 5: Optymalizacja rurociągu

Po zdefiniowaniu potoku możemy go teraz zoptymalizować za pomocą optymalizatorów DSPy. W tym przykładzie użyjemy optymalizatora BootstrapFewShot, który generuje i wybiera skuteczne podpowiedzi dla naszych modułów w oparciu o zestaw szkoleniowy i metrykę do walidacji.

from dspy.teleprompt import BootstrapFewShot
# Validation metric
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
# Set up the optimizer
teleprompter = BootstrapFewShot(metric=validate_context_and_answer)
# Compile the program
compiled_rag = teleprompter.compile(RAG(), trainset=trainset)

Krok 6: Ocena rurociągu

Po skompilowaniu programu należy ocenić jego działanie na zestawie deweloperskim, aby upewnić się, że spełnia on wymaganą dokładność i niezawodność.

from dspy.evaluate import Evaluate
# Set up the evaluator
evaluate = Evaluate(devset=devset, metric=validate_context_and_answer, num_threads=4, display_progress=True, display_table=0)
# Evaluate the compiled RAG program
evaluation_result = evaluate(compiled_rag)
print(f"Evaluation Result: {evaluation_result}")

Krok 7: Sprawdzanie historii modelu

Aby lepiej zrozumieć interakcje modelu, możesz przejrzeć najnowsze generacje, sprawdzając historię modelu.

# Inspect the model's history
turbo.inspect_history(n=1)

Krok 8: Dokonywanie prognoz

Po zoptymalizowaniu i ocenie potoku możesz teraz używać go do przewidywania nowych pytań.

# Example question
question = "Which award did Gary Zukav's first book receive?"
# Make a prediction using the compiled RAG program
prediction = compiled_rag(question)
print(f"Question: {question}")
print(f"Answer: {prediction.answer}")
print(f"Retrieved Contexts: {prediction.context}")

Minimalny przykład działania z DSPy

Teraz przeanalizujmy kolejny minimalny przykład działania, używając metody Zbiór danych GSM8K oraz model OpenAI GPT-3.5-turbo do symulacji zadań podpowiedzi w DSPy.

ustawienie

Najpierw upewnij się, że środowisko jest poprawnie skonfigurowane:

import dspy
from dspy.datasets.gsm8k import GSM8K, gsm8k_metric
# Set up the LM
turbo = dspy.OpenAI(model='gpt-3.5-turbo-instruct', max_tokens=250)
dspy.settings.configure(lm=turbo)
# Load math questions from the GSM8K dataset
gsm8k = GSM8K()
gsm8k_trainset, gsm8k_devset = gsm8k.train[:10], gsm8k.dev[:10]
print(gsm8k_trainset)

Połączenia gsm8k_trainset i gsm8k_devset zbiory danych zawierają listę przykładów, przy czym każdy przykład zawiera pole pytań i odpowiedzi.

Zdefiniuj moduł

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

class CoT(dspy.Module):
def __init__(self):
super().__init__()
self.prog = dspy.ChainOfThought("question -> answer")
def forward(self, question):
return self.prog(question=question)

Skompiluj i oceń model

Teraz skompiluj go za pomocą pliku BootstrapFewShot teleprompter:

from dspy.teleprompt import BootstrapFewShot
# Set up the optimizer
config = dict(max_bootstrapped_demos=4, max_labeled_demos=4)
# Optimize using the gsm8k_metric
teleprompter = BootstrapFewShot(metric=gsm8k_metric, **config)
optimized_cot = teleprompter.compile(CoT(), trainset=gsm8k_trainset)
# Set up the evaluator
from dspy.evaluate import Evaluate
evaluate = Evaluate(devset=gsm8k_devset, metric=gsm8k_metric, num_threads=4, display_progress=True, display_table=0)
evaluate(optimized_cot)
# Inspect the model's history
turbo.inspect_history(n=1)

Ten przykład pokazuje, jak skonfigurować środowisko, zdefiniować moduł niestandardowy, skompilować model i rygorystycznie ocenić jego wydajność przy użyciu dostarczonego zestawu danych i konfiguracji telepromptera.

Zarządzanie danymi w DSPy

DSPy obsługuje zestawy szkoleniowe, rozwojowe i testowe. Dla każdego przykładu w danych zazwyczaj masz trzy typy wartości: dane wejściowe, etykiety pośrednie i etykiety końcowe. Chociaż etykiety pośrednie lub końcowe są opcjonalne, posiadanie kilku przykładowych danych wejściowych jest niezbędne.

Tworzenie przykładowych obiektów

Przykładowe obiekty w DSPy są podobne do słowników Pythona, ale zawierają przydatne narzędzia:

qa_pair = dspy.Example(question="This is a question?", answer="This is an answer.")
print(qa_pair)
print(qa_pair.question)
print(qa_pair.answer)

Wyjście:

Example({'question': 'This is a question?', 'answer': 'This is an answer.'}) (input_keys=None)
This is a question?
This is an answer.

Określanie kluczy wejściowych

W DSPy przykładowe obiekty mają metodę with_inputs() do oznaczania określonych pól jako danych wejściowych:

print(qa_pair.with_inputs("question"))
print(qa_pair.with_inputs("question", "answer"))

Dostęp do wartości można uzyskać za pomocą operatora kropki, a metody takie jak inputs() i Labels() zwracają nowe Przykładowe obiekty zawierające odpowiednio tylko klucze wejściowe lub niewprowadzające.

Optymalizatory w DSPy

Optymalizator DSPy dostraja parametry programu DSPy (tj. podpowiedzi i/lub wagi LM), aby zmaksymalizować określone metryki. DSPy oferuje różne wbudowane optymalizatory, z których każdy wykorzystuje inną strategię.

Dostępne optymalizatory

  • BootstrapFewShot: Generuje kilka przykładów, korzystając z dostarczonych oznaczonych etykietami punktów danych wejściowych i wyjściowych.
  • BootstrapFewShotWithRandomSearch: Stosuje BootstrapFewShot wielokrotnie z losowym wyszukiwaniem wygenerowanych demonstracji.
  • COPRO: Generuje i udoskonala nowe instrukcje dla każdego kroku, optymalizując je przy wznoszeniu współrzędnych.
  • MIPRO: Optymalizuje instrukcje i przykłady składające się z kilku strzałów przy użyciu optymalizacji Bayesa.

Wybór optymalizatora

Jeśli nie masz pewności, od czego zacząć, użyj BootstrapFewShotWithRandomSearch:

W przypadku bardzo małych danych (10 przykładów) użyj BootstrapFewShot.
Aby uzyskać nieco więcej danych (50 przykładów), użyj BootstrapFewShotWithRandomSearch.
W przypadku większych zbiorów danych (ponad 300 przykładów) użyj MIPRO.

Oto jak korzystać z BootstrapFewShotWithRandomSearch:

from dspy.teleprompt import BootstrapFewShotWithRandomSearch
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)

Zapisywanie i ładowanie zoptymalizowanych programów

Po uruchomieniu programu za pomocą optymalizatora zapisz go do wykorzystania w przyszłości:

zoptymalizowany_program.zapisz(Twoja_ścieżka_zapisu)

Załaduj zapisany program:

loaded_program = YOUR_PROGRAM_CLASS()
loaded_program.load(path=YOUR_SAVE_PATH)

Zaawansowane funkcje: asercje DSPy

Asercje DSPy automatyzują egzekwowanie ograniczeń obliczeniowych na LM, zwiększając niezawodność, przewidywalność i poprawność wyników LM.

Korzystanie z asercji

Zdefiniuj funkcje walidacyjne i zadeklaruj asercje po wygenerowaniu odpowiedniego modelu. Na przykład:

dspy.Suggest(
len(query) <= 100,
"Query should be short and less than 100 characters",
)
dspy.Suggest(
validate_query_distinction_local(prev_queries, query),
"Query should be distinct from: " + "; ".join(f"{i+1}) {q}" for i, q in enumerate(prev_queries)),
)

Przekształcanie programów za pomocą asercji

from dspy.primitives.assertions import assert_transform_module, backtrack_handler
baleen_with_assertions = assert_transform_module(SimplifiedBaleenAssertions(), backtrack_handler)

Alternatywnie aktywuj asercje bezpośrednio w programie:

baleen_with_assertions = SimplifiedBaleenAssertions().activate_assertions()

Optymalizacje oparte na asercjach

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

  • Kompilacja z asercjami
  • Kompilacja + wnioskowanie z asercjami

Wnioski

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

Niezależnie od tego, czy budujesz prosty system odpowiadania 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.

Ostatnie pięć lat spędziłem zanurzając się w fascynującym świecie uczenia maszynowego i głębokiego uczenia się. Moja pasja i wiedza sprawiły, że uczestniczyłem w ponad 50 różnorodnych projektach z zakresu inżynierii oprogramowania, ze szczególnym uwzględnieniem AI/ML. Moja ciągła ciekawość przyciągnęła mnie również w stronę przetwarzania języka naturalnego – dziedziny, którą chcę dalej zgłębiać.