Computer Vision con PyTorch nel 2026: CNN, Transfer Learning e Domande da Colloquio

Computer vision con PyTorch: costruire CNN da zero, applicare transfer learning con modelli pre-addestrati e prepararsi ai colloqui tecnici.

Computer Vision con PyTorch nel 2026: CNN, Transfer Learning e Domande da Colloquio

La computer vision con PyTorch si è affermata nel 2026 come approccio dominante per la classificazione di immagini, il rilevamento di oggetti e le attività di riconoscimento visivo. Questo tutorial copre la costruzione di CNN da zero, l'applicazione del transfer learning con modelli pre-addestrati e la preparazione alle domande dei colloqui tecnici.

Performance di PyTorch 2.4

PyTorch 2.4 introduce torch.compile() come default per le operazioni CNN, offrendo miglioramenti di velocità del 30-50% sulle GPU moderne senza modifiche al codice. Tutti gli esempi in questo articolo sono compatibili con PyTorch 2.4+.

Comprendere le Reti Neurali Convoluzionali per la Classificazione di Immagini

Le Reti Neurali Convoluzionali estraggono caratteristiche gerarchiche dalle immagini attraverso filtri apprendibili. A differenza delle reti completamente connesse, le CNN preservano le relazioni spaziali tra i pixel, rendendole ideali per compiti visivi. L'architettura è composta da strati convoluzionali che rilevano bordi e pattern, strati di pooling che riducono la dimensionalità e strati completamente connessi che eseguono la classificazione.

Un'operazione di convoluzione fa scorrere un piccolo filtro (tipicamente 3x3 o 5x5) sull'immagine di input, calcolando prodotti scalari in ogni posizione. Questo produce feature map che evidenziano pattern specifici come bordi, texture o forme. Gli strati più profondi combinano queste caratteristiche di basso livello in rappresentazioni complesse—volti, oggetti o scene.

python
# cnn_architecture.py
import torch
import torch.nn as nn

class SimpleCNN(nn.Module):
    """Basic CNN for CIFAR-10 classification (32x32 RGB images, 10 classes)"""
    
    def __init__(self, num_classes: int = 10):
        super().__init__()
        # First conv block: 3 input channels (RGB) -> 32 feature maps
        self.conv1 = nn.Sequential(
            nn.Conv2d(3, 32, kernel_size=3, padding=1),  # Output: 32x32x32
            nn.BatchNorm2d(32),  # Normalize activations for stable training
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2)  # Output: 16x16x32
        )
        # Second conv block: increase depth for richer features
        self.conv2 = nn.Sequential(
            nn.Conv2d(32, 64, kernel_size=3, padding=1),  # Output: 16x16x64
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2)  # Output: 8x8x64
        )
        # Third conv block: capture high-level patterns
        self.conv3 = nn.Sequential(
            nn.Conv2d(64, 128, kernel_size=3, padding=1),  # Output: 8x8x128
            nn.BatchNorm2d(128),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2)  # Output: 4x4x128
        )
        # Classifier head
        self.classifier = nn.Sequential(
            nn.Flatten(),
            nn.Linear(128 * 4 * 4, 256),
            nn.ReLU(inplace=True),
            nn.Dropout(0.5),  # Prevent overfitting
            nn.Linear(256, num_classes)
        )
    
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        return self.classifier(x)

Questa architettura aumenta progressivamente il numero di filtri (32 → 64 → 128) riducendo le dimensioni spaziali attraverso il pooling. BatchNorm stabilizza l'addestramento normalizzando gli output degli strati, mentre Dropout impedisce al classificatore di memorizzare gli esempi di training.

Addestrare una CNN con i DataLoader di PyTorch

Il caricamento efficiente dei dati è fondamentale per l'utilizzo della GPU durante l'addestramento. Il DataLoader di PyTorch gestisce automaticamente batching, shuffling e caricamento parallelo dei dati. Una corretta data augmentation—ritagli casuali, flip e variazioni di colore—migliora significativamente la generalizzazione su immagini non viste.

python
# train_cnn.py
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

# Data augmentation for training (reduces overfitting)
train_transform = transforms.Compose([
    transforms.RandomHorizontalFlip(p=0.5),
    transforms.RandomCrop(32, padding=4),  # Random crop with padding
    transforms.ColorJitter(brightness=0.2, contrast=0.2),
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465),  # CIFAR-10 mean
                         (0.2470, 0.2435, 0.2616))  # CIFAR-10 std
])

# No augmentation for validation (deterministic evaluation)
val_transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465),
                         (0.2470, 0.2435, 0.2616))
])

def train_model(model: nn.Module, epochs: int = 20) -> dict:
    """Train CNN with standard best practices"""
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model = model.to(device)
    
    # Load CIFAR-10 dataset
    train_data = datasets.CIFAR10(root="./data", train=True, 
                                   download=True, transform=train_transform)
    val_data = datasets.CIFAR10(root="./data", train=False,
                                 transform=val_transform)
    
    # DataLoaders with num_workers for parallel loading
    train_loader = DataLoader(train_data, batch_size=128, shuffle=True,
                              num_workers=4, pin_memory=True)
    val_loader = DataLoader(val_data, batch_size=256, shuffle=False,
                            num_workers=4, pin_memory=True)
    
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)
    scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)
    
    best_acc = 0.0
    for epoch in range(epochs):
        model.train()
        for images, labels in train_loader:
            images, labels = images.to(device), labels.to(device)
            optimizer.zero_grad()
            outputs = model(images)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()
        
        scheduler.step()
        
        # Validation
        model.eval()
        correct, total = 0, 0
        with torch.no_grad():
            for images, labels in val_loader:
                images, labels = images.to(device), labels.to(device)
                outputs = model(images)
                _, predicted = outputs.max(1)
                total += labels.size(0)
                correct += predicted.eq(labels).sum().item()
        
        acc = 100. * correct / total
        best_acc = max(best_acc, acc)
        print(f"Epoch {epoch+1}/{epochs} - Val Acc: {acc:.2f}%")
    
    return {"best_accuracy": best_acc}

Le ottimizzazioni chiave per l'addestramento includono: pin_memory=True per trasferimenti CPU-GPU più veloci, num_workers per il caricamento parallelo dei dati, ottimizzatore AdamW con weight decay per la regolarizzazione e scheduling del learning rate con cosine annealing per una convergenza graduale.

Transfer Learning con ResNet ed EfficientNet Pre-addestrati

Il transfer learning sfrutta modelli pre-addestrati su ImageNet (1,2 milioni di immagini, 1000 classi) e li adatta a dataset personalizzati. Questo approccio raggiunge una maggiore accuratezza con meno dati di training e tempo di calcolo. Il modulo torchvision.models di PyTorch fornisce architetture all'avanguardia con pesi pre-addestrati.

L'approccio standard congela gli strati convoluzionali iniziali (che rilevano caratteristiche universali come i bordi) e affina gli strati successivi più una nuova testa di classificazione per il compito target.

python
# transfer_learning.py
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 = "resnet50",
    num_classes: int = 10,
    freeze_backbone: bool = True
) -> nn.Module:
    """Create a pretrained model with custom classification head"""
    
    if model_name == "resnet50":
        # Load ResNet50 with ImageNet weights
        model = models.resnet50(weights=ResNet50_Weights.IMAGENET1K_V2)
        # Replace final fully connected layer
        in_features = model.fc.in_features  # 2048 for ResNet50
        model.fc = nn.Sequential(
            nn.Dropout(0.3),
            nn.Linear(in_features, num_classes)
        )
        
    elif model_name == "efficientnet_b0":
        # EfficientNet: better accuracy/compute tradeoff
        model = models.efficientnet_b0(weights=EfficientNet_B0_Weights.IMAGENET1K_V1)
        in_features = model.classifier[1].in_features  # 1280 for B0
        model.classifier = nn.Sequential(
            nn.Dropout(0.2),
            nn.Linear(in_features, num_classes)
        )
    
    if freeze_backbone:
        # Freeze all layers except classifier
        for name, param in model.named_parameters():
            if "fc" not in name and "classifier" not in name:
                param.requires_grad = False
    
    return model

def count_trainable_params(model: nn.Module) -> int:
    """Count parameters that will be updated during training"""
    return sum(p.numel() for p in model.parameters() if p.requires_grad)

# Example usage
model = create_transfer_model("resnet50", num_classes=5, freeze_backbone=True)
print(f"Trainable parameters: {count_trainable_params(model):,}")
# Output: ~10,245 (only classifier) vs ~25 million (full ResNet50)

Il congelamento del backbone riduce i parametri addestrabili da 25 milioni a circa 10.000, consentendo l'addestramento su piccoli dataset senza overfitting. Per dataset più grandi (10.000+ immagini), gli strati successivi possono essere scongelati gradualmente utilizzando learning rate differenziali—tassi più bassi per gli strati pre-addestrati, tassi più alti per il nuovo classificatore.

Pronto a superare i tuoi colloqui su Data Science & ML?

Pratica con i nostri simulatori interattivi, flashcards e test tecnici.

Strategie di Data Augmentation per la Computer Vision

La data augmentation espande artificialmente il training set applicando trasformazioni che preservano il contenuto semantico. L'augmentation moderna va oltre i semplici flip e rotazioni—tecniche come MixUp e CutOut creano esempi di training sintetici che migliorano la robustezza del modello.

python
# augmentation_strategies.py
import torch
import torchvision.transforms.v2 as T
from torchvision.transforms.v2 import functional as F

# Modern augmentation pipeline using torchvision v2 transforms
advanced_augmentation = T.Compose([
    T.RandomResizedCrop(224, scale=(0.8, 1.0)),  # Random crop and resize
    T.RandomHorizontalFlip(p=0.5),
    T.RandomVerticalFlip(p=0.1),  # Less common but useful for some domains
    T.RandomRotation(degrees=15),
    T.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.3, hue=0.1),
    T.RandomAffine(degrees=0, translate=(0.1, 0.1)),  # Small translations
    T.GaussianBlur(kernel_size=3, sigma=(0.1, 2.0)),
    T.ToImage(),
    T.ToDtype(torch.float32, scale=True),
    T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

class CutOut:
    """Randomly mask out square regions of the image"""
    def __init__(self, n_holes: int = 1, length: int = 16):
        self.n_holes = n_holes
        self.length = length
    
    def __call__(self, img: torch.Tensor) -> torch.Tensor:
        h, w = img.shape[1], img.shape[2]
        mask = torch.ones_like(img)
        
        for _ in range(self.n_holes):
            y = torch.randint(0, h, (1,)).item()
            x = torch.randint(0, w, (1,)).item()
            y1 = max(0, y - self.length // 2)
            y2 = min(h, y + self.length // 2)
            x1 = max(0, x - self.length // 2)
            x2 = min(w, x + self.length // 2)
            mask[:, y1:y2, x1:x2] = 0
        
        return img * mask

def mixup_data(
    x: torch.Tensor,
    y: torch.Tensor,
    alpha: float = 0.4
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, float]:
    """MixUp: blend two images and their labels"""
    lam = torch.distributions.Beta(alpha, alpha).sample().item()
    batch_size = x.size(0)
    index = torch.randperm(batch_size)
    
    mixed_x = lam * x + (1 - lam) * x[index]
    y_a, y_b = y, y[index]
    
    return mixed_x, y_a, y_b, lam

CutOut costringe il modello a fare affidamento su più indizi visivi invece che su una singola regione discriminativa. MixUp crea combinazioni convesse di coppie di training, risultando in confini decisionali più uniformi. Queste tecniche migliorano costantemente l'accuratezza dell'1-3% sui benchmark standard.

Domande da Colloquio su CNN e Computer Vision

I colloqui tecnici per ruoli di computer vision testano sia la comprensione teorica che le competenze pratiche di implementazione. La preparazione dovrebbe includere domande sulla progettazione dell'architettura, sfide di ottimizzazione e considerazioni sul deployment nel mondo reale.

Argomenti Comuni nei Colloqui

Gli intervistatori chiedono frequentemente calcoli del campo recettivo, lo scopo della batch normalization, perché le convoluzioni funzionano per le immagini e come diagnosticare l'overfitting nei modelli di visione.

D: Cos'è il campo recettivo e perché è importante?

Il campo recettivo è la regione dell'immagine di input che influenza una particolare posizione nella feature map. Gli strati più profondi hanno campi recettivi più ampi, permettendo loro di catturare un contesto più ampio. Per una rete con n strati convoluzionali che usano kernel 3x3, il campo recettivo cresce come (2n + 1) × (2n + 1). La progettazione dell'architettura richiede di bilanciare la dimensione del campo recettivo con il costo computazionale—le convoluzioni dilatate espandono i campi recettivi senza aumentare i parametri.

D: Perché si usa la batch normalization nelle CNN?

La batch normalization normalizza gli input degli strati a media zero e varianza unitaria, affrontando lo shift interno delle covariate. I benefici includono: convergenza più veloce (learning rate più alti diventano stabili), effetto di regolarizzazione (riduce la necessità di dropout) e miglioramento del flusso dei gradienti (previene i gradienti che svaniscono nelle reti profonde). Durante l'inferenza, le statistiche running sostituiscono le statistiche del batch per output deterministici.

D: Spiega la differenza tra padding same e valid.

Il padding same aggiunge zeri attorno all'input per preservare le dimensioni spaziali dopo la convoluzione—un input 32×32 con un kernel 3×3 produce un output 32×32. Il padding valid non applica padding, riducendo le dimensioni—la stessa operazione produce un output 30×30. Il padding same è preferito nelle reti profonde per evitare una riduzione spaziale aggressiva.

D: Come si gestisce lo sbilanciamento delle classi nella classificazione di immagini?

Le strategie includono: funzione di perdita cross-entropy pesata (pesi maggiori per le classi minoritarie), oversampling delle classi minoritarie durante l'addestramento, data augmentation focalizzata sulle classi sottorappresentate e focal loss che riduce il peso degli esempi facili. Per sbilanciamenti severi (>100:1), è consigliabile combinare più tecniche e considerare metriche oltre l'accuratezza—F1 score, precision-recall AUC o analisi della matrice di confusione.

Per ulteriore preparazione ai colloqui di deep learning, è possibile esplorare i moduli CNN & Classificazione di Immagini e Fondamenti di Deep Learning.

Deploy di Modelli Vision PyTorch con TorchScript

Il deployment in produzione richiede la conversione dei modelli PyTorch in formati ottimizzati. TorchScript compila i modelli in una rappresentazione portabile che funziona senza Python, abilitando il deployment in applicazioni C++, dispositivi mobili o ambienti serverless.

python
# deployment_export.py
import torch
from torchvision import models
from torchvision.models import ResNet18_Weights

def export_for_production(model: torch.nn.Module, save_path: str) -> None:
    """Export model to TorchScript for production deployment"""
    model.eval()  # Set to evaluation mode (disables dropout, uses running stats)
    
    # Create example input matching expected dimensions
    example_input = torch.randn(1, 3, 224, 224)
    
    # Method 1: Tracing (for models without control flow)
    traced_model = torch.jit.trace(model, example_input)
    traced_model.save(save_path.replace(".pt", "_traced.pt"))
    
    # Method 2: Scripting (handles if/else, loops)
    scripted_model = torch.jit.script(model)
    scripted_model.save(save_path.replace(".pt", "_scripted.pt"))
    
    # Verify outputs match
    with torch.no_grad():
        original_out = model(example_input)
        traced_out = traced_model(example_input)
        scripted_out = scripted_model(example_input)
    
    assert torch.allclose(original_out, traced_out, atol=1e-5)
    assert torch.allclose(original_out, scripted_out, atol=1e-5)
    print(f"Model exported successfully to {save_path}")

def optimize_for_inference(model_path: str) -> torch.jit.ScriptModule:
    """Load and optimize TorchScript model for inference"""
    model = torch.jit.load(model_path)
    
    # Optimize for inference (fuses operations, removes dropout)
    optimized = torch.jit.optimize_for_inference(model)
    
    return optimized

# Export ResNet18
model = models.resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)
export_for_production(model, "resnet18_production.pt")

Per la massima velocità di inferenza, è possibile considerare l'export ONNX per la compatibilità cross-framework o TensorRT per il deployment su GPU NVIDIA. Il confronto PyTorch vs TensorFlow tratta i compromessi di deployment tra i framework.

Conclusione

La computer vision con PyTorch combina astrazioni potenti con strumenti pronti per la produzione:

  • Le architetture CNN estraggono caratteristiche gerarchiche attraverso strati convoluzionali, di pooling e di normalizzazione
  • Il transfer learning con modelli pre-addestrati (ResNet, EfficientNet) raggiunge alta accuratezza con dati limitati
  • La data augmentation (MixUp, CutOut, trasformazioni geometriche) migliora la generalizzazione e previene l'overfitting
  • I colloqui tecnici testano campi recettivi, batch normalization, strategie di padding e gestione dello sbilanciamento delle classi
  • TorchScript abilita il deployment in produzione senza dipendenze Python

Inizia a praticare!

Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.

Anthony Fillion-Maillet

Scritto da

Anthony Fillion-Maillet

Sviluppatore fullstack, fondatore di SharpSkill

Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.

Aggiornato il 14 agosto 2026

Condividi

Articoli correlati