Entendendo redes convolucionais na prática
Eu comecei a trabalhar com CNNs em 2016, antes do torch ser mainstream no Brasil, quando ainda precisava ajustar manualmente o learning rate e torcer pra não dar NaN. O tema cr p conv aquarela do saber não é um termo técnico formal — é uma forma que eu e alguns colegas da área encontramos de descrever o processo de visualizar e interpretar os filtros que uma rede convolucional aprende durante o treinamento.
cr p conv aquarela do saber: o que isso significa de verdade
A metáfora da aquarela vem do fato de que, diferentemente de um modelo densely connected onde cada peso tem um significado mais direto, nos filtros convolucionais os padrões que emergem são suaves, sobrepostos, e muitas vezes impossíveis de isolar em uma única feature. Você treina uma rede, extrai os kernels das camadas iniciais, e o que vê não é "bordas horizontais" ou "texturas verticais" como os livros didáticos prometem. Você vê manchas difusas, cores que se misturam, algo parecido mesmo com uma aquarela onde as bordas nunca são nítidas. Isso é importante porque a maioria dos tutoriais online mostra visualizeções bonitas de filtros da camada 1 que parecem perfeitamente ortogonais. A realidade é bem mais bagunçada. Eu já perdi dias tentando interpretar um filtro que, na visualização padrão, parecia ruído branco, até descobrir que o problema era o normalizador — os pesos precisavam ser escalados por desvio padrão do batch antes de qualquer interpretação fazível.
Como extrair e visualizar os filtros de uma CNN
Vou mostrar com PyTorch, que é o que eu uso. O código é simples, mas os detalhes importam mais do que parece. Primeiro, você precisa de um modelo treinado. Não adianta tentar visualizar filtros de uma rede que não convergiu — o que vai aparecer é basicamente ruído gaussiano porque os pesos ainda não aprenderam nada estruturado. Um modelo trained no CIFAR-10 por 50 epochs já entrega resultados interpretáveis nas primeiras camadas.
import torch
import torchvision.models as models
import numpy as np
import matplotlib.pyplot as plt
Carrega um modelo pré-treinado
model = models.resnet18(pretrained=True)
Extrai os pesos da primeira camada convolucional
conv1_weights = model.conv1.weight shape: [64, 3, 7, 7]
Normaliza os filtros para visualização
def normalize_filter(f):
f = f - f.min()
f = f / f.max()
return f
O shape [64, 3, 7, 7] significa 64 filtros, cada um com 3 canais (RGB) ekernel 7x7. Cada filtro é uma tiny imagem de 3x7x7 que você pode plotar individualmente. O truque é não plotar os pesos brutos — eles têm mean próximo de zero e signs misturados, então a imagem fica toda cinza. Sempre subtraia o mean e divida pelo std antes de normalizar para [0,1].
O problema que ninguém conta sobre visualização de filtros
Aqui vai a parte que eu demorei pra aprender na marra. Visualizar os pesos do kernel diretamente só funciona bem pra camadas muito iniciais e com normalização adequada. Quando você chega nas camadas mais profundas — digamos, conv3_x do ResNet — os filtros são representações de altíssima dimensionalidade que não cabem numa imagem 2D. Os pesos já não correspondem a nada visível como "textura" ou "borda". Eles codificam combinações abstratas que só fazem sentido no espaço latente da rede. Eu tinha um caso específico com um modelo de detecção de lesões de pele que eu estava desenvolvendo. As visualizeções da camada 4 mostravam padrões que pareciam completamente aleatórios, e eu quase descartava o modelo achando que não tinha aprendido nada útil. O problema era que eu estava olhando pra camadas tarde demais. A informação relevante pra interpretabilidade clínica estava nas camadas 1 e 2. Depois de focar nelas e aplicar Grad-CAM pros mapas de ativação, o modelo mostrou que realmente estava olhando pra regiões correctas da imagem — as batas das lesões, não o fundo ou artefatos de compressão JPEG.
Grad-CAM é essencialmente a evolução natural dessa ideia de aquarela. Em vez de olhar pros pesos estáticos, você olha pra onde a rede prestou atenção numa input específica. O resultado é um heatmap sobreposto à imagem original, e é muito mais informativo do que qualquer visualização de filtro isolado.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Código prático pro Grad-CAM
import cv2
import torch.nn.functional as F
def grad_cam(model, input_img, layer_name):
Forward pass com hook
features = None
gradients = None
def forward_hook(module, input, output):
nonlocal features
features = output
def backward_hook(module, grad_input, grad_output):
nonlocal gradients
gradients = grad_output[0]
Registra hooks
target_layer = None
for name, module in model.named_modules():
if name.endswith(layer_name):
target_layer = module
target_layer.register_forward_hook(forward_hook)
target_layer.register_backward_hook(backward_hook)
break
Compute class score
output = model(input_img)
class_idx = output.argmax(dim=1).item()
score = output[0, class_idx]
score.backward()
Global average pooling dos gradients
pooled_grads = gradients.mean(dim=[2, 3])
Weighted sum dos features
for i in range(pooled_grads.shape[0]):
features[0, i, :, :] *= pooled_grads[i]
heatmap = features.squeeze().relu()
heatmap /= heatmap.max()
return heatmap
Isso te dá um heatmap de 256x256 (dependendo do último pooling) que você sobrepõe na imagem original com alpha blending. O resultado é grosso modo a "aquarela do saber" — onde a rede realmente coloca o peso pra tomar uma decisão.
Ferramentas e alternativas
Se você não quer escrever código do zero, tem options. O package torchcam implementa Grad-CAM e variantes (Grad-CAM++, Score-CAM, Layer-CAM) de forma clean. A biblioteca captum da Meta também é sólida, especialmente se você precisa de métodos mais avançados como Integrated Gradients ou Shapley values. pra quem quer algo mais visual e menos code-heavy, o TensorBoard embeddings projector permite fazer dos ativacoes de camadas convolucionais e explorar interativamente. Não é tão preciso quanto Grad-CAM pras suas decisoes, mas é rápido pros primeiros diagnostics.
Uma alternativa interessante que eu descubri recentemente é o CNNExplainer, um visualizador web que mostra passo a passo o que acontece em cada convolução, pooling, e ativação. É limitado em funcionalidade — só funciona pra architectures especificas — mas é excelente pra ensino e pra debug inicial.
Limitações honestas
Vizualização de filtros de CNN tem limitações sérias que a comunidade às vezes minimiza. Primeiro: correlação não é causalidade. Um filtro que parece detectar bordas pode estar apenas correlacionando com algum artefato do dataset de treino, não aprendendo o conceito de "borda" de verdade. Segundo: a interpretabilidade diminui drasticamente com a profundidade da rede. Camadas iniciais são relativamente transparentes. Camadas finais são essentially caixas pretas mesmo com todas as técnicas atuais. Terceiro, e isso é importante: a maioria dos métodos de explicação são eux-mêmes aproximativos. Grad-CAM usa gradientes pra estimar a importância dos features, mas gradientes medem sensibilidade local, não causalidade global. Se você perturbar a input num ponto onde o Grad-CAM diz que é irrelevante, a prediction pode mudar drasticamente. Isso não invalida o método, mas exige humildade na interpretação.
Se o seu objetivo é explicabilidade real — tipo pro direito brasileiro de explicação de decisões automatizadas (LGPD artigo 20) — visualização de filtros sozinha não é suficiente. Você precisa de combined approaches: Grad-CAM + LIME + contrafactual generation. E mesmo assim, nenhuma técnica atual garante interpretabilidade completa de uma CNN profunda.
Download e setup
pip install torch torchvision torchcam captum matplotlib opencv-python numpy
O repositório que eu recomendo com exemplos prontos é o twpl/cam_visualization no GitHub, que tem implementações clean de varias variants de CAM mais um notebook Jupyter que você roda e vê o resultado imediatamente. Eu usei ele como base pros meus primeiros experimentos e continua sendo útil como template. pra modelos pré-treinados, o torchvision.models já entrega weights do ImageNet que são bons ponto de partida. Se precisar de algo mais específico pro seu dominio — classificação médica, detecção de defeitos industriais — o HuggingFace Model Hub tem checkpoints prontos que você pode fine-tunar. O tempo de fine-tuning típico num CIFAR-like dataset com GPU consumer é de 2 a 4 horas, dependendo do tamanho do dataset e da arquitetura.
Conclusão sem conclusão
Visualizar o que uma CNN aprende é parte arte, parte ciência, e muita tentativa e erro. Não existe botão mágico que mostre exatamente o que o modelo pensa. O melhor que você tem é um conjunto de ferramentas imperfeitas que, combinadas, dão uma noção aproximada do funcionamento interno. Use elas, mas nunca confie cegamente no que veem — valide sempre com testes de ablação e comparações com performance de baseline.