池化层:空间下采样与不变性
您将在卷积层后添加 MaxPool2d,计算输出尺寸,并理解池化如何提供位置容忍度以及减少计算量。
池化层:空间下采样与不变性 是 CoddyKit 上的免费 Machine Learning Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Machine Learning Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Machine Learning Academy 课程共包含 4 节课。
本课时的部分内容尚未翻译,以英文显示。
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)=7Average 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) # 4128Typical 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.
常见问题解答
「池化层:空间下采样与不变性」课时是免费的吗?
是的 — 「池化层:空间下采样与不变性」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Machine Learning Academy 课程的其余内容,请升级到 CoddyKit PRO。 Machine Learning Academy 课程共包含 4 节课。
「池化层:空间下采样与不变性」这节课中我会学到什么?
您将在卷积层后添加 MaxPool2d,计算输出尺寸,并理解池化如何提供位置容忍度以及减少计算量。 你通过在浏览器中直接运行的动手代码来练习 Machine Learning Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Machine Learning Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Machine Learning Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「池化层:空间下采样与不变性」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Machine Learning Academy 课中编写并运行代码吗?
能。每节 Machine Learning Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 卷积与滤波器:检测边缘与模式
- 池化层:空间下采样与不变性
- 在 CIFAR-10 上构建并训练 CNN
- 数据增强:提升鲁棒性的变换