Machine Learning Academy · Leçon

Couches de regroupement : sous-échantillonnage spatial et invariance

Vous ajouterez MaxPool2d après les couches de convolution, calculerez les dimensions des sorties et comprendrez comment le regroupement tolère les variations de position tout en réduisant les calculs.

Leçon 2 sur 413 étapes

Couches de regroupement : sous-échantillonnage spatial et invariance est une leçon Machine Learning Academy gratuite sur CoddyKit. Ceci est la leçon 2 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Machine Learning Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Machine Learning Academy comprend 4 leçons au total.

Certaines parties de cette leçon n'ont pas encore été traduites et s'affichent en anglais.

Why Pooling Layers Exist

Pooling layers reduce the spatial dimensions of feature maps, decreasing computation and memory while making representations more compact. They also introduce a degree of spatial invariance — small translations of a feature in the input produce the same pooled output. Without pooling (or stride-2 convolutions), the spatial dimensions would remain constant through all layers, making the network computationally prohibitive for large images.

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: Taking the Maximum

MaxPool2d divides the input feature map into non-overlapping windows and takes the maximum value in each window. The maximum corresponds to the strongest activation of the filter at any position in that window — it captures whether the feature was present anywhere in the region, regardless of its exact position. A 2x2 max pool with stride 2 reduces height and width by half, cutting the total spatial size by 4x.

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 vs Max Pooling

AvgPool2d takes the average of values in each pooling window instead of the maximum. Average pooling smooths the feature map and retains information from all activations, while max pooling discards the weaker activations. Max pooling is preferred in classification networks where the presence of a feature matters more than its average strength. Average pooling is commonly used for global pooling at the end of a CNN to collapse spatial dimensions.

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) collapses the entire spatial dimension to a single value per channel by averaging all spatial positions. For a feature map of shape (B, C, H, W), GAP produces (B, C) — a vector of C values representing the average activation of each filter across the entire image. GAP replaces the flatten + large fully connected layers at the top of classic CNNs (VGG), drastically reducing parameters and acting as a powerful regulariser. It is used in ResNet, MobileNet, and 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)

Computing Output Dimensions

Computing the output spatial dimensions after pooling is straightforward: output_size = floor((input_size - kernel_size) / stride) + 1. For a 2x2 pool with stride 2 on a 28x28 input, the output is 14x14. Misjudging dimensions is a common source of shape errors. AdaptiveMaxPool2d and AdaptiveAvgPool2d solve this by accepting the desired output size directly, automatically computing the required kernel and stride — ideal when you want a fixed output regardless of input size.

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 for Translation Invariance

Pooling provides local translation invariance: if a feature shifts by a few pixels, it likely still falls within the same pooling window and produces the same maximum activation. This means the network recognises a cat whether it is slightly to the left or right in the image. However, pooling is only locally invariant — large translations still produce different outputs. Data augmentation (random crops, flips) is needed to achieve global translation invariance.

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 vs Stride-2 Convolutions

Modern CNN architectures (ResNet, EfficientNet) increasingly use stride-2 convolutions instead of separate max pooling layers. The advantage is that stride-2 convolutions are learnable — the network decides how to downsample, potentially preserving more useful information than the fixed max operation. Max pooling is still used in classic architectures (VGGNet, AlexNet) and for its computational simplicity. Both approaches achieve the same goal: reducing spatial resolution by a factor of 2.

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!

Pooling Has No Learnable Parameters

An important property of pooling layers is that they have no learnable parameters. Max pooling simply selects the maximum; average pooling computes the mean. This makes pooling layers very fast and memory-free — they do not contribute to the parameter count. The lack of parameters also means pooling cannot overfit. This is why global average pooling at the end of a network is a strong regulariser — it forces the network to encode information in each channel's average, not in spatial positions.

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

Typical CNN Architecture with Pooling

A standard CNN architecture alternates between convolutional blocks (Conv+BN+ReLU) and pooling layers, progressively reducing spatial dimensions while increasing channel count. After the convolutional blocks, global average pooling collapses spatial dimensions, and a linear layer maps to class scores. This pattern produces a network with manageable parameter count, good generalisation, and strong image classification performance.

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)

Fractional and Adaptive Pooling

AdaptiveMaxPool2d(output_size) automatically computes the kernel size and stride needed to produce the specified output dimensions, regardless of input size. This is invaluable when you want a fixed-size vector for the classifier but the input images have varying sizes (e.g., object detection, variable-resolution datasets). FractionalMaxPool2d uses randomised pooling regions to add stochasticity, acting as an additional regulariser in some architectures.

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)

Visualising the Effect of Pooling

To understand what pooling does visually: a feature map before max pooling shows the raw filter response at each pixel. After pooling, the output is spatially coarser — each value represents the strongest response in its neighbourhood. The feature map becomes more abstract and position-independent. Deep in a network, after many rounds of pooling, each neuron's receptive field covers most of the original image, enabling global pattern recognition.

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)

Quick Check

Test your understanding of Machine Learning with Python concepts from this lesson.

Lesson Recap

In this lesson you learned: MaxPool2d and AvgPool2d reduce spatial dimensions by taking the max or average in each pooling window, providing compact representations and local translation invariance, AdaptiveAvgPool2d produces a fixed output size regardless of input dimensions (used as Global Average Pooling), and pooling has no learnable parameters, making it fast and non-overfitting. Next up we build and train a complete CNN on CIFAR-10.

Gratuit pour commencer

Apprends Python avec un tuteur IA — gratuit

Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.

Cours
30
Leçons
120

Questions Fréquemment Posées

La leçon « Couches de regroupement : sous-échantillonnage spatial et invariance » est-elle gratuite ?

Oui — le texte complet de « Couches de regroupement : sous-échantillonnage spatial et invariance » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Machine Learning Academy, passe à CoddyKit PRO. Le cours Machine Learning Academy comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Couches de regroupement : sous-échantillonnage spatial et invariance » ?

Vous ajouterez MaxPool2d après les couches de convolution, calculerez les dimensions des sorties et comprendrez comment le regroupement tolère les variations de position tout en réduisant les calculs. Tu pratiques Machine Learning Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer Machine Learning Academy ?

Aucune expérience préalable n'est requise. Machine Learning Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 2 sur 4.

Combien de temps prend la leçon « Couches de regroupement : sous-échantillonnage spatial et invariance » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon Machine Learning Academy ?

Oui. Chaque leçon Machine Learning Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Convolution et filtres : détecter les contours et les motifs
  2. Couches de regroupement : sous-échantillonnage spatial et invariance
  3. Construire et entraîner un CNN sur CIFAR-10
  4. Augmentation des données : transformations pour la robustesse
← Retour à Machine Learning Academy