Visão Computacional com PyTorch em 2026: CNN, Transfer Learning e Perguntas de Entrevista

Guia completo sobre visão computacional com PyTorch: redes neurais convolucionais, transfer learning e preparação para entrevistas técnicas em deep learning.

Visão Computacional com PyTorch em 2026: CNN, Transfer Learning e Perguntas de Entrevista

A visão computacional consolidou-se como uma das áreas mais relevantes da inteligência artificial. Com PyTorch como framework principal, desenvolvedores e cientistas de dados podem construir modelos avançados para classificação de imagens, detecção de objetos e segmentação semântica. Este guia aborda os fundamentos das CNN, técnicas de transfer learning e as perguntas mais frequentes em entrevistas técnicas.

O domínio das CNN e do transfer learning representa uma vantagem competitiva significativa para posições em machine learning. Recrutadores frequentemente avaliam a compreensão prática desses conceitos durante as entrevistas técnicas.

Fundamentos das Redes Neurais Convolucionais

As redes neurais convolucionais (CNN) constituem a arquitetura fundamental para o processamento de imagens. Diferentemente das redes fully-connected, as CNN aproveitam a estrutura espacial dos dados visuais através de operações de convolução.

A arquitetura típica de uma CNN inclui vários tipos de camadas: as camadas de convolução extraem características locais, as camadas de pooling reduzem a dimensionalidade, e as camadas fully-connected realizam a classificação final.

python
import torch
import torch.nn as nn
import torch.nn.functional as F

class SimpleCNN(nn.Module):
    def __init__(self, num_classes: int = 10):
        super(SimpleCNN, self).__init__()
        # Convolutional layers
        self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
        self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
        
        # Batch normalization
        self.bn1 = nn.BatchNorm2d(32)
        self.bn2 = nn.BatchNorm2d(64)
        self.bn3 = nn.BatchNorm2d(128)
        
        # Pooling and dropout
        self.pool = nn.MaxPool2d(2, 2)
        self.dropout = nn.Dropout(0.5)
        
        # Fully connected layers
        self.fc1 = nn.Linear(128 * 4 * 4, 512)
        self.fc2 = nn.Linear(512, num_classes)
    
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        # Block 1: 32x32 -> 16x16
        x = self.pool(F.relu(self.bn1(self.conv1(x))))
        
        # Block 2: 16x16 -> 8x8
        x = self.pool(F.relu(self.bn2(self.conv2(x))))
        
        # Block 3: 8x8 -> 4x4
        x = self.pool(F.relu(self.bn3(self.conv3(x))))
        
        # Flatten and classify
        x = x.view(-1, 128 * 4 * 4)
        x = self.dropout(F.relu(self.fc1(x)))
        x = self.fc2(x)
        
        return x

Esse modelo ilustra os componentes essenciais: batch normalization estabiliza o treinamento, dropout previne o overfitting, e max pooling reduz progressivamente as dimensões espaciais.

Configuração do Ambiente e Preparação de Dados

Antes de treinar um modelo, a preparação de dados constitui uma etapa crítica. PyTorch fornece ferramentas robustas para carregamento e aumentação de dados.

python
import torch
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
from typing import Tuple

def create_data_loaders(
    data_dir: str,
    batch_size: int = 32,
    num_workers: int = 4
) -> Tuple[DataLoader, DataLoader]:
    """Create training and validation data loaders with augmentation."""
    
    # Training transforms with augmentation
    train_transform = transforms.Compose([
        transforms.RandomResizedCrop(224),
        transforms.RandomHorizontalFlip(),
        transforms.RandomRotation(15),
        transforms.ColorJitter(
            brightness=0.2, 
            contrast=0.2, 
            saturation=0.2
        ),
        transforms.ToTensor(),
        transforms.Normalize(
            mean=[0.485, 0.456, 0.406],
            std=[0.229, 0.224, 0.225]
        )
    ])
    
    # Validation transforms (no augmentation)
    val_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]
        )
    ])
    
    # Create datasets
    train_dataset = datasets.ImageFolder(
        root=f"{data_dir}/train",
        transform=train_transform
    )
    
    val_dataset = datasets.ImageFolder(
        root=f"{data_dir}/val",
        transform=val_transform
    )
    
    # Create data loaders
    train_loader = DataLoader(
        train_dataset,
        batch_size=batch_size,
        shuffle=True,
        num_workers=num_workers,
        pin_memory=True
    )
    
    val_loader = DataLoader(
        val_dataset,
        batch_size=batch_size,
        shuffle=False,
        num_workers=num_workers,
        pin_memory=True
    )
    
    return train_loader, val_loader

A aumentação de dados melhora a robustez do modelo ao criar variações artificiais das imagens de treinamento. As transformações geométricas e colorimétricas aumentam a diversidade do dataset sem necessitar de novos dados.

Transfer Learning com Modelos Pré-treinados

O transfer learning permite aproveitar modelos treinados em datasets extensos como ImageNet. Essa abordagem reduz significativamente o tempo de treinamento e melhora o desempenho, especialmente com datasets limitados.

python
import torch
import torch.nn as nn
from torchvision import models
from torchvision.models import ResNet50_Weights, EfficientNet_B0_Weights

def create_transfer_model(
    model_name: str,
    num_classes: int,
    freeze_backbone: bool = True
) -> nn.Module:
    """Create a transfer learning model with a custom classifier."""
    
    if model_name == "resnet50":
        # Load pre-trained ResNet50
        weights = ResNet50_Weights.IMAGENET1K_V2
        model = models.resnet50(weights=weights)
        
        # Freeze backbone if specified
        if freeze_backbone:
            for param in model.parameters():
                param.requires_grad = False
        
        # Replace classifier
        num_features = model.fc.in_features
        model.fc = nn.Sequential(
            nn.Dropout(0.5),
            nn.Linear(num_features, 512),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(512, num_classes)
        )
        
    elif model_name == "efficientnet_b0":
        # Load pre-trained EfficientNet
        weights = EfficientNet_B0_Weights.IMAGENET1K_V1
        model = models.efficientnet_b0(weights=weights)
        
        if freeze_backbone:
            for param in model.parameters():
                param.requires_grad = False
        
        # Replace classifier
        num_features = model.classifier[1].in_features
        model.classifier = nn.Sequential(
            nn.Dropout(0.5),
            nn.Linear(num_features, num_classes)
        )
    
    else:
        raise ValueError(f"Unknown model: {model_name}")
    
    return model


def unfreeze_layers(model: nn.Module, num_layers: int) -> None:
    """Gradually unfreeze layers for fine-tuning."""
    # Get all parameters as a list
    params = list(model.parameters())
    
    # Unfreeze the last n layers
    for param in params[-num_layers:]:
        param.requires_grad = True

A estratégia de congelamento progressivo de camadas otimiza o processo de treinamento. As primeiras camadas, que detectam características genéricas como bordas e texturas, permanecem congeladas. As camadas superiores, específicas da tarefa, são treinadas com o novo dataset.

Loop de Treinamento Completo

Um loop de treinamento robusto integra validação, acompanhamento de métricas e salvamento dos melhores modelos.

python
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from typing import Dict, List
import copy

class Trainer:
    def __init__(
        self,
        model: nn.Module,
        train_loader: DataLoader,
        val_loader: DataLoader,
        learning_rate: float = 1e-4,
        device: str = "cuda"
    ):
        self.model = model.to(device)
        self.train_loader = train_loader
        self.val_loader = val_loader
        self.device = device
        
        self.criterion = nn.CrossEntropyLoss()
        self.optimizer = optim.AdamW(
            filter(lambda p: p.requires_grad, model.parameters()),
            lr=learning_rate,
            weight_decay=0.01
        )
        self.scheduler = optim.lr_scheduler.CosineAnnealingLR(
            self.optimizer, 
            T_max=50
        )
        
        self.best_model_weights = None
        self.best_accuracy = 0.0
        self.history: Dict[str, List[float]] = {
            "train_loss": [],
            "val_loss": [],
            "val_accuracy": []
        }
    
    def train_epoch(self) -> float:
        """Train for one epoch."""
        self.model.train()
        running_loss = 0.0
        
        for images, labels in self.train_loader:
            images = images.to(self.device)
            labels = labels.to(self.device)
            
            self.optimizer.zero_grad()
            outputs = self.model(images)
            loss = self.criterion(outputs, labels)
            loss.backward()
            self.optimizer.step()
            
            running_loss += loss.item()
        
        return running_loss / len(self.train_loader)
    
    @torch.no_grad()
    def validate(self) -> tuple[float, float]:
        """Validate the model."""
        self.model.eval()
        running_loss = 0.0
        correct = 0
        total = 0
        
        for images, labels in self.val_loader:
            images = images.to(self.device)
            labels = labels.to(self.device)
            
            outputs = self.model(images)
            loss = self.criterion(outputs, labels)
            
            running_loss += loss.item()
            _, predicted = outputs.max(1)
            total += labels.size(0)
            correct += predicted.eq(labels).sum().item()
        
        accuracy = correct / total
        avg_loss = running_loss / len(self.val_loader)
        
        return avg_loss, accuracy
    
    def fit(self, epochs: int) -> Dict[str, List[float]]:
        """Full training loop."""
        for epoch in range(epochs):
            train_loss = self.train_epoch()
            val_loss, val_accuracy = self.validate()
            self.scheduler.step()
            
            # Save history
            self.history["train_loss"].append(train_loss)
            self.history["val_loss"].append(val_loss)
            self.history["val_accuracy"].append(val_accuracy)
            
            # Save best model
            if val_accuracy > self.best_accuracy:
                self.best_accuracy = val_accuracy
                self.best_model_weights = copy.deepcopy(
                    self.model.state_dict()
                )
            
            print(
                f"Epoch {epoch+1}/{epochs} - "
                f"Train Loss: {train_loss:.4f} - "
                f"Val Loss: {val_loss:.4f} - "
                f"Val Acc: {val_accuracy:.4f}"
            )
        
        # Load best weights
        self.model.load_state_dict(self.best_model_weights)
        return self.history

Essa implementação utiliza o otimizador AdamW com weight decay para regularização e um scheduler cosine annealing para ajustar dinamicamente o learning rate.

Técnicas Avançadas para Visão Computacional

As arquiteturas modernas incorporam mecanismos de atenção que melhoram a capacidade do modelo de focar em regiões relevantes da imagem.

python
import torch
import torch.nn as nn
import torch.nn.functional as F

class ChannelAttention(nn.Module):
    """Squeeze-and-Excitation channel attention."""
    
    def __init__(self, channels: int, reduction: int = 16):
        super().__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.fc = nn.Sequential(
            nn.Linear(channels, channels // reduction, bias=False),
            nn.ReLU(inplace=True),
            nn.Linear(channels // reduction, channels, bias=False),
            nn.Sigmoid()
        )
    
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        b, c, _, _ = x.size()
        y = self.avg_pool(x).view(b, c)
        y = self.fc(y).view(b, c, 1, 1)
        return x * y.expand_as(x)


class SpatialAttention(nn.Module):
    """CBAM spatial attention module."""
    
    def __init__(self, kernel_size: int = 7):
        super().__init__()
        self.conv = nn.Conv2d(
            2, 1, 
            kernel_size=kernel_size, 
            padding=kernel_size // 2,
            bias=False
        )
        self.sigmoid = nn.Sigmoid()
    
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        avg_out = torch.mean(x, dim=1, keepdim=True)
        max_out, _ = torch.max(x, dim=1, keepdim=True)
        attention = torch.cat([avg_out, max_out], dim=1)
        attention = self.sigmoid(self.conv(attention))
        return x * attention

Os mecanismos de atenção de canal e espacial permitem que a rede aprenda quais características e quais regiões são mais importantes para a tarefa de classificação.

Pronto para mandar bem nas entrevistas de Data Science & ML?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Perguntas Frequentes em Entrevistas de Visão Computacional

As entrevistas técnicas em deep learning geralmente cobrem conceitos teóricos e práticos. A seguir estão as perguntas mais comuns com suas respostas.

Pergunta: Explique a diferença entre valid e same padding nas convoluções.

O padding "valid" não adiciona pixels ao redor da imagem, o que reduz as dimensões de saída. O padding "same" adiciona pixels para manter as dimensões de entrada idênticas às de saída. A escolha depende da arquitetura desejada e da preservação de informação espacial nas bordas.

Pergunta: Como batch normalization melhora o treinamento das CNN?

Batch normalization normaliza as ativações em cada mini-batch, reduzindo o covariate shift interno. Essa técnica acelera a convergência, permite utilizar learning rates mais altos e atua como regularizador. Durante a inferência, as estatísticas de média e variância são calculadas sobre todo o dataset de treinamento.

Pergunta: Quando usar transfer learning versus treinar do zero?

O transfer learning é preferível com datasets limitados, tarefas similares ao dataset fonte, ou restrições de recursos computacionais. O treinamento do zero convém para datasets massivos, domínios muito diferentes do ImageNet (imagens médicas, satélite), ou quando as características de baixo nível diferem significativamente.

Pergunta: Explique o conceito de receptive field nas CNN.

O receptive field representa a região da imagem de entrada que influencia um neurônio particular. Ele aumenta com a profundidade da rede através de convoluções e poolings sucessivos. Um receptive field maior permite ao modelo capturar dependências espaciais em maior escala, essencial para a compreensão contextual.

Pergunta: Como lidar com desbalanceamento de classes em classificação de imagens?

Existem várias estratégias: ponderação de classes na função de perda, oversampling de classes minoritárias, undersampling de classes majoritárias, ou geração de dados sintéticos através de aumentação direcionada. A escolha depende da severidade do desbalanceamento e do tamanho do dataset.

Métricas de Avaliação para Visão Computacional

A avaliação rigorosa de um modelo requer métricas adaptadas à tarefa e ao contexto empresarial.

python
import torch
import numpy as np
from sklearn.metrics import (
    confusion_matrix, 
    classification_report,
    precision_recall_fscore_support
)
from typing import Dict, List

@torch.no_grad()
def evaluate_model(
    model: torch.nn.Module,
    test_loader: torch.utils.data.DataLoader,
    class_names: List[str],
    device: str = "cuda"
) -> Dict:
    """Comprehensive model evaluation."""
    model.eval()
    all_predictions = []
    all_labels = []
    all_probabilities = []
    
    for images, labels in test_loader:
        images = images.to(device)
        outputs = model(images)
        probabilities = torch.softmax(outputs, dim=1)
        _, predicted = outputs.max(1)
        
        all_predictions.extend(predicted.cpu().numpy())
        all_labels.extend(labels.numpy())
        all_probabilities.extend(probabilities.cpu().numpy())
    
    # Calculate metrics
    precision, recall, f1, support = precision_recall_fscore_support(
        all_labels, 
        all_predictions, 
        average=None
    )
    
    # Per-class metrics
    per_class_metrics = {
        class_names[i]: {
            "precision": float(precision[i]),
            "recall": float(recall[i]),
            "f1_score": float(f1[i]),
            "support": int(support[i])
        }
        for i in range(len(class_names))
    }
    
    # Overall metrics
    accuracy = np.mean(np.array(all_predictions) == np.array(all_labels))
    macro_f1 = np.mean(f1)
    
    return {
        "accuracy": float(accuracy),
        "macro_f1": float(macro_f1),
        "per_class": per_class_metrics,
        "confusion_matrix": confusion_matrix(
            all_labels, 
            all_predictions
        ).tolist()
    }

A accuracy sozinha pode ser enganosa com classes desbalanceadas. O F1-score macro fornece uma visão mais equilibrada do desempenho em todas as classes.

Conclusão

A visão computacional com PyTorch oferece possibilidades consideráveis para resolver problemas complexos de processamento de imagens. O domínio das CNN, transfer learning e técnicas de atenção constitui uma base sólida para os profissionais de deep learning. A preparação para entrevistas técnicas requer uma compreensão profunda desses conceitos, combinada com experiência prática em sua implementação. O ecossistema PyTorch continua evoluindo com novas arquiteturas e otimizações, tornando essencial manter-se atualizado nesse campo em rápida evolução.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Desenvolvedor fullstack, fundador da SharpSkill

Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.

Atualizado em 14 de agosto de 2026

Compartilhar

Artigos relacionados