Machine Learning: Redes Neurais Convolucionais

Última atualização: 2026-08-26

CNNs dão aos computadores "olhos" — os kernels de convolução agem como receptores visuais, reconhecendo imagens camada por camada, de bordas a objetos.

1. O que você vai aprender


2. A história real de um e-commerce

(1) O problema: categorização manual de produtos é lenta e inconsistente

A plataforma do Bob lista 1 mil novos produtos todos os dias, e a equipe de operações deve classificar manualmente as imagens dos produtos nas categorias corretas. A classificação manual é lenta (5 segundos por imagem), inconsistente (pessoas diferentes atribuem o mesmo produto a categorias diferentes) e cara ($30 mil USD por mês em custos de mão de obra). A classificação de imagens é o gargalo nas operações de e-commerce.

(2) A solução com CNN

CNNs aprendem automaticamente features visuais a partir de imagens de produtos para realizar a classificação — velocidade de milissegundos, forte consistência e zero custo de mão de obra.

PYTHON
import torchvision.models as models

# Transfer learning: usar ResNet pré-treinado
model = models.resnet18(pretrained=True)
model.fc = nn.Linear(model.fc.in_features, num_classes)  # Substituir a última camada

(3) O resultado: 95% de acurácia na classificação, $30K economizados por mês

Bob usou CNN + transfer learning para automatizar a classificação de produtos, alcançando mais de 95% de acurácia. A velocidade de processamento melhorou de 5 segundos por imagem para 0,01 segundo por imagem, economizando $30 mil USD em custos mensais de mão de obra.


3. Como a convolução funciona

(1) Kernels e extração de features

100%
graph TB
    INPUT[Imagem de Entrada<br/>H x W x C] --> CONV1[Camada Conv 1<br/>Bordas e Linhas]
    CONV1 --> POOL1[Camada Pool 1<br/>Reduz Espacial]
    POOL1 --> CONV2[Camada Conv 2<br/>Texturas e Partes]
    CONV2 --> POOL2[Camada Pool 2]
    POOL2 --> CONV3[Camada Conv 3<br/>Objetos e Formas]
    CONV3 --> FLATTEN[Flatten]
    FLATTEN --> FC[Totalmente Conectada]
    FC --> OUTPUT[Probabilidades das Classes]

▶ Exemplo: Implementando convolução manualmente

PYTHON
import torch
import torch.nn as nn

# Entrada: 1 imagem, 1 canal, 5x5
input_img = torch.tensor([[
    [1, 2, 0, 1, 3],
    [0, 1, 2, 3, 1],
    [1, 3, 1, 0, 2],
    [2, 0, 3, 1, 1],
    [0, 1, 2, 0, 1],
]], dtype=torch.float32).unsqueeze(0)  # shape: (1, 1, 5, 5)

# Conv2d: 1 canal de entrada, 1 canal de saída, kernel 3x3
conv = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=0)
print(f"Pesos do kernel:\n{conv.weight.data.squeeze()}")
print(f"Bias: {conv.bias.data.item():.4f}")

output = conv(input_img)
print(f"\nShape da entrada: {input_img.shape}")
print(f"Shape da saída: {output.shape}")
print(f"Saída:\n{output.squeeze().detach()}")

Saída:

TEXT 📖 Somente leitura
# Execução bem-sucedida

(2) Parâmetros de convolução

Parâmetro Significado Efeito
kernel_size Tamanho do kernel 3x3 (comum) / 5x5 / 7x7
stride Tamanho do passo 1 (padrão) / 2 (downsampling)
padding Preenchimento com zeros 0 (encolhe) / 1 (preserva o tamanho)
in_channels Número de canais de entrada RGB=3, escala de cinza=1
out_channels Número de canais de saída Igual ao número de kernels

▶ Exemplo: Comparando diferentes configurações de convolução

PYTHON
import torch.nn as nn

x = torch.randn(1, 3, 32, 32)  # 1 batch, 3 canais, 32x32

configs = {
    "3x3, stride=1, pad=1": nn.Conv2d(3, 16, 3, stride=1, padding=1),
    "3x3, stride=2, pad=1": nn.Conv2d(3, 16, 3, stride=2, padding=1),
    "5x5, stride=1, pad=2": nn.Conv2d(3, 16, 5, stride=1, padding=2),
    "5x5, stride=2, pad=0": nn.Conv2d(3, 16, 5, stride=2, padding=0),
}

for name, layer in configs.items():
    out = layer(x)
    print(f"{name:25s}: {x.shape} → {out.shape}")

Saída:

TEXT 📖 Somente leitura
# Execução bem-sucedida

4. Camadas de Pooling e arquiteturas CNN

(1) Operações de pooling

▶ Exemplo: MaxPool vs. AvgPool

PYTHON
import torch
import torch.nn as nn

x = torch.tensor([[
    [1, 3, 2, 4],
    [5, 6, 7, 8],
    [9, 2, 1, 3],
    [4, 5, 6, 7],
]], dtype=torch.float32).unsqueeze(0)  # (1, 1, 4, 4)

maxpool = nn.MaxPool2d(kernel_size=2, stride=2)
avgpool = nn.AvgPool2d(kernel_size=2, stride=2)

print(f"Entrada:\n{x.squeeze()}")
print(f"\nMaxPool (2x2):\n{maxpool(x).squeeze()}")
print(f"\nAvgPool (2x2):\n{avgpool(x).squeeze()}")

Saída:

TEXT 📖 Somente leitura
# Execução bem-sucedida

(2) Evolução das arquiteturas clássicas

Arquitetura Ano Inovação Profundidade Parâmetros
LeNet 1998 Primeira CNN 5 60K
AlexNet 2012 ReLU + Dropout + GPU 8 60M
VGG 2014 Kernels pequenos empilhados (3x3) 16-19 138M
ResNet 2015 Conexões residuais (skip connections) 18-152 11-60M

▶ Exemplo: Construindo uma CNN simples com PyTorch

PYTHON
import torch
import torch.nn as nn

class SimpleCNN(nn.Module):
    def __init__(self, num_classes=5):
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 32, 3, padding=1),   # 3→32 canais, 32x32
            nn.ReLU(),
            nn.MaxPool2d(2, 2),               # 32→16x16
            nn.Conv2d(32, 64, 3, padding=1),  # 32→64 canais, 16x16
            nn.ReLU(),
            nn.MaxPool2d(2, 2),               # 16→8x8
            nn.Conv2d(64, 128, 3, padding=1), # 64→128 canais, 8x8
            nn.ReLU(),
            nn.MaxPool2d(2, 2),               # 8→4x4
        )
        self.classifier = nn.Sequential(
            nn.Flatten(),
            nn.Linear(128 * 4 * 4, 256),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(256, num_classes),
        )

    def forward(self, x):
        x = self.features(x)
        x = self.classifier(x)
        return x

model = SimpleCNN(num_classes=5)
print(model)
print(f"Parâmetros: {sum(p.numel() for p in model.parameters()):,}")

Saída:

TEXT 📖 Somente leitura
# Função definida com sucesso

5. Transfer Learning

(1) Por que o transfer learning funciona

Modelos pré-treinados no ImageNet já aprenderam features visuais gerais (bordas → texturas → partes de objetos). Você só precisa substituir a última camada para adaptar a uma nova tarefa.

▶ Exemplo: Transfer learning com ResNet18

PYTHON
import torch
import torch.nn as nn
import torchvision.models as models
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import numpy as np

# Carregar ResNet18 pré-treinado
model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)

# Congelar todas as camadas (modo extrator de features)
for param in model.parameters():
    param.requires_grad = False

# Substituir a última camada totalmente conectada
num_classes = 5  # Eletrônicos, Roupas, Alimentos, Livros, Casa
model.fc = nn.Linear(model.fc.in_features, num_classes)

# Apenas a nova camada fc tem parâmetros treináveis
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
total = sum(p.numel() for p in model.parameters())
print(f"Treináveis: {trainable:,} / Total: {total:,} ({trainable/total*100:.1f}%)")

# Transformações de dados para o modelo pré-treinado
transform = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

Saída:

TEXT 📖 Somente leitura
# Execução bem-sucedida

(2) Estratégias de ajuste fino

Estratégia Camadas congeladas Tamanho dos dados de treino Tempo de treino Melhor para
Extrator de features Todas (treinar só fc) Pequeno (<1k) Rápido Dados muito limitados
Ajuste fino parcial Últimas camadas Médio (1-10k) Médio Dados moderados
Ajuste fino completo Nenhuma Grande (>10k) Lento Dados abundantes

▶ Exemplo: Ajuste fino das últimas camadas

PYTHON
import torchvision.models as models
import torch.nn as nn

model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)

# Congelar camadas iniciais, descongelar layer4
for name, param in model.named_parameters():
    if "layer4" in name or "fc" in name:
        param.requires_grad = True
    else:
        param.requires_grad = False

model.fc = nn.Linear(model.fc.in_features, 5)

trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"Parâmetros treináveis para ajuste fino: {trainable:,}")

Saída:

TEXT 📖 Somente leitura
# Execução bem-sucedida

6. Classificação de Imagens de Produtos do SalesPredict

▶ Exemplo: Pipeline completo de treinamento com transfer learning

PYTHON
import torch
import torch.nn as nn
import torchvision.models as models
from torch.utils.data import DataLoader, TensorDataset
from sklearn.model_selection import train_test_split
import numpy as np

# Simular dados de imagem (na prática, carregue imagens reais com ImageFolder)
rng = np.random.default_rng(42)
n = 2000
X = rng.standard_normal((n, 3, 224, 224)).astype(np.float32)  # 3 canais 224x224
y = rng.integers(0, 5, n)  # 5 categorias de produtos

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

train_ds = TensorDataset(torch.FloatTensor(X_train), torch.LongTensor(y_train))
test_ds = TensorDataset(torch.FloatTensor(X_test), torch.LongTensor(y_test))
train_loader = DataLoader(train_ds, batch_size=32, shuffle=True)
test_loader = DataLoader(test_ds, batch_size=64)

# ResNet18 pré-treinado com cabeça personalizada
model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
for param in model.parameters():
    param.requires_grad = False
model.fc = nn.Linear(model.fc.in_features, 5)

criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.fc.parameters(), lr=0.001)

# Treinamento
for epoch in range(10):
    model.train()
    correct, total = 0, 0
    for X_batch, y_batch in train_loader:
        y_pred = model(X_batch)
        loss = criterion(y_pred, y_batch)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        correct += (y_pred.argmax(1) == y_batch).sum().item()
        total += y_batch.size(0)

    if epoch % 2 == 0:
        print(f"Época {epoch}: perda={loss.item():.4f}, acurácia={correct/total:.3f}")

# Avaliar
model.eval()
correct, total = 0, 0
with torch.no_grad():
    for X_batch, y_batch in test_loader:
        y_pred = model(X_batch)
        correct += (y_pred.argmax(1) == y_batch).sum().item()
        total += y_batch.size(0)

print(f"\nAcurácia no teste: {correct/total:.3f}")

Saída:

TEXT 📖 Somente leitura
# Execução bem-sucedida

❓ Perguntas Frequentes

P: Por que as CNNs são mais adequadas para imagens do que os MLPs? R: Três razões — 1) Compartilhamento de parâmetros (o mesmo kernel varre toda a imagem, reduzindo parâmetros); 2) Conectividade local (cada neurônio só vê uma região local); 3) Invariância à translação (objetos são reconhecidos independentemente da posição). MLPs achatam imagens e perdem a estrutura espacial.

P: Quantos dados o transfer learning exige? R: O modo Extrator de Features (treinando apenas a camada fc) precisa de apenas algumas centenas de imagens; o ajuste fino das últimas camadas exige 1-10 mil imagens; treinar do zero precisa de 10 mil+ imagens. Quanto menos dados você tiver, mais camadas deve congelar.

P: Por que o kernel 3x3 é o mais usado? R: Duas convoluções 3x3 empilhadas têm o mesmo campo receptivo que um kernel 5x5, mas com menos parâmetros (18 vs. 25) e mais não linearidade (2 ativações ReLU). O VGG demonstrou que empilhar kernels pequenos supera kernels grandes.

P: O que são conexões residuais no ResNet? R: y = F(x) + x — a rede aprende o resíduo (incremento) em vez do mapeamento completo. Benefícios: resolve o problema do gradiente desaparecendo em redes profundas e permite o treinamento de redes com 100+ camadas.

P: Por que precisamos do Normalize no pré-processamento de imagens? R: Modelos pré-treinados foram normalizados usando a média e o desvio padrão do ImageNet. Novos dados devem usar a mesma normalização; caso contrário, as distribuições das features não vão coincidir e o desempenho do transfer learning será prejudicado.

P: Como lidar com desbalanceamento de classes? R: Três abordagens — 1) Função de perda ponderada (nn.CrossEntropyLoss(weight=class_weights)); 2) Oversampling da classe minoritária; 3) Data augmentation (flips/rotações/cortes aleatórios).


📖 Resumo


📝 Exercícios

  1. Básico (Dificuldade ⭐): Construa uma CNN de 2 camadas usando nn.Conv2d + nn.MaxPool2d com entrada 3x32x32 e saída 16x8x8. Imprima o shape após cada camada. Dica: Conv2d(3,8,3,pad=1) → Pool → Conv2d(8,16,3,pad=1) → Pool.
  2. Intermediário (Dificuldade ⭐⭐): Use transfer learning (ResNet18) para classificar CIFAR-10 (10 classes). Congele todas as camadas exceto fc e treine por 5 épocas. Dica: torchvision.datasets.CIFAR10 + transforms.
  3. Desafio (Dificuldade ⭐⭐⭐): Compare Extrator de Features vs. Ajuste Fino Parcial vs. Ajuste Fino Completo no CIFAR-10 em termos de acurácia e tempo de treinamento. Plote curvas de acurácia vs. época para todas as três estratégias. Dica: Congele diferentes números de camadas e registre a acurácia de teste a cada época.

← Lição Anterior: Introdução ao Aprendizado Profundo | Próxima Lição: Avaliação e Ajuste de Modelos →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%