Salta al contenuto principale

02 - Reti Neurali Convoluzionali (CNN): Computer Vision Pratica

Introduzione: Come le CNN Vedono il Mondo Le Reti Neurali Convoluzionali (CNN) hanno rivoluzionato la computer vision, rendendo possibile il...

Cosa ne pensi di questo articolo?

02 - Reti Neurali Convoluzionali (CNN): Computer Vision Pratica
Deep LearningFoto:Brett SaylesPexels

In Breve

Introduzione: Come le CNN Vedono il Mondo Le Reti Neurali Convoluzionali (CNN) hanno rivoluzionato la computer vision, rendendo possibile il...

Introduzione: Come le CNN Vedono il Mondo

Le Reti Neurali Convoluzionali (CNN) hanno rivoluzionato la computer vision, rendendo possibile il riconoscimento automatico di immagini con accuratezza sovrumana. A differenza delle reti fully-connected, le CNN sfruttano la struttura spaziale dei dati: pixel vicini tendono ad essere correlati, e pattern locali (bordi, texture) si ripetono in diverse posizioni dell'immagine.

L'idea chiave delle CNN e l'operazione di convoluzione: un filtro (kernel) scorre sull'immagine estraendo feature locali. Strato dopo strato, la rete costruisce una gerarchia di feature sempre più astratte: da bordi semplici a forme complesse fino a oggetti completi.

Cosa Imparerai

  • L'operazione di convoluzione: kernel, stride, padding
  • Pooling: riduzione dimensionale e invarianza alla posizione
  • Architetture storiche: LeNet, AlexNet, VGG, ResNet
  • Skip connections e il problema del vanishing gradient nelle reti profonde
  • Transfer learning: riutilizzare modelli pre-addestrati su ImageNet
  • Data augmentation per migliorare la robustezza del modello
  • Implementazione completa in PyTorch con training e valutazione

L'Operazione di Convoluzione

Il cuore di una CNN e il layer convoluzionale. Un piccolo filtro (kernel), tipicamente di dimensione 3x3 o 5x5, scorre sull'immagine calcolando il prodotto elemento per elemento tra il filtro e la porzione di immagine sottostante. Il risultato e una feature map che evidenzia la presenza di un pattern specifico in ogni posizione.

Due parametri controllano il comportamento della convoluzione:

  • Stride: il passo con cui il kernel si muove. Stride=1 produce feature map della stessa dimensione, stride=2 dimezza le dimensioni
  • Padding: aggiunta di zeri ai bordi dell'immagine. "Same" padding mantiene le dimensioni originali, "valid" padding le riduce

import torch
import torch.nn as nn

# Convoluzione 2D: 3 canali input (RGB), 16 filtri output, kernel 3x3
conv_layer = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3,
                       stride=1, padding=1)

# Input: batch di 4 immagini RGB 32x32
x = torch.randn(4, 3, 32, 32)
output = conv_layer(x)
print(f"Input shape:  {x.shape}")      # [4, 3, 32, 32]
print(f"Output shape: {output.shape}")  # [4, 16, 32, 32]

# Con stride=2 e padding=1
conv_stride2 = nn.Conv2d(3, 16, kernel_size=3, stride=2, padding=1)
output_s2 = conv_stride2(x)
print(f"Stride 2 output: {output_s2.shape}")  # [4, 16, 16, 16]
      

Pooling: Riduzione Dimensionale

Dopo la convoluzione, i layer di pooling riducono le dimensioni spaziali delle feature map, diminuendo il numero di parametri e il costo computazionale. Il pooling introduce anche una forma di invarianza alla traslazione: piccoli spostamenti dell'oggetto nell'immagine non cambiano la feature estratta.

I due tipi principali sono:

  • Max Pooling: seleziona il valore massimo in ogni finestra. Preserva le feature più prominenti ed e il tipo più usato
  • Average Pooling: calcola la media in ogni finestra. Produce feature più smooth, usato tipicamente prima dell'output

perchè le CNN Funzionano Cosi Bene

Le CNN sfruttano tre proprietà fondamentali delle immagini: localita (feature importanti sono locali), condivisione dei pesi (lo stesso filtro si applica ovunque, riducendo drasticamente i parametri) e invarianza alla traslazione (un gatto e un gatto sia al centro che in un angolo dell'immagine). Queste proprietà rendono le CNN ordini di grandezza più efficienti delle reti fully-connected per dati con struttura spaziale.

Architetture Storiche: Da LeNet a ResNet

LeNet-5 (1998)

Progettata da Yann LeCun per il riconoscimento di cifre scritte a mano, LeNet-5 e la prima CNN di successo. Con soli 5 layer (2 convoluzioni + 3 fully connected), dimostro che le reti convoluzionali potevano battere i metodi tradizionali di feature engineering.

VGG (2014)

VGGNet dimostro che la profondità conta: usando esclusivamente kernel 3x3 impilati in 16 o 19 layer, raggiunse performance eccellenti su ImageNet. Due filtri 3x3 in sequenza coprono lo stesso campo recettivo di un filtro 5x5, ma con meno parametri e più non-linearita.

ResNet (2015)

ResNet (Residual Network) risolse il problema del training di reti molto profonde con le skip connections: invece di imparare una trasformazione diretta F(x), ogni blocco impara la differenza residua F(x) + x. Questo permette al gradiente di fluire direttamente attraverso i layer, rendendo possibile l'addestramento di reti con 152+ layer.


import torch
import torch.nn as nn

class ResidualBlock(nn.Module):
    """Blocco residuo base di ResNet"""
    def __init__(self, channels):
        super().__init__()
        self.conv1 = nn.Conv2d(channels, channels, kernel_size=3, padding=1)
        self.bn1 = nn.BatchNorm2d(channels)
        self.conv2 = nn.Conv2d(channels, channels, kernel_size=3, padding=1)
        self.bn2 = nn.BatchNorm2d(channels)
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        identity = x                    # Skip connection
        out = self.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        out += identity                  # Residual: F(x) + x
        return self.relu(out)

class SimpleCNN(nn.Module):
    """CNN per classificazione CIFAR-10"""
    def __init__(self, num_classes=10):
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 32, kernel_size=3, padding=1),
            nn.BatchNorm2d(32),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2),          # 32x32 -> 16x16

            nn.Conv2d(32, 64, kernel_size=3, padding=1),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2),          # 16x16 -> 8x8

            nn.Conv2d(64, 128, kernel_size=3, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(inplace=True),
            nn.AdaptiveAvgPool2d((1, 1)) # 8x8 -> 1x1
        )
        self.classifier = nn.Linear(128, num_classes)

    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), -1)
        return self.classifier(x)

model = SimpleCNN()
x = torch.randn(8, 3, 32, 32)
print(f"Output: {model(x).shape}")  # [8, 10]
      

Transfer Learning: Riutilizzare Modelli Pre-Addestrati

Addestrare una CNN da zero su grandi dataset richiede enormi risorse computazionali. Il transfer learning risolve questo problema: si prende un modello pre-addestrato su un dataset enorme (tipicamente ImageNet con 14 milioni di immagini) e lo si adatta al proprio task specifico.

La strategia più comune prevede due fasi:

  1. Feature extraction: si congelano i pesi del modello pre-addestrato e si sostituisce solo il classificatore finale
  2. Fine-tuning: si scongelano alcuni layer superiori e si ri-addestrano con un learning rate molto basso

import torchvision.models as models

# Caricare ResNet50 pre-addestrato su ImageNet
model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)

# Congelare tutti i parametri
for param in model.parameters():
    param.requires_grad = False

# Sostituire il classificatore finale per 5 classi
num_features = model.fc.in_features
model.fc = nn.Sequential(
    nn.Dropout(0.3),
    nn.Linear(num_features, 256),
    nn.ReLU(),
    nn.Dropout(0.2),
    nn.Linear(256, 5)   # 5 classi custom
)

# Solo i parametri del nuovo classificatore saranno addestrati
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
total = sum(p.numel() for p in model.parameters())
print(f"Trainable: {trainable:,} / {total:,} parametri")
      

Data Augmentation: Robustezza attraverso le Trasformazioni

La data augmentation e una tecnica di regolarizzazione che aumenta artificialmente la diversità del dataset di training applicando trasformazioni casuali alle immagini. Rotazioni, ritagli, flip orizzontali e variazioni di colore insegnano alla rete ad essere invariante a queste trasformazioni.


from torchvision import transforms

# Pipeline di data augmentation per training
train_transform = transforms.Compose([
    transforms.RandomResizedCrop(224, scale=(0.8, 1.0)),
    transforms.RandomHorizontalFlip(p=0.5),
    transforms.RandomRotation(15),
    transforms.ColorJitter(brightness=0.2, contrast=0.2,
                          saturation=0.2, hue=0.1),
    transforms.RandomAffine(degrees=0, translate=(0.1, 0.1)),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406],
                        [0.229, 0.224, 0.225]),
    transforms.RandomErasing(p=0.1)
])

# Per validation/test: solo resize e normalize
val_transform = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406],
                        [0.229, 0.224, 0.225])
])
      

Prossimi Passi nella Serie

  • Nel prossimo articolo esploreremo le Reti Neurali Ricorrenti (RNN) e LSTM per l'elaborazione di sequenze
  • Vedremo come LSTM risolvono il vanishing gradient e modellano dipendenze temporali
  • Implementeremo un modello di sentiment analysis e text generation

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti