LECCIÓN 8 · BLOQUE 2

Evaluación y métricas

Después de entrenar el modelo, ¿cómo sabes si realmente funciona? La accuracy sola casi siempre miente. Esta lección enseña las métricas que sí dicen la verdad y cómo detectar si el modelo aprendió o memorizó.

Recuerda de las lecciones anteriores: en la Lección 6 preparaste el dataset (train/validation/test) y en la Lección 7 configuraste el Trainer con EarlyStopping. Cuando el Trainer para, reporta una métrica — normalmente accuracy. Hoy aprendemos por qué esa métrica puede engañarte y qué métricas usar en su lugar.

1. Por qué accuracy sola no es suficiente

La accuracy (exactitud) es la métrica más simple: de todos los ejemplos que el modelo procesó, ¿en qué porcentaje acertó? Parece perfecta. Y para muchos problemas simples, lo es. Pero hay un caso donde engaña terriblemente: cuando las clases están desbalanceadas.

✍️ El detector de cáncer que nunca detecta cáncer — con números reales
Dataset: 1.000 pacientes. 990 sanos, 10 con cáncer.

Modelo A (nuestro clasificador): predice siempre "sano"
  Acierta en los 990 sanos   → 990 correctos
  Falla en los 10 con cáncer →  10 incorrectos
  Accuracy = 990 / 1000 = 99% ← ¡parece increíble!

Pero este modelo NO detecta cáncer nunca.
  De los 10 enfermos, detectó: 0 (cero)
  Esto podría costar vidas.

Modelo B (el útil): accuracy = 94%
  Detecta 8 de los 10 enfermos
  Se equivoca en algunos sanos

El modelo B es mucho mejor aunque tenga MENOR accuracy.

Esto no es un problema de modelos de IA solamente — es un problema matemático fundamental de medir con la herramienta incorrecta. Para problemas con clases desbalanceadas o donde los errores no tienen el mismo coste, necesitamos Precision, Recall y F1.

2. La matriz de confusión

La matriz de confusión es una tabla que muestra todos los tipos de aciertos y errores del modelo de un vistazo. Para un problema de dos clases (positivo y negativo), la tabla tiene 2×2 celdas:

MATRIZ DE CONFUSIÓN — LECTURA COMPLETA
Matriz de confusión 2x2 con etiquetas de VP, FP, FN, VN y sus definiciones Tabla de 2x2 con predicciones del modelo en columnas y valores reales en filas, mostrando Verdaderos Positivos, Falsos Positivos, Falsos Negativos y Verdaderos Negativos. PREDICCIÓN DEL MODELO Predice POSITIVO Predice NEGATIVO REALIDAD ES POSITIVO (ej: tiene cáncer) ES NEGATIVO (ej: está sano) VP Verdadero Positivo ✓ Acierto FN Falso Negativo ✗ Lo perdí FP Falso Positivo ✗ Alarma falsa VN Verdadero Negativo ✓ Acierto Tenía cáncer → dijimos sí ✓ Tenía cáncer → dijimos no ✗ Estaba sano → dijimos sí ✗ Estaba sano → dijimos no ✓ accuracy = (VP + VN) / (VP + VN + FP + FN)

Los cuatro cuadrantes: dos tipos de aciertos (VP, VN) y dos tipos de errores (FP, FN). La accuracy solo cuenta los aciertos totales.

✍️ Ejemplo concreto con el detector de spam — llenar la matriz
Tenemos 200 correos para evaluar:
  · 180 son correos normales (negativos)
  · 20 son spam (positivos)

Nuestro modelo predijo:
  · De los 20 spam: detectó 15 como spam, perdió 5 (los clasificó como normal)
  · De los 180 normales: clasificó 170 bien, pero marcó 10 como spam por error

Resultado en la matriz:
                    Predice SPAM    Predice NORMAL
  ES SPAM (20)     VP = 15         FN = 5
  ES NORMAL (180)  FP = 10         VN = 170

Comprobación: 15 + 5 + 10 + 170 = 200 ✓ (todos los correos)

Accuracy = (VP + VN) / total = (15 + 170) / 200 = 185/200 = 92.5%

3. Precision — ¿cuántos de mis alertas son reales?

La precision (precisión) responde a esta pregunta: de todos los ejemplos que el modelo marcó como positivos, ¿qué porcentaje eran realmente positivos?

En el ejemplo del detector de spam: de todos los correos que el modelo marcó como spam (VP + FP = 15 + 10 = 25 correos), ¿cuántos eran spam de verdad?

Precision = VP / (VP + FP)
✍️ Cálculo de precision — paso a paso
Del ejemplo anterior:
  VP = 15  (spam que detectamos correctamente)
  FP = 10  (normales que marcamos como spam — alarma falsa)

  Precision = VP / (VP + FP)
            = 15 / (15 + 10)
            = 15 / 25
            = 0.60 = 60%

Interpretación: de cada 100 correos que nuestro modelo dice que son spam,
                60 son spam de verdad y 40 son correos normales inocentes.

¿Es buena esta precision?
  → Para un filtro de spam de email: NO. Perderías muchos correos importantes.
  → Queremos precision alta: pocas alarmas falsas (FP pequeño).
Analogía: la precision es como la reputación del "chico que llama al lobo". Si cada vez que gritas "¡lobo!" hay lobo de verdad, tu precision es alta. Si gritas muchas veces y solo a veces hay lobo, tu precision es baja. Alta precision = pocas falsas alarmas.

4. Recall — ¿cuántos de los casos reales encontré?

El recall (también llamado "sensibilidad" o sensitivity en inglés) responde a esta pregunta: de todos los casos que eran realmente positivos, ¿qué porcentaje detecté?

En el ejemplo del detector de spam: de los 20 correos spam que existían en total, ¿cuántos encontré?

Recall = VP / (VP + FN)
✍️ Cálculo de recall — paso a paso
Del ejemplo anterior:
  VP = 15  (spam que detectamos correctamente)
  FN = 5   (spam que no detectamos — se nos escapó)

  Recall = VP / (VP + FN)
         = 15 / (15 + 5)
         = 15 / 20
         = 0.75 = 75%

Interpretación: de cada 100 correos spam que existen, nuestro modelo
                encuentra 75 y pierde 25.

¿Es bueno este recall?
  → Para un detector de cáncer: NO. Perder 25 de cada 100 enfermos es inaceptable.
  → Para spam: razonable, pero podría ser mejor.
  → Queremos recall alto: que ningún caso positivo se escape (FN pequeño).
Analogía: el recall es como la cobertura de un guardabosque. Si hay 100 incendios y el guardabosque detecta 90, su recall es 90%. No importa cuántas falsas alarmas dé — lo que importa es no perder un incendio real.
El dilema precision-recall: generalmente, cuando sube uno, baja el otro. Si el modelo se vuelve muy "agresivo" (marca todo como positivo), el recall sube (encuentra todos los positivos) pero la precision cae (muchas falsas alarmas). Si el modelo es muy "conservador" (solo marca como positivo cuando está muy seguro), la precision sube pero el recall cae. El F1 score equilibra esta tensión.

5. F1 score — el equilibrio entre precision y recall

El F1 score combina precision y recall en un solo número. Usa algo llamado media armónica — no la media normal — y ahora entendemos por qué.

Por qué no usar la media normal

La media normal (aritmética) de precision y recall sería: (0.60 + 0.75) / 2 = 0.675. ¿Cuál es el problema?

✍️ Por qué la media normal puede engañar
Caso extremo:
  Modelo A: Precision = 1.0, Recall = 0.01
  (muy conservador — casi nunca predice positivo, pero cuando lo hace es correcto)

  Media normal: (1.0 + 0.01) / 2 = 0.505 = 50.5%

¿Pero este modelo es útil? Tiene recall de 1% — encuentra 1 de cada 100 casos.
¡Un modelo así es prácticamente inútil!

Media armónica (F1): 2 × (1.0 × 0.01) / (1.0 + 0.01) = 2 × 0.01 / 1.01 = 0.0198 ≈ 2%

La media armónica castiga duramente cuando uno de los dos valores es muy bajo.
Un F1 de 2% refleja correctamente que el modelo es casi inútil.

La media armónica se lee así: "dos veces el producto de A y B, dividido entre la suma de A y B". En fórmula:

F1 = 2 × (Precision × Recall) / (Precision + Recall)
✍️ Cálculo de F1 para nuestro detector de spam
Precision = 0.60
Recall    = 0.75

F1 = 2 × (Precision × Recall) / (Precision + Recall)
   = 2 × (0.60 × 0.75) / (0.60 + 0.75)
   = 2 × 0.45 / 1.35
   = 0.90 / 1.35
   = 0.667 = 66.7%

Compara:
  · Accuracy = 92.5% (optimista, engaña)
  · F1       = 66.7% (más honesto con el dataset desbalanceado)

El F1 revela que el modelo, aunque tenga 92.5% de accuracy,
tiene bastante trabajo por mejorar en la clase spam.

F1 macro vs F1 micro vs F1 weighted

Cuando hay más de dos clases, hay tres formas de calcular el F1:

VarianteCómo funcionaCuándo usarla
F1 macro Calcula F1 para cada clase y toma la media. Trata todas las clases igual. Cuando todas las clases importan igual (aunque sean raras)
F1 micro Suma todos los VP, FP, FN de todas las clases y calcula un F1 global. Cuando quieres el rendimiento global considerando frecuencia
F1 weighted Pondera cada F1 de clase por cuántos ejemplos tiene esa clase. El más común en datasets desbalanceados

6. Perplexity para modelos de lenguaje

Las métricas anteriores (precision, recall, F1) son para clasificación. Pero si estás evaluando un modelo generativo (que predice la siguiente palabra), no hay clases — hay probabilidades sobre todo el vocabulario. Para eso existe la perplexity (perplejidad).

Intuición: la perplexity mide qué tan "sorprendido" está el modelo cuando ve texto real. Si el modelo dice "la próxima palabra tiene 90% de probabilidad de ser 'casa'" y efectivamente aparece "casa", no está sorprendido — perplexity baja. Si el modelo dice "la próxima palabra tiene 0.01% de ser 'casa'" y aparece "casa", está muy sorprendido — perplexity alta.

Formalmente, la perplexity de un modelo sobre una secuencia de palabras es la exponencial de la pérdida (cross-entropy loss) media por token:

Perplexity = e^(loss_promedio) = 2.718…^(loss_promedio)

Aquí e es el número de Euler, aproximadamente 2.718 (lo estudiaste en el curso de cálculo como la base del logaritmo natural). Y loss_promedio es el cross-entropy loss promedio por token.

✍️ Cómo interpretar la perplexity con números reales
Frase: "El gato se sienta en la"

El modelo predice la siguiente palabra con estas probabilidades:
  "silla":   35%
  "mesa":    28%
  "cama":    18%
  "alfombra": 12%
  ...otras:    7%

La palabra real es "alfombra" (12% de probabilidad)
  loss = -log(0.12) = 2.12 nats
  Perplexity para este token = e^2.12 = 8.3

Interpretación de valores típicos:
  · Perplexity ≈ 2:  el modelo casi siempre sabe la siguiente palabra (muy bueno)
  · Perplexity ≈ 10: el modelo tiene dudas razonables (bueno para texto general)
  · Perplexity ≈ 50: el modelo está bastante confundido (modelo mediocre)
  · Perplexity ≈ 200: el modelo no sabe nada (equivale a elegir al azar en vocab de 200)

Modelos de referencia:
  · GPT-2 (774M): perplexity ≈ 18 en WikiText-103
  · GPT-3 (175B): perplexity ≈ 20 en PennTreebank
  · LLaMA-7B:     perplexity ≈ 12 en WikiText-2

⚠️ Solo compara perplexity entre modelos evaluados en el MISMO dataset.

Calcular perplexity con HuggingFace

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
import math

model_name = "gpt2"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)
model.eval()

texto = "La inteligencia artificial está transformando cómo trabajamos y vivimos cada día."
inputs = tokenizer(texto, return_tensors="pt")

with torch.no_grad():
    outputs = model(**inputs, labels=inputs["input_ids"])
    loss = outputs.loss  # cross-entropy loss promedio por token

perplexity = math.exp(loss.item())
print(f"Loss promedio: {loss.item():.4f}")
print(f"Perplexity:    {perplexity:.2f}")
salida realLoss promedio: 3.2841 Perplexity: 26.72

GPT-2 obtiene una perplexity de 26.7 para este texto en español — no sorprende, porque GPT-2 fue entrenado principalmente en inglés. Un modelo entrenado en español tendría perplexity mucho menor para este texto.

7. La librería evaluate de HuggingFace

En vez de calcular estas métricas a mano, HuggingFace tiene la librería evaluate que incluye implementaciones oficiales de decenas de métricas. Las cargamos con evaluate.load() y las usamos directamente.

import evaluate
import numpy as np

# Ejemplo con datos simulados de clasificación binaria
predicciones = [1, 0, 1, 1, 0, 1, 0, 0, 1, 1]
etiquetas_reales = [1, 0, 1, 0, 0, 1, 1, 0, 1, 0]

# Accuracy
metric_acc = evaluate.load("accuracy")
resultado_acc = metric_acc.compute(predictions=predicciones, references=etiquetas_reales)
print("Accuracy:", resultado_acc)

# F1
metric_f1 = evaluate.load("f1")
resultado_f1 = metric_f1.compute(
    predictions=predicciones,
    references=etiquetas_reales,
    average="weighted"   # o "macro" o "micro"
)
print("F1 weighted:", resultado_f1)

# Precision y Recall
metric_prec = evaluate.load("precision")
metric_rec  = evaluate.load("recall")

resultado_prec = metric_prec.compute(predictions=predicciones, references=etiquetas_reales, average="weighted")
resultado_rec  = metric_rec.compute(predictions=predicciones, references=etiquetas_reales, average="weighted")

print("Precision:", resultado_prec)
print("Recall:",    resultado_rec)
salida realAccuracy: {'accuracy': 0.7} F1 weighted: {'f1': 0.6857142857142857} Precision: {'precision': 0.7142857142857143} Recall: {'recall': 0.7}

Usar varias métricas juntas en el Trainer

def compute_metrics(eval_pred):
    logits, labels = eval_pred
    preds = np.argmax(logits, axis=-1)

    # Cargar varias métricas
    acc   = evaluate.load("accuracy")
    f1    = evaluate.load("f1")
    prec  = evaluate.load("precision")
    rec   = evaluate.load("recall")

    return {
        "accuracy":  acc.compute(predictions=preds, references=labels)["accuracy"],
        "f1":        f1.compute(predictions=preds, references=labels, average="weighted")["f1"],
        "precision": prec.compute(predictions=preds, references=labels, average="weighted")["precision"],
        "recall":    rec.compute(predictions=preds, references=labels, average="weighted")["recall"],
    }
salida real (en el Trainer){'eval_accuracy': 0.9134, 'eval_f1': 0.9118, 'eval_precision': 0.9129, 'eval_recall': 0.9134, 'eval_loss': 0.2581, 'epoch': 3.0}

8. Overfitting vs underfitting

Hay dos formas de que un modelo fracase. Ambas se detectan observando las curvas de entrenamiento — cómo evoluciona el loss (pérdida) en train y validation a lo largo de las épocas.

CURVAS DE ENTRENAMIENTO — LOS TRES ESCENARIOS
Tres gráficas mostrando underfitting, ajuste correcto y overfitting según las curvas de train loss y val loss Tres paneles: izquierda muestra underfitting donde ambas curvas quedan altas; centro muestra ajuste correcto donde ambas bajan juntas; derecha muestra overfitting donde train sigue bajando pero val sube. épocas → UNDERFITTING (el modelo no aprende) Ambas curvas altas → modelo demasiado simple train val CORRECTO ✓ (aprende y generaliza) Ambas bajan juntas → modelo generaliza bien OVERFITTING (memoriza los datos) mejor val sube mientras train baja → memorizando, no aprendiendo

El punto donde el val loss empieza a subir (panel derecho, círculo) es el momento óptimo de parada — que es exactamente donde actúa el EarlyStopping.

ProblemaSíntomaSoluciones
Underfitting Train loss y val loss altos. El modelo no mejora. Más épocas, learning rate mayor, modelo más grande, revisar datos
Overfitting Train loss baja, val loss sube. Divergen las curvas. EarlyStopping, más datos, weight decay, dropout, reducir épocas
Correcto Ambas curvas bajan juntas y se estabilizan. Monitorear, ajustar si es necesario

🎮 Pruébalo: calculadora de matriz de confusión

Introduce los valores VP, FP, FN y VN y la calculadora muestra todas las métricas derivadas al instante.

85
15
20
180

10. Lo que aprendiste

Lo que aprendiste hoy: accuracy es engañosa cuando las clases están desbalanceadas — el "detector de cáncer" que nunca detecta cáncer tiene 99% de accuracy. La matriz de confusión desglosa los aciertos y errores en cuatro tipos: VP, FP, FN, VN. Precision mide qué fracción de tus alertas son reales (VP/(VP+FP)); Recall mide qué fracción de los casos reales encontraste (VP/(VP+FN)). El F1 los combina con la media armónica, que castiga cuando uno de los dos es muy bajo. Para modelos generativos, la Perplexity mide qué tan "sorprendido" está el modelo (más baja es mejor). El overfitting ocurre cuando el train loss baja pero el val loss sube — el EarlyStopping lo maneja automáticamente.

En la próxima lección: Por qué fine-tuning completo es caro — cuánta memoria necesita cada componente del entrenamiento, por qué los modelos grandes no caben en una GPU normal, y por qué eso motiva las técnicas eficientes que vienen después.