En la lección anterior hiciste una convolución a mano y viste que un filtro 3×3 sobre una imagen 5×5 producía un mapa de 3×3, pero dejamos varias preguntas abiertas: ¿por qué ese tamaño exactamente? ¿Cómo se convoluciona una imagen en color con 3 canales? ¿Qué hace el pooling que aparecía en el diagrama de anatomía? Esta lección responde todo eso: es la lección de "ingeniería" de las CNN, donde aprenderás a configurar una capa convolucional (filtros, stride, padding, canales), a predecir el tamaño de su salida y a contar sus parámetros —habilidades imprescindibles para leer y diseñar cualquier arquitectura, incluidas las famosas que veremos en 03-03. Cerraremos construyendo capas Conv2D y MaxPooling2D reales en Keras, interpretando su summary() línea a línea y visualizando qué "ven" los filtros sobre una imagen tipo producto.
Contenido
- La capa convolucional en detalle: filtros, stride y padding
- Canales de entrada y salida
- Tamaño de salida y recuento de parámetros
- Pooling: max y average
- Conv2D y MaxPooling2D en Keras: summary() línea a línea
- Visualización de feature maps con matplotlib
La capa convolucional en detalle: filtros, stride y padding
Una capa convolucional queda definida por unas pocas decisiones de diseño (sus hiperparámetros):
- Número de filtros: cuántos detectores de patrones distintos tiene la capa. Cada filtro produce su propio mapa de características.
- Tamaño del filtro (kernel size): la ventana que se desliza, típicamente 3×3 o 5×5. Ventanas pequeñas capturan patrones locales finos; hoy en día 3×3 es el estándar (en 03-03 verás por qué VGG lo consagró).
- Stride (paso): cuántos píxeles se desplaza el filtro en cada salto.
- Padding (relleno): si se añade o no un borde de ceros alrededor de la imagen antes de convolucionar.
Stride: el tamaño del paso
En 03-01 deslizamos el filtro de una posición en una posición: stride = 1. Con stride = 2, el filtro salta de dos en dos, evaluando la mitad de posiciones por dimensión, con lo que el mapa de salida sale aproximadamente a la mitad de ancho y de alto (una cuarta parte de posiciones en total).
Stride 1 sobre 5 columnas: Stride 2 sobre 5 columnas: posiciones 0,1,2 (3 columnas) posiciones 0,2 (2 columnas)
- Stride 1: máxima resolución de análisis; es lo habitual en capas convolucionales.
- Stride 2: reduce el tamaño espacial (a veces se usa en lugar del pooling para "encoger" los mapas).
Padding: valid vs. same
Al convolucionar sin más, el mapa encoge (de 5×5 pasamos a 3×3) y, peor aún, los píxeles del borde participan en menos ventanas que los del centro: la esquina superior izquierda solo aparece en 1 ventana, mientras que un píxel central aparece en 9. Si el logo del producto está pegado al borde de la foto, se analiza "menos".
La solución es el padding: rodear la imagen con un marco de ceros antes de convolucionar.
| Modo | Qué hace | Tamaño de salida (stride 1) | Cuándo usarlo |
|---|---|---|---|
valid |
Sin relleno: el filtro solo se coloca donde cabe entero | Encoge: n - f + 1 |
Cuando no importa perder el borde o se quiere reducir tamaño |
same |
Rellena con ceros justo lo necesario | Igual que la entrada: n |
Cuando quieres apilar muchas capas sin que la imagen se evapore |
Ejemplo con nuestra imagen 5×5 y filtro 3×3: con same se añade un marco de 1 píxel de ceros (la imagen pasa a 7×7) y la salida vuelve a ser 5×5. Sin padding, tras 2 capas 3×3 la imagen habría pasado de 5×5 a 1×1: con imágenes pequeñas y redes profundas, valid se come la imagen enseguida.
Canales de entrada y salida
Hasta ahora convolucionamos imágenes en escala de grises (1 canal). Una foto de producto real tiene 3 canales (RGB), es decir, es un bloque de alto × ancho × 3. ¿Cómo se adapta el filtro?
Regla de oro: el filtro siempre tiene tantos canales como su entrada. Un "filtro 3×3" sobre una entrada RGB es en realidad un bloque de pesos 3×3×3 (27 pesos): tiene una rebanada 3×3 para el canal rojo, otra para el verde y otra para el azul. En cada posición se multiplican y suman los 27 productos (más el sesgo) y sale un único número. Por eso:
- 1 filtro → 1 mapa de características 2D, da igual cuántos canales tenga la entrada.
- Una capa con 32 filtros → salida con 32 canales (un bloque
alto × ancho × 32).
Y aquí viene el encadenamiento que hace profundas a las CNN: la siguiente capa convolucional recibe ese bloque de 32 canales, así que sus filtros serán de 3×3×32. Los "canales" dejan de ser colores y pasan a ser mapas de patrones detectados: el canal 7 puede ser "bordes verticales", el 21 "textura metálica". Cada capa combina los patrones de la anterior — la jerarquía de características de 03-01 en acción.
flowchart LR
A["Entrada RGB<br/>64x64x3"] -->|"32 filtros de 3x3x3"| B["Bloque<br/>64x64x32"]
B -->|"64 filtros de 3x3x32"| C["Bloque<br/>64x64x64"]
Tamaño de salida y recuento de parámetros
La fórmula del tamaño de salida
Con entrada de tamaño n × n, filtro f × f, padding p (píxeles de relleno por lado) y stride s:
(⌊·⌋ = redondear hacia abajo.) Comprobaciones y ejemplos habituales:
| Entrada (n) | Filtro (f) | Padding | Stride (s) | Cálculo | Salida |
|---|---|---|---|---|---|
| 5 | 3 | valid (p=0) | 1 | (5+0−3)/1 + 1 | 3 |
| 5 | 3 | same (p=1) | 1 | (5+2−3)/1 + 1 | 5 |
| 28 | 3 | valid (p=0) | 1 | (28+0−3)/1 + 1 | 26 |
| 28 | 3 | same (p=1) | 1 | (28+2−3)/1 + 1 | 28 |
| 64 | 3 | same (p=1) | 2 | ⌊(64+2−3)/2⌋ + 1 | 32 |
| 224 | 7 | p=3 | 2 | ⌊(224+6−7)/2⌋ + 1 | 112 |
La primera fila es exactamente nuestro ejemplo a mano de 03-01: de 5×5 a 3×3. La última es la primera capa de ResNet, que verás en 03-03: ya sabes de dónde sale su "112".
Para same con stride 1, el padding necesario es p = (f − 1) / 2: con filtro 3×3, p=1; con 5×5, p=2 (por eso los filtros suelen ser de tamaño impar).
Parámetros de una capa convolucional
El +1 es el sesgo de cada filtro. Ejemplo: Conv2D de 32 filtros 3×3 sobre entrada RGB:
- (3 × 3 × 3 + 1) × 32 = 28 × 32 = 896 parámetros.
Comparemos con una capa densa "equivalente" que conectara la misma entrada con la misma salida, para una imagen 64×64×3 y salida 64×64×32 (padding same):
| Capa | Cálculo | Parámetros |
|---|---|---|
| Conv2D, 32 filtros 3×3 | (3·3·3 + 1) × 32 | 896 |
| Densa de 64·64·3 = 12 288 entradas a 64·64·32 = 131 072 salidas | 12 288 × 131 072 + 131 072 | ~1 610 743 808 |
896 frente a 1 610 millones: un factor de casi dos millones. Es la consecuencia directa de la conectividad local y los pesos compartidos de 03-01. Además, fíjate en que los parámetros de la conv no dependen del tamaño de la imagen, solo del filtro y de los canales: la misma capa de 896 parámetros procesa fotos de 64×64 o de 1024×768.
Pooling: max y average
El pooling es la operación de "resumen" que vimos en el diagrama de 03-01. Divide cada mapa de características en ventanas (típicamente 2×2, con stride 2, sin solapamiento) y sustituye cada ventana por un solo valor:
- Max pooling: se queda con el máximo de la ventana. Interpretación: "¿se ha detectado el patrón en algún punto de esta zona?" — conserva la detección más fuerte y descarta el resto.
- Average pooling: la media de la ventana. Resume la activación general de la zona; es más suave, y su variante global (promediar todo el mapa) se usa al final de arquitecturas modernas como sustituto del Flatten (lo verás en 03-03).
Ejemplo a mano de max pooling 2×2 con stride 2 sobre un mapa 4×4:
Mapa de características: Max pooling 2x2: 1 3 | 2 0 4 2 | 1 1 -> 4 2 ------+------ 6 8 0 6 | 5 8 1 2 | 3 0
Cada bloque 2×2 se reduce a su máximo: max(1,3,4,2)=4, max(2,0,1,1)=2, max(0,6,1,2)=6, max(5,8,3,0)=8.
Efectos del pooling 2×2:
- Reduce el ancho y alto a la mitad → la siguiente capa procesa 4 veces menos posiciones → menos cómputo y memoria. Los canales no cambian.
- Tiene 0 parámetros: no hay nada que aprender, es una operación fija.
- Aporta una pequeña invarianza a traslaciones locales: si el borde detectado se mueve 1 píxel dentro de la ventana, el máximo no cambia. A la CNN le da igual que el asa de la cafetera esté 2 píxeles más a la izquierda en la foto del vendedor.
- Al reducir el mapa, cada píxel de capas posteriores "ve" una zona mayor de la imagen original (crece el campo receptivo), lo que ayuda a pasar de detectar bordes a detectar partes y objetos.
El patrón clásico que ya adelantamos en 03-01 es alternar: conv (extraer) → pool (resumir y encoger) → conv → pool... duplicando habitualmente el número de filtros tras cada pooling (16 → 32 → 64...): a medida que perdemos resolución espacial, ganamos riqueza de patrones.
Conv2D y MaxPooling2D en Keras: summary() línea a línea
Construyamos en Keras la etapa convolucional para fotos de producto de TecnoMarket reducidas a 64×64 RGB, con 4 categorías de salida (portátil, cafetera, monitor, tostadora). Aún no vamos a entrenar (el proyecto guiado completo es 07-01); el objetivo aquí es leer el modelo.
from tensorflow import keras
from tensorflow.keras import layers
modelo = keras.Sequential([
keras.Input(shape=(64, 64, 3)), # foto RGB 64x64
layers.Conv2D(32, (3, 3), padding="same", activation="relu"),
layers.MaxPooling2D((2, 2)),
layers.Conv2D(64, (3, 3), padding="same", activation="relu"),
layers.MaxPooling2D((2, 2)),
layers.Conv2D(128, (3, 3), padding="same", activation="relu"),
layers.MaxPooling2D((2, 2)),
layers.Flatten(),
layers.Dense(64, activation="relu"),
layers.Dense(4, activation="softmax"), # 4 categorias de producto
])
modelo.summary()Desglose de los argumentos de Conv2D(32, (3, 3), padding="same", activation="relu"):
32: número de filtros → la salida tendrá 32 canales.(3, 3): tamaño del filtro.padding="same": la salida conserva el ancho y alto de la entrada (el stride por defecto es 1; se cambiaría constrides=2).activation="relu": la activación tras cada convolución + sesgo, como recomendamos para capas ocultas en 02-02.
MaxPooling2D((2, 2)) usa ventana 2×2 y, por defecto, stride igual a la ventana (2): reduce a la mitad.
El summary() produce (formateado):
Layer (type) Output Shape Param # ========================================================= conv2d (Conv2D) (None, 64, 64, 32) 896 max_pooling2d (MaxPooling2D) (None, 32, 32, 32) 0 conv2d_1 (Conv2D) (None, 32, 32, 64) 18,496 max_pooling2d_1 (MaxPooling) (None, 16, 16, 64) 0 conv2d_2 (Conv2D) (None, 16, 16, 128) 73,856 max_pooling2d_2 (MaxPooling) (None, 8, 8, 128) 0 flatten (Flatten) (None, 8192) 0 dense (Dense) (None, 64) 524,352 dense_1 (Dense) (None, 4) 260 ========================================================= Total params: 617,860
Verifiquemos cada línea con nuestras fórmulas (el None es la dimensión del batch, como en 02-05):
- conv2d: padding same → sigue en 64×64; 32 filtros → 32 canales. Parámetros: (3·3·3 + 1) × 32 = 896. ✔ (nuestro ejemplo de antes)
- max_pooling2d: 64/2 = 32 → (32, 32, 32). 0 parámetros, como toda capa de pooling.
- conv2d_1: entrada con 32 canales → filtros de 3×3×32. (3·3·32 + 1) × 64 = 289 × 64 = 18 496. ✔
- conv2d_2: (3·3·64 + 1) × 128 = 577 × 128 = 73 856. ✔
- flatten: 8 × 8 × 128 = 8192 valores. Aplanar aquí es razonable: son 8192 características abstractas, no píxeles crudos.
- dense: 8192 × 64 + 64 = 524 352. Observa que la capa densa tras el Flatten concentra el 85 % de los parámetros del modelo — el motivo por el que las arquitecturas modernas la sustituyen por global average pooling (03-03).
- dense_1: 64 × 4 + 4 = 260, con softmax para las 4 categorías (02-02).
Total: ~618 000 parámetros para procesar imágenes de 64×64×3. La red densa de 02-05 necesitaba ~100 000 solo para MNIST de 28×28×1; una densa equivalente para 64×64×3 dispararía millones. Y de esos 618 000, solo 93 248 están en la parte convolucional: el extractor de características es baratísimo.
Visualización de feature maps con matplotlib
Para intuir qué hace cada filtro, apliquemos una convolución + ReLU y max pooling a una "foto de producto" sintética (una caja clara sobre fondo oscuro, como el embalaje de un producto de TecnoMarket) y pintemos los mapas resultantes. Usamos filtros fijados a mano para que el resultado sea interpretable; en un modelo entrenado harías lo mismo con los filtros aprendidos.
import numpy as np
import matplotlib.pyplot as plt
# 1) Imagen sintetica 32x32: una "caja de producto" clara sobre fondo oscuro
imagen = np.zeros((32, 32))
imagen[8:24, 10:22] = 1.0 # rectangulo claro (el producto)
# 2) Dos filtros deteccion de bordes
filtro_vertical = np.array([[1, 0, -1]] * 3, dtype=float) # bordes verticales
filtro_horizontal = filtro_vertical.T # bordes horizontales
def convolucion2d(imagen, filtro):
fi, ci = imagen.shape
ff, cf = filtro.shape
salida = np.zeros((fi - ff + 1, ci - cf + 1))
for i in range(salida.shape[0]):
for j in range(salida.shape[1]):
salida[i, j] = np.sum(imagen[i:i + ff, j:j + cf] * filtro)
return salida
def relu(x):
return np.maximum(0, x) # la ReLU de 02-02
def max_pooling(mapa, tam=2):
f, c = mapa.shape
salida = np.zeros((f // tam, c // tam))
for i in range(salida.shape[0]):
for j in range(salida.shape[1]):
salida[i, j] = np.max(mapa[i*tam:(i+1)*tam, j*tam:(j+1)*tam])
return salida
# 3) Pipeline conv -> ReLU -> pooling para cada filtro
mapas = {
"Bordes verticales": max_pooling(relu(convolucion2d(imagen, filtro_vertical))),
"Bordes horizontales": max_pooling(relu(convolucion2d(imagen, filtro_horizontal))),
}
# 4) Visualizacion
fig, ejes = plt.subplots(1, 3, figsize=(12, 4))
ejes[0].imshow(imagen, cmap="gray")
ejes[0].set_title("Imagen original 32x32")
for eje, (nombre, mapa) in zip(ejes[1:], mapas.items()):
eje.imshow(mapa, cmap="gray")
eje.set_title(f"{nombre}\n{mapa.shape[0]}x{mapa.shape[1]} tras pooling")
plt.tight_layout()
plt.show()Qué verás al ejecutarlo:
- El mapa de bordes verticales se ilumina solo a lo largo del lado izquierdo de la caja (transición oscuro→claro con este filtro; el lado derecho da valores negativos que la ReLU pone a cero — un segundo filtro con signos opuestos capturaría ese lado, y por eso las capas llevan muchos filtros).
- El mapa de bordes horizontales se ilumina en el lado superior de la caja.
- Ambos mapas son de 15×15: la convolución
validdeja 30×30 y el pooling 2×2 lo reduce a la mitad.
Cada filtro produce un "mapa de dónde está su patrón", y el pooling condensa esos mapas conservando las detecciones. Con los filtros de un modelo entrenado, esta misma técnica (extraer la salida de una capa intermedia y pintarla) es una herramienta habitual para inspeccionar qué ha aprendido una CNN.
Errores Comunes y Consejos
- Olvidar los canales al contar parámetros. El error más frecuente: contar (3·3 + 1) × 64 en vez de (3·3·32 + 1) × 64. El filtro siempre abarca todos los canales de su entrada.
- Creer que el pooling aprende algo. Tiene 0 parámetros; es una operación fija. Reduce tamaño, no "extrae características" por sí mismo.
- Confundir el efecto de padding y stride. El padding decide si se pierde tamaño por los bordes (same lo evita); el stride decide cada cuántos píxeles se evalúa el filtro (stride 2 divide el tamaño por 2). Son controles independientes.
- Apilar convs
validsobre imágenes pequeñas. Cada capa 3×3validresta 2 píxeles por dimensión; diez capas sobre una imagen de 28×28 la dejan en 8×8 antes de que hayas empezado a hacer pooling. Para apilar en profundidad,samees tu amiga. - Ignorar dónde están los parámetros. Mira siempre el
summary(): si una densa tras el Flatten concentra la mayoría de los parámetros, ahí tienes al principal candidato a sobreajustar (recuerda 02-05) y a engordar el modelo. - Consejo: ante cualquier
summary(), reproduce a mano las columnas Output Shape y Param # de las dos primeras capas. Es el ejercicio que más rápido consolida esta lección.
Ejercicios
Ejercicio 1: predecir formas y parámetros
Sin ejecutar nada, calcula la Output Shape y el Param # de cada capa de este modelo para fotos de TecnoMarket de 32×32 RGB:
modelo = keras.Sequential([
keras.Input(shape=(32, 32, 3)),
layers.Conv2D(16, (5, 5), padding="valid", activation="relu"),
layers.MaxPooling2D((2, 2)),
layers.Conv2D(32, (3, 3), padding="same", activation="relu"),
layers.MaxPooling2D((2, 2)),
])Ejercicio 2: pooling a mano
Aplica max pooling y average pooling (ventana 2×2, stride 2) a este mapa de características, y explica cuál de los dos conservaría mejor la detección de un patrón débil pero real que solo activa un píxel:
Ejercicio 3: diseñar la reducción
Las fotos de producto llegan a 128×128×3. Quieres una etapa convolucional que termine en mapas de 16×16 usando bloques [Conv2D 3×3 same + MaxPooling2D 2×2]. ¿Cuántos bloques necesitas? Si empiezas con 16 filtros y los duplicas en cada bloque, ¿cuántos canales tendrá la salida final y cuántos parámetros tendrá la última capa conv?
Soluciones
Ejercicio 1:
| Capa | Output Shape | Cálculo de parámetros | Param # |
|---|---|---|---|
| Conv2D 16 filtros 5×5 valid | (None, 28, 28, 16) — (32−5)/1+1 = 28 | (5·5·3 + 1) × 16 = 76 × 16 | 1216 |
| MaxPooling2D 2×2 | (None, 14, 14, 16) | — | 0 |
| Conv2D 32 filtros 3×3 same | (None, 14, 14, 32) | (3·3·16 + 1) × 32 = 145 × 32 | 4640 |
| MaxPooling2D 2×2 | (None, 7, 7, 32) | — | 0 |
Total: 5856 parámetros.
Ejercicio 2:
(Máximos: max(2,0,0,0)=2, max(1,1,1,3)=3, max(8,1,2,1)=8, max(0,0,0,0)=0. Medias: 2/4=0.5, 6/4=1.5, 12/4=3.0, 0/4=0.0.)
El max pooling conserva mejor una detección débil pero real que activa un solo píxel: el máximo la mantiene intacta (el 8 sobrevive como 8), mientras que la media la diluye entre los vecinos inactivos (8 → 3.0). Por eso max pooling es la opción por defecto en las etapas intermedias: interesa saber si el patrón apareció, no cuánto activó la zona en promedio.
Ejercicio 3:
Cada bloque divide el tamaño por 2: 128 → 64 → 32 → 16. Se necesitan 3 bloques. Los filtros: 16 → 32 → 64, así que la salida final es de 16×16×64 canales. La última capa conv recibe 32 canales y tiene 64 filtros 3×3: (3·3·32 + 1) × 64 = 289 × 64 = 18 496 parámetros.
Conclusión
Ya dominas la mecánica completa de los dos bloques que forman la etapa de extracción de una CNN. De la capa convolucional sabes configurar sus cuatro decisiones (filtros, tamaño de kernel, stride, padding), sabes que los filtros abarcan todos los canales de su entrada y que la capa produce un canal por filtro, y sabes predecir su salida con la fórmula ⌊(n + 2p − f)/s⌋ + 1 y contar sus parámetros con (f·f·canales + 1) × filtros — 896 parámetros donde una densa equivalente necesitaría 1600 millones. Del pooling sabes que resume ventanas (max conserva detecciones, average promedia), que reduce el cómputo a una cuarta parte por bloque sin añadir parámetros y que regala invarianza local. Y has traducido todo esto a Keras, verificando a mano cada línea de un summary() y visualizando feature maps con matplotlib.
Con estas piezas ya puedes leer los planos de cualquier CNN. Y eso es justo lo que haremos en la siguiente lección: recorrer las arquitecturas populares que marcaron la historia del campo —LeNet, AlexNet, VGG, Inception, ResNet— entendiendo qué idea aportó cada una y comprobando, summary() en mano, que ya sabes descifrarlas.
Curso de Deep Learning
Módulo 1: Introducción a Deep Learning
- ¿Qué es Deep Learning?
- Historia y evolución del Deep Learning
- Aplicaciones de Deep Learning
- Conceptos básicos de redes neuronales
- Preparación del entorno de trabajo
Módulo 2: Fundamentos de Redes Neuronales
- Perceptrón y Perceptrón Multicapa
- Función de activación
- Propagación hacia adelante y hacia atrás
- Optimización y función de pérdida
- Tu primera red neuronal completa
Módulo 3: Redes Neuronales Convolucionales (CNN)
- Introducción a las CNN
- Capas convolucionales y de pooling
- Arquitecturas populares de CNN
- Aplicaciones de CNN en reconocimiento de imágenes
Módulo 4: Redes Neuronales Recurrentes (RNN)
- Introducción a las RNN
- LSTM y GRU
- Aplicaciones de RNN en procesamiento del lenguaje natural
- Secuencias y series temporales
Módulo 5: Técnicas Avanzadas en Deep Learning
- Redes Generativas Adversariales (GAN)
- Autoencoders
- Transfer Learning
- Regularización y técnicas de mejora
- Mecanismos de atención y Transformers
Módulo 6: Herramientas y Frameworks
- Introducción a TensorFlow
- Introducción a PyTorch
- Comparación de frameworks
- Entornos de desarrollo y recursos adicionales
- Guardar, cargar y desplegar modelos
Módulo 7: Proyectos Prácticos
- Clasificación de imágenes con CNN
- Generación de texto con RNN
- Detección de anomalías con Autoencoders
- Creación de una GAN para generación de imágenes
- Fine-tuning de un modelo preentrenado
