Concepto clave
La Optimización Directa de Preferencias (DPO) es una técnica que reformula el problema de alineación de modelos de lenguaje con preferencias humanas como un problema de clasificación simple, evitando la complejidad del aprendizaje por refuerzo tradicional. En lugar de entrenar un modelo de recompensa separado y luego usar algoritmos como PPO, DPO optimiza directamente el modelo de lenguaje para maximizar la probabilidad de respuestas preferidas sobre las no preferidas.
Imagina que estás entrenando a un asistente virtual para dar respuestas útiles. En lugar de recompensar cada acción individual (como en RL tradicional), simplemente muestras pares de respuestas: una buena y una mala. El modelo aprende a distinguir qué tipo de respuestas son preferidas por los humanos, ajustando sus parámetros para generar más respuestas como las buenas y menos como las malas. La magia matemática de DPO permite que esto se haga con una función de pérdida simple que no requiere muestreo durante el entrenamiento.
Cómo funciona en la práctica
El proceso de implementación de DPO sigue estos pasos clave:
- Preparación de datos: Necesitas un dataset con tripletas (prompt, respuesta preferida, respuesta rechazada). Cada ejemplo muestra claramente qué respuesta es mejor según evaluadores humanos o criterios específicos.
- Configuración del modelo: Cargas un modelo base (como Llama 2 o Mistral) y su tokenizer. DPO funciona como fine-tuning supervisado, manteniendo la arquitectura original.
- Definición de la función de pérdida: Implementas la fórmula de pérdida DPO que compara las probabilidades logarítmicas de las respuestas preferidas vs. rechazadas, con un término de regularización que previene la desviación excesiva del modelo base.
- Entrenamiento: Optimizas directamente los parámetros del modelo de lenguaje usando la pérdida DPO, actualizando los pesos para que el modelo genere respuestas más alineadas con las preferencias humanas.
La fórmula central de DPO es:
L_DPO = -log(σ(β * (log π_θ(y_pref|x) - log π_ref(y_pref|x) - log π_θ(y_rej|x) + log π_ref(y_rej|x))))
Donde π_θ es el modelo que estamos entrenando, π_ref es el modelo de referencia (usualmente una copia congelada del modelo base), β controla la fuerza de la penalización por desviarse del modelo de referencia, y σ es la función sigmoide.
Caso de estudio
Vamos a aplicar DPO para mejorar un modelo de chat en español. Supongamos que tenemos un modelo base Mistral-7B y queremos que dé respuestas más útiles y menos tóxicas.
Dataset: Creamos 1000 ejemplos con prompts en español y pares de respuestas etiquetadas. Por ejemplo:
| Prompt | Respuesta preferida | Respuesta rechazada |
|---|---|---|
| "¿Cómo puedo mejorar mi código Python?" | "Te recomiendo revisar los principios PEP 8, usar type hints, y escribir tests unitarios. ¿Tienes algún código específico que quieras mejorar?" | "Python es fácil, solo escribe lo que funcione. No pierdas tiempo con buenas prácticas." |
| "Explícame la teoría de la relatividad" | "La teoría de la relatividad de Einstein propone que el tiempo y el espacio son relativos al observador. En términos simples..." | "Eso es muy complicado, mejor busca en Google porque yo no sé explicarlo bien." |
Resultados: Después de 3 épocas de entrenamiento DPO con β=0.1 y learning rate de 1e-5, el modelo genera respuestas:
- 35% más útiles según evaluación humana
- 60% menos probabilidad de generar contenido tóxico
- Mantiene 95% de la capacidad factual del modelo base
La clave fue balancear β: valores muy altos hacen que el modelo olvide conocimiento previo, valores muy bajos no logran suficiente alineación.
Errores comunes
- Dataset desbalanceado: Si todas tus respuestas preferidas son muy similares, el modelo aprenderá un patrón estrecho. Solución: Asegura diversidad en prompts y estilos de respuesta.
- β mal configurado: Un β muy alto (ej: 1.0) puede causar "catastrophic forgetting" donde el modelo pierde capacidades útiles. Un β muy bajo (ej: 0.01) no produce cambios significativos. Recomendación: Empezar con β=0.1 y ajustar según resultados.
- No congelar el modelo de referencia: Si accidentalmente entrenas también el modelo de referencia, la regularización KL no funciona. Siempre usa .requires_grad_(False) o .eval() en el modelo de referencia.
- Ignorar el formato de respuesta: DPO es sensible a cómo tokenizas las respuestas. Asegúrate de incluir tokens especiales (como [INST], [/INST] para Llama) consistentemente en ambas respuestas del par.
- Overfitting a preferencias específicas: Si entrenas demasiado (más de 5-10 épocas), el modelo puede memorizar tus ejemplos en lugar de generalizar. Usa early stopping y conjuntos de validación.
Checklist de dominio
- ✓ Puedo explicar la diferencia entre DPO y PPO en una oración
- ✓ He implementado la función de pérdida DPO desde cero en PyTorch
- ✓ Sé preparar un dataset de tripletas (prompt, preferida, rechazada) para DPO
- ✓ Entiendo el efecto del parámetro β en el trade-off entre alineación y retención de conocimiento
- ✓ Puedo debuggear problemas comunes como pérdida que no disminuye o overfitting
- ✓ Sé evaluar un modelo después de DPO usando métricas como win rate vs. modelo base
- ✓ Conozco las limitaciones de DPO (ej: no maneja preferencias múltiples o escalares)
Fine-tuning de un modelo para respuestas de código más seguras
En este ejercicio, implementarás DPO para mejorar un modelo pequeño (como Phi-2 o TinyLlama) que da consejos de programación, haciendo que sus respuestas sean más seguras (evitando sugerir código vulnerable).
- Prepara el entorno: Instala transformers, torch, datasets y trl. Crea un notebook Colab o script local.
- Carga el modelo: Usa AutoModelForCausalLM.from_pretrained() para cargar un modelo base pequeño. Crea una copia como modelo de referencia con .clone() y congélala.
- Genera datos sintéticos: Crea 50 ejemplos con este patrón:
Prompt: "¿Cómo implementar [función] en [lenguaje]?"
Respuesta preferida: Código seguro con validaciones y manejo de errores.
Respuesta rechazada: Código que funciona pero tiene vulnerabilidades (ej: SQL injection, buffer overflow). - Implementa DPO: Escribe la función compute_dpo_loss() que tome logits del modelo, logits del modelo de referencia, y máscaras de atención, y calcule la pérdida según la fórmula DPO.
- Entrena: Configura un optimizer (AdamW, lr=5e-6) y entrena por 2 épocas con batch size 4. Monitorea la pérdida.
- Evalúa: Genera respuestas para 5 prompts de test y compara con el modelo base. ¿Las respuestas son más seguras sin perder utilidad?
- Documenta: Registra tus hiperparámetros (β, lr, épocas) y resultados cualitativos.
- Para datos sintéticos, usa ChatGPT o similar generando pares de respuestas, luego etiquétalas manualmente como preferida/rechazada basado en seguridad.
- En la pérdida DPO, asegúrate de aplicar la máscara de atención antes de calcular los logprobs para ignorar padding tokens.
- Si la pérdida no disminuye, prueba reducir el learning rate o aumentar β ligeramente (ej: de 0.1 a 0.2).
Evalua tu comprension
Completa el quiz interactivo de arriba para ganar XP.