Duración estimada de este módulo: 1.5 - 2 horas
Objetivo: Construir un sistema completo de pregunta-respuesta extractiva, que recibe un texto de contexto y una pregunta, y devuelve la respuesta más probable extraída directamente del texto.
Requisitos: Solo necesitas lo aprendido en los módulos anteriores + un editor de código (o Google Colab).
Antes de empezar, aclaremos el tipo de QA que vamos a construir.
🔹 QA Generativo:
El modelo inventa una respuesta nueva, con sus propias palabras.
Pregunta: "¿Qué es un Transformer?"
Respuesta generada: "Un Transformer es una arquitectura de red neuronal que usa mecanismos de atención para procesar secuencias..."
🔹 QA Extractivo (el que haremos):
El modelo extrae un fragmento literal del texto de contexto.
Pregunta: "¿Qué es un Transformer?"
Contexto: "...el Transformer, introducido en 2017, es una arquitectura basada en atención que procesa todas las palabras simultáneamente..."
Respuesta extraída: "una arquitectura basada en atención que procesa todas las palabras simultáneamente"
✅ Ventaja del QA extractivo:
Para este proyecto, usaremos un modelo de tipo encoder-only, específicamente entrenado para QA extractivo.
🔹 Modelo elegido: deepset/roberta-base-squad2
🌐 Puedes verlo en el Model Hub
Primero, instalamos (si no lo hicimos antes) y cargamos el modelo y el tokenizer.
from transformers import AutoTokenizer, AutoModelForQuestionAnswering, pipeline
# Nombre del modelo
model_name = "deepset/roberta-base-squad2"
# Cargar tokenizer y modelo
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForQuestionAnswering.from_pretrained(model_name)
# Opcional: crear un pipeline para simplificar
qa_pipeline = pipeline("question-answering", model=model, tokenizer=tokenizer)
Vamos a usar un texto de ejemplo. Puede ser cualquier cosa: un artículo, un fragmento de libro, un manual, etc.
context = """
Los Transformers son una arquitectura de red neuronal introducida en 2017 por Vaswani et al.
en el artículo "Attention Is All You Need". A diferencia de las redes recurrentes,
los Transformers procesan todas las palabras de una secuencia simultáneamente,
usando un mecanismo llamado "atención" que permite a cada palabra relacionarse
con cualquier otra en la oración. Esta arquitectura es la base de modelos como
BERT, GPT, T5 y muchos otros que dominan hoy en día el procesamiento del lenguaje natural.
"""
question = "¿Qué mecanismo usan los Transformers para relacionar palabras?"
result = qa_pipeline(question=question, context=context)
print("Pregunta:", question)
print("Respuesta:", result['answer'])
print("Score:", result['score'])
print("Inicio:", result['start'])
print("Fin:", result['end'])
Salida esperada:
Pregunta: ¿Qué mecanismo usan los Transformers para relacionar palabras?
Respuesta: atención
Score: 0.9321
Inicio: 234
Fin: 242
¡Funciona! El modelo extrajo la palabra "atención" como respuesta.
Ahora, hagámoslo paso a paso, como en el Módulo 5, para ver qué ocurre internamente.
# Tokenizar pregunta + contexto (juntos)
inputs = tokenizer(question, context, return_tensors="pt", truncation=True)
# Pasar por el modelo
outputs = model(**inputs)
# Obtener logits de inicio y fin de la respuesta
start_logits = outputs.start_logits
end_logits = outputs.end_logits
# Encontrar las posiciones con mayor probabilidad
start_index = torch.argmax(start_logits)
end_index = torch.argmax(end_logits)
# Convertir los tokens de vuelta a texto
answer_tokens = inputs.input_ids[0][start_index:end_index + 1]
answer = tokenizer.decode(answer_tokens)
print("Respuesta (manual):", answer)
Salida:
Respuesta (manual): atención
🔹 ¿Qué hace el modelo?
Predice dos cosas:
Luego, extrae todos los tokens entre esas dos posiciones.
A veces, el modelo puede equivocarse si solo toma el start_index y end_index con mayor puntaje. Una mejor práctica es considerar combinaciones válidas (donde start <= end) y elegir la de mayor puntaje combinado.
import torch
def get_best_answer(start_logits, end_logits, input_ids, tokenizer, top_k=5):
# Tomar los top_k índices para inicio y fin
start_probs, start_indices = torch.topk(start_logits, top_k)
end_probs, end_indices = torch.topk(end_logits, top_k)
best_score = -float('inf')
best_answer = ""
# Probar combinaciones válidas
for i in range(top_k):
for j in range(top_k):
start = start_indices[i].item()
end = end_indices[j].item()
if start <= end: # válido
score = (start_probs[i] + end_probs[j]).item()
if score > best_score:
best_score = score
answer_tokens = input_ids[0][start:end+1]
best_answer = tokenizer.decode(answer_tokens, skip_special_tokens=True)
return best_answer, best_score
# Usar la función
answer, score = get_best_answer(start_logits, end_logits, inputs.input_ids, tokenizer)
print("Mejor respuesta:", answer)
print("Mejor score:", score)
Esto hace que el sistema sea más robusto ante errores puntuales.
¡Ahora es tu turno de experimentar! Prueba con:
context2 = """
La inteligencia artificial generativa permite crear contenido nuevo: texto, imágenes, música, código.
Modelos como DALL-E, Stable Diffusion y GPT-4 son ejemplos populares.
Estos modelos aprenden patrones de grandes conjuntos de datos y luego generan salidas originales
basadas en prompts o instrucciones dadas por el usuario.
"""
question2 = "¿Qué tipo de contenido puede crear la IA generativa?"
result2 = qa_pipeline(question=question2, context=context2)
print(result2['answer']) # Esperado: "texto, imágenes, música, código"
O en español:
context_es = """
Barcelona es una ciudad situada en la costa mediterránea de España.
Es conocida por su arquitectura única, especialmente las obras de Antoni Gaudí,
como la Sagrada Familia y el Parque Güell. También es famosa por su gastronomía,
sus playas y su vida cultural vibrante.
"""
question_es = "¿Qué arquitecto es famoso en Barcelona?"
result_es = qa_pipeline(question=question_es, context=context_es)
print(result_es['answer']) # Esperado: "Antoni Gaudí"
🔹 Limitación 1: El modelo solo puede responder si la respuesta está en el texto.
Si preguntas "¿Cuál es la capital de Francia?" y el texto no menciona París, el modelo puede inventar algo o dar una respuesta errónea.
🔹 Solución:
handle_impossible_answer=True (si el modelo lo soporta, como SQuAD 2.0). if result['score'] < 0.1:
print("No encontré una respuesta confiable en el texto.")
else:
print("Respuesta:", result['answer'])
🔹 Limitación 2: El contexto tiene límite de longitud (512 tokens aprox).
Si el texto es muy largo, se trunca y se pierde información.
🔹 Solución:
Toma un artículo de Wikipedia (o un capítulo de un libro) que te guste.
Copia 3-4 párrafos como contexto.
Formula 5 preguntas distintas (fáciles, difíciles, ambiguas).
Ejecuta el sistema QA y evalúa:
- ¿Cuántas respuestas son correctas?
- ¿En qué falla?
- ¿Cómo podrías mejorarlo?
[Pregunta + Contexto] → Tokenizer → input_ids → Modelo QA → start_logits + end_logits →
↑ ↑ ↑ ↑
Texto plano Convierte a IDs de tokens Predice posiciones
tokens + (pregunta + de inicio y fin
separadores contexto)
→ Selecciona mejor combinación inicio-fin → Extrae tokens → Decodifica → [Respuesta final]
¡Felicidades! Acabas de construir un sistema de inteligencia artificial funcional, basado en uno de los modelos más avanzados del mundo (Transformer), sin necesidad de entrenar nada, sin GPUs caras, y en menos de 50 líneas de código.
Este sistema puede ser la base de:
- Un chatbot para manuales técnicos.
- Un asistente para leer artículos científicos.
- Un tutor que responde preguntas sobre un libro.
Y lo mejor: ¡ya sabes cómo funciona por dentro! No es una caja negra. Sabes qué es un embedding, qué hace la atención, cómo se codifica la posición, y cómo elige el modelo la respuesta.