0Pricing
Machine Learning Academy · レッスン

プーリング層:空間的ダウンサンプリングと不変性

畳み込み層の後にMaxPool2dを追加し、出力次元を計算するとともに、プーリングによって位置の変化に頑健になり、計算量が削減される仕組みを理解します。

「プーリング層:空間的ダウンサンプリングと不変性」はCoddyKit上の無料Machine Learning Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応の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)=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.

よくある質問

「プーリング層:空間的ダウンサンプリングと不変性」レッスンは無料ですか?

はい。「プーリング層:空間的ダウンサンプリングと不変性」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Machine Learning Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Machine Learning Academyコースには全4レッスンが含まれています。

「プーリング層:空間的ダウンサンプリングと不変性」で何を学びますか?

畳み込み層の後にMaxPool2dを追加し、出力次元を計算するとともに、プーリングによって位置の変化に頑健になり、計算量が削減される仕組みを理解します。 ブラウザで直接実行するハンズオンコードでMachine Learning Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Machine Learning Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのMachine Learning Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「プーリング層:空間的ダウンサンプリングと不変性」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このMachine Learning Academyレッスンでコードを書いて実行できますか?

はい。すべてのMachine Learning Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. 畳み込みとフィルター:エッジとパターンの検出
  2. プーリング層:空間的ダウンサンプリングと不変性
  3. CIFAR-10でCNNを構築して学習する
  4. データ拡張:頑健性を高める変換
← Machine Learning Academyに戻る