PDI+VC · Ejercicio de Programación

EP09_01 — 🟢 Convolución 2D Manual ( Forward de una Capa Aprendida)

9.10.1 EP09_01 🟢 Convolución 2D Manual (Forward de una Capa Aprendida)

PyTorch, presentado en este capítulo, ejecuta nn.Conv2d(x) en una única llamada — pero detrás de ella solo está la correlación cruzada entre un kernel (ya entrenado) y una vecindad de la entrada, seguida de la suma de un sesgo y de una activación, exactamente como se formalizó en la Sección “Capa Convolucional”. La diferencia esencial respecto a la convolución de kernels fijos del Capítulo 3 es que, aquí, los valores del kernel y del sesgo ya vienen listos (como si hubieran sido aprendidos por gradiente), y te corresponde a ti reproducir manualmente la pasada directa (forward pass) que el framework ejecuta internamente.

Antes de entrenar una CNN real, se te ha encargado implementar esta pasada directa desde cero, para una única capa convolucional con un único canal de entrada y un único filtro de salida, incluyendo soporte para padding y stride arbitrarios.

9.10.1.1 📋 Directrices de Implementación

  1. Entrada: Leer las dimensiones \(H \times W\) del mapa de características de entrada y, a continuación, sus \(H \times W\) valores reales.

  2. Kernel y sesgo: Leer las dimensiones \(k_h \times k_w\) del kernel (ya entrenado), sus valores reales, y el sesgo \(b\) (real, escalar).

  3. Hiperparámetros: Leer el padding \(p\) (entero, número de ceros añadidos en cada borde) y el stride \(s\) (entero, paso del deslizamiento).

  4. Relleno: Añadir \(p\) ceros en cada uno de los cuatro bordes del mapa de entrada antes de la correlación.

  5. Correlación cruzada: Para cada posición de salida \((i, j)\), calcular \[ z(i,j) = b + \sum_{u=0}^{k_h-1} \sum_{v=0}^{k_w-1} K(u,v) \cdot X_{pad}(i \cdot s + u,\; j \cdot s + v), \] recorriendo la entrada sin invertir el kernel (convención de los frameworks de aprendizaje profundo, diferente de la convolución matemática clásica).

  6. Activación: Aplicar ReLU a cada valor: \(a(i,j) = \max(0, z(i,j))\).

  7. Dimensiones de salida: \(O_h = \lfloor (H + 2p - k_h)/s \rfloor + 1\) y \(O_w = \lfloor (W + 2p - k_w)/s \rfloor + 1\).

  8. Salida: Imprimir \(O_h\) y \(O_w\) en la primera línea, seguidos de \(O_h\) líneas con \(O_w\) valores reales cada una (el mapa de características de salida, ya con ReLU aplicada), formateados con 4 decimales.

9.10.1.2 📌 Restricciones Computacionales

  • Un canal de entrada, un filtro de salida: no es necesario manejar múltiples canales ni múltiples filtros en esta versión simplificada.
  • Sin inversión del kernel: implementa correlación cruzada, no la convolución matemática clásica con kernel invertido — es esa la operación que PyTorch (y la mayoría de los frameworks) llama “convolución”.
  • Relleno con ceros: los \(p\) píxeles añadidos en cada borde valen siempre \(0\).
  • Formato: todos los valores de salida deben tener exactamente 4 decimales, incluso cuando el valor es entero (ej.: 2.0000).

9.10.1.3 🧠 Fundamentación Teórica

Elemento Papel en la capa convolucional
Kernel \(K\) Parámetros aprendidos por gradiente, análogos a los coeficientes de un filtro fijo del Capítulo 3, pero ajustados por retropropagación
Sesgo \(b\) Desplazamiento aprendido, sumado tras la correlación — permite que la neurona “se active” incluso con entrada nula
Padding Controla la dimensión espacial de salida y evita la pérdida de información en los bordes en cada capa
Stride Controla el paso del desplazamiento; valores \(> 1\) reducen la resolución espacial, como una forma de submuestreo integrado en la propia convolución
ReLU Introduce no linealidad tras la combinación lineal, exactamente como en la Sección “Función de Activación”

9.10.1.4 🧩 Métodos del morph.py que pueden ayudar

  • mm.readImg(h, w, dtype='float') — lee directamente una matriz \(h \times w\) de valores reales de la entrada estándar, ahorrando el parseo manual del mapa de características y del kernel.
  • mm.correlacao0(f, kernel, bias) — implementa la misma suma de correlación cruzada + sesgo que vas a calcular a mano, pero sin soporte para padding o stride, y convierte el resultado a uint8 (trunca valores negativos y decimales). Puede servir como referencia conceptual o para comprobar el caso más simple (\(p=0\), \(s=1\)), pero no sustituye tu implementación completa — que debe preservar signo, decimales, padding, stride y ReLU.

9.10.1.5 📦 Especificación de Entrada y Salida (VPL)

Entrada:

  • Línea 1: Enteros \(H\) y \(W\).
  • Siguientes \(H\) líneas: \(W\) valores reales cada una (mapa de entrada).
  • Siguiente línea: Enteros \(k_h\) y \(k_w\).
  • Siguientes \(k_h\) líneas: \(k_w\) valores reales cada una (kernel).
  • Siguiente línea: Real \(b\) (sesgo).
  • Siguiente línea: Enteros \(p\) y \(s\).

Salida:

  • Línea 1: Enteros \(O_h\) y \(O_w\).
  • Siguientes \(O_h\) líneas: \(O_w\) valores reales cada una, con 4 decimales.

9.10.1.6 📌 Ejemplos

Entrada Salida Observación
3 3
1 2 0
0 1 2
1 0 1
2 2
1 1
1 1
-2
0 1
2 2
2.0000 3.0000
0.0000 2.0000
Padding 0, stride 1: salida \(2\times2\) sin relleno.
3 3
1 2 0
0 1 2
1 0 1
2 2
1 0
0 1
0
1 2
2 2
1.0000 0.0000
1.0000 2.0000
Padding 1, stride 2: entrada rellenada con ceros antes de la correlación.
🎮 Simulador: Convolución 2D Manual 🟢 correlación cruzada + sesgo + ReLU

Entrada 4×4 fija, kernel 2×2 fijo (resaltado en azul) — ajuste padding (p), stride (s) y sesgo (b), exactamente los parámetros que EP09_01 pide en la entrada, y observa cómo cambian el tamaño y los valores de la salida.

Padding (p)
Stride (s)
Sesgo (b)
(0,0)
Entrada X rellenada (con padding)
original padding (0) ventana actual
Kernel K (2×2)
Salida Y = ReLU(X⊛K + b)

💡 Cada posición del control deslizante revela una celda de la matriz de salida. Recorre todas las posiciones para completar el mapa de salida. Cambiar p, s o b reinicia la exploración, porque el mapa de salida cambia de tamaño y/o de valores.

Figura 9.43: Simulador EP09_01: Convolución 2D Manual (correlación cruzada + sesgo + ReLU, con padding y stride ajustables)
%%writefile EP09_01.py
# Código Python
Overwriting EP09_01.py
TestSuite("EP09_01.py").run()
✔️ EP09_01.cases ya existe en casos/
📋 3 caso(s) cargado(s) de casos/EP09_01.cases

🔍 Probando Python: EP09_01.py
⚠️ EP09_01.py: archivo vacío (menos de 3 líneas). Pruebas omitidas.