Connect with us

Engenharia de prompts

Otimizar LLM com DSPy: Um Guia Passo a Passo para Construir, Otimizar e Avaliar Sistemas de IA

mm
DSPy is a framework for algorithmically optimizing LM prompts and weights

À medida que as capacidades dos grandes modelos de linguagem (LLMs) continuam a expandir, desenvolver sistemas de IA robustos que aproveitem seu potencial tornou-se cada vez mais complexo. As abordagens convencionais frequentemente envolvem técnicas de prompting intricadas, geração de dados para fine-tuning e orientação manual para garantir a aderência a restrições específicas de domínio. No entanto, esse processo pode ser tedioso, propenso a erros e fortemente dependente de intervenção humana.

Entre com o DSPy, um framework revolucionário projetado para simplificar 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 construam aplicações sofisticadas com esforço manual mínimo.

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

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

O DSPy é um framework que separa o fluxo do seu programa (módulos) dos parâmetros (prompts e pesos de LM) de cada etapa. Essa separação permite a otimização sistemática de prompts e pesos de LM, permitindo que você construa sistemas de IA complexos com maior confiabilidade, previsibilidade e aderência a restrições específicas de domínio.

Tradicionalmente, desenvolver sistemas de IA com LLMs envolvia um processo laborioso de quebrar o problema em etapas, criar prompts intricados para cada etapa, gerar exemplos sintéticos para fine-tuning e orientar manualmente os LMs para aderir a restrições específicas. Essa abordagem não era apenas demorada, mas também propensa a erros, pois até mesmo mudanças menores no pipeline, LM ou dados poderiam exigir uma reestruturação extensiva de prompts e etapas de fine-tuning.

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

Arquitetura Modular do DSPy

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

Cada módulo encapsula parâmetros aprendíveis, incluindo instruções, exemplos de few-shot e pesos de LM. Quando um módulo é invocado, os otimizadores do DSPy podem ajustar finamente esses parâmetros para maximizar a métrica desejada, garantindo que as saídas do LM adiram às restrições e requisitos especificados.

Otimizando com o DSPy

O DSPy introduz uma variedade de otimizadores poderosos projetados para melhorar o desempenho e a confiabilidade dos seus sistemas de IA. Esses otimizadores utilizam algoritmos impulsionados por LM para ajustar os prompts e pesos das chamadas de LM, maximizando a métrica especificada enquanto adere às restrições específicas de domínio.

Alguns dos principais otimizadores disponíveis no DSPy incluem:

  1. BootstrapFewShot: Este otimizador estende a assinatura automaticamente, incluindo exemplos otimizados dentro do prompt enviado ao modelo, implementando o aprendizado de few-shot.
  2. BootstrapFewShotWithRandomSearch: Aplica BootstrapFewShot várias vezes com busca aleatória sobre demonstrações geradas, selecionando o melhor programa sobre a otimização.
  3. MIPRO: Gera instruções e exemplos de few-shot em cada etapa, com a geração de instruções sendo consciente dos dados e das demonstrações. Ele usa Otimização Bayesiana para efetivamente pesquisar o espaço de instruções de geração e demonstrações em seus módulos.
  4. BootstrapFinetune: Destila um programa do DSPy baseado em prompt em atualizações de peso para LMs menores, permitindo que você ajuste finamente os LMs subjacentes para eficiência melhorada.

Ao utilizar esses otimizadores, os desenvolvedores podem otimizar sistematicamente seus sistemas de IA, garantindo saídas de alta qualidade enquanto adere às restrições e requisitos específicos de domínio.

Iniciando com o DSPy

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

Etapa 1: Configurando o Modelo de Linguagem e o Modelo de Recuperação

A primeira etapa envolve configurar o modelo de linguagem (LM) e o modelo de recuperação (RM) dentro do DSPy.

Para instalar o DSPy, execute:


pip install dspy-ai

O DSPy suporta várias APIs de LM e RM, bem como hospedagem de modelo local, tornando fácil integrar seus modelos preferidos.


import dspy

# Configure o LM e o 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

Em seguida, carregaremos o conjunto de dados HotPotQA, que contém uma coleção de pares de perguntas e respostas complexas normalmente respondidas de forma multi-etapa.


from dspy.datasets import HotPotQA

# Carregue o conjunto de dados
dataset = HotPotQA(train_seed=1, train_size=20, eval_seed=2023, dev_size=50, test_size=0)

# Especifique o campo 'pergunta' como entrada
trainset = [x.with_inputs('pergunta') for x in dataset.train]
devset = [x.with_inputs('pergunta') for x in dataset.dev]

Etapa 3: Construindo Assinaturas

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


class GenerateAnswer(dspy.Signature):
"""Responda perguntas com respostas factoides curtas."""

contexto = dspy.InputField(desc='pode conter fatos relevantes')
pergunta = dspy.InputField()
resposta = dspy.OutputField(desc='geralmente entre 1 e 5 palavras')

Etapa 4: Construindo o Pipeline

Construiremos nosso pipeline RAG como um módulo do DSPy, que consiste em um método de inicialização (__init__) para declarar os sub-módulos (dspy.Retrieve e dspy.ChainOfThought) e um método forward (forward) para descrever o fluxo de controle de responder à pergunta usando esses módulos.


class RAG(dspy.Module):
def __init__(self, num_passagens=3):
super().__init__()

self.recuperar = dspy.Retrieve(k=num_passagens)
self.gerar_resposta = dspy.ChainOfThought(GenerateAnswer)

def forward(self, pergunta):
contexto = self.recuperar(pergunta).passagens
previsao = self.gerar_resposta(contexto=contexto, pergunta=pergunta)
return dspy.Prediction(contexto=contexto, resposta=previsao.resposta)

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 eficazes para nossos módulos com base em um conjunto de treinamento e uma métrica de validação.


from dspy.teleprompt import BootstrapFewShot

# Métrica de validação
def validar_contexto_e_resposta(exemplo, pred, trace=None):
resposta_EM = dspy.evaluate.answer_exact_match(exemplo, pred)
resposta_PM = dspy.evaluate.answer_passage_match(exemplo, pred)
return resposta_EM and resposta_PM

# Configure o otimizador
teleprompter = BootstrapFewShot(metric=validar_contexto_e_resposta)

# Compile o programa
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

# Configure o avaliador
evaluate = Evaluate(devset=devset, metric=validar_contexto_e_resposta, num_threads=4, display_progress=True, display_table=0)

# Avalie o programa RAG compilado
evaluation_result = evaluate(compiled_rag)

print(f"Avaliação do Resultado: {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.


# Inspecione o histórico do modelo
turbo.inspect_history(n=1)

Etapa 8: Fazendo Previsões

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


# Pergunta de exemplo
pergunta = "Qual prêmio o primeiro livro de Gary Zukav recebeu?"

# Faça uma previsão usando o programa RAG compilado
previsao = compiled_rag(pergunta)

print(f"Pergunta: {pergunta}")
print(f"Resposta: {previsao.resposta}")
print(f"Contextos Recuperados: {previsao.contexto}")

Exemplo Mínimo Funcional com o DSPy

Agora, vamos percorrer outro exemplo mínimo funcional usando o conjunto de dados GSM8K e o modelo OpenAI GPT-3.5-turbo para simular tarefas de prompting dentro do DSPy.

Configuração

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


import dspy
from dspy.datasets.gsm8k import GSM8K, gsm8k_metric

# Configure o LM
turbo = dspy.OpenAI(model='gpt-3.5-turbo-instruct', max_tokens=250)
dspy.settings.configure(lm=turbo)

# Carregue perguntas matemáticas do conjunto de dados GSM8K
gsm8k = GSM8K()
gsm8k_trainset, gsm8k_devset = gsm8k.train[:10], gsm8k.dev[:10]

print(gsm8k_trainset)

O gsm8k_trainset e gsm8k_devset contêm uma lista de exemplos com cada exemplo tendo um campo de pergunta e resposta.

Defina o Módulo

Em seguida, 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("pergunta -> resposta")

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

Compile e Avalie o Modelo

Agora, compile-o com o otimizador BootstrapFewShot:


from dspy.teleprompt import BootstrapFewShot

# Configure o otimizador
config = dict(max_bootstrapped_demos=4, max_labeled_demos=4)

# Otimizar usando a métrica gsm8k
teleprompter = BootstrapFewShot(metric=gsm8k_metric, **config)
optimized_cot = teleprompter.compile(CoT(), trainset=gsm8k_trainset)

# Configure o avaliador
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)

# Inspecione o histórico do modelo
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 e as configurações do otimizador fornecidas.

Gerenciamento de Dados no DSPy

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

Criando Objetos de Exemplo

Os objetos de exemplo no DSPy são semelhantes a dicionários Python, mas vêm com utilitários úteis:


par_pergunta_resposta = dspy.Example(pergunta="Isso é uma pergunta?", resposta="Isso é uma resposta.")

print(par_pergunta_resposta)
print(par_pergunta_resposta.pergunta)
print(par_pergunta_resposta.resposta)

Saída:


Example({ 'pergunta': 'Isso é uma pergunta?', 'resposta': 'Isso é uma resposta.'}) (input_keys=None)
Isso é uma pergunta?
Isso é uma resposta.

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(par_pergunta_resposta.with_inputs("pergunta"))
print(par_pergunta_resposta.with_inputs("pergunta", "resposta"))

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

Otimizadores no DSPy

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

Otimizadores Disponíveis

  • BootstrapFewShot: Gera exemplos de few-shot usando pontos de dados de entrada e saída rotulados.
  • BootstrapFewShotWithRandomSearch: Aplica BootstrapFewShot várias vezes com busca aleatória sobre demonstrações geradas.
  • COPRO: Gera e refina novas instruções para cada etapa, otimizando-as com ascensão de coordenadas.
  • MIPRO: Otimiza instruções e exemplos de few-shot usando Otimização Bayesiana.

Escolhendo um Otimizador

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

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

Aqui está 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 por meio de um otimizador, salve-o para uso futuro:

optimized_program.save(YOUR_SAVE_PATH)

Eu passei os últimos cinco anos me imergindo no fascinante mundo de Aprendizado de Máquina e Aprendizado Profundo. Minha paixão e especialização me levaram a contribuir para mais de 50 projetos diversificados de engenharia de software, com um foco particular em IA/ML. Minha curiosidade contínua também me atraiu para o Processamento de Linguagem Natural, um campo que estou ansioso para explorar mais.