Evaluación estratégica de modelos en el proceso KDD
2026-08-16
“No se puede mejorar lo que no se puede medir — pero medir mal puede ser peor que no medir.”
La Evaluación no es el paso final — es el guardián del conocimiento descubierto.
Escenario: Un modelo predice si un paciente tiene cáncer.
Resultado: \text{Accuracy} = \frac{950}{1000} = 95\%
Paradoja de la Exactitud
Un modelo con 95% de accuracy detecta 0 de los 50 enfermos.
Elegir la métrica incorrecta puede matar conocimiento valioso — y en medicina, pacientes.
Diagrama de resultados de un clasificador
Diagrama de distribucion de clasificacion
\begin{array}{c|cc} & \textbf{Predicho +} & \textbf{Predicho -} \\ \hline \textbf{Real +} & TP & FN \\ \textbf{Real -} & FP & TN \end{array}
| Símbolo | Nombre | Significado |
|---|---|---|
| TP | Verdadero Positivo | Enfermo → predicho Enfermo ✓ |
| TN | Verdadero Negativo | Sano → predicho Sano ✓ |
| FP | Falso Positivo | Sano → predicho Enfermo ✗ |
| FN | Falso Negativo | Enfermo → predicho Sano ✗ |
FN es el peor error
Preferimos más FP (alarmas falsas) antes que perder un caso real.
FP es el peor error
Preferimos más FN (fraudes no detectados) antes que bloquear clientes reales.
Para esta clase emplearemos un dataset sintético de diagnóstico médico desbalanceado (92% sanos, 8% enfermos) para ilustrar la importancia de las métricas más allá del accuracy.
Matriz de confusión:
[[272 3]
[ 20 5]]
TP=5, TN=272, FP=3, FN=20
Importante
No existe una métrica universalmente “correcta”. La elección depende del costo asimétrico del error.
\text{Accuracy} = \frac{TP + TN}{TP + TN + FP + FN}
Interpretación: Proporción de predicciones correctas sobre el total.
from sklearn.metrics import accuracy_score
acc = accuracy_score(y_test, y_pred)
print(f"Accuracy del modelo: {acc:.4f} ({acc*100:.2f}%)")
# El modelo "trivial" que siempre dice "sano"
y_trivial = np.zeros_like(y_test) # Siempre predice clase 0 (sano)
acc_trivial = accuracy_score(y_test, y_trivial)
print(f"Accuracy modelo trivial: {acc_trivial:.4f} ({acc_trivial*100:.2f}%)")Accuracy del modelo: 0.9233 (92.33%)
Accuracy modelo trivial: 0.9167 (91.67%)
Trampa del Accuracy
El modelo trivial puede tener accuracy igual o mayor que el modelo real, en datasets desbalanceados. ¿Cuál usarías en producción?
El modelo trivial tiene Recall = 0% en la clase positiva. No detecta ningún enfermo.
\text{Precision} = \frac{TP}{TP + FP}
Intuición: De todos los casos que el modelo dijo “positivo”, ¿cuántos realmente lo eran?
Alta Precision: - Pocas alarmas falsas - Cuando el modelo alerta, hay que hacerle caso - Importante cuando los FP son costosos
Ej: Spam → mejor no bloquear email legítimo
Baja Precision: - Muchas alarmas falsas - Modelo “gritador de lobo” - Pérdida de confianza del usuario
Ej: Fraude → muchos clientes bloqueados por error
\text{Recall} = \text{Sensibilidad} = \frac{TP}{TP + FN}
Intuición: De todos los casos que realmente eran positivos, ¿cuántos encontré?
Recall es crítico cuando los FN son costosos
Limitación: Un modelo que predice siempre positivo tiene Recall = 100%
\text{Si } \hat{y} = 1 \text{ siempre}: \quad \text{Recall} = \frac{TP}{TP + 0} = 1.0
\text{Specificity} = \frac{TN}{TN + FP}
Intuición: De todos los verdaderamente negativos, ¿cuántos identifiqué correctamente?
| Métrica | Denominator | ¿Qué protege? |
|---|---|---|
| Recall | TP + FN (todos los positivos reales) | Detectar los positivos |
| Specificity | TN + FP (todos los negativos reales) | Proteger a los negativos |
Complemento: \text{Tasa de Falsos Positivos} = 1 - \text{Specificity} = \frac{FP}{TN + FP}
En medicina: Sensibilidad = no perder enfermos, Especificidad = no alarmar sanos.
from sklearn.metrics import classification_report, precision_score, recall_score
cm_vals = confusion_matrix(y_test, y_pred)
TP = cm_vals[1, 1]
TN = cm_vals[0, 0]
FP = cm_vals[0, 1]
FN = cm_vals[1, 0]
precision = TP / (TP + FP)
recall = TP / (TP + FN)
specificity = TN / (TN + FP)
print(f"TP={TP}, TN={TN}, FP={FP}, FN={FN}")
print(f"\nPrecision: {precision:.4f} ({precision*100:.1f}%)")
print(f"Recall: {recall:.4f} ({recall*100:.1f}%)")
print(f"Specificity: {specificity:.4f} ({specificity*100:.1f}%)")TP=5, TN=272, FP=3, FN=20
Precision: 0.6250 (62.5%)
Recall: 0.2000 (20.0%)
Specificity: 0.9891 (98.9%)
A medida que sube el umbral: ↑ Precision, ↓ Recall. No hay umbral “correcto” — depende del costo del error.
F1 = 2 \cdot \frac{\text{Precision} \cdot \text{Recall}}{\text{Precision} + \text{Recall}}
¿Por qué media armónica y no aritmética?
La media armónica penaliza más los valores extremos:
\text{Si P=1.0, R=0.01}: \quad \bar{x} = 0.505 \quad \text{vs} \quad F1 = 0.0198
Cuándo usar F1
F1_{\text{macro}} = \frac{1}{C}\sum_{c=1}^{C} F1_c \qquad \text{(cada clase pesa igual)}
F1_{\text{micro}} = \frac{2 \cdot \sum TP_c}{2 \cdot \sum TP_c + \sum FP_c + \sum FN_c} \qquad \text{(cada instancia pesa igual)}
F1_{\text{weighted}} = \sum_{c=1}^{C} w_c \cdot F1_c, \quad w_c = \frac{|c|}{N} \qquad \text{(pondera por frecuencia)}
| Variante | Favorece | Usar cuando |
|---|---|---|
| Macro | Clases minoritarias | Todas las clases importan igual |
| Micro | Clases mayoritarias | Las instancias importan más que las clases |
| Weighted | Clases más frecuentes | Importancia proporcional a la frecuencia |
F1-Score (binario): 0.3030
precision recall f1-score support
Sano 0.93 0.99 0.96 275
Enfermo 0.62 0.20 0.30 25
accuracy 0.92 300
macro avg 0.78 0.59 0.63 300
weighted avg 0.91 0.92 0.90 300
\text{Eje X: FPR} = \frac{FP}{FP + TN} = 1 - \text{Specificity} \text{Eje Y: TPR} = \frac{TP}{TP + FN} = \text{Recall}
Interpretación probabilística del AUC:
P(\hat{p}(x^+) > \hat{p}(x^-))= \text{AUC}
Si AUC = 0.85, hay un 85% de probabilidad de que el modelo asigne mayor score a una instancia positiva que a una negativa escogida al azar.
¿Por qué Precision-Recall para clases desbalanceadas?
La curva ROC incluye TN en su denominador (especificidad). Con millones de TN, el FPR parece pequeño aunque haya muchos FP. La curva PR ignora los TN y se enfoca solo en la clase positiva (minoritaria).
| Criterio | ROC-AUC | Precision-Recall |
|---|---|---|
| Dataset balanceado | ✅ Ideal | ✅ Válida |
| Clase minoritaria importa | ⚠️ Puede ser optimista | ✅ Más informativa |
| Comparación general | ✅ Estándar | ✅ Más rigurosa |
| Interpretación intuitiva | ✅ Fácil (prob.) | ⚠️ Área promedio |
| Cuando TN son irrelevantes | ❌ Los incluye | ✅ Los ignora |
La mayoría de los clasificadores producen una probabilidad, no una clase directa:
\hat{y} = \begin{cases} 1 & \text{si } \hat{p}(x) \geq \theta \\ 0 & \text{si } \hat{p}(x) < \theta \end{cases}
El umbral \theta = 0.5 es una convención, no una verdad absoluta.
Bajar el umbral (\theta < 0.5): - ↑ Recall (más positivos detectados) - ↓ Precision (más falsos positivos) - Modelo más “alarmista”
Subir el umbral (\theta > 0.5): - ↓ Recall (menos positivos detectados) - ↑ Precision (más confiable cuando alerta) - Modelo más “conservador”
# Encontrar el umbral que maximiza el F1-Score
f1_scores = []
for u in np.arange(0.05, 0.95, 0.01):
y_u = (y_prob >= u).astype(int)
f1_scores.append(f1_score(y_test, y_u, zero_division=0))
umbral_optimo_f1 = np.arange(0.05, 0.95, 0.01)[np.argmax(f1_scores)]
print(f"Umbral óptimo (max F1): θ = {umbral_optimo_f1:.2f}")
# Para medicina: priorizar Recall >= 0.90
for u in np.arange(0.05, 0.95, 0.01):
y_u = (y_prob >= u).astype(int)
r = recall_score(y_test, y_u, zero_division=0)
p = precision_score(y_test, y_u, zero_division=0)
if r >= 0.90:
print(f"Umbral médico (Recall≥90%): θ={u:.2f} → P={p:.3f}, R={r:.3f}")
breakUmbral óptimo (max F1): θ = 0.23
| Situación | Métrica recomendada | Referencia clave |
|---|---|---|
| Dataset balanceado | Accuracy, F1 macro | Han et al. (2011) |
| Clase positiva rara (< 10 %) | F1 clase positiva, PR-AUC | Saito & Rehmsmeier (2015) |
| FN muy costoso (medicina, seguridad) | Recall / Sensibilidad | James et al. (2021) |
| FP muy costoso (spam, fraude leve) | Precision | Fawcett (2006) |
| FP y FN con costo similar | F1-Score | Han et al. (2011) |
| Comparar modelos sin fijar umbral | ROC-AUC | Fawcett (2006); Bamber (1975) |
| Clase escasa + interés en ranking | Precision-Recall AUC | Saito & Rehmsmeier (2015) |
| Multiclase, todas las clases importan igual | F1 macro | Pedregosa et al. (2011) |
El modelo médico tiene menor accuracy pero mayor recall. ¿Cuál es mejor para el médico?
Un banco detecta fraudes con 99.9% de accuracy. El dataset tiene 0.1% de fraudes reales. ¿Confiarías en ese modelo?
Un modelo A tiene ROC-AUC = 0.95 y otro modelo B tiene ROC-AUC = 0.80. Para detectar una enfermedad rara (1 en 10,000), ¿cuál es “mejor”?
Si el costo de un FN es 100 veces mayor que el costo de un FP, ¿cómo ajustarías el umbral de decisión?
¿Por qué un F1-Score macro puede ser peor métrica que el F1-Score de la clase positiva en un problema de detección de anomalías?
¿Puede un modelo tener AUC-ROC = 0.95 pero ser inútil en producción? Explica el escenario.
Han, J., Kamber, M., & Pei, J. (2011). Data Mining: Concepts and Techniques (3rd ed.). Morgan Kaufmann.
Fawcett, T. (2006). An introduction to ROC analysis. Pattern Recognition Letters, 27(8), 861–874.
Saito, T., & Rehmsmeier, M. (2015). The precision-recall plot is more informative than the ROC plot when evaluating binary classifiers on imbalanced datasets. PLOS ONE, 10(3), e0118432.
Provost, F., & Fawcett, T. (2013). Data Science for Business. O’Reilly Media.
James, G., Witten, D., Hastie, T., & Tibshirani, R. (2021). An Introduction to Statistical Learning (2nd ed.). Springer.
Pedregosa, F., et al. (2011). Scikit-learn: Machine Learning in Python. Journal of Machine Learning Research, 12, 2825–2830.
“Elige la métrica que penalice el error que más te cuesta — y desconfía de cualquier número que parezca demasiado bueno.”
Material disponible: slides + código Python ejecutable
¿Preguntas?

Métricas de Clasificación — Minería de Datos