
Proyecto Laennec — Sesión 3: la matriz de confusión, y si el árbol reconoce bien la extrasístole
En la Sesión 2 convertimos cada sonido en 13 números y entrenamos un árbol de decisión, del que sacamos un único número: la exactitud. Hoy hacemos lo mismo que ya hicimos en Taussig con las arritmias: abrir la caja de las predicciones y comprobar, clase por clase, dónde acierta el árbol y dónde se equivoca — con un motivo añadido, porque aquí el desequilibrio entre clases es todavía mayor que en Taussig.
Por qué un solo número no basta aquí tampoco
Recordad los números de la Sesión 2: 320 latidos normales, 95 con murmullo, 46 con extrasístole. La extrasístole es, con diferencia, la clase minoritaria — casi 7 veces menos frecuente que la normal. Un árbol que acertara siempre con “normal” y fallara sistemáticamente con “extrasístole” podría sacar igualmente una exactitud que suena decente, simplemente porque hay pocos casos de extrasístole en el conjunto de prueba para que esos fallos pesen mucho en el número global. La exactitud, sola, no distingue ese escenario de uno donde el árbol reconoce de verdad las tres clases.
Dibujando la matriz de confusión
Con tres clases, la matriz tiene 9 casillas: 3 aciertos en la diagonal, 6 formas distintas de equivocarse fuera de ella — bastante más manejable que las 25 casillas de Taussig, así que hoy es buen momento para acostumbrarnos a leerla con calma.
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
etiquetas = ["normal", "murmullo", "extrasistole"]
# 1. Obtenemos las predicciones del árbol sobre los sonidos de prueba
predicciones = arbol.predict(X_prueba)
# 2. Calculamos y dibujamos la matriz de confusión, 3x3
cm = confusion_matrix(y_prueba, predicciones, labels=etiquetas)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=etiquetas)
disp.plot(cmap="Blues")
plt.title("Matriz de confusión — clasificación de sonidos del corazón (3 clases)")
plt.show()
Fijaos en un detalle práctico: aquí labels=etiquetas recibe directamente los nombres de texto (“normal”, “murmullo”, “extrasistole”), no números como en Taussig — porque en y guardamos la clase como texto desde que construimos la tabla en la Sesión 2, no la codificamos como 0, 1, 2. confusion_matrix y classification_report funcionan igual de bien con texto que con números, siempre que se lo digamos explícitamente con labels.
Cómo leer esta matriz
El principio es idéntico al de Taussig: la diagonal son los aciertos, todo lo demás fuera de la diagonal son los errores. Con solo tres clases podéis fijaros en las nueve casillas de un vistazo. Buscad en concreto la fila “Real extrasistole”: de los pocos casos de extrasístole que hay en el conjunto de prueba (recordad que solo hay 46 en total, y una parte de esos se fue a entrenamiento), ¿cuántos reconoce el árbol correctamente, y con qué otra clase los confunde más?
[COMPLETAR EN CLASE: mirad la fila “Real extrasistole” de vuestra matriz. ¿Cuántos aciertos y cuántos fallos tiene? ¿Con qué clase se confunde más — normal o murmullo?]
El informe completo: macro avg contra weighted avg, otra vez
Ya conocéis esta tabla de Taussig, así que hoy vais a leerla más rápido — pero conviene repasar la idea, porque aquí el contraste entre macro avg y weighted avg puede ser todavía más llamativo que con las arritmias.
from sklearn.metrics import classification_report
print(classification_report(y_prueba, predicciones, labels=etiquetas, target_names=etiquetas))
Fijaos en que aquí pasamos labels=etiquetas además de target_names=etiquetas — no es redundante, aunque lo parezca. Sin labels=etiquetas, classification_report ordena las clases alfabéticamente por su cuenta (extrasistole, murmullo, normal) y les pone encima los nombres de target_names en el orden en que los escribisteis (normal, murmullo, extrasistole); como los dos órdenes no coinciden, el informe sale con las etiquetas cambiadas de sitio y los números de una clase aparecen bajo el nombre de otra, sin ningún error que lo avise. labels=etiquetas obliga a que las clases se ordenen tal y como las escribisteis, para que cada fila lleve de verdad su nombre correcto.
Recordad la diferencia: macro avg trata a las tres clases por igual, como si extrasístole (46 casos) pesara lo mismo que normal (320 casos). weighted avg da más peso a las clases con más ejemplos, así que aquí se va a parecer mucho a “cómo de bien reconoce el árbol los latidos normales” — y puede salir bastante alto aunque el árbol tenga problemas de verdad con la extrasístole.
[COMPLETAR CON VUESTROS DATOS REALES: exhaustividad de extrasistole = ___ | macro avg = ___ | weighted avg = ___ → ¿cuánto se separan macro avg y weighted avg esta vez, comparado con lo que visteis en Taussig?]
Una comparación con Taussig que merece la pena hacer
En Taussig, la clase minoritaria (V, ventricular) tenía un motivo clínico muy claro para importarnos más que las demás — era la que podía matar. Aquí, la extrasístole no es tan alarmante en general (recordad de la Sesión 1: “le pasa a mucha gente, sobre todo a niños”), pero el problema técnico es el mismo: un modelo entrenado con pocos ejemplos de una clase tiende a aprenderla peor, sea o no la más grave desde el punto de vista médico. Es un recordatorio útil de que el desequilibrio de clases no es solo un problema de “la clase importante” — es un problema de “la clase con pocos datos”, y a veces coinciden y a veces no.
Próximo paso
Hemos visto si el árbol reconoce bien las tres clases o si el desequilibrio le pasa factura, sobre todo a la extrasístole. En la próxima sesión probamos si un Random Forest —un comité de árboles, en vez de uno solo— ayuda con este mismo problema, igual que ayudó en Taussig con la clase V. Y, como novedad de este proyecto, podremos preguntarle al bosque en qué coeficiente MFCC concreto se fija más para distinguir un murmullo de un latido normal.





