Entre em contato

Otimize LLM com DSPy: um guia passo a passo para construir, otimizar e avaliar sistemas de IA

Engenharia imediata

Otimize LLM com DSPy: um guia passo a passo para construir, otimizar e avaliar sistemas de IA

mm

Publicado

 on

DSPy é uma estrutura para otimizar algoritmos de prompts e pesos de LM

À medida que as capacidades dos grandes modelos de linguagem (LLMs) continuam a expandir-se, o desenvolvimento de sistemas robustos de IA que aproveitem o seu potencial tornou-se cada vez mais complexo. As abordagens convencionais geralmente envolvem técnicas complexas de solicitação, geração de dados para ajuste fino e orientação manual para garantir a adesão às restrições específicas do domínio. No entanto, este processo pode ser tedioso, sujeito a erros e fortemente dependente da intervenção humana.

Entrar DSPy, uma estrutura revolucionária projetada para agilizar o desenvolvimento de sistemas de IA alimentados por LLMs. O DSPy introduz uma abordagem sistemática para otimizar prompts e pesos de LM, permitindo que os desenvolvedores criem aplicativos sofisticados com o mínimo de esforço manual.

Neste guia abrangente, exploraremos os princípios básicos do DSPy, sua arquitetura modular e a variedade de recursos poderosos que ele oferece. Também mergulharemos em exemplos práticos, demonstrando como o DSPy pode transformar a maneira como você desenvolve sistemas de IA com LLMs.

O que é DSPy e por que você precisa dele?

DSPy é uma estrutura que separa o fluxo do seu programa (modules) dos parâmetros (prompts e pesos do LM) de cada etapa. Essa separação permite a otimização sistemática de prompts e pesos de LM, permitindo construir sistemas de IA complexos com maior confiabilidade, previsibilidade e adesão a restrições específicas de domínio.

Tradicionalmente, o desenvolvimento de sistemas de IA com LLMs envolvia um processo trabalhoso de dividir o problema em etapas, elaborando instruções complexas para cada etapa, gerando exemplos sintéticos para ajuste fino e orientando manualmente os LMs para aderir a restrições específicas. Essa abordagem não era apenas demorada, mas também propensa a erros, pois mesmo pequenas alterações no pipeline, no LM ou nos dados poderiam exigir um extenso retrabalho de prompts e etapas de ajuste fino.

O DSPy aborda esses desafios introduzindo um novo paradigma: otimizadores. Esses algoritmos orientados por LM podem ajustar os prompts e os pesos de suas chamadas LM, de acordo com uma métrica que você deseja maximizar. Ao automatizar o processo de otimização, o DSPy capacita os desenvolvedores a construir sistemas robustos de IA com intervenção manual mínima, aumentando a confiabilidade e a previsibilidade dos resultados do LM.

Arquitetura Modular do DSPy

No coração do DSPy está uma arquitetura modular que facilita a composição de sistemas complexos de IA. A estrutura fornece um conjunto de módulos integrados que abstraem várias técnicas de prompt, como dspy.ChainOfThought e dspy.ReAct. Esses módulos podem ser combinados e compostos em programas maiores, permitindo que os desenvolvedores construam pipelines complexos adaptados às suas necessidades específicas.

Cada módulo encapsula parâmetros que podem ser aprendidos, incluindo instruções, exemplos de poucas tentativas e pesos LM. Quando um módulo é invocado, os otimizadores do DSPy podem ajustar esses parâmetros para maximizar a métrica desejada, garantindo que as saídas do LM cumpram as restrições e requisitos especificados.

Otimizando com DSPy

DSPy apresenta uma gama de otimizadores poderosos projetados para aprimorar o desempenho e a confiabilidade de seus sistemas de IA. Esses otimizadores aproveitam algoritmos orientados por LM para ajustar os prompts e pesos de suas chamadas LM, maximizando a métrica especificada e ao mesmo tempo aderindo às restrições específicas do domínio.

Alguns dos principais otimizadores disponíveis no DSPy incluem:

  1. BootstrapFewShot: este otimizador estende a assinatura gerando e incluindo automaticamente exemplos otimizados no prompt enviado ao modelo, implementando aprendizado rápido.
  2. BootstrapFewShotWithRandomSearch: Aplica-se BootstrapFewShot diversas vezes com busca aleatória nas demonstrações geradas, selecionando o melhor programa em detrimento da otimização.
  3. MIPRO: Gera instruções e exemplos rápidos em cada etapa, com a geração de instruções ciente de dados e de demonstração. Ele usa otimização bayesiana para pesquisar com eficácia o espaço de instruções de geração e demonstrações em seus módulos.
  4. BootstrapFinetune: destila um programa DSPy baseado em prompt em atualizações de peso para LMs menores, permitindo ajustar o(s) LLM(s) subjacente(s) para maior eficiência.

Ao aproveitar esses otimizadores, os desenvolvedores podem otimizar sistematicamente seus sistemas de IA, garantindo resultados de alta qualidade e, ao mesmo tempo, aderindo às restrições e requisitos específicos do domínio.

Introdução ao DSPy

Para ilustrar o poder do DSPy, vamos examinar um exemplo prático de construção de um sistema de geração aumentada de recuperação (RAG) para resposta a perguntas.

Etapa 1: Configurando o modelo de linguagem e o modelo de recuperação

A primeira etapa envolve a configuração do modelo de linguagem (LM) e do modelo de recuperação (RM) no DSPy.

Para instalar o DSPy, execute:

pip install dspy-ai

O DSPy oferece suporte a várias APIs LM e RM, bem como hospedagem de modelos locais, facilitando a integração de seus modelos preferidos.

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)

Etapa 2: Carregando o conjunto de dados

A seguir, carregaremos o conjunto de dados HotPotQA, que contém uma coleção de pares complexos de perguntas e respostas, normalmente respondidos em vários saltos.

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]

Etapa 3: Construindo Assinaturas

DSPy usa assinaturas para definir o comportamento dos módulos. Neste exemplo, definiremos uma assinatura para a tarefa de geração de resposta, especificando os campos de entrada (contexto e pergunta) e o campo de saída (resposta).

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")

Etapa 4: Construindo o Pipeline

Construiremos nosso pipeline RAG como um módulo DSPy, que consiste em um método de inicialização (__init__) para declarar os submódulos (dspy.Retrieve e dspy.ChainOfThought) e um método forward (forward) para descrever o fluxo de controle de resposta a questão usando esses módulos.

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)

Etapa 5: Otimizando o Pipeline

Com o pipeline definido, agora podemos otimizá-lo usando os otimizadores do DSPy. Neste exemplo, usaremos o otimizador BootstrapFewShot, que gera e seleciona prompts efetivos para nossos módulos com base em um conjunto de treinamento e uma métrica para validação.

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)

Etapa 6: avaliando o pipeline

Depois de compilar o programa, é essencial avaliar seu desempenho em um conjunto de desenvolvimento para garantir que atenda à precisão e confiabilidade desejadas.

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}")

Etapa 7: inspecionando o histórico do modelo

Para uma compreensão mais profunda das interações do modelo, você pode revisar as gerações mais recentes inspecionando o histórico do modelo.

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

Passo 8: Fazendo Previsões

Com o pipeline otimizado e avaliado, agora você pode usá-lo para fazer previsões sobre novas questões.

# 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}")

Exemplo mínimo de trabalho com DSPy

Agora, vamos examinar outro exemplo de trabalho mínimo usando o Conjunto de dados GSM8K e o modelo OpenAI GPT-3.5-turbo para simular tarefas de prompt no DSPy.

instalação

Primeiro, certifique-se de que seu ambiente esteja configurado corretamente:

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)

A gsm8k_trainset e gsm8k_devset conjuntos de dados contêm uma lista de exemplos com cada exemplo tendo um campo de perguntas e respostas.

Defina o Módulo

A seguir, defina um programa personalizado utilizando o módulo ChainOfThought para raciocínio passo a passo:

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

def forward(self, question):
return self.prog(question=question)

Compilar e avaliar o modelo

Agora compile-o com o 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)

Este exemplo demonstra como configurar seu ambiente, definir um módulo personalizado, compilar um modelo e avaliar rigorosamente seu desempenho usando o conjunto de dados fornecido e as configurações de teleprompter.

Gerenciamento de dados em DSPy

DSPy opera com conjuntos de treinamento, desenvolvimento e teste. Para cada exemplo nos seus dados, normalmente você tem três tipos de valores: entradas, rótulos intermediários e rótulos finais. Embora os rótulos intermediários ou finais sejam opcionais, é essencial ter alguns exemplos de entradas.

Criando objetos de exemplo

Objetos de exemplo em DSPy são semelhantes aos dicionários Python, mas vêm com utilitários úteis:

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)

Saída:

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

Especificando chaves de entrada

No DSPy, os objetos de exemplo têm um método with_inputs() para marcar campos específicos como entradas:

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

Os valores podem ser acessados ​​usando o operador ponto, e métodos como inputs() e rótulos() retornam novos objetos de exemplo contendo apenas chaves de entrada ou não, respectivamente.

Otimizadores em DSPy

Um otimizador DSPy ajusta os parâmetros de um programa DSPy (isto é, prompts e/ou pesos LM) para maximizar métricas especificadas. O DSPy oferece vários otimizadores integrados, cada um empregando estratégias diferentes.

Otimizadores disponíveis

  • BootstrapFewShot: gera exemplos rápidos usando pontos de dados de entrada e saída rotulados fornecidos.
  • BootstrapFewShotWithRandomSearch: aplica BootstrapFewShot várias vezes com pesquisa aleatória nas demonstrações geradas.
  • COPRO: Gera e refina novas instruções para cada etapa, otimizando-as com subida coordenada.
  • MIPRO: otimiza instruções e exemplos rápidos usando a otimização bayesiana.

Escolhendo um otimizador

Se você não souber por onde começar, use BootstrapFewShotWithRandomSearch:

Para poucos dados (10 exemplos), use BootstrapFewShot.
Para um pouco mais de dados (50 exemplos), use BootstrapFewShotWithRandomSearch.
Para conjuntos de dados maiores (mais de 300 exemplos), use MIPRO.

Veja como usar 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)

Salvando e carregando programas otimizados

Depois de executar um programa através de um otimizador, salve-o para uso futuro:

otimizado_program.save(SEU_SAVE_PATH)

Carregue um programa salvo:

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

Recursos avançados: Asserções DSPy

As asserções DSPy automatizam a aplicação de restrições computacionais em LMs, aumentando a confiabilidade, previsibilidade e correção das saídas de LM.

Usando Asserções

Definir funções de validação e declarar asserções após a respectiva geração do modelo. Por exemplo:

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)),
)

Transformando Programas com Asserções

from dspy.primitives.assertions import assert_transform_module, backtrack_handler

baleen_with_assertions = assert_transform_module(SimplifiedBaleenAssertions(), backtrack_handler)

Alternativamente, ative as asserções diretamente no programa:

baleen_with_assertions = SimplifiedBaleenAssertions().activate_assertions()

Otimizações baseadas em asserções

As asserções DSPy funcionam com otimizações DSPy, especialmente com BootstrapFewShotWithRandomSearch, incluindo configurações como:

  • Compilação com Asserções
  • Compilação + Inferência com Asserções

Conclusão

DSPy oferece uma abordagem poderosa e sistemática para otimizar modelos de linguagem e seus prompts. Seguindo as etapas descritas nestes exemplos, você pode construir, otimizar e avaliar sistemas complexos de IA com facilidade. O design modular e os otimizadores avançados do DSPy permitem a integração eficiente e eficaz de vários modelos de linguagem, tornando-o uma ferramenta valiosa para qualquer pessoa que trabalhe na área de PNL e IA.

Esteja você construindo um sistema simples de resposta a perguntas ou um pipeline mais complexo, o DSPy fornece a flexibilidade e a robustez necessárias para alcançar alto desempenho e confiabilidade.

Passei os últimos cinco anos mergulhando no fascinante mundo do Machine Learning e Deep Learning. Minha paixão e experiência me levaram a contribuir para mais de 50 projetos diversos de engenharia de software, com foco particular em AI/ML. Minha curiosidade contínua também me atraiu para o Processamento de Linguagem Natural, um campo que estou ansioso para explorar mais.