PDI+VC · Exercício de Programação

EP09_01 — 🟢 Convolução 2D Manual ( Forward de uma Camada Aprendida)

9.10.1 EP09_01 🟢 Convolução 2D Manual (Forward de uma Camada Aprendida)

O PyTorch, apresentado neste capítulo, executa nn.Conv2d(x) em uma única chamada — mas por trás dela está apenas a correlação cruzada entre um kernel (já treinado) e uma vizinhança da entrada, seguida da soma de um viés e de uma ativação, exatamente como formalizado na Seção “Camada Convolucional”. A diferença essencial em relação à convolução de kernels fixos do Capítulo 3 é que, aqui, os valores do kernel e do viés já vêm prontos (como se tivessem sido aprendidos por gradiente), e cabe a você reproduzir manualmente a passagem direta (forward pass) que o framework executa internamente.

Antes de treinar uma CNN de verdade, você foi encarregado de implementar essa passagem direta do zero, para uma única camada convolucional com um único canal de entrada e um único filtro de saída, incluindo suporte a padding e stride arbitrários.

9.10.1.1 📋 Diretrizes de Implementação

  1. Entrada: Ler as dimensões \(H \times W\) do mapa de características de entrada e, em seguida, seus \(H \times W\) valores reais.

  2. Kernel e viés: Ler as dimensões \(k_h \times k_w\) do kernel (já treinado), seus valores reais, e o viés \(b\) (real, escalar).

  3. Hiperparâmetros: Ler o padding \(p\) (inteiro, número de zeros adicionados em cada borda) e o stride \(s\) (inteiro, passo do deslizamento).

  4. Preenchimento: Adicionar \(p\) zeros em cada uma das quatro bordas do mapa de entrada antes da correlação.

  5. Correlação cruzada: Para cada posição de saída \((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), \] percorrendo a entrada sem inverter o kernel (convenção dos frameworks de aprendizado profundo, diferente da convolução matemática clássica).

  6. Ativação: Aplicar ReLU a cada valor: \(a(i,j) = \max(0, z(i,j))\).

  7. Dimensões de saída: \(O_h = \lfloor (H + 2p - k_h)/s \rfloor + 1\) e \(O_w = \lfloor (W + 2p - k_w)/s \rfloor + 1\).

  8. Saída: Imprimir \(O_h\) e \(O_w\) na primeira linha, seguidos de \(O_h\) linhas com \(O_w\) valores reais cada (o mapa de características de saída, já com ReLU aplicada), formatados com 4 casas decimais.

9.10.1.2 📌 Restrições Computacionais

  • Um canal de entrada, um filtro de saída: não é necessário lidar com múltiplos canais ou múltiplos filtros nesta versão simplificada.
  • Sem inversão do kernel: implemente correlação cruzada, não a convolução matemática clássica com kernel invertido — é essa a operação que o PyTorch (e a maioria dos frameworks) chama de “convolução”.
  • Preenchimento por zeros: os \(p\) pixels adicionados em cada borda valem sempre \(0\).
  • Formatação: todos os valores de saída devem ter exatamente 4 casas decimais, mesmo quando o valor é inteiro (ex.: 2.0000).

9.10.1.3 🧠 Fundamentação Teórica

Elemento Papel na camada convolucional
Kernel \(K\) Parâmetros aprendidos por gradiente, análogos aos coeficientes de um filtro fixo do Capítulo 3, mas ajustados por retropropagação
Viés \(b\) Deslocamento aprendido, somado após a correlação — permite que o neurônio “dispare” mesmo com entrada nula
Padding Controla a dimensão espacial de saída e evita a perda de informação nas bordas a cada camada
Stride Controla o passo do deslocamento; valores \(> 1\) reduzem a resolução espacial, como uma forma de subamostragem embutida na própria convolução
ReLU Introduz não linearidade após a combinação linear, exatamente como na Seção “Função de Ativação”

9.10.1.4 🧩 Métodos do morph.py que podem ajudar

  • mm.readImg(h, w, dtype='float') — lê diretamente uma matriz \(h \times w\) de valores reais da entrada padrão, poupando o parsing manual do mapa de características e do kernel.
  • mm.correlacao0(f, kernel, bias) — implementa a mesma soma de correlação cruzada + viés que você vai calcular à mão, mas sem suporte a padding ou stride, e converte o resultado para uint8 (trunca valores negativos e decimais). Pode servir de referência conceitual ou para conferir o caso mais simples (\(p=0\), \(s=1\)), mas não substitui sua implementação completa — que precisa preservar sinal, casas decimais, padding, stride e ReLU.

9.10.1.5 📦 Especificação de Entrada e Saída (VPL)

Entrada:

  • Linha 1: Inteiros \(H\) e \(W\).
  • Próximas \(H\) linhas: \(W\) valores reais cada (mapa de entrada).
  • Próxima linha: Inteiros \(k_h\) e \(k_w\).
  • Próximas \(k_h\) linhas: \(k_w\) valores reais cada (kernel).
  • Próxima linha: Real \(b\) (viés).
  • Próxima linha: Inteiros \(p\) e \(s\).

Saída:

  • Linha 1: Inteiros \(O_h\) e \(O_w\).
  • Próximas \(O_h\) linhas: \(O_w\) valores reais cada, com 4 casas decimais.

9.10.1.6 📌 Exemplos

Entrada Saída Observação
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: saída \(2\times2\) sem preenchimento.
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 preenchida com zeros antes da correlação.
🎮 Simulador: Convolução 2D Manual 🟢 correlação cruzada + viés + ReLU

Entrada 4×4 fixa, kernel 2×2 fixo (destacado em azul) — ajuste padding (p), stride (s) e viés (b), exatamente os parâmetros que o EP09_01 pede na entrada, e veja como eles mudam o tamanho e os valores da saída.

Padding (p)
Stride (s)
Viés (b)
(0,0)
Entrada X preenchida (com padding)
original padding (0) janela atual
Kernel K (2×2)
Saída Y = ReLU(X⊛K + b)

💡 Cada posição do slider revela uma célula da matriz de saída. Percorra todas as posições para completar o mapa de saída. Trocar p, s ou b reinicia a exploração, porque o mapa de saída muda de tamanho e/ou de valores.

Figura 9.43: Simulador EP09_01: Convolução 2D Manual (correlação cruzada + viés + ReLU, com padding e stride ajustáveis)
%%writefile EP09_01.py
# Código Python
Writing EP09_01.py
TestSuite("EP09_01.py").run()
✔️ EP09_01.cases já existe em casos/
📋 3 caso(s) carregado(s) de casos/EP09_01.cases

🔍 Testando Python: EP09_01.py
⚠️ EP09_01.py: Arquivo sem conteúdo (menos de 3 linhas). Testes ignorados.