🛠️ MÓDULO 6: Proyecto guiado — Construye tu primer sistema de QA (Question Answering)

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


Lección 6.1 — ¿Qué es el QA extractivo? La diferencia entre "generar" y "extraer"

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:

  • No requiere fine-tuning (hay modelos preentrenados excelentes).
  • La respuesta es siempre fiel al texto fuente (no alucina).
  • Ideal para documentos técnicos, legales, manuales, artículos, etc.

Lección 6.2 — Elegir el modelo: nuestro aliado será "deepset/roberta-base-squad2"

Para este proyecto, usaremos un modelo de tipo encoder-only, específicamente entrenado para QA extractivo.

🔹 Modelo elegido: deepset/roberta-base-squad2

  • Basado en RoBERTa (variante mejorada de BERT).
  • Entrenado en SQuAD 2.0 (dataset de QA con preguntas que a veces no tienen respuesta).
  • Soporta español razonablemente bien (aunque fue entrenado principalmente en inglés).
  • Liviano (base), corre en CPU sin problemas.

🌐 Puedes verlo en el Model Hub


Lección 6.3 — Paso 1: Configuración inicial y carga del modelo

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)

Lección 6.4 — Paso 2: Preparar el contexto y la pregunta

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

Lección 6.5 — Paso 3: Usar el pipeline (la forma más fácil)

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.


Lección 6.6 — Paso 4: Hacerlo manualmente (para entender el proceso)

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:

  • La posición (token) donde empieza la respuesta.
  • La posición (token) donde termina la respuesta.

Luego, extrae todos los tokens entre esas dos posiciones.


Lección 6.7 — Paso 5: Mejorar la robustez — manejar múltiples candidatos

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.


Lección 6.8 — Paso 6: Probar con diferentes contextos y preguntas

¡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í"

Lección 6.9 — Paso 7: Limitaciones y cómo superarlas

🔹 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:

  • Usa el parámetro handle_impossible_answer=True (si el modelo lo soporta, como SQuAD 2.0).
  • O filtra por score: si el score es bajo (< 0.1), responde "No sé" o "No está en el texto".
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:

  • Divide el texto en fragmentos solapados.
  • Haz la pregunta en cada fragmento.
  • Elige la respuesta con mayor score.

✍️ Ejercicio de reflexión 6.1

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?

📊 Diagrama conceptual 6.1 — Flujo del sistema QA (descrito)

[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]

🧠 Conclusión del Módulo 6

¡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.


Course Info

Course: AI-course2

Language: ES

Lesson: Module6