Módulo 3 · Aprendizaje supervisado
Baseline y métricas
En esta páginaEl problema
El problema
Un compañero entrenó un modelo que clasifica piezas de una línea de producción como buenas (clase 0) o defectuosas (clase 1). Cada pieza tiene seis mediciones sintéticas. Evaluó el modelo en 250 piezas de prueba que no usó para entrenar:
| clase real | piezas de prueba |
|---|---|
| buena (0) | 235 |
| defectuosa (1) | 15 |
| total | 250 |
El modelo acierta en 238 de las 250 piezas. Su reporte dice: «exactitud 0.952».
Exactitud y baseline
La exactitud es la proporción de predicciones correctas. Un número aislado no dice si el modelo aprendió algo. Hace falta un punto de comparación: un baseline, un predictor trivial que no mira las mediciones.
DummyClassifier(strategy="most_frequent") aprende solo cuál es la clase más frecuente en entrenamiento. Después predice esa clase para cada fila, sin importar X.
Las piezas se generan con make_classification: 1000 filas de seis mediciones, con un 94 % de piezas buenas. train_test_split aparta 250 para prueba, y stratify=y conserva en ambas partes la proporción de clases.
Mostrar códigoOcultar código
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
X, y = make_classification(
n_samples=1000,
n_features=6,
n_informative=3,
weights=[0.94],
class_sep=0.8,
flip_y=0,
random_state=3,
)
X_entrena, X_prueba, y_entrena, y_prueba = train_test_split(
X, y, test_size=250, stratify=y, random_state=0
)
X_entrena tiene forma (750, 6) y X_prueba, (250, 6): una fila por pieza y una columna por medición. np.bincount cuenta cuántas veces aparece cada clase en las etiquetas de prueba.
print(np.bincount(y_prueba))
La salida esperada está oculta.
LogisticRegression es el modelo del compañero. Cada predictor se ajusta con fit sobre los datos de entrenamiento, y predict devuelve una clase por pieza de prueba.
from sklearn.dummy import DummyClassifier
from sklearn.linear_model import LogisticRegression
modelo = LogisticRegression().fit(X_entrena, y_entrena)
baseline = DummyClassifier(strategy="most_frequent").fit(X_entrena, y_entrena)
pred_modelo = modelo.predict(X_prueba)
pred_baseline = baseline.predict(X_prueba)
accuracy_score compara las clases reales con las predichas y devuelve la proporción de coincidencias. np.unique lista las clases distintas que aparecen en las predicciones.
Pregunta. Con la tabla del problema, calcula la exactitud del baseline en las 250 piezas de prueba. Decide qué clase predice para todas.
from sklearn.metrics import accuracy_score
print(np.unique(pred_baseline))
print(f"modelo: {accuracy_score(y_prueba, pred_modelo):.3f}")
print(f"baseline: {accuracy_score(y_prueba, pred_baseline):.3f}")
La salida esperada está oculta.
Matriz de confusión
La exactitud mezcla dos tipos de acierto y dos tipos de error. La matriz de confusión los separa. En scikit-learn, cada fila es la clase real y cada columna es la clase predicha, en el orden 0, 1:
| predicha buena | predicha defectuosa | |
|---|---|---|
| real buena | VN | FP |
| real defectuosa | FN | VP |
VN son verdaderos negativos, FP falsos positivos, FN falsos negativos y VP verdaderos positivos. La clase positiva es la que se busca detectar: aquí, defectuosa.
confusion_matrix recibe las clases reales y las predichas y devuelve esa tabla como un arreglo de 2 × 2.
Pregunta. Escribe la matriz completa del baseline. Para el modelo, decide qué casillas deben sumar 238 y cuáles 12.
from sklearn.metrics import confusion_matrix
print(confusion_matrix(y_prueba, pred_baseline))
print(confusion_matrix(y_prueba, pred_modelo))
La salida esperada está oculta.
Precisión, recall y F1
Tres métricas resumen la clase positiva a partir de la matriz:
- precisión = VP / (VP + FP): de las piezas marcadas como defectuosas, qué fracción lo era
- recall = VP / (VP + FN): de las piezas defectuosas, qué fracción se detectó
- F1 = 2 · precisión · recall / (precisión + recall): la media armónica de ambas
Ninguna de las tres usa VN. En scikit-learn, precision_score, recall_score y f1_score reciben las clases reales y las predichas y toman 1 como clase positiva.
Pregunta. Con la matriz del modelo, calcula a mano su precisión, su recall y su F1. Predice también los tres valores del baseline.
from sklearn.metrics import f1_score, precision_score, recall_score
for nombre, pred in [("modelo", pred_modelo), ("baseline", pred_baseline)]:
precision = precision_score(y_prueba, pred, zero_division=0)
recall = recall_score(y_prueba, pred)
f1 = f1_score(y_prueba, pred)
print(f"{nombre:8} precisión={precision:.3f} recall={recall:.3f} F1={f1:.3f}")
La salida esperada está oculta.
ROC-AUC y ordenamiento
predict entrega una decisión por pieza. predict_proba entrega, en su segunda columna, una probabilidad de que la pieza sea defectuosa. ROC-AUC evalúa esas probabilidades sin convertirlas en decisiones: es la proporción de pares (defectuosa, buena) en los que la defectuosa recibe una probabilidad mayor. Un empate cuenta como medio par. El nombre viene del área bajo la curva ROC, y esa área coincide con la proporción de pares bien ordenados.
En este ejemplo pequeño hay 3 piezas defectuosas y 5 buenas, así que hay 3 × 5 = 15 pares. La máscara p_mini[y_mini == 1] separa las probabilidades de las defectuosas, y roc_auc_score recibe las clases reales y las probabilidades.
Pregunta. Cuenta cuántos de los 15 pares tienen a la defectuosa por encima de la buena. Calcula el ROC-AUC.
from sklearn.metrics import roc_auc_score
y_mini = np.array([1, 1, 1, 0, 0, 0, 0, 0])
p_mini = np.array([0.90, 0.60, 0.30, 0.80, 0.40, 0.20, 0.10, 0.05])
defectuosas = p_mini[y_mini == 1]
buenas = p_mini[y_mini == 0]
# recorre los 15 pares (defectuosa, buena)
bien_ordenados = sum(int(d > b) for d in defectuosas for b in buenas)
print(bien_ordenados, defectuosas.size * buenas.size)
print(round(bien_ordenados / (defectuosas.size * buenas.size), 3))
print(round(roc_auc_score(y_mini, p_mini), 3))
La salida esperada está oculta.
Pregunta. El baseline asigna la misma probabilidad a todas las piezas. Predice su ROC-AUC. Predice también si el modelo, que solo detectó 5 de 15 con predict, queda cerca o lejos de 1.
# [:, 1] toma la columna de la clase 1 (defectuosa)
auc_modelo = roc_auc_score(y_prueba, modelo.predict_proba(X_prueba)[:, 1])
auc_baseline = roc_auc_score(y_prueba, baseline.predict_proba(X_prueba)[:, 1])
print(f"modelo: {auc_modelo:.3f}")
print(f"baseline: {auc_baseline:.3f}")
La salida esperada está oculta.
Umbral y curva ROC
Mueve el umbral y observa cómo cambian la matriz de confusión, las métricas y el punto sobre la curva ROC.
Qué probar
Con 10 % de defectuosas, compara la exactitud con la del baseline que marca todas como buenas.
Una pieza se marca como defectuosa si su probabilidad es mayor o igual que el umbral. Cada umbral da un punto de la curva ROC: tasa de FP = FP / (FP + VN) en el eje horizontal y recall en el vertical. También puedes arrastrar la línea sobre la franja o moverla con las flechas.
MAE y RMSE
La misma línea estima cuántos minutos tarda la reparación de cada pieza defectuosa. En regresión, el error de una fila es la predicción menos el valor real, en minutos:
- MAE es el promedio de los errores en valor absoluto
- RMSE es la raíz cuadrada del promedio de los errores al cuadrado
DummyRegressor(strategy="mean") predice para cada fila el promedio de los tiempos de entrenamiento. Igual que DummyClassifier, ignora los valores de X, aunque fit y predict necesitan una tabla con una fila por observación.
Hay dos modelos. A se equivoca por 4 minutos en cada pieza. B acierta cinco piezas y se equivoca por 24 minutos en la última. mean_absolute_error y root_mean_squared_error calculan cada métrica a partir de los valores reales y los predichos.
Pregunta. Calcula el valor que predice el baseline. Después, ordena baseline, A y B por MAE y por RMSE.
from sklearn.dummy import DummyRegressor
from sklearn.metrics import mean_absolute_error, root_mean_squared_error
tiempos_entrena = np.array([18, 22, 25, 27, 30, 33, 36, 49], dtype=float)
tiempos_prueba = np.array([20, 24, 28, 32, 36, 40], dtype=float)
pred_a = np.array([24, 20, 32, 28, 40, 36], dtype=float)
pred_b = np.array([20, 24, 28, 32, 36, 64], dtype=float)
# X de ceros: solo fija una fila por pieza
baseline_reg = DummyRegressor(strategy="mean").fit(np.zeros((8, 1)), tiempos_entrena)
pred_base_reg = baseline_reg.predict(np.zeros((6, 1)))
print(pred_base_reg)
for nombre, pred in [("baseline", pred_base_reg), ("A", pred_a), ("B", pred_b)]:
mae = mean_absolute_error(tiempos_prueba, pred)
rmse = root_mean_squared_error(tiempos_prueba, pred)
print(f"{nombre:8} MAE={mae:.2f} RMSE={rmse:.2f}")
La salida esperada está oculta.
Práctica
Traza guiada
Antes de procesar, escribe VP, FP, FN y VN para estas diez piezas. Después calcula exactitud, precisión y recall.
y_real = np.array([1, 0, 0, 1, 0, 0, 1, 0, 0, 0])
y_pred = np.array([1, 0, 1, 0, 0, 0, 1, 0, 0, 1])
Comprueba con confusion_matrix, accuracy_score, precision_score y recall_score. Si una casilla no coincide, recorre las diez posiciones y clasifica cada una como VP, FP, FN o VN.
Baseline estratificado
Cambia la estrategia del baseline del problema a DummyClassifier(strategy="stratified", random_state=0). Esta estrategia predice al azar con las proporciones de clase de entrenamiento. Antes de procesar, predice si su exactitud sube o baja frente a most_frequent, si aparecen falsos positivos y si su ROC-AUC queda cerca de 0.5 o de 1. Comprueba con la matriz de confusión y roc_auc_score.
Tabla de comparación
Genera un problema nuevo con make_classification(n_samples=1000, n_features=6, n_informative=3, weights=[0.8], random_state=1). Divídelo con estratificación, ajusta un baseline most_frequent y un LogisticRegression, y construye un DataFrame con una fila por predictor y estas columnas:
- exactitud
- precisión
- recall
- F1
- ROC-AUC
Supón que dejar pasar un positivo cuesta diez veces más que una falsa alarma. Escribe qué métrica reportarías primero y qué casilla de la matriz de confusión quieres reducir.
Reto opcional
Calcula el ROC-AUC del modelo del problema contando pares: recorre las 15 probabilidades de piezas defectuosas y las 235 de piezas buenas, suma 1 por cada par bien ordenado y 0.5 por cada empate, y divide entre 15 × 235. Comprueba que coincide con roc_auc_score.
Errores que revisar
- Si la exactitud de un modelo parece alta, calcula primero la del baseline
most_frequentcon el conteo de clases de prueba. - Si
precision_scoreemiteUndefinedMetricWarning, el predictor no marcó ninguna fila como positiva; revisa la columna de la clase positiva en la matriz de confusión. - Si la matriz parece invertida, recuerda que las filas son la clase real y las columnas la predicha; con etiquetas
0y1, VP está en la esquina inferior derecha. - Si pasas la salida de
predictaroc_auc_score, solo evalúas dos valores posibles y el orden se pierde; usa la columna de la clase positiva depredict_proba. - Si el RMSE es mucho mayor que el MAE, busca las filas con los errores más grandes antes de comparar modelos.
Cierre
Escribe una respuesta de tres líneas al reporte «exactitud 0.952»: qué baseline agregarías, qué dos métricas de la clase defectuosa pondrías a su lado y cuál reportarías primero si una pieza defectuosa entregada al cliente es el error más caro.
Lista de comprobación
- ¿Puedes calcular la exactitud de
most_frequentcon solo el conteo de clases de prueba? - ¿Sabes qué casilla de la matriz cuenta las piezas defectuosas que pasaron como buenas?
- ¿Distingues lo que mide ROC-AUC (orden de probabilidades) de lo que miden precisión y recall (decisiones)?
- ¿Puedes construir dos conjuntos de predicciones con el mismo MAE y distinto RMSE?
- ¿Puedes justificar una métrica a partir del error que cuesta más?
Documentación oficial
- Métricas y puntajes de scikit-learn: exactitud, matriz de confusión, precisión, recall, F1, ROC-AUC, MAE y RMSE, con sus definiciones.
DummyClassifier: estrategiasmost_frequent,prior,stratified,uniformyconstant.DummyRegressor: estrategiasmean,median,quantileyconstant.roc_auc_score: parámetros y formato de las probabilidades que recibe.