ml · temario
2.3tema 3 de 4

Model Evaluation — Metrics, Confusion Matrix, Class Imbalance

Más allá del accuracy: elegir la métrica correcta cambia qué modelo construyes.

El accuracy es la métrica más intuitiva y la más peligrosa cuando las clases están desbalanceadas. Si el 95 % de tus ejemplos son clase negativa, un modelo que predice 'negativo' siempre tiene 95 % de accuracy sin haber aprendido nada. La pregunta correcta no es '¿cuántos acerté?' sino '¿qué tipo de errores estoy cometiendo y cuál me importa más?'

La matriz de confusión es la herramienta fundamental para responder esa pregunta. Tiene cuatro celdas: verdaderos positivos (TP), falsos positivos (FP), verdaderos negativos (TN) y falsos negativos (FN). De ella se derivan todas las métricas relevantes. Precision = TP / (TP + FP): de los que predije como positivos, ¿cuántos lo eran realmente? Recall = TP / (TP + FN): de todos los positivos reales, ¿cuántos encontré?

El F1-score es la media armónica de precision y recall. Es útil cuando quieres un balance entre ambas, pero en muchos problemas de negocio una importa más que la otra. En detección de fraude o diagnóstico médico, el FN (dejar pasar un fraude o no detectar una enfermedad) es mucho más costoso que el FP. En ese caso, optimizar recall es la decisión correcta, incluso si precision baja.

ROC-AUC mide la capacidad del modelo de ordenar los ejemplos positivos por encima de los negativos, independientemente del umbral de decisión. Un AUC de 0.5 equivale a un modelo aleatorio; 1.0 es perfecto. PR-AUC (curva precision-recall) es más informativa que ROC-AUC cuando la clase positiva es muy minoritaria, porque ROC puede ser optimistamente alto en datasets muy desbalanceados.

El desbalance de clases es la regla, no la excepción, en problemas reales: detección de fraude, churn, diagnóstico clínico. Las estrategias incluyen class_weight='balanced' en sklearn (penaliza más los errores en la clase minoritaria), SMOTE (genera ejemplos sintéticos de la clase minoritaria) y ajuste del umbral de decisión. Ver cómo estas técnicas se aplican al problema de predicción de fuga bancaria en: https://churn.benjacode.com

La elección de métrica debe hacerse antes de entrenar cualquier modelo, junto con el product owner o stakeholder. Es una decisión de negocio disfrazada de decisión técnica: ¿qué error es más costoso para tu problema?

structure.txt
# Métricas de clasificación
from sklearn.metrics import (
    classification_report,
    confusion_matrix,
    roc_auc_score,
    average_precision_score,
    f1_score
)

y_pred = model.predict(X_val)
y_proba = model.predict_proba(X_val)[:, 1]

print(classification_report(y_val, y_pred, digits=3))
print('ROC-AUC:', roc_auc_score(y_val, y_proba))
print('PR-AUC :', average_precision_score(y_val, y_proba))

# Matriz de confusión visual
import seaborn as sns, matplotlib.pyplot as plt
cm = confusion_matrix(y_val, y_pred)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.xlabel('Predicted'); plt.ylabel('Actual'); plt.show()

# Manejo de desbalance — class_weight
from sklearn.linear_model import LogisticRegression
model = LogisticRegression(class_weight='balanced', max_iter=1000)

# Manejo de desbalance — SMOTE
from imblearn.over_sampling import SMOTE
from imblearn.pipeline import Pipeline as ImbPipeline

pipe = ImbPipeline([
    ('smote', SMOTE(random_state=42)),
    ('model', LogisticRegression(max_iter=1000))
])
pipe.fit(X_train, y_train)  # SMOTE solo aplica en train

Debugging lab

Detecta y corrige el error en el código.

0/5 tests passing0%
  1. 2.3.5.1

    model.fit(X_train, y_train) print('Score:', model.score(X_test, y_test)) # 0.97 # Dataset: 97 % clase 0, 3 % clase 1. ¿Qué hay de malo?

  2. 2.3.5.2

    cm = confusion_matrix(y_test, y_pred) # cm = [[850, 50], [10, 90]] recall = cm[0][0] / (cm[0][0] + cm[0][1]) # ¿Cuál es el error?

  3. 2.3.5.3

    from imblearn.over_sampling import SMOTE smote = SMOTE(random_state=42) X_res, y_res = smote.fit_resample(X, y) # X incluye train+val+test X_train, X_test, y_train, y_test = train_test_split(X_res, y_res, test_size=0.2)

  4. 2.3.5.4

    scores = cross_val_score(model, X_train, y_train, cv=5, scoring='accuracy') # Clase minoritaria: 8 %. El equipo celebra 94 % de accuracy.

  5. 2.3.5.5

    model = LogisticRegression() model.fit(X_train, y_train) y_pred = model.predict(X_test) f1 = f1_score(y_test, y_pred) # 0.42, muy bajo # El equipo decide cambiar de modelo directamente a XGBoost