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ó.
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.
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.
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:
Los cuatro cuadrantes: dos tipos de aciertos (VP, VN) y dos tipos de errores (FP, FN). La accuracy solo cuenta los aciertos totales.
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%
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?
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).
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é?
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).
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é.
La media normal (aritmética) de precision y recall sería:
(0.60 + 0.75) / 2 = 0.675. ¿Cuál es el problema?
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:
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.
Cuando hay más de dos clases, hay tres formas de calcular el F1:
| Variante | Cómo funciona | Cuá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 |
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).
Formalmente, la perplexity de un modelo sobre una secuencia de palabras es la exponencial de la pérdida (cross-entropy loss) media por token:
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.
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.
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}")
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.
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)
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"],
}
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.
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.
| Problema | Síntoma | Soluciones |
|---|---|---|
| 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 |
Introduce los valores VP, FP, FN y VN y la calculadora muestra todas las métricas derivadas al instante.
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.