Machine Learning Academy · Lezione

Layer di pooling: downsampling spaziale e invarianza

Imparerete ad aggiungere MaxPool2d dopo i layer convoluzionali, calcolare le dimensioni dell’output e capire come il pooling renda la posizione meno rilevante e riduca i calcoli.

Lezione 2 di 413 passaggi

Layer di pooling: downsampling spaziale e invarianza è una lezione Machine Learning Academy gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Machine Learning Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Machine Learning Academy include 4 lezioni in totale.

Perché esistono i layer di pooling

I layer di pooling riducono le dimensioni spaziali delle mappe delle caratteristiche, diminuendo il costo computazionale e l'uso di memoria e rendendo le rappresentazioni più compatte. Introducono inoltre un certo grado di invarianza spaziale: piccole traslazioni di una caratteristica nell'input producono lo stesso output del pooling. Senza il pooling (o convoluzioni con stride 2), le dimensioni spaziali rimarrebbero costanti in tutti i layer, rendendo la rete proibitiva dal punto di vista computazionale per le immagini di grandi dimensioni.

import torch
import torch.nn as nn

# Without pooling: feature map grows in channels but not reduced
conv1 = nn.Conv2d(3, 32, 3, padding=1)   # (B, 32, H, W)
conv2 = nn.Conv2d(32, 64, 3, padding=1)  # (B, 64, H, W)

# With pooling: spatial dims are halved each time
pool = nn.MaxPool2d(kernel_size=2, stride=2)

x = torch.randn(4, 3, 32, 32)
out = pool(torch.relu(conv1(x)))
print(out.shape)   # (4, 32, 16, 16) -- halved!

Max pooling: selezionare il valore massimo

MaxPool2d divide la mappa delle caratteristiche di input in finestre non sovrapposte e seleziona il valore massimo in ciascuna finestra. Il massimo corrisponde all'attivazione più forte del filtro in una qualsiasi posizione della finestra: indica se la caratteristica era presente in un punto qualsiasi della regione, indipendentemente dalla sua posizione esatta. Un max pooling 2x2 con stride 2 dimezza altezza e larghezza, riducendo di 4 volte la dimensione spaziale complessiva.

import torch
import torch.nn as nn

pool = nn.MaxPool2d(kernel_size=2, stride=2)

# Simple 4x4 feature map
x = torch.tensor([[[[ 1., 3., 2., 4.],
                     [ 5., 6., 7., 8.],
                     [ 9., 2., 1., 3.],
                     [ 4., 5., 6., 7.]]]])

out = pool(x)
print(out.shape)   # (1, 1, 2, 2)
print(out)
# Max of top-left 2x2: max(1,3,5,6)=6
# Max of top-right 2x2: max(2,4,7,8)=8
# Max of bottom-left 2x2: max(9,2,4,5)=9
# Max of bottom-right 2x2: max(1,3,6,7)=7

Average pooling e max pooling a confronto

AvgPool2d calcola la media dei valori in ciascuna finestra di pooling invece del massimo. L'average pooling rende più uniforme la mappa delle caratteristiche e conserva le informazioni di tutte le attivazioni, mentre il max pooling scarta le attivazioni più deboli. Il max pooling è preferito nelle reti di classificazione, dove conta più la presenza di una caratteristica che la sua intensità media. L'average pooling viene comunemente utilizzato per il global pooling alla fine di una CNN, per ridurre le dimensioni spaziali.

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.]]]])

max_pool = nn.MaxPool2d(2, stride=2)
avg_pool = nn.AvgPool2d(2, stride=2)

print('MaxPool:', max_pool(x))
# tensor([[[[6., 8.], [9., 7.]]]])

print('AvgPool:', avg_pool(x))
# tensor([[[[3.75, 5.25], [5.00, 4.25]]]])

Global average pooling

Global Average Pooling (GAP) riduce l'intera dimensione spaziale a un singolo valore per canale, calcolando la media di tutte le posizioni spaziali. Per una mappa delle caratteristiche di forma (B, C, H, W), GAP produce (B, C): un vettore di C valori che rappresentano l'attivazione media di ciascun filtro sull'intera immagine. GAP sostituisce l'operazione di flatten e i grandi layer completamente connessi nella parte superiore delle CNN classiche (VGG), riducendo drasticamente il numero di parametri e agendo come un potente regolarizzatore. Viene utilizzato in ResNet, MobileNet ed EfficientNet.

import torch
import torch.nn as nn

# Global Average Pooling (AdaptiveAvgPool2d to any output size)
gap = nn.AdaptiveAvgPool2d(output_size=(1, 1))

x = torch.randn(8, 512, 7, 7)   # typical final feature map
out = gap(x)
print(out.shape)   # (8, 512, 1, 1)

# Flatten to (B, C) for the classifier
flat = out.view(out.size(0), -1)
print(flat.shape)  # (8, 512)

# Then: nn.Linear(512, num_classes)

Calcolo delle dimensioni dell'output

Calcolare le dimensioni spaziali dell'output dopo il pooling è semplice: output_size = floor((input_size - kernel_size) / stride) + 1. Per un pooling 2x2 con stride 2 su un input 28x28, l'output è 14x14. Una valutazione errata delle dimensioni è una causa comune degli errori di shape. AdaptiveMaxPool2d e AdaptiveAvgPool2d risolvono questo problema accettando direttamente la dimensione dell'output desiderata e calcolando automaticamente kernel e stride necessari: sono ideali quando si desidera un output fisso indipendentemente dalla dimensione dell'input.

import torch
import torch.nn as nn

# Classic fixed-size pooling
x = torch.randn(1, 32, 28, 28)
pool = nn.MaxPool2d(kernel_size=2, stride=2)
print(pool(x).shape)   # (1, 32, 14, 14)

# Adaptive pooling: specify desired output size
adaptive_pool = nn.AdaptiveAvgPool2d((4, 4))
print(adaptive_pool(x).shape)  # (1, 32, 4, 4) -- any input

# Works even with irregular input sizes:
x2 = torch.randn(1, 32, 17, 23)
print(adaptive_pool(x2).shape)  # (1, 32, 4, 4) -- still 4x4!

Pooling e invarianza alle traslazioni

Il pooling fornisce invarianza locale alle traslazioni: se una caratteristica si sposta di alcuni pixel, probabilmente rimane nella stessa finestra di pooling e produce la stessa attivazione massima. In questo modo la rete riconosce un gatto sia che si trovi leggermente più a sinistra sia che si trovi leggermente più a destra nell'immagine. Tuttavia, il pooling è invariante solo localmente: traslazioni ampie producono comunque output diversi. Per ottenere l'invarianza globale alle traslazioni è necessario applicare l'aumento dei dati (ritagli e ribaltamenti casuali).

import torch
import torch.nn as nn

pool = nn.MaxPool2d(2, 2)

# Feature map with an 'activated' pixel at position (1,1)
fm1 = torch.zeros(1, 1, 4, 4)
fm1[0, 0, 1, 1] = 1.0   # activation at (1,1)

# Shift by 1 pixel to (1,2)
fm2 = torch.zeros(1, 1, 4, 4)
fm2[0, 0, 1, 2] = 1.0

# Both fall in the same 2x2 window -> same pool output
print(torch.allclose(pool(fm1), pool(fm2)))  # True!

Pooling e convoluzioni con stride 2 a confronto

Le architetture CNN moderne (ResNet, EfficientNet) utilizzano sempre più spesso convoluzioni con stride 2 invece di layer separati di max pooling. Il vantaggio è che le convoluzioni con stride 2 sono apprendibili: la rete decide come effettuare il sottocampionamento e può conservare più informazioni utili rispetto all'operazione max fissa. Il max pooling viene ancora utilizzato nelle architetture classiche (VGGNet, AlexNet) e per la sua semplicità computazionale. Entrambi gli approcci raggiungono lo stesso obiettivo: ridurre di 2 volte la risoluzione spaziale.

import torch
import torch.nn as nn

# Option 1: Conv + explicit MaxPool
block_maxpool = nn.Sequential(
    nn.Conv2d(32, 64, 3, padding=1),
    nn.ReLU(),
    nn.MaxPool2d(2, 2)      # separate pooling
)

# Option 2: Stride-2 Conv (learnable downsampling)
block_stride = nn.Sequential(
    nn.Conv2d(32, 64, 3, padding=1, stride=2),  # stride=2
    nn.ReLU()
)

x = torch.randn(4, 32, 16, 16)
print(block_maxpool(x).shape)  # (4, 64, 8, 8)
print(block_stride(x).shape)   # (4, 64, 8, 8) -- same!

Il pooling non ha parametri apprendibili

Una proprietà importante dei layer di pooling è che non hanno parametri apprendibili. Il max pooling seleziona semplicemente il valore massimo, mentre l'average pooling calcola la media. Questo rende i layer di pooling molto veloci e privi di parametri in memoria: non contribuiscono al conteggio dei parametri. L'assenza di parametri significa inoltre che il pooling non può causare overfitting. Per questo il global average pooling alla fine di una rete è un potente regolarizzatore: costringe la rete a codificare le informazioni nella media di ciascun canale, non nelle posizioni spaziali.

import torch.nn as nn

pool = nn.MaxPool2d(2, 2)
avg_pool = nn.AvgPool2d(2, 2)

# Count parameters
max_params = sum(p.numel() for p in pool.parameters())
avg_params = sum(p.numel() for p in avg_pool.parameters())

print('MaxPool parameters:', max_params)    # 0
print('AvgPool parameters:', avg_params)    # 0

# Compare with a Conv2d layer
conv = nn.Conv2d(32, 32, 2, stride=2)  # similar operation
conv_params = sum(p.numel() for p in conv.parameters())
print('Conv2d (stride-2) parameters:', conv_params)  # 4128

Architettura CNN tipica con pooling

Un'architettura CNN standard alterna blocchi convoluzionali (Conv+BN+ReLU) e layer di pooling, riducendo progressivamente le dimensioni spaziali e aumentando al contempo il numero di canali. Dopo i blocchi convoluzionali, il global average pooling riduce le dimensioni spaziali e un layer lineare mappa l'output nei punteggi delle classi. Questo schema produce una rete con un numero gestibile di parametri, una buona capacità di generalizzazione e prestazioni elevate nella classificazione delle immagini.

import torch
import torch.nn as nn

class SimpleCNN(nn.Module):
    def __init__(self, num_classes=10):
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 32, 3, padding=1), nn.ReLU(),
            nn.MaxPool2d(2),                # 32->16
            nn.Conv2d(32, 64, 3, padding=1), nn.ReLU(),
            nn.MaxPool2d(2),                # 16->8
            nn.Conv2d(64, 128, 3, padding=1), nn.ReLU(),
        )
        self.pool = nn.AdaptiveAvgPool2d((1, 1))  # GAP
        self.classifier = nn.Linear(128, num_classes)

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

model = SimpleCNN()
x = torch.randn(4, 3, 32, 32)
print(model(x).shape)  # (4, 10)

Pooling frazionale e adattivo

AdaptiveMaxPool2d(output_size) calcola automaticamente la dimensione del kernel e lo stride necessari per produrre le dimensioni di output specificate, indipendentemente dalla dimensione dell'input. È prezioso quando si desidera un vettore di dimensione fissa per il classificatore, ma le immagini di input hanno dimensioni variabili (ad esempio nel rilevamento degli oggetti o con dataset a risoluzione variabile). FractionalMaxPool2d utilizza regioni di pooling randomizzate per introdurre stocasticità, agendo come ulteriore regolarizzatore in alcune architetture.

import torch
import torch.nn as nn

# Adaptive pooling: always produces 3x3 output
adaptive = nn.AdaptiveMaxPool2d((3, 3))

sizes = [(32, 32), (48, 64), (100, 100)]
for h, w in sizes:
    x = torch.randn(2, 64, h, w)
    out = adaptive(x)
    print(f'Input {h}x{w} -> Output {out.shape[2]}x{out.shape[3]}')
# Always 3x3 regardless of input!

# Fractional max pooling (randomised pooling regions)
frac = nn.FractionalMaxPool2d(kernel_size=2, output_size=(6, 6))
x = torch.randn(2, 32, 12, 12)
print('Fractional pool:', frac(x).shape)  # (2, 32, 6, 6)

Visualizzazione dell'effetto del pooling

Per comprendere visivamente il funzionamento del pooling: una mappa delle caratteristiche prima del max pooling mostra la risposta grezza del filtro in ogni pixel. Dopo il pooling, l'output è spazialmente più grossolano: ogni valore rappresenta la risposta più forte nel suo intorno. La mappa delle caratteristiche diventa più astratta e indipendente dalla posizione. In profondità nella rete, dopo numerose operazioni di pooling, il campo recettivo di ciascun neurone copre gran parte dell'immagine originale, consentendo il riconoscimento di pattern globali.

import torch
import torch.nn as nn

# Simulate pooling effect on a feature map
pool2 = nn.MaxPool2d(2, 2)
pool4 = nn.MaxPool2d(4, 4)

x = torch.randn(1, 1, 16, 16)
print('Before pooling:', x.shape)        # (1,1,16,16)
print('After 2x2 pool:', pool2(x).shape) # (1,1,8,8)
print('After 4x4 pool:', pool4(x).shape) # (1,1,4,4)

# After 3 rounds of 2x2 pooling on 32x32 input:
pool_3x = nn.Sequential(
    nn.MaxPool2d(2), nn.MaxPool2d(2), nn.MaxPool2d(2)
)
print('After 3x 2x2 pool:', pool_3x(torch.randn(1,1,32,32)).shape)
# (1, 1, 4, 4)

Verifica rapida

Verifichi la Sua comprensione dei concetti di Machine Learning con Python presentati in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato che: MaxPool2d e AvgPool2d riducono le dimensioni spaziali selezionando il massimo o la media in ciascuna finestra di pooling, fornendo rappresentazioni compatte e invarianza locale alle traslazioni; AdaptiveAvgPool2d produce una dimensione di output fissa indipendentemente dalle dimensioni dell'input (e viene utilizzato come Global Average Pooling); e il pooling non ha parametri apprendibili, quindi è veloce e non causa overfitting. Ora costruiremo e addestreremo una CNN completa su CIFAR-10.

Gratis per iniziare

Impara Python con un tutor IA — gratis

Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.

Corsi
30
Lezioni
120

Domande Frequenti

La lezione «Layer di pooling: downsampling spaziale e invarianza» è gratuita?

Sì — il testo completo di «Layer di pooling: downsampling spaziale e invarianza» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Machine Learning Academy, passa a CoddyKit PRO. Il corso Machine Learning Academy include 4 lezioni in totale.

Cosa imparerò in «Layer di pooling: downsampling spaziale e invarianza»?

Imparerete ad aggiungere MaxPool2d dopo i layer convoluzionali, calcolare le dimensioni dell’output e capire come il pooling renda la posizione meno rilevante e riduca i calcoli. Eserciti Machine Learning Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Machine Learning Academy?

Non è richiesta alcuna esperienza precedente. Machine Learning Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.

Quanto tempo richiede la lezione «Layer di pooling: downsampling spaziale e invarianza»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Machine Learning Academy?

Sì. Ogni lezione Machine Learning Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Convoluzione e filtri: rilevare bordi e pattern
  2. Layer di pooling: downsampling spaziale e invarianza
  3. Costruire e addestrare una CNN su CIFAR-10
  4. Data augmentation: trasformazioni per la robustezza
← Torna a Machine Learning Academy