
Proyecto Asadi — Sesión 2: la primera red neuronal convolucional
En la Sesión 1 vimos que a simple vista cuesta distinguir una célula sana de un blasto leucémico. Hoy construimos nuestro primer modelo capaz de aprender a hacerlo: una red neuronal convolucional (CNN), con la librería Keras (parte de TensorFlow).
Por qué no vale un árbol de decisión aquí
En Proyecto Javierre, cada paciente eran cuatro números (los cuatro genes) y un árbol de decisión sabía perfectamente qué hacer con cuatro números. Una imagen de 224×224 píxeles son, en el fondo, más de 150.000 números (un valor de color por cada píxel) — pero si a un árbol de decisión le diéramos esos 150.000 números sueltos, perdería toda la información de que están organizados en una cuadrícula: no sabría que dos píxeles vecinos forman parte de la misma forma. Hace falta un tipo de modelo que entienda que una imagen tiene estructura espacial. De eso se encarga una CNN.
Cómo “mira” una imagen una CNN, sin matemáticas de por medio
Una CNN se construye apilando dos tipos de capas que hacen trabajos distintos:
- Capas convolucionales: cada una contiene varios filtros pequeños (como ventanitas de unos pocos píxeles) que se deslizan por toda la imagen, buscando un patrón visual concreto — un borde, un cambio de color, una textura. El resultado es un mapa que marca dónde aparece ese patrón en la imagen. Una red con varias capas convolucionales apiladas empieza detectando patrones muy simples (bordes) y, capa a capa, va combinándolos en patrones cada vez más complejos (formas, texturas de núcleo celular).
- Capas de agrupación (pooling): reducen el tamaño de esos mapas, quedándose con la señal más fuerte de cada zona. Esto hace la red más rápida y más tolerante a que un blasto aparezca un poco desplazado o girado en la imagen, en vez de exactamente en el mismo sitio.
Al final de la red, una o dos capas normales (como las que ya conoceríais de una red neuronal sencilla) cogen todo lo que las capas convolucionales han detectado y lo combinan para dar la decisión final: benigno o maligno.
Los datos ya están listos
Para esta primera sesión simplificamos el problema a dos clases, como dijimos en la Sesión 1: benigno frente a maligno (juntando los tres subtipos de ALL). Ya he preparado esa organización dentro de vuestra carpeta imagenes_asadi:
imagenes_asadi/Original_binario/
Benigno/ (504 imágenes)
Maligno/ (2.752 imágenes, Early + Pre + Pro juntas)
Keras sabe leer automáticamente una carpeta organizada así (una subcarpeta por clase) y asignar las etiquetas él solo, sin que tengamos que escribir nada a mano.
Cargando las imágenes y separando entrenamiento y examen
from tensorflow import keras
carpeta_datos = "imagenes_asadi/Original_binario"
entrenamiento = keras.utils.image_dataset_from_directory(
carpeta_datos,
validation_split=0.2,
subset="training",
seed=42,
image_size=(224, 224),
batch_size=32,
)
prueba = keras.utils.image_dataset_from_directory(
carpeta_datos,
validation_split=0.2,
subset="validation",
seed=42,
image_size=(224, 224),
batch_size=32,
)
print("Clases detectadas:", entrenamiento.class_names)
validation_split=0.2 hace exactamente lo mismo que stratify y test_size en train_test_split: reserva un 20% de las imágenes como examen, sin que el modelo las vea durante el entrenamiento. image_size=(224, 224) no cambia nada aquí porque las imágenes ya vienen justo a ese tamaño, pero es buena costumbre indicarlo siempre por si algún día trabajáis con imágenes de tamaños distintos entre sí.

Construyendo la red
from tensorflow.keras import layers
modelo = keras.Sequential([
layers.Input(shape=(224, 224, 3)),
layers.Rescaling(1./255), # Pasa los píxeles de 0-255 a 0-1
layers.Conv2D(16, 3, activation="relu"),
layers.MaxPooling2D(),
layers.Conv2D(32, 3, activation="relu"),
layers.MaxPooling2D(),
layers.Flatten(),
layers.Dense(64, activation="relu"),
layers.Dense(1, activation="sigmoid"), # Un único número entre 0 y 1: benigno o maligno
])
modelo.compile(optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"])
modelo.summary()
Dos capas convolucionales (con 16 y 32 filtros respectivamente) intercaladas con dos de agrupación, y al final dos capas normales que toman la decisión. Rescaling(1./255) es un paso de preparación de datos: los píxeles vienen originalmente como números de 0 a 255, y la red aprende mejor si están entre 0 y 1.


Entrenando
historial = modelo.fit(entrenamiento, validation_data=prueba, epochs=10)
A diferencia del árbol o el Random Forest, que se entrenan de golpe, una red neuronal aprende por rondas (épocas): en cada una repasa todas las imágenes de entrenamiento una vez y ajusta un poco lo que ha aprendido. Con 10 épocas y unos 2.600 pacientes de entrenamiento, calculad que el entrenamiento puede tardar varios minutos en un ordenador normal — es buen momento para ir a por un café mientras tanto.

[COMPLETAR CON VUESTROS DATOS REALES: exactitud (accuracy) final en el conjunto de validación = ___]
Próximo paso
Con la primera red ya entrenada, en la próxima sesión vamos a mirar sus aciertos y sus errores con más detalle — igual que hicimos con la matriz de confusión en Proyecto Javierre — para saber si a la red le cuesta más reconocer los casos benignos o los malignos.
Proyecto Asadi — Sesión 2: la primera red neuronal convolucional
En la Sesión 1 vimos que a simple vista cuesta distinguir una célula sana de un blasto leucémico. Hoy construimos nuestro primer modelo capaz de aprender a hacerlo: una red neuronal convolucional (CNN), con la librería Keras (parte de TensorFlow).
Por qué no vale un árbol de decisión aquí
En Proyecto Javierre, cada paciente eran cuatro números (los cuatro genes) y un árbol de decisión sabía perfectamente qué hacer con cuatro números. Una imagen de 224×224 píxeles son, en el fondo, más de 150.000 números (un valor de color por cada píxel) — pero si a un árbol de decisión le diéramos esos 150.000 números sueltos, perdería toda la información de que están organizados en una cuadrícula: no sabría que dos píxeles vecinos forman parte de la misma forma. Hace falta un tipo de modelo que entienda que una imagen tiene estructura espacial. De eso se encarga una CNN.
Cómo “mira” una imagen una CNN, sin matemáticas de por medio
Una CNN se construye apilando dos tipos de capas que hacen trabajos distintos:
- Capas convolucionales: cada una contiene varios filtros pequeños (como ventanitas de unos pocos píxeles) que se deslizan por toda la imagen, buscando un patrón visual concreto — un borde, un cambio de color, una textura. El resultado es un mapa que marca dónde aparece ese patrón en la imagen. Una red con varias capas convolucionales apiladas empieza detectando patrones muy simples (bordes) y, capa a capa, va combinándolos en patrones cada vez más complejos (formas, texturas de núcleo celular).
- Capas de agrupación (pooling): reducen el tamaño de esos mapas, quedándose con la señal más fuerte de cada zona. Esto hace la red más rápida y más tolerante a que un blasto aparezca un poco desplazado o girado en la imagen, en vez de exactamente en el mismo sitio.
Al final de la red, una o dos capas normales (como las que ya conoceríais de una red neuronal sencilla) cogen todo lo que las capas convolucionales han detectado y lo combinan para dar la decisión final: benigno o maligno.
Los datos ya están listos
Para esta primera sesión simplificamos el problema a dos clases, como dijimos en la Sesión 1: benigno frente a maligno (juntando los tres subtipos de ALL). Ya he preparado esa organización dentro de vuestra carpeta imagenes_asadi:
imagenes_asadi/Original_binario/
Benigno/ (504 imágenes)
Maligno/ (2.752 imágenes, Early + Pre + Pro juntas)
Keras sabe leer automáticamente una carpeta organizada así (una subcarpeta por clase) y asignar las etiquetas él solo, sin que tengamos que escribir nada a mano.
Cargando las imágenes y separando entrenamiento y examen
from tensorflow import keras
carpeta_datos = "imagenes_asadi/Original_binario"
entrenamiento = keras.utils.image_dataset_from_directory(
carpeta_datos,
validation_split=0.2,
subset="training",
seed=42,
image_size=(224, 224),
batch_size=32,
)
prueba = keras.utils.image_dataset_from_directory(
carpeta_datos,
validation_split=0.2,
subset="validation",
seed=42,
image_size=(224, 224),
batch_size=32,
)
print("Clases detectadas:", entrenamiento.class_names)
validation_split=0.2 hace exactamente lo mismo que stratify y test_size en train_test_split: reserva un 20% de las imágenes como examen, sin que el modelo las vea durante el entrenamiento. image_size=(224, 224) no cambia nada aquí porque las imágenes ya vienen justo a ese tamaño, pero es buena costumbre indicarlo siempre por si algún día trabajáis con imágenes de tamaños distintos entre sí.
Construyendo la red
from tensorflow.keras import layers
modelo = keras.Sequential([
layers.Input(shape=(224, 224, 3)),
layers.Rescaling(1./255), # Pasa los píxeles de 0-255 a 0-1
layers.Conv2D(16, 3, activation="relu"),
layers.MaxPooling2D(),
layers.Conv2D(32, 3, activation="relu"),
layers.MaxPooling2D(),
layers.Flatten(),
layers.Dense(64, activation="relu"),
layers.Dense(1, activation="sigmoid"), # Un único número entre 0 y 1: benigno o maligno
])
modelo.compile(optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"])
modelo.summary()
Dos capas convolucionales (con 16 y 32 filtros respectivamente) intercaladas con dos de agrupación, y al final dos capas normales que toman la decisión. Rescaling(1./255) es un paso de preparación de datos: los píxeles vienen originalmente como números de 0 a 255, y la red aprende mejor si están entre 0 y 1.
Entrenando
historial = modelo.fit(entrenamiento, validation_data=prueba, epochs=10)
A diferencia del árbol o el Random Forest, que se entrenan de golpe, una red neuronal aprende por rondas (épocas): en cada una repasa todas las imágenes de entrenamiento una vez y ajusta un poco lo que ha aprendido. Con 10 épocas y unos 2.600 pacientes de entrenamiento, calculad que el entrenamiento puede tardar varios minutos en un ordenador normal — es buen momento para ir a por un café mientras tanto.
[COMPLETAR CON VUESTROS DATOS REALES: exactitud (accuracy) final en el conjunto de validación = ___]
Próximo paso
Con la primera red ya entrenada, en la próxima sesión vamos a mirar sus aciertos y sus errores con más detalle — igual que hicimos con la matriz de confusión en Proyecto Javierre — para saber si a la red le cuesta más reconocer los casos benignos o los malignos.



