Ingeniería de prompts

Optimice LLM con DSPy: Una guía paso a paso para construir, optimizar y evaluar sistemas de inteligencia artificial

mm
Añade Unite.AI a tus fuentes preferidas en Google
DSPy is a framework for algorithmically optimizing LM prompts and weights

A medida que las capacidades de los grandes modelos de lenguaje (LLM) siguen expandiéndose, desarrollar sistemas de inteligencia artificial robustos que aprovechen su potencial se ha vuelto cada vez más complejo. Los enfoques convencionales a menudo involucran técnicas de prompting intrincadas, generación de datos para fine-tuning y orientación manual para garantizar el cumplimiento de restricciones específicas del dominio. Sin embargo, este proceso puede ser tedioso, propenso a errores y muy dependiente de la intervención humana.

Entonces, entra DSPy, un marco revolucionario diseñado para simplificar el desarrollo de sistemas de inteligencia artificial. DSPy introduce un enfoque sistemático para optimizar las prompts y pesos de los LLM, lo que permite a los desarrolladores construir aplicaciones sofisticadas con un esfuerzo manual mínimo.

En esta guía integral, exploraremos los principios básicos de DSPy, su arquitectura modular y la variedad de características poderosas que ofrece. También profundizaremos en ejemplos prácticos, demostrando cómo DSPy puede transformar la forma en que desarrolla sistemas de inteligencia artificial con LLM.

¿Qué es DSPy y por qué lo necesitas?

DSPy es un marco que separa el flujo de tu programa (módulos) de los parámetros (prompts y pesos de LLM) de cada paso. Esta separación permite la optimización sistemática de las prompts y pesos de LLM, lo que te permite construir sistemas de inteligencia artificial complejos con mayor confiabilidad, previsibilidad y cumplimiento de restricciones específicas del dominio.

Tradicionalmente, desarrollar sistemas de inteligencia artificial con LLM involucraba un proceso laborioso de descomposición del problema en pasos, creación de prompts intrincados para cada paso, generación de ejemplos sintéticos para fine-tuning y orientación manual para que los LLM cumplan con restricciones específicas. Este enfoque no solo era tedioso sino también propenso a errores, ya que incluso pequeños cambios en la canalización, LLM o datos podrían requerir una re-elaboración extensa de prompts y pasos de fine-tuning.

DSPy aborda estos desafíos al introducir un nuevo paradigma: optimizadores. Estos algoritmos impulsados por LLM pueden ajustar las prompts y pesos de tus llamadas a LLM, dado un métrico que deseas maximizar. Al automatizar el proceso de optimización, DSPy permite a los desarrolladores construir sistemas de inteligencia artificial robustos con una intervención manual mínima, mejorando la confiabilidad y previsibilidad de las salidas de LLM.

Arquitectura modular de DSPy

En el corazón de DSPy se encuentra una arquitectura modular que facilita la composición de sistemas de inteligencia artificial complejos. El marco proporciona un conjunto de módulos integrados que abstraen varias técnicas de prompting, como dspy.ChainOfThought y dspy.ReAct. Estos módulos se pueden combinar y componer en programas más grandes, lo que permite a los desarrolladores construir canalizaciones intrincadas adaptadas a sus requisitos específicos.

Cada módulo encapsula parámetros aprendibles, incluyendo instrucciones, ejemplos de few-shot y pesos de LLM. Cuando se invoca un módulo, los optimizadores de DSPy pueden ajustar estos parámetros para maximizar el métrico deseado, garantizando que las salidas de LLM cumplan con las restricciones y requisitos especificados.

Optimización con DSPy

DSPy introduce una serie de optimizadores poderosos diseñados para mejorar el rendimiento y la confiabilidad de tus sistemas de inteligencia artificial. Estos optimizadores aprovechan algoritmos impulsados por LLM para ajustar las prompts y pesos de tus llamadas a LLM, maximizando el métrico especificado mientras se cumplen las restricciones específicas del dominio.

Algunos de los optimizadores disponibles en DSPy incluyen:

  1. BootstrapFewShot: Este optimizador extiende la firma generando y incluyendo automáticamente ejemplos optimizados dentro de la prompt enviada al modelo, implementando el aprendizaje de few-shot.
  2. BootstrapFewShotWithRandomSearch: Aplica BootstrapFewShot varias veces con búsqueda aleatoria sobre demostraciones generadas, seleccionando el mejor programa sobre la optimización.
  3. MIPRO: Genera instrucciones y ejemplos de few-shot en cada paso, con la generación de instrucciones siendo consciente de los datos y las demostraciones. Utiliza la optimización bayesiana para buscar efectivamente en el espacio de generación de instrucciones y demostraciones a través de tus módulos.
  4. BootstrapFinetune: Destila un programa de DSPy basado en prompts en actualizaciones de pesos para LLM más pequeños, lo que te permite fine-tuning el LLM subyacente para una mayor eficiencia.

Al aprovechar estos optimizadores, los desarrolladores pueden optimizar sistemáticamente sus sistemas de inteligencia artificial, garantizando salidas de alta calidad mientras se cumplen las restricciones y requisitos específicos del dominio.

Empezando con DSPy

Para ilustrar el poder de DSPy, vamos a pasar por un ejemplo práctico de construcción de un sistema de generación de respuestas con recuperación (RAG) para preguntas y respuestas.

Paso 1: Configuración del modelo de lenguaje y el modelo de recuperación

El primer paso implica configurar el modelo de lenguaje (LM) y el modelo de recuperación (RM) dentro de DSPy.

Para instalar DSPy, ejecuta:


pip install dspy-ai

DSPy admite múltiples APIs de LM y RM, así como alojamiento de modelos locales, lo que facilita la integración de tus modelos preferidos.


import dspy

<p># Configura el LM y el RM
turbo = dspy.OpenAI(model=&#039;gpt-3.5-turbo&#039;)
colbertv2_wiki17_abstracts = dspy.ColBERTv2(url=&#039;http://20.102.90.50:2017/wiki17_abstracts&#039;)</p>

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

Paso 2: Carga del conjunto de datos

A continuación, cargaremos el conjunto de datos HotPotQA, que contiene una colección de pares de preguntas y respuestas complejas que suelen responderse de manera multi-paso.


from dspy.datasets import HotPotQA

<p># Carga el conjunto de datos
dataset = HotPotQA(train_seed=1, train_size=20, eval_seed=2023, dev_size=50, test_size=0)</p>

<p># Especifica el campo &#039;pregunta&#039; como entrada
trainset = [x.with_inputs(&#039;pregunta&#039;) for x in dataset.train]
devset = [x.with_inputs(&#039;pregunta&#039;) for x in dataset.dev]</p>

Paso 3: Creación de firmas

DSPy utiliza firmas para definir el comportamiento de los módulos. En este ejemplo, definiremos una firma para la tarea de generación de respuestas, especificando los campos de entrada (contexto y pregunta) y el campo de salida (respuesta).


<p>class GenerateAnswer(dspy.Signature):
&amp;quot;&amp;quot;&amp;quot;Responde preguntas con respuestas breves y con hechos.&amp;quot;&amp;quot;&amp;quot;</p>

<p>contexto = dspy.InputField(desc=&amp;quot;puede contener hechos relevantes&amp;quot;)
pregunta = dspy.InputField()
respuesta = dspy.OutputField(desc=&amp;quot;a menudo entre 1 y 5 palabras&amp;quot;)</p>

Paso 4: Creación de la canalización

Construiremos nuestra canalización RAG como un módulo de DSPy, que consiste en un método de inicialización (__init__) para declarar los submódulos (dspy.Retrieve y dspy.ChainOfThought) y un método de avance (forward) para describir el flujo de control de responder a la pregunta utilizando estos módulos.


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

<p>self.retrieve = dspy.Retrieve(k=num_passages)
self.generate_answer = dspy.ChainOfThought(GenerateAnswer)</p>

<p>def forward(self, pregunta):
contexto = self.retrieve(pregunta).passages
predicción = self.generate_answer(contexto=contexto, pregunta=pregunta)
return dspy.Prediction(contexto=contexto, respuesta=predicción.answer)</p>

Paso 5: Optimización de la canalización

Con la canalización definida, ahora podemos optimizarla utilizando los optimizadores de DSPy. En este ejemplo, utilizaremos el optimizador BootstrapFewShot, que genera y selecciona prompts efectivos para nuestros módulos en función de un conjunto de entrenamiento y un métrico de validación.


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

<p># Métrico de validación
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># Configura el optimizador
teleprompter = BootstrapFewShot(metric=validate_context_and_answer)</p>

<p># Compila el programa
compiled_rag = teleprompter.compile(RAG(), trainset=trainset)</p>

Paso 6: Evaluación de la canalización

Después de compilar el programa, es esencial evaluar su rendimiento en un conjunto de desarrollo para garantizar que cumpla con la precisión y confiabilidad deseada.


from dspy.evaluate import Evaluate

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

<p># Evalúa el programa RAG compilado
evaluation_result = evaluate(compiled_rag)</p>

<p>print(f&amp;quot;Resultado de la evaluación: {evaluation_result}&amp;quot;)</p>

Paso 7: Inspección de la historia del modelo

Para una comprensión más profunda de las interacciones del modelo, puedes revisar las generaciones más recientes inspeccionando la historia del modelo.


<p># Inspecciona la historia del modelo
turbo.inspect_history(n=1)</p>

Paso 8: Realización de predicciones

Con la canalización optimizada y evaluada, ahora puedes utilizarla para realizar predicciones sobre nuevas preguntas.


<p># Pregunta de ejemplo
pregunta = &amp;quot;¿Qué premio recibió el primer libro de Gary Zukav?&amp;quot;</p>

<p># Realiza una predicción utilizando el programa RAG compilado
predicción = compiled_rag(pregunta)</p>

<p>print(f&amp;quot;Pregunta: {pregunta}&amp;quot;)
print(f&amp;quot;Respuesta: {predicción.answer}&amp;quot;)
print(f&amp;quot;Contextos recuperados: {predicción.context}&amp;quot;)</p>

Ejemplo mínimo de trabajo con DSPy

Ahora, vamos a pasar por otro ejemplo mínimo de trabajo utilizando el conjunto de datos GSM8K y el modelo OpenAI GPT-3.5-turbo para simular tareas de prompting dentro de DSPy.

Configuración

Primero, asegúrate de que tu entorno esté configurado correctamente:


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

<p># Configura el LM
turbo = dspy.OpenAI(model=&#039;gpt-3.5-turbo-instruct&#039;, max_tokens=250)
dspy.settings.configure(lm=turbo)</p>

<p># Carga preguntas matemáticas del conjunto de datos GSM8K
gsm8k = GSM8K()
gsm8k_trainset, gsm8k_devset = gsm8k.train[:10], gsm8k.dev[:10]</p>

print(gsm8k_trainset)

El gsm8k_trainset y gsm8k_devset contienen una lista de ejemplos con cada ejemplo que tiene un campo de pregunta y respuesta.

Definir el módulo

A continuación, define un programa personalizado que utilice el módulo ChainOfThought para razonamiento paso a paso:


<p>class CoT(dspy.Module):
def __init__(self):
super().__init__()
self.prog = dspy.ChainOfThought(&amp;quot;pregunta -&amp;amp;amp;amp;amp;gt; respuesta&amp;quot;)</p>

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

Compilación y evaluación del modelo

Ahora, compila con el optimizador BootstrapFewShot:


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

<p># Configura el optimizador
config = dict(max_bootstrapped_demos=4, max_labeled_demos=4)</p>

<p># Optimiza utilizando el gsm8k_metric
teleprompter = BootstrapFewShot(metric=gsm8k_metric, **config)
optimized_cot = teleprompter.compile(CoT(), trainset=gsm8k_trainset)</p>

<p># Configura el evaluador
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># Inspecciona la historia del modelo
turbo.inspect_history(n=1)</p>

Este ejemplo demuestra cómo configurar tu entorno, definir un módulo personalizado, compilar un modelo y evaluar rigurosamente su rendimiento utilizando el conjunto de datos y la configuración del teleprompter proporcionados.

Gestión de datos en DSPy

DSPy opera con conjuntos de entrenamiento, desarrollo y prueba. Para cada ejemplo en tus datos, típicamente tienes tres tipos de valores: entradas, etiquetas intermedias y etiquetas finales. Mientras que las etiquetas intermedias o finales son opcionales, tener algunas entradas de ejemplo es esencial.

Creación de objetos de ejemplo

Los objetos de ejemplo en DSPy son similares a los diccionarios de Python, pero vienen con utilidades útiles:


<p>qa_pair = dspy.Example(pregunta=&amp;quot;Esta es una pregunta?&amp;quot;, respuesta=&amp;quot;Esta es una respuesta.&amp;quot;)</p>

<p>print(qa_pair)
print(qa_pair.pregunta)
print(qa_pair.respuesta)</p>

Salida:


<p>Example({&#039;pregunta&#039;: &#039;Esta es una pregunta?&#039;, &#039;respuesta&#039;: &#039;Esta es una respuesta.&#039;}) (input_keys=None)
Esta es una pregunta?
Esta es una respuesta.</p>

Especificar claves de entrada

En DSPy, los objetos de ejemplo tienen un método with_inputs() para marcar campos específicos como entradas:


<p>print(qa_pair.with_inputs(&amp;quot;pregunta&amp;quot;))
print(qa_pair.with_inputs(&amp;quot;pregunta&amp;quot;, &amp;quot;respuesta&amp;quot;))</p>

Los valores se pueden acceder utilizando el operador de punto, y los métodos como inputs() y labels() devuelven nuevos objetos de ejemplo que contienen solo claves de entrada o no de entrada, respectivamente.

Optimizadores en DSPy

Un optimizador de DSPy ajusta los parámetros de un programa de DSPy (es decir, prompts y/o pesos de LLM) para maximizar métricos especificados. DSPy ofrece varios optimizadores integrados, cada uno con estrategias diferentes.

Optimizadores disponibles

  • BootstrapFewShot: Genera ejemplos de few-shot utilizando datos de entrada y salida etiquetados.
  • BootstrapFewShotWithRandomSearch: Aplica BootstrapFewShot varias veces con búsqueda aleatoria sobre demostraciones generadas.
  • COPRO: Genera y refina nuevas instrucciones para cada paso, optimizándolas con ascenso de coordenadas.
  • MIPRO: Optimiza instrucciones y ejemplos de few-shot utilizando optimización bayesiana.

Selección de un optimizador

Si no estás seguro de por dónde empezar, utiliza BootstrapFewShotWithRandomSearch:

Para muy pocos datos (10 ejemplos), utiliza BootstrapFewShot.
Para un poco más de datos (50 ejemplos), utiliza BootstrapFewShotWithRandomSearch.
Para conjuntos de datos más grandes (300+ ejemplos), utiliza MIPRO.

Aquí está cómo utilizar 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>

Guardado y carga de programas optimizados

Después de ejecutar un programa a través de un optimizador, guárdalo para su uso futuro:

optimized_program.save(YOUR_SAVE_PATH)

Carga un programa guardado:


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

Características avanzadas: Asertaciones de DSPy

Las asertaciones de DSPy automatizan la aplicación de restricciones computacionales en LLM, mejorando la confiabilidad, previsibilidad y corrección de las salidas de LLM.

Uso de asertaciones

Define funciones de validación y declara asertaciones después de la generación del modelo. Por ejemplo:


<p>dspy.Suggest(
len(query) &amp;amp;amp;amp;lt;= 100,
&amp;quot;La consulta debe ser corta y tener menos de 100 caracteres&amp;quot;,
)</p>

<p>dspy.Suggest(
validate_query_distinction_local(prev_queries, query),
&amp;quot;La consulta debe ser distinta de: &amp;quot; + &amp;quot;; &amp;quot;.join(f&amp;quot;{i+1}) {q}&amp;quot; for i, q in enumerate(prev_queries)),
)</p>

Transformación de programas con asertaciones


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

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

Alternativamente, activa las asertaciones directamente en el programa:


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

Optimizaciones impulsadas por asertaciones

Las asertaciones de DSPy funcionan con las optimizaciones de DSPy, particularmente con BootstrapFewShotWithRandomSearch, incluyendo configuraciones como:

  • Compilación con asertaciones
  • Compilación + inferencia con asertaciones

Conclusión

DSPy ofrece un enfoque poderoso y sistemático para optimizar modelos de lenguaje y sus prompts. Al seguir los pasos descritos en estos ejemplos, puedes construir, optimizar y evaluar sistemas de inteligencia artificial complejos con facilidad. La arquitectura modular de DSPy y sus optimizadores avanzados permiten una integración eficiente y efectiva de varios modelos de lenguaje, lo que lo convierte en una herramienta valiosa para cualquier persona que trabaje en el campo del NLP y la inteligencia artificial.

Ya sea que estés construyendo un sistema de preguntas y respuestas simple o una canalización más compleja, DSPy proporciona la flexibilidad y la robustez necesarias para lograr un alto rendimiento y confiabilidad.

He dedicado los últimos cinco años sumergiéndome en el fascinante mundo de Machine Learning y Deep Learning. Mi pasión y experiencia me han llevado a contribuir a más de 50 proyectos de ingeniería de software diversos, con un enfoque particular en AI/ML. Mi curiosidad en curso también me ha llevado hacia el Procesamiento de Lenguaje Natural, un campo que estoy ansioso por explorar más a fondo.