
Proyecto Laennec — Sesión 4: Random Forest, y en qué coeficiente MFCC se fija el bosque
En la Sesión 3 vimos que un solo árbol puede tener una exactitud aceptable y aun así reconocer bastante peor la clase minoritaria, la extrasístole. Hoy probamos si un comité de árboles hace mejor ese trabajo — el mismo paso que ya dimos en CUTLER, en Javierre y en Taussig — y descubrimos, de paso, en cuál de los 13 coeficientes MFCC se apoya más el modelo para distinguir un sonido de otro.
Del árbol al bosque, otra vez
Ya conocéis la idea: un único árbol de decisión es sensible a los datos concretos con los que ha aprendido. Un Random Forest entrena muchos árboles independientes —cada uno con una muestra distinta de sonidos y de coeficientes MFCC— y los deja votar. Aquí el cambio es idéntico al de Taussig, solo que ahora cada “latido” son 13 números en vez de 187.
from sklearn.ensemble import RandomForestClassifier
# 1. Crear el bosque con 100 árboles de decisión
bosque = RandomForestClassifier(n_estimators=100, random_state=42)
# 2. Entrenar a todos los árboles a la vez
bosque.fit(X_entrenamiento, y_entrenamiento)
# 3. Evaluar la exactitud en el conjunto de prueba
exactitud_bosque = bosque.score(X_prueba, y_prueba)
print(f"Exactitud del Random Forest en el conjunto de prueba: {exactitud_bosque:.2%}")
A diferencia de Taussig, aquí el bosque solo tiene que procesar 13 números por sonido en vez de 187 por latido, y unos pocos cientos de filas en vez de decenas de miles — esta celda debería tardar segundos, no minutos.
[COMPLETAR CON VUESTROS DATOS REALES: exactitud del árbol de la Sesión 2 = ___ | exactitud del Random Forest = ___]

¿Mejora el bosque justo donde más nos importa?
Igual que en Taussig, la exactitud global es solo el primer vistazo. Lo que de verdad queremos saber, después de la Sesión 3, es si el bosque reconoce mejor la extrasístole que el árbol único.
from sklearn.metrics import classification_report
etiquetas = ["normal", "murmullo", "extrasistole"]
predicciones_bosque = bosque.predict(X_prueba)
print("--- INFORME DEL RANDOM FOREST ---")
print(classification_report(y_prueba, predicciones_bosque, labels=etiquetas, target_names=etiquetas))
Un detalle que puede jugaros una mala pasada aquí: si os fijáis, esta vez pasamos labels=etiquetas además de target_names=etiquetas, algo que en la Sesión 3 con confusion_matrix ya hacíamos pero que aquí es todavía más fácil olvidar. Sin el labels=etiquetas, classification_report ordena las clases alfabéticamente por su cuenta (extrasistole, murmullo, normal) y les pega los nombres de target_names en el orden en que los escribisteis vosotros (normal, murmullo, extrasistole) — como los dos órdenes no coinciden, el informe sale con las etiquetas cambiadas de sitio: los números reales de extrasistole aparecen bajo la fila normal, y viceversa. Es un fallo silencioso, sin ningún error ni aviso que lo delate directamente (el UndefinedMetricWarning que sí veis es un aviso distinto, sobre otra cosa), así que conviene fijarse siempre en el support de cada fila: si la fila normal os sale con un support de 9 en vez de rondar los 64 (recordad, normal es la clase mayoritaria), es la señal de que las etiquetas están descolocadas.
Comparad la exhaustividad de la fila extrasistole con la que obtuvisteis en la Sesión 3 con el árbol único: ¿sube, baja o se queda igual? Fijaos también en el macro avg, que es donde más se nota cualquier cambio en una clase tan minoritaria.
[COMPLETAR CON VUESTROS DATOS REALES: exhaustividad de extrasistole con el árbol = ___ | con el Random Forest = ___. Macro avg con el árbol = ___ | con el bosque = ___]

Lo nuevo de hoy: qué coeficiente MFCC pesa más
En Taussig, feature_importances_ os decía en qué instante exacto del latido se fijaba el bosque. Aquí no tenemos instantes de tiempo: tenemos 13 coeficientes MFCC, cada uno resumiendo un aspecto distinto del timbre del sonido. La misma herramienta nos va a responder ahora una pregunta distinta: ¿qué coeficiente MFCC es el más útil para distinguir entre un latido normal, un murmullo y una extrasístole?
import matplotlib.pyplot as plt
# La importancia de cada uno de los 13 coeficientes MFCC
importancias = bosque.feature_importances_
plt.figure(figsize=(10, 4))
plt.bar(range(1, 14), importancias)
plt.title("Importancia de cada coeficiente MFCC según el Random Forest")
plt.xlabel("Número de coeficiente MFCC")
plt.ylabel("Importancia relativa")
plt.xticks(range(1, 14))
plt.show()
Fijaos en que aquí usamos plt.bar en vez de plt.plot: en Taussig tenía sentido dibujar una línea, porque los 187 números eran instantes consecutivos de tiempo, uno detrás de otro. Aquí los 13 coeficientes MFCC no forman una secuencia continua —cada uno mide algo distinto del sonido, no un momento distinto—, así que unas barras separadas representan mejor lo que son: 13 medidas independientes, no un recorrido en el tiempo.
[COMPLETAR CON VUESTROS DATOS REALES: ¿qué número de coeficiente tiene la barra más alta?]

Los primeros coeficientes MFCC (los de número más bajo) suelen capturar los rasgos más generales del timbre de un sonido —a grandes rasgos, si es “grave” o “agudo” en conjunto—, mientras que los últimos capturan detalles más finos de la textura. Si el coeficiente más importante para el bosque resulta ser uno de los primeros, es una pista de que lo que más diferencia a un murmullo de un latido normal es un cambio de textura bastante general, no un detalle sutil. No hace falta que memoricéis esta correspondencia —basta con quedaros con la idea de que ese número, el que ha resultado más importante, apunta a un aspecto concreto y real del sonido, no a un capricho del algoritmo.
Y aquí vuelve a aparecer el mismo eco de Taussig que ya visteis con Helen Taussig y sus dedos: no tenemos oídos ni estetoscopio, pero el bosque acaba de encontrar, con matemáticas, cuál de los trece “resúmenes del sonido” le resulta más útil para distinguir un corazón sano de uno con un soplo.
Próximo paso
Hemos construido un modelo más robusto que un solo árbol y hemos visto en qué coeficiente MFCC se apoya más. Pero toda esta comparación depende del reparto concreto que hizo train_test_split en la Sesión 2. En la próxima sesión comprobaremos, con validación cruzada, si esa partición nos está dando una imagen fiable del comportamiento de nuestros modelos o si tuvimos suerte (o mala suerte) con ella — un cuidado especial, teniendo en cuenta lo pequeña que es la clase extrasístole.



