
Proyecto Asadi — Sesión 3: la matriz de confusión de la CNN
En la Sesión 2 entrenamos nuestra primera red neuronal convolucional y medimos su exactitud. Hoy hacemos exactamente lo que ya sabemos hacer desde Proyecto Javierre: abrir la caja de las predicciones con una matriz de confusión, para ver si a la red le cuesta más un tipo de error que el otro.
Un detalle que no podemos ignorar aquí: las clases no están repartidas igual
Antes de mirar los resultados, fijaos en un número de la Sesión 1: 504 imágenes benignas frente a 2.752 malignas — más de 5 veces más ejemplos malignos que benignos. Una red que viera muchísimos más ejemplos de un tipo que del otro podría “hacer trampa” sin querer: acertar mucho en general simplemente por predecir “maligno” casi siempre, sin haber aprendido de verdad a distinguir los dos casos. Guardad esta idea en la cabeza — es la primera pregunta que hay que hacerse al mirar la matriz de confusión de hoy.
Obteniendo las predicciones
Con un modelo de scikit-learn, arbol.predict(X_test) nos daba las predicciones directamente. Con Keras hace falta un paso más: el modelo no da una etiqueta, da un número entre 0 y 1 (la probabilidad de que la imagen sea de la segunda clase, Maligno), así que tenemos que decidir a partir de qué valor lo consideramos “maligno” — lo habitual es 0,5.
import numpy as np
# Las etiquetas reales del conjunto de prueba
y_verdadero = np.concatenate([etiquetas for imagenes, etiquetas in prueba], axis=0)
# Las probabilidades que da el modelo, convertidas en 0 (Benigno) o 1 (Maligno)
y_prob = modelo.predict(prueba)
y_predicho = (y_prob > 0.5).astype(int).flatten()

La matriz de confusión, con las mismas herramientas de siempre
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix, classification_report, ConfusionMatrixDisplay
cm = confusion_matrix(y_verdadero, y_predicho)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=entrenamiento.class_names)
disp.plot(cmap="Blues")
plt.title("Matriz de confusión — CNN, benigno vs maligno")
plt.show()
print(classification_report(y_verdadero, y_predicho, target_names=entrenamiento.class_names))
Exactamente el mismo código que en Proyecto Javierre — confusion_matrix, classification_report, ConfusionMatrixDisplay son de scikit-learn, la misma librería de siempre, aunque el modelo que estemos evaluando sea de Keras.

Cómo leerla, con la sospecha de la desproporción en mente
Mirad la fila de Benigno en el informe: la exhaustividad (recall) de esa fila os dice qué porcentaje de las imágenes benignas de verdad detectó la red como benignas. Si ese número es mucho más bajo que el de Maligno, es la señal de que la sospecha del principio se está cumpliendo: la red reconoce mejor la clase de la que ha visto muchos más ejemplos.
[COMPLETAR CON VUESTROS DATOS REALES: exhaustividad de Benigno = ___ | exhaustividad de Maligno = ___. ¿Hay una diferencia grande entre las dos?]
Si la diferencia es notable, no significa que el modelo esté mal entrenado ni que el código tenga un fallo — es una limitación real y muy conocida en aprendizaje automático cuando las clases están desequilibradas, y es exactamente el tipo de cosa que un buen científico de datos tiene que saber detectar, no solo saber programar el modelo.
Próximo paso
Con esto tenemos un diagnóstico honesto de nuestra primera CNN: no solo cuánto acierta, sino a costa de qué. En la próxima sesión veremos si hay margen para mejorarla.



