Motivación estructural: por qué las convoluciones funcionan tan bien en imágenes
En visión por computadora, una imagen no es solo un vector largo: tiene estructura espacial (pixeles vecinos suelen estar relacionados) y patrones locales (bordes, esquinas, texturas) que se repiten en distintas posiciones. Una red convolucional (CNN) aprovecha esto con dos ideas clave:
- Conectividad local: una neurona “mira” solo una región pequeña (ventana) de la imagen, en lugar de conectarse a todos los pixeles.
- Pesos compartidos: el mismo conjunto de pesos (un kernel o filtro) se aplica en todas las posiciones. Así, el modelo aprende “detector de borde horizontal”, “textura”, etc., independientemente de dónde aparezca.
Esto reduce drásticamente el número de parámetros frente a una capa totalmente conectada sobre la imagen completa y, además, introduce una propiedad útil: si un patrón aparece en otra zona, el filtro puede detectarlo igual.
Componentes principales de una CNN
Capas convolucionales (Conv2D): filtros, kernels, canales y mapas de activación
Una capa convolucional aplica varios filtros a la entrada. Cada filtro produce un mapa de activación (también llamado feature map) que indica “dónde” se detecta ese patrón.
- Kernel (filtro): matriz pequeña, por ejemplo
3x3o5x5. En imágenes RGB, el kernel realmente tiene formakH x kW x C_in(incluye todos los canales de entrada). - Canales: una imagen RGB tiene
C=3canales. Tras una convolución conFfiltros, la salida tendráC_out=Fcanales (uno por filtro). - Mapa de activación: tensor de salida por filtro. Si la entrada es
H x W x C, la salida seráH_out x W_out x F.
Intuición: cada filtro aprende una plantilla. Al deslizarse por la imagen, calcula una respuesta alta donde la plantilla “encaja”. En capas iniciales suelen aparecer bordes y contrastes; en capas más profundas, combinaciones más complejas (partes, texturas específicas, etc.).
Stride (paso)
El stride es cuánto se desplaza el kernel cada vez. Con stride=1 se evalúan posiciones contiguas; con stride=2 se “salta” una posición, reduciendo la resolución espacial y el costo computacional.
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
Padding (relleno)
El padding agrega bordes (normalmente ceros) alrededor de la imagen para controlar el tamaño de salida:
- Valid: sin padding, la salida se hace más pequeña.
- Same: padding elegido para mantener aproximadamente el mismo
HyW(constride=1se mantiene igual).
El padding ayuda a no “perder” información de los bordes y a apilar muchas capas sin que el mapa se reduzca demasiado rápido.
Pooling (submuestreo): max pooling y average pooling
El pooling reduce la resolución espacial agregando información local. El más común es max pooling (toma el máximo en una ventana, por ejemplo 2x2), aunque también existe average pooling (promedio).
- Ventaja práctica: reduce cómputo y hace la representación más robusta a pequeñas traslaciones.
- Alternativa moderna: usar convoluciones con
stride>1en lugar de pooling explícito, según la arquitectura.
Capas totalmente conectadas al final (clasificación)
En clasificación, una CNN típica termina convirtiendo los mapas de activación en un vector y luego en probabilidades por clase:
- Flatten: aplana
H x W x Ca un vector largo. - Dense: combina globalmente características para decidir la clase.
- Global Average Pooling (GAP): alternativa a Flatten; promedia cada canal y reduce parámetros, a menudo mejorando estabilidad.
Flujo de tensores y arquitectura típica para clasificación
Una arquitectura didáctica (estilo “bloques conv + pooling”) podría verse así:
Entrada: (H, W, 3) # imagen RGB
Bloque 1: Conv(3x3, F=16, stride=1, padding=same) -> Activación -> Pool(2x2)
Bloque 2: Conv(3x3, F=32, stride=1, padding=same) -> Activación -> Pool(2x2)
Bloque 3: Conv(3x3, F=64, stride=1, padding=same) -> Activación
Cabeza: GAP o Flatten -> Dense(64) -> Dense(#clases)Ejemplo de dimensiones (imagen pequeña 32x32x3, pooling 2x2):
| Etapa | Operación | Forma del tensor |
|---|---|---|
| Entrada | Imagen | (32, 32, 3) |
| Conv1 | 3x3, F=16, same | (32, 32, 16) |
| Pool1 | 2x2 | (16, 16, 16) |
| Conv2 | 3x3, F=32, same | (16, 16, 32) |
| Pool2 | 2x2 | (8, 8, 32) |
| Conv3 | 3x3, F=64, same | (8, 8, 64) |
| GAP | Promedio espacial | (64) |
| Salida | Dense(#clases) | (#clases) |
Lectura: a medida que avanzas, disminuye la resolución espacial (H, W) y aumenta la profundidad (C): se pasa de detalles locales a descriptores más abstractos.
Normalización y regularización frecuentes en CNN (en la práctica)
En CNN modernas es común incluir componentes que estabilizan el entrenamiento y mejoran el desempeño:
- Batch Normalization: suele colocarse entre la convolución y la activación. Ayuda a estabilizar escalas internas y permite entrenamientos más robustos.
- Dropout: más habitual en la “cabeza” (capas densas) que en las capas conv, aunque también se usa SpatialDropout para apagar canales completos.
- Data augmentation: en visión es especialmente importante (rotaciones pequeñas, flips, crops, cambios de brillo/contraste). Se aplica al cargar datos, no como capa “aprendible”.
- Weight decay (L2): muy usado en convoluciones para controlar complejidad efectiva.
En plataformas como TensorFlow/Keras o PyTorch, estos elementos se integran como capas o parámetros del optimizador y del dataloader.
Ejemplo didáctico paso a paso: clasificación simple con imágenes pequeñas
Objetivo: construir una CNN pequeña para clasificar imágenes 32x32 en pocas clases (por ejemplo, “círculo vs cuadrado” en imágenes sintéticas, o un subconjunto pequeño de un dataset educativo). La idea es entender el flujo y luego inspeccionar qué aprende.
Paso 1: preparar datos (forma y normalización)
- Organiza tus datos como tensores
(N, 32, 32, 1)si son en escala de grises o(N, 32, 32, 3)si son RGB. - Escala los pixeles a
[0, 1](por ejemplo, dividir por 255) para que las activaciones trabajen en rangos manejables. - Divide en entrenamiento/validación (por ejemplo 80/20).
Paso 2: definir una CNN pequeña (Keras)
import tensorflow as tf
from tensorflow.keras import layers, models
num_classes = 2
model = models.Sequential([
layers.Input(shape=(32, 32, 1)),
layers.Conv2D(16, (3,3), padding="same", use_bias=False),
layers.BatchNormalization(),
layers.ReLU(),
layers.MaxPooling2D((2,2)),
layers.Conv2D(32, (3,3), padding="same", use_bias=False),
layers.BatchNormalization(),
layers.ReLU(),
layers.MaxPooling2D((2,2)),
layers.Conv2D(64, (3,3), padding="same", use_bias=False),
layers.BatchNormalization(),
layers.ReLU(),
layers.GlobalAveragePooling2D(),
layers.Dropout(0.2),
layers.Dense(num_classes, activation="softmax")
])
model.summary()Notas didácticas:
use_bias=Falsees común cuando usas BatchNorm, porque BN ya incorpora un desplazamiento aprendible.GlobalAveragePooling2Dreduce parámetros y hace la cabeza más simple para un ejemplo pequeño.
Paso 3: compilar y entrenar
model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3),
loss="sparse_categorical_crossentropy",
metrics=["accuracy"]
)
history = model.fit(
x_train, y_train,
validation_data=(x_val, y_val),
epochs=10,
batch_size=64
)Si tus etiquetas están en one-hot, cambia la pérdida a categorical_crossentropy. Para un dataset pequeño, prueba también data augmentation (por ejemplo con tf.keras.layers.RandomFlip, RandomRotation) al inicio del modelo o en el pipeline de datos.
Paso 4: verificar el flujo de tensores con un ejemplo
Una forma rápida de “ver” el flujo es pasar un batch y revisar formas intermedias. En Keras puedes crear un modelo que exponga salidas de capas:
layer_outputs = [layer.output for layer in model.layers if isinstance(layer, (layers.Conv2D, layers.MaxPooling2D))]
probe = tf.keras.Model(inputs=model.input, outputs=layer_outputs)
acts = probe(x_val[:1])
for a in acts:
print(a.shape)Interpretación: cada salida corresponde a un bloque; observa cómo cambian H, W y el número de canales.
Paso 5: analizar características aprendidas (filtros y mapas de activación)
Dos inspecciones simples y muy instructivas:
5.1 Visualizar kernels de la primera capa
En la primera capa, los filtros suelen parecer detectores de bordes/gradientes. Puedes extraer pesos y graficarlos (en escala de grises si C_in=1):
import numpy as np
w = model.layers[1].get_weights()[0] # Conv2D: (3,3,C_in,F)
print(w.shape)
# Ejemplo: tomar el filtro 0
k0 = w[:, :, 0, 0]
# Luego lo graficas con matplotlib (imshow) normalizando a [0,1]Qué buscar: patrones tipo “borde horizontal/vertical”, “diagonal”, “centro-surround”. Si no aparecen, revisa si el modelo está aprendiendo (accuracy estancada) o si el dataset es demasiado simple/ruidoso.
5.2 Visualizar mapas de activación para una imagen
Los mapas muestran dónde responde cada filtro. Para una imagen de validación:
# Tomar la salida del primer bloque conv (por ejemplo, después de ReLU)
# Ajusta el índice según tu modelo
conv1_out_model = tf.keras.Model(model.input, model.layers[3].output)
feat = conv1_out_model(x_val[:1]) # (1, 32, 32, 16)
# feat[0, :, :, i] es el mapa del canal iInterpretación práctica:
- Si un canal se activa en contornos del objeto, probablemente aprendió un detector de borde.
- Si se activa en regiones repetitivas, puede estar capturando textura.
- En capas más profundas, los mapas suelen ser más “abstractos” y menos interpretables visualmente, pero aún reflejan partes relevantes.
Paso 6: pequeñas variaciones para experimentar (y entender el rol de cada componente)
- Stride vs pooling: reemplaza
MaxPooling2DporConv2D(..., strides=2)y compara precisión y velocidad. - Padding: cambia
sameporvalidy observa cómo se reduce el tamaño y cómo afecta el rendimiento. - Número de filtros: duplica
Fen cada bloque (16-32-64) vs mantenerlo constante; observa capacidad y sobreajuste. - GAP vs Flatten: prueba
Flatten+Dense(128)y compara número de parámetros y desempeño.