PyTorch 2.x 深度学习专题【左扬精讲】—— 池化层的工作原理:从下采样到特征聚合

PyTorch 2.x 深度学习专题【左扬精讲】—— 池化层的工作原理:从下采样到特征聚合

在卷积神经网络(CNN)中,卷积层负责从输入中学习局部特征,但仅靠堆叠卷积很难解决两个核心问题:特征图尺寸过大导致计算量爆炸,以及对小幅平移、旋转过于敏感。

池化层(Pooling Layer)正是为这两个问题而设计的下采样机制。

本文系统讲解池化层的工作原理:最大池化(Max Pooling)、平均池化(Average Pooling)、全局平均池化(Global Average Pooling)、自适应池化(Adaptive Pooling),以及在 PyTorch 中的标准实现(torch.nn.MaxPool2d、torch.nn.AvgPool2d、torch.nn.AdaptiveAvgPool2d)。通过尺寸公式推导与代码验证,掌握池化层在 CNN 中的核心作用。

torch.nn.MaxPool2d            ← 二维最大池化(下采样)
torch.nn.AvgPool2d            ← 二维平均池化(下采样)
torch.nn.AdaptiveMaxPool2d    ← 自适应输出尺寸的最大池化
torch.nn.AdaptiveAvgPool2d    ← 自适应输出尺寸的平均池化
torch.nn.functional.max_pool2d ← 函数式最大池化(无参数)
torch.nn.functional.avg_pool2d ← 函数式平均池化(无参数)

PyTorchCNNPooling下采样MaxPool2dAvgPool2dAdaptive PoolingGlobal Average Pooling

学习重点

  • 必须掌握
  • 池化的核心目的:空间下采样、平移鲁棒、降参防过拟合
  • 最大池化与平均池化的差异、对特征的偏好(纹理 vs. 平滑)
  • 输出尺寸公式:H_out = floor((H_in + 2×padding − kernel_size) / stride) + 1
  • 自适应池化(Adaptive Pooling)的工作机制与为何能输出固定尺寸
  • 全局平均池化(GAP)在分类头中的标准用法
  • 理解即可
  • 随机池化(Random Pool)、L^p 池化、分数最大池化等变体
  • 池化与步长卷积(Strided Convolution)的取舍

目录


一、概述:池化在 CNN 中扮演什么角色

What — 池化层是什么?

池化(Pooling)是卷积神经网络中一种无参数(或近无参数)的下采样操作。它在一个固定大小的窗口(如 2×2)内,按某种统计规则(最大值、均值、求和等)将多个输入元素聚合为单个输出元素,从而降低特征图的空间分辨率。在 PyTorch 中,池化层既可以以 torch.nn.MaxPool2d、torch.nn.AvgPool2d 等 nn.Module 子类形式作为网络层使用,也可以以 torch.nn.functional.max_pool2d、torch.nn.functional.avg_pool2d 函数形式调用,两者在数学上完全等价。

设计动机与典型位置

在标准 CNN 架构(如 VGG、ResNet)中,卷积层(Conv)负责特征提取,池化层(Pool)负责空间降采样,两者交替堆叠:

  • 每一次池化之后,特征图尺寸减半(步长 2、窗口 2×2),通道数通常由后续卷积翻倍
  • 到最后一层卷积后,全局平均池化(GAP)将每个特征图压缩为单个标量,送入全连接分类器
  • 整个过程形成 H × W 逐步减小、C 逐步增大的金字塔结构

与卷积的关键区别

  • 卷积层有可学习权重,会改变输入的语义(如从边缘→纹理→物体部件)
  • 池化层没有可学习参数(或仅是固定的统计规则),只做几何位置上的聚合,不引入额外训练复杂度
池化与卷积的角色分工图
输入图像 224×224×3
       │
       ▼
┌──────────────────────────────────────┐
│  Conv2d(3 → 64, 3×3, pad=1)         │   ← 卷积:提取边缘、颜色基础特征
│  + BatchNorm + ReLU                  │
│  输出:224×224×64                    │
└──────────────────────────────────────┘
       │
       ▼
┌──────────────────────────────────────┐
│  MaxPool2d(kernel_size=2, stride=2)  │   ← 池化:空间维度减半
│  输出:112×112×64                    │
└──────────────────────────────────────┘
       │
       ▼
┌──────────────────────────────────────┐
│  Conv2d(64 → 128, 3×3, pad=1)       │   ← 卷积:提取纹理、角点
│  输出:112×112×128                   │
└──────────────────────────────────────┘
       │
       ▼
┌──────────────────────────────────────┐
│  MaxPool2d(kernel_size=2, stride=2)  │   ← 池化:减半
│  输出:56×56×128                     │
└──────────────────────────────────────┘
       ... (重复 N 次)
       │
       ▼
┌──────────────────────────────────────┐
│  AdaptiveAvgPool2d(output_size=1)    │   ← 全局平均池化:每张特征图一个值
│  输出:1×1×512                       │
└──────────────────────────────────────┘
       │
       ▼
┌──────────────────────────────────────┐
│  Flatten + Linear(512, num_classes)  │   ← 分类头
└──────────────────────────────────────┘

池化层的工作原理核心:「几何聚合 + 不引入可学习参数」

本节小结

  • 池化的本质:在空间窗口内按固定规则聚合元素,输出尺寸更小的特征图
  • 核心作用:与卷积交替使用,构建「空间维度逐步缩小、通道维度逐步增大」的金字塔结构
  • 关键区别:池化无学习参数、卷积有学习参数
  • PyTorch 两套 API:torch.nn 模块形式 + torch.nn.functional 函数形式,行为等价

二、为什么需要池化层:空间维度爆炸与平移敏感性

Why — 为什么必须有池化(或某种下采样)?

卷积层本身并不改变特征图尺寸(当 padding=1, kernel_size=3 时输出尺寸等于输入尺寸)。如果只在网络中堆叠卷积,会遇到两个实质性问题:(1)特征图始终保持输入分辨率,计算量与显存随网络深度线性增长,难以训练深层网络;(2)输出对输入的微小平移异常敏感,导致过拟合与泛化能力差。池化层(或等效的步长卷积)正是解决这两个问题的标准机制。

没有池化(或下采样)会发生什么?

  • 后果 1 — 计算量爆炸:32 层 3×3 卷积作用于 224×224 输入(带 padding=1 保持尺寸),前向 + 反向显存可达数十 GB,单卡无法训练
  • 后果 2 — 平移敏感性:目标物体在图像中右移 1 个像素,特征向量的所有元素几乎都会改变,下游分类器学不到「不变性」
  • 后果 3 — 感受野受限:在不缩小特征图的前提下,要让深层神经元看到整张图像,必须叠加非常深的网络;引入下采样后,每缩小一倍,深层一个神经元对应原图的感受野自动扩大一倍
  • 后果 4 — 难以迁移到不同分辨率:没有自适应池化时,输入分辨率变化会直接改变全连接层输入维度,模型无法直接处理新尺寸

池化解决的两个具体问题

  1. 空间降采样:典型 2×2 步长 2 池化使 H × W 各减半,对应计算量与显存降低 4 倍
  2. 局部平移不变性:在 2×2 窗口内取最大/平均,使小幅平移(≤1 像素)不会改变输出特征,强化模型对目标位置的鲁棒性

经典架构中的池化位置

网络池化方式位置
LeNet-5(1998) 2×2 平均池化,步长 2 每个卷积层之后
AlexNet(2012) 3×3 最大池化,步长 2(重叠池化) 部分卷积层之后
VGG-16/19(2014) 2×2 最大池化,步长 2 每个 Block 之后(共 5 次)
ResNet-50(2015) 卷积 + 步长 2 实现下采样
末端 7×7 全局平均池化
残差 stage 转换处、末端

本节小结

  • 空间降采样:池化把特征图 H × W 缩小,直接降低后续层的计算与显存
  • 平移不变性:窗口内聚合让小幅平移不再显著改变输出
  • 扩大感受野:每做一次 2×2 池化,深层神经元对应原图的视野面积自动扩大一倍
  • 迁移到不同分辨率:自适应池化使全连接层的输入维度固定,模型可处理任意输入尺寸

三、最大池化 MaxPool2d:保留最显著特征

What — 最大池化是什么?

最大池化(Max Pooling)在每个池化窗口内取所有元素的最大值作为该窗口的输出。在 PyTorch 中,二维最大池化的标准类是 torch.nn.MaxPool2d,其函数式版本是 torch.nn.functional.max_pool2d。最大池化的核心特性是只保留窗口内最显著的特征响应,丢弃其余响应,因此特别适合提取「是否存在某类模式」的强响应(如边缘是否在某位置出现)。

数学定义

对于输入张量 X ∈ R^{C × H_in × W_in},二维最大池化的输出为:

output(c, i, j) = max
    X(c,
      i × stride_h + di − pad_h,
      j × stride_w + dj − pad_w)
  其中 di ∈ [0, kernel_h), dj ∈ [0, kernel_w)

参数:
  kernel_size / kernel_size=(kH, kW):窗口大小
  stride / stride=(sH, sW):步长,默认 = kernel_size
  padding / padding=(pH, pW):输入两侧 0 填充(默认 0)
  dilation / dilation=(dH, dW):空洞卷积式扩张(默认 1)
  return_indices=False:是否返回最大值索引(用于 nn.MaxUnpool2d 反池化)
  ceil_mode=False:输出尺寸公式选 floor 还是 ceil

最大池化的几何直觉

用一个 2×2、步长 2 的最大池化作用于 4×4 输入,每个 2×2 不重叠窗口内取最大值,将输出缩小为 2×2。可以理解为「局部最强响应被保留,弱响应被丢弃」——例如边缘检测卷积后,某 2×2 窗口中只要有一个像素响应特别强(强边缘),整个窗口输出就是该强响应。

标准写法 — torch.nn.MaxPool2d 与 F.max_pool2d 等价用法
import torch
import torch.nn as nn
import torch.nn.functional as F

# ===== 方式 1:nn.Module 子类(推荐在 __init__ 中构造)=====
pool = nn.MaxPool2d(kernel_size=2, stride=2)            # 2x2 不重叠最大池化
x = torch.randn(1, 64, 32, 32)                          # [N=1, C=64, H=32, W=32] 输入
y = pool(x)
print(y.shape)  # torch.Size([1, 64, 16, 16])           # H/W 各减半

# ===== 方式 2:functional 函数式调用(推荐在 forward() 中使用)=====
y = F.max_pool2d(x, kernel_size=2, stride=2)
print(y.shape)  # torch.Size([1, 64, 16, 16])           # 与方式 1 输出形状完全一致

# ===== 常见参数组合 =====
pool_overlap = nn.MaxPool2d(kernel_size=3, stride=2)    # 3x3 窗口、步长 2(重叠池化,AlexNet 风格)
pool_with_pad = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)  # 保尺寸池化
pool_dilated = nn.MaxPool2d(kernel_size=3, stride=1, dilation=2)   # 空洞池化(dilation > 1)
pool_return_idx = nn.MaxPool2d(kernel_size=2, stride=2, return_indices=True)
y, indices = pool_return_idx(x)                         # indices 用于 nn.MaxUnpool2d 反池化

# ===== 非方形窗口与不同维度 =====
pool_rect = nn.MaxPool2d((2, 3), stride=(2, 3))         # 矩形窗口
pool_1d = nn.MaxPool1d(2)                                # 一维最大池化(用于序列)
pool_3d = nn.MaxPool3d(2)                                # 三维最大池化(用于视频 / 体积数据)

# 验证两种 API 行为完全等价
a = pool(x)
b = F.max_pool2d(x, kernel_size=2, stride=2)
print(torch.equal(a, b))  # True

两种写法在数学上等价:nn.MaxPool2d 是 nn.Module 子类,会被 model.parameters() 枚举到但无任何参数;F.max_pool2d 是纯函数,没有状态。经验法则:在 __init__ 中用 nn.MaxPool2d(...),在 forward() 函数体里可用 F.max_pool2d(...)。

手工验证最大池化(帮助理解计算过程)
import torch
import torch.nn as nn

# 构造一个 1×1×4×4 的输入特征图,方便观察每个窗口的最大值
x = torch.tensor([[[[1.0, 3.0, 2.0, 4.0],
                    [5.0, 2.0, 6.0, 1.0],
                    [0.0, 7.0, 3.0, 2.0],
                    [4.0, 1.0, 8.0, 5.0]]]])  # shape (1, 1, 4, 4)

pool = nn.MaxPool2d(kernel_size=2, stride=2)
y = pool(x)
# y = tensor([[[[5., 6.],
#               [7., 8.]]]])  shape (1, 1, 2, 2)
print(y)

# 验证:左上 2x2 窗口 = [[1,3],[5,2]],最大值是 5
#       右上 2x2 窗口 = [[2,4],[6,1]],最大值是 6
#       左下 2x2 窗口 = [[0,7],[4,1]],最大值是 7
#       右下 2x2 窗口 = [[3,2],[8,5]],最大值是 8

注意事项

  • 最大池化通常使用 ceil_mode=False(默认):当输入尺寸不能被步长整除时,右侧 / 底部的窗口会被丢弃。例如 7×7 输入、3×3 步长 3 池化,输出 2×2 而非 3×3
  • 重叠池化:kernel_size=3, stride=2 是 AlexNet 提出的设置,相邻窗口有重叠区域,被证明比不重叠池化更能减轻过拟合
  • return_indices=True:记录每个窗口最大值的位置索引,可在解码器中配合 nn.MaxUnpool2d 使用(SegNet 风格)
  • 默认 dilation=1:当 dilation > 1 时窗口按空洞方式扩张,相当于「稀疏采样后再取最大值」,不常用

本节小结

  • 最大池化语义:窗口内取最大值,保留最显著响应
  • PyTorch 标准 API:torch.nn.MaxPool2d 与 F.max_pool2d 等价
  • 关键参数:kernel_size、stride、padding、dilation、return_indices、ceil_mode
  • 典型组合:2×2 步长 2(VGG 风格)、3×3 步长 2(AlexNet 重叠池化风格)

四、平均池化 AvgPool2d:保留整体统计特征

What — 平均池化是什么?

平均池化(Average Pooling)在每个池化窗口内计算所有元素的算术平均值作为输出。在 PyTorch 中,对应 torch.nn.AvgPool2d(模块形式)与 torch.nn.functional.avg_pool2d(函数形式)。与最大池化只保留「最强」响应不同,平均池化保留窗口内所有响应的整体水平,对噪声更平滑,适合提取「背景」或「整体亮度」类特征。

数学定义

output(c, i, j) = (1 / (kH × kW)) × Σ_{di, dj}
    X(c, i × stride_h + di − pad_h, j × stride_w + dj − pad_w)

参数:
  kernel_size / (kH, kW)         :窗口大小
  stride / (sH, sW)              :步长(默认 = kernel_size)
  padding / (pH, pW)             :输入两侧 0 填充(默认 0)
  ceil_mode=False                :floor 还是 ceil 取整
  count_include_pad=True         :均值分母是否包含 padding 的 0
  divisor_override=None          :手动指定分母(覆盖默认窗口元素数)

最大池化 vs 平均池化:特征选择偏好

  • 最大池化:输出对窗口内任意一个强响应都很敏感,对弱响应不敏感。学到的是「纹理的强烈性」
  • 平均池化:输出取决于所有响应的平均水平,平滑了单个极端值的影响。学到的是「区域的整体激活水平」
  • 在大多数视觉任务中,最大池化的表现略优或至少持平,因为它强化了边缘、角点等「稀疏但显著」的视觉信号
标准写法 — AvgPool2d 的不同语义
import torch
import torch.nn as nn
import torch.nn.functional as F

x = torch.arange(16, dtype=torch.float32).reshape(1, 1, 4, 4)
# tensor([[[[ 0.,  1.,  2.,  3.],
#           [ 4.,  5.,  6.,  7.],
#           [ 8.,  9., 10., 11.],
#           [12., 13., 14., 15.]]]])
print(x.shape)  # torch.Size([1, 1, 4, 4])

# ===== 默认:2x2 不重叠平均池化 =====
pool = nn.AvgPool2d(kernel_size=2, stride=2)
y = pool(x)
# y = tensor([[[[ 2.5,  4.5],   # 左上窗口 (0+1+4+5)/4=2.5,右上 (2+3+6+7)/4=4.5
#               [10.5, 12.5]]]]) # 下排类似
print(y)

# ===== count_include_pad=False 的差异:padding 的 0 不计入分母 =====
x_with_pad = torch.tensor([[[[1., 2., 3.],
                              [4., 5., 6.],
                              [7., 8., 9.]]]])   # shape (1, 1, 3, 3)

# 3x3 步长 1 padding=1 的平均池化
# 输出中心元素 = (1+2+3+4+5+6+7+8+9) / 9 = 5.0 (count_include_pad=True)
pool_a = nn.AvgPool2d(kernel_size=3, stride=1, padding=1, count_include_pad=True)
print(pool_a(x_with_pad)[0, 0, 1, 1])  # tensor(5.),分母 = 9

# count_include_pad=False 时,分母只算有效像素
pool_b = nn.AvgPool2d(kernel_size=3, stride=1, padding=1, count_include_pad=False)
print(pool_b(x_with_pad)[0, 0, 1, 1])
# 有效像素数 = 9(全在原图内),所以与上面相同
# 但对于边角元素,会少算 padding 的 0,分母变小

# ===== divisor_override 手动指定分母 =====
pool_div = nn.AvgPool2d(kernel_size=3, stride=1, padding=1,
                        divisor_override=5)        # 强制除以 5
print(pool_div(x_with_pad)[0, 0, 1, 1])           # = 45 / 5 = 9.0

# ===== 函数式调用 =====
y_fn = F.avg_pool2d(x, kernel_size=2, stride=2)
print(torch.equal(y, y_fn))  # True,与 nn.AvgPool2d 等价

注意事项

  • count_include_pad:True(默认)时 padding 位的 0 计入分母,等价于标准「扩展输入再算平均」;False 时只算有效像素,等价于 LPPool 的一种边界处理
  • divisor_override:在拼接 / 重建场景下使用,例如把池化输出上采样后与未池化特征相加时需要控制平均的「分母」
  • 现代 ResNet 等架构:已不再使用 AvgPool2d 做中间下采样,改用 stride-2 卷积代替;AvgPool2d / AdaptiveAvgPool2d 主要用于末端的全局平均池化

本节小结

  • 平均池化语义:窗口内取均值,保留整体统计水平
  • PyTorch 标准 API:torch.nn.AvgPool2d 与 F.avg_pool2d 等价
  • 关键参数:kernel_size、stride、padding、ceil_mode、count_include_pad、divisor_override
  • 与 MaxPool 的对比:MaxPool 偏好强响应(纹理),AvgPool 偏好整体水平(亮度)

五、输出尺寸公式:kernel_size、stride、padding 三参数推导

What — 池化输出尺寸如何计算?

给定输入尺寸 H_in、池化参数 kernel_size=k、stride=s、padding=p(dilation=d),输出高度为:

H_out = floor((H_in + 2×p − d × (k − 1) − 1) / s) + 1   (ceil_mode=False,默认)

H_out = ceil((H_in + 2×p − d × (k − 1) − 1) / s) + 1    (ceil_mode=True)

宽度方向 W_out 同理。
dilated pooling 中 d=1 是常见设置,等价于 d × (k − 1) = (k − 1)。

对应到 PyTorch 实际行为:MaxPool2d、AvgPool2d 的输出尺寸都遵循上述公式,差别只在「对 floor / ceil 的选择」可通过 ceil_mode 切换。

尺寸公式演练表(ceil_mode=False 默认)
设 H_in = 32,常用组合下的 H_out:

┌─────────────────────────┬──────────┬─────────┬─────────┬──────────┐
│ 输入 32                 │ kernel=k │ stride=s│ padding=p│ H_out    │
├─────────────────────────┼──────────┼─────────┼─────────┼──────────┤
│ VGG/ResNet 经典下采样    │    2     │    2    │    0    │   16     │
│ LeNet 下采样             │    2     │    2    │    0    │   16     │
│ 重叠池化 AlexNet          │    3     │    2    │    0    │   15     │
│ 保尺寸池化                │    3     │    1    │    1    │   32     │
│ 1/3 步长截断(罕见)       │    3     │    3    │    0    │   10     │
│ Padding 缩边 7→7         │    7     │    7    │    0    │    4     │
└─────────────────────────┴──────────┴─────────┴─────────┴──────────┘

验证示例:H_in=32, k=3, s=2, p=0, d=1
  H_out = floor((32 + 0 − 1×(3−1) − 1) / 2) + 1
        = floor((32 − 2 − 1) / 2) + 1
        = floor(29 / 2) + 1
        = floor(14.5) + 1
        = 14 + 1
        = 15                             ← 与表一致
代码验证:构建一个会抛错的尺寸组合
import torch
import torch.nn as nn

# 用公式推导 + 实际输出尺寸双向验证
cases = [
    # (H_in, kernel, stride, padding, ceil_mode, expected_H_out)
    (32, 2, 2, 0, False, 16),   # VGG 经典
    (32, 3, 2, 0, False, 15),   # AlexNet 重叠池化
    (32, 3, 1, 1, False, 32),   # 保尺寸
    (35, 2, 2, 0, False, 17),   # 奇数输入、2x2 步长 2
    (35, 2, 2, 0, True,  18),   # ceil_mode=True 时多 1 像素被保留
    (7,  7, 7, 0, False, 1),    # 全局池化 k=s=7
]

for H_in, k, s, p, ceil, expected in cases:
    pool = nn.MaxPool2d(kernel_size=k, stride=s, padding=p, ceil_mode=ceil)
    x = torch.zeros(1, 1, H_in, H_in)
    y = pool(x)
    h_out = y.shape[-1]
    status = "OK" if h_out == expected else "FAIL"
    print(f"H_in={H_in:>3}  k={k}  s={s}  p={p}  ceil={ceil}  →  "
          f"H_out={h_out:>3}  (expected {expected}, {status})")

输出尺寸的几个常见坑

  • 坑 1:输入尺寸为奇数 + 步长 2:例如 35×35 经过 2×2 步长 2 池化,输出 17×17 而非 18×18(ceil_mode=False,丢弃边界)。若想保留尾部像素,需设 ceil_mode=True
  • 坑 2:不对称输入:H 与 W 应分别计算,不能假设输入是正方形
  • 坑 3:stride 缺省值:当 stride=None 时 PyTorch 默认 stride = kernel_size(不重叠)。若要重叠池化必须显式设置 stride

本节小结

  • 核心公式:H_out = floor((H_in + 2×p − d×(k−1) − 1) / s) + 1
  • PyTorch 一致性:MaxPool2d、AvgPool2d 使用同一公式,仅 ceil_mode 决定取整方向
  • 常见下采样组合:2×2 步长 2(VGG)/ 3×3 步长 2(AlexNet)/ k=s=H(全局池化)

六、自适应池化 Adaptive Pooling:输出固定尺寸

What — 自适应池化是什么?

torch.nn.AdaptiveAvgPool2d 与 torch.nn.AdaptiveMaxPool2d 是两种特殊的池化:用户只需指定输出尺寸 output_size,PyTorch 自动计算 kernel_size 和 stride,使得不同输入尺寸都能得到相同大小的输出。这对全连接层下游特别重要——只要网络的卷积部分输出任意尺寸 (H, W),经过自适应池化到 output_size 后,扁平化就能送入固定大小的全连接分类头。

自适应池化的工作原理

对于目标输出尺寸 (OH, OW),自适应池化按如下规则自动确定 kernel 与 stride(以 AvgPool 为例,MaxPool 类似):

stride_h = floor(H_in / OH)
stride_w = floor(W_in / OW)
kernel_h = H_in − (OH − 1) × stride_h
kernel_w = W_in − (OW − 1) × stride_w
padding = 0

并按需要修正使得 kernel ≤ stride_h × OH、kernel ≥ stride_h
(PyTorch 源码通过 start_index / end_index 隐式划分区间,
 不暴露 kernel/stride 字段)

它解决了什么核心问题?

  • 输入尺寸灵活:同一个模型可处理 224×224 与 256×256 输入(甚至 384×384 微调)
  • 替换 flatten 输入:分类头常配 AdaptiveAvgPool2d((1,1)),等价于全局平均池化
  • 目标检测特征对齐:多尺度特征金字塔中用 AdaptiveMaxPool2d(out) 将不同尺寸的特征统一为一个尺寸
标准写法 — AdaptiveAvgPool2d 输出固定尺寸
import torch
import torch.nn as nn

# 任意尺寸的输入 → 固定 (1, 1) 输出(最常见的用法,分类头)
gap = nn.AdaptiveAvgPool2d(output_size=(1, 1))

x_224 = torch.randn(8, 512, 224, 224)
x_256 = torch.randn(8, 512, 256, 256)   # 任意不同尺寸!
print(gap(x_224).shape)  # torch.Size([8, 512, 1, 1])
print(gap(x_256).shape)  # torch.Size([8, 512, 1, 1])
# 两种输入都得到相同的输出尺寸,可直接送入同尺寸的 Linear 层

# 多尺度特征对齐(用于 FPN / 检测)
adaptive = nn.AdaptiveMaxPool2d(output_size=(7, 7))   # 强制输出 7×7

x1 = torch.randn(8, 256, 56, 56)      # 浅层大尺寸特征
x2 = torch.randn(8, 512, 28, 28)      # 中层中等尺寸特征
x3 = torch.randn(8, 1024, 14, 14)     # 深层小尺寸特征
print(adaptive(x1).shape)  # torch.Size([8, 256, 7, 7])
print(adaptive(x2).shape)  # torch.Size([8, 512, 7, 7])
print(adaptive(x3).shape)  # torch.Size([8, 1024, 7, 7])
# 三种不同输入尺寸都变成 7×7,可堆叠 / Concat 后送入下游网络

# 自适应最大池化版本(保留最强响应)
adaptive_max = nn.AdaptiveMaxPool2d(output_size=(1, 1))
print(adaptive_max(x_224).shape)  # torch.Size([8, 512, 1, 1])

注意事项

  • output_size:可以是 int(方形)或 (OH, OW)(矩形)
  • 没有 kernel / stride 参数:用户无法干预窗口大小,PyTorch 自动调整;这与 MaxPool2d 风格相反
  • output_size=(1, 1):等价于全局池化(Global Pooling),但与显式的 MaxPool2d(k=H, s=H) 略有差异:自适应池化按等分区间聚合,普通全局池化在同一个窗口内聚合

本节小结

  • 核心特性:指定输出尺寸,自动推导 kernel / stride
  • 关键优势:使同一模型支持任意输入分辨率;分类头常配 AdaptiveAvgPool2d((1,1))
  • 两种变体:AdaptiveAvgPool2d(取均值)与 AdaptiveMaxPool2d(取最大)
  • 典型应用:分类头全局池化、FPN 多尺度特征对齐

七、全局平均池化 GAP:分类头的标准做法

What — 全局平均池化(GAP)是什么?

全局平均池化(Global Average Pooling,GAP)是将每个特征图(C × H × W 张量)压缩为单个标量、输出尺寸为 (N, C, 1, 1) 的操作。在 PyTorch 中,GAP 等价于 nn.AdaptiveAvgPool2d(output_size=(1, 1)),也可以用 F.adaptive_avg_pool2d(x, (1, 1))。论文《Network In Network》最早将 GAP 引入分类头,ResNet、Inception 等现代架构几乎全部沿用这一设计。

Why — 为什么 GAP 替代了传统的 flatten + 大全连接层?

传统做法(AlexNet、VGG)的问题:

  • 最后一个卷积层输出 7×7×512 = 25088 维向量,直接送入 Linear(25088, 4096) 全连接层
  • 仅这一层就有 25088 × 4096 ≈ 1.03 亿 个参数,占整个网络参数量的相当大比例
  • 过拟合风险高:大量参数需要海量数据支撑
  • 无法迁移到不同输入分辨率:7×7 是特定输入尺寸下的输出,改变输入会破坏 Linear 的输入维度

GAP 的解决方案:

  • 每个通道用「全局平均」代替扁平化,输出 (N, C, 1, 1)
  • 直接 view(N, C) 后送入 Linear(C, num_classes),参数量从 C × H × W × num_classes 降到 C × num_classes
  • 网络总参数大幅减少,对过拟合更鲁棒
  • 天然支持任意输入分辨率(因为 GAP 输出尺寸与 H、W 无关)
标准写法 — 全局平均池化的三种等价形式
import torch
import torch.nn as nn
import torch.nn.functional as F

x = torch.randn(4, 512, 7, 7)  # 假设是某个 CNN backbone 的最终输出

# ===== 方式 1:nn.AdaptiveAvgPool2d(最标准,推荐)=====
gap = nn.AdaptiveAvgPool2d((1, 1))
y = gap(x)
print(y.shape)  # torch.Size([4, 512, 1, 1]),每个通道一个标量

# ===== 方式 2:F.adaptive_avg_pool2d(函数式写法)=====
y = F.adaptive_avg_pool2d(x, (1, 1))
print(y.shape)  # torch.Size([4, 512, 1, 1])

# ===== 方式 3:F.adaptive_avg_pool2d 输出 (None, 1, 1)(让 W 自动计算)=====
y = F.adaptive_avg_pool2d(x, (None, 1))  # H 保持不变,W 压缩为 1
print(y.shape)  # torch.Size([4, 512, 7, 1])

# ===== 接入全连接分类头(标准 ImageNet 模式)=====
class Classifier(nn.Module):
    def __init__(self, in_features, num_classes):
        super().__init__()
        self.gap = nn.AdaptiveAvgPool2d((1, 1))    # GAP
        self.flatten = nn.Flatten(1)                # (N, C, 1, 1) → (N, C)
        self.fc = nn.Linear(in_features, num_classes)

    def forward(self, x):
        x = self.gap(x)
        x = self.flatten(x)
        x = self.fc(x)
        return x

# 不管输入是 224x224 还是 256x256,最终都得到 (N, num_classes)
clf = Classifier(in_features=512, num_classes=1000)
out1 = clf(torch.randn(2, 512, 7, 7))      # backbone 输出 7x7
out2 = clf(torch.randn(2, 512, 10, 10))    # backbone 输出 10x10(其他 backbone)
print(out1.shape, out2.shape)  # torch.Size([2, 1000]) torch.Size([2, 1000])

全局最大池化(GMP)

  • nn.AdaptiveMaxPool2d((1, 1)):与 GAP 类似,但每个通道取最大值而非平均
  • GAP 偏好「整体激活水平」(如背景纹理)、GMP 偏好「最强响应」(如物体部件)
  • 分类头首选 GAP:参数量更少 + 抗过拟合能力更强,是 ResNet / MobileNet 等所有现代分类网络的默认选择
  • GAN、风格迁移中常见 GMP 用于提取判别性特征

本节小结

  • GAP 定义:每个特征图取均值,输出 (N, C, 1, 1)
  • 核心优势:替代全连接扁平化、参数量大降、任意输入分辨率友好
  • PyTorch 标准实现:nn.AdaptiveAvgPool2d((1, 1)),最常用
  • 现代分类头标配:GAP + Flatten + Linear(ResNet / MobileNet / EfficientNet)

八、常见变体:分数池化、LP 池化、随机池化

What — 除了 MaxPool 和 AvgPool,还有哪些池化变体?

在标准最大池化与平均池化之外,文献中提出了多种改进变体。它们在 PyTorch 中大多没有专门的 nn.Module 类,需要在 forward 中用 torch 操作自行实现。下面介绍最常见的几种。

常见池化变体一览

变体核心思路PyTorch 实现方式
分数最大池化(Fractional MaxPool) 随机或固定生成「分数」下降比例(如 1.5× 下降) torch.nn.functional.fractional_max_pool2d(仅函数式)
Lp 池化 output = (1/k² × Σ xp)^(1/p),p=1 是平均池化、p=∞ 趋向最大池化 需用 torch.norm 或 unfold 手动实现
随机池化(Stochastic Pooling) 按窗口内元素值大小采样(S3Pool 论文) 需手动 multinomial 采样
空间金字塔池化(SPP) 多尺度池化(如 1×1 + 2×2 + 4×4)后拼接,统一输出尺寸 需用 adaptive_avg_pool2d × 不同 output_size 自行实现
注意力池化 / Feature Pooling 用注意力权重对窗口元素加权求和 需用 softmax × 窗口元素自行实现
空间金字塔池化(SPP)手动实现
import torch
import torch.nn as nn
import torch.nn.functional as F

class SpatialPyramidPool2d(nn.Module):
    """SPP:把任意尺寸输入池化到固定的多尺度表示并拼接。

    给定 input_size=(h, w),通过 (out1, out2, out3) 三种输出尺寸同时池化,
    得到 1+4+16=21 个向量化空间位置,拼接后长度固定。
    """
    def __init__(self, out_sizes=(1, 2, 4)):
        super().__init__()
        self.out_sizes = out_sizes

    def forward(self, x):
        # x: [N, C, H, W]
        n, c = x.shape[:2]
        levels = []
        for out_size in self.out_sizes:
            level = F.adaptive_avg_pool2d(x, output_size=(out_size, out_size))
            # 展平为 [N, C × out_size^2]
            levels.append(level.reshape(n, -1))
        # 拼接所有尺度:[N, C × (1 + 4 + 16)]
        return torch.cat(levels, dim=1)

# 任意尺寸输入都得到固定长度表示
spp = SpatialPyramidPool2d(out_sizes=(1, 2, 4))
out1 = spp(torch.randn(2, 256, 14, 14))   # 输入 14x14
out2 = spp(torch.randn(2, 256, 28, 28))   # 输入 28x28
print(out1.shape, out2.shape)
# torch.Size([2, 5376]) torch.Size([2, 5376]),两个完全相同 → 可送入同一全连接层

# 注意:这是 SPP 模块(用于检测 / 多尺度分类)的常见写法。
# 主流框架现在更常用的是 SPPF(SPP-Fast,YOLOv5 中)的串行实现。
分数最大池化:fractional_max_pool2d 函数式调用
import torch
import torch.nn.functional as F

x = torch.randn(1, 64, 32, 32)

# 固定随机器种子,使输出可复现
torch.manual_seed(0)

# 分数最大池化:通过 random_samples 参数控制输出尺寸
# output_ratio = (H_out × W_out) / (H_in × W_in)
# random_samples 是 [H_out × W_out] 个范围 [0, 1) 的随机数,
#   PyTorch 内部按这些分数决定每个池化窗口的边界位置
# 这里 output_size = (16, 16),因此 ratio ≈ 0.25
random_samples = torch.rand(16 * 16)
y = F.fractional_max_pool2d(
    x,
    kernel_size=2,
    output_size=(16, 16),
    random_samples=random_samples
)
print(y.shape)  # torch.Size([1, 64, 16, 16])

# 同一种 random_samples 在不同输入尺寸上得到不同位置
# 这是 YOLOv3 等检测器中用于「不规则下采样」的关键机制

注意事项

  • 变体适用场景有限:在主流视觉任务(分类 / 检测 / 分割)中,标准最大池化 + 自适应池化已足够;变体多用于论文或特殊任务
  • 没有 PyTorch nn 类的变体:如 Lp 池化、随机池化、注意力池化都需要 unfold + 矩阵操作自行实现
  • 分数最大池化的优势:可以生成「非整数倍」下采样(如 5×5→3×3),缓解传统池化只能整倍降采样的限制

本节小结

  • 主流变体:分数最大池化、Lp 池化、随机池化、空间金字塔池化(SPP)
  • PyTorch 支持现状:分数最大池化有 F.fractional_max_pool2d,其余需自行实现
  • 实用建议:分类 / 检测 / 分割等主流任务用 标准 MaxPool2d + AdaptiveAvgPool2d 已足够,无需自造池化

九、FAQ(20 组)

FAQ — 精选 20 问,深入理解池化层

Q1. 池化和卷积有什么区别?为什么不直接用步长卷积替代池化?

核心区别在于「是否引入可学习参数」:池化是固定规则的窗口聚合(如取最大 / 均值),无参数;卷积是有可学习权重的线性变换。 步长卷积(Strided Conv)也能实现下采样,但它会修改输入的语义(因为有权重),因此更接近「下采样版的卷积」而非「池化的替代」。现代 ResNet / MobileNet V2 等架构确实倾向于用 stride-2 卷积替换中间的池化层,因为在深层网络中下采样与特征提取可合并;而 VGG 等纯堆叠架构沿用独立的 MaxPool 层。在分类头(最后一层卷积后)几乎所有架构都仍用全局平均池化,没有任何卷积能直接替代它。

Q2. 池化层有没有可学习参数?

标准池化层(MaxPool2d、AvgPool2d)没有任何可学习参数,model.parameters() 中不会列出它们。 它们是「固定算子」,由 PyTorch 源码直接实现为按窗口读取 + amax / mean 的高效 C++/CUDA 内核。对比之下,卷积层有 weight 与 bias,归一化层有 weight、bias、running_mean、running_var 等可训练或运行时统计量。在自定义网络时,如果需要打印网络的参数总数,可以放心把池化层忽略。

Q3. 最大池化能反向传播吗?

可以,最大池化的反向传播只把梯度传给前向时取到的最大值位置(其他位置梯度为 0)。 在 PyTorch 中,F.max_pool2d 的反向实现通过 torch.autograd 自动维护「窗口最大值索引」的张量,反向传播时把上游梯度 ∂L/∂y 按这些索引散射回输入的相应位置。这种「稀疏梯度」是最大池化的本质特性——它把梯度只分配给「最显著」的输入位置,弱响应位置得不到梯度信号,类似于一种隐式的「硬注意力」机制。

Q4. 平均池化的反向传播与最大池化有什么不同?

平均池化的反向传播把梯度均匀分摊给窗口内所有位置(每个位置收到 ∂L/∂y / (kH × kW))。 这是因为前向时每个输入像素对输出的贡献相等,求导时每个位置都获得 1/(kH × kW) 的梯度权重。对比最大池化梯度只流向最大值位置,平均池化梯度是均匀分布——这意味着即使窗口内某个位置响应很低,它也会获得梯度(数值虽小但非零)。这种梯度特性使平均池化在分割等任务中通常优于最大池化,因为它鼓励所有空间位置都对损失负责。

Q5. 输出尺寸公式里 padding 的作用是什么?

padding 在输入两侧补 0,使边缘像素也能作为窗口中心被聚合,避免「边缘像素被丢弃」的问题。 例如 5×5 输入 + 3×3 步长 1 池化:不带 padding 时只有中间 3×3=9 个位置能成为窗口中心(输出 3×3),边缘像素贡献只到窗口的边角而非中心;带 padding=1 时输出尺寸提升到 floor((5 + 2 − 3)/1) + 1 = 5,所有原图位置都能被覆盖。在分类头或解码器中,保尺寸 池化(kernel_size=3, stride=1, padding=1)很常见。

Q6. ceil_mode 这个参数什么时候需要设成 True?

当输入尺寸不能被步长整除、且不希望丢失边缘像素时,应设 ceil_mode=True。 默认 ceil_mode=False 时右 / 下边缘的窗口会被丢弃,可能在「剩余像素 < 窗口大小」时丢失这部分信息。ceil_mode=True 改用向上取整,即使末尾窗口不满也保留参与计算。在目标检测(保留所有特征像素)和语义分割(避免边缘类别被截断)中常设 ceil_mode=True。但要注意:此时输出尺寸可能比标准公式算出来的大 1。

Q7. 全局平均池化(GAP)和把特征图「flatten + Linear」有什么区别?

GAP 把每个通道降为 1 个标量,全连接层只做通道间线性映射;flatten + Linear 把 H × W × C 展平成单一向量再加权重,参数量通常比 GAP 大数百倍。 举例:512 通道 × 7×7 特征图,flatten 后是 25088 维向量,全连接分类头 Linear(25088, 1000) 有 25088 × 1000 ≈ 2500 万 参数;改用 GAP + Linear(512, 1000) 只需要 512 × 1000 ≈ 51 万 参数,参数量约缩小 50 倍。同时 GAP 提供空间不变性,对位置变化鲁棒,对过拟合更友好。这是 ResNet 论文里被反复验证的设计选择。

Q8. 自适应池化和普通池化在数学上完全等价吗?

不严格等价:对同一输入,自适应池化与普通池化的输出通常尺寸相同,但窗口的具体划分位置可能不同。 普通池化按固定的 kernel / stride 对齐窗口边界,可能在右 / 下边缘丢弃一小块;自适应池化按输出尺寸等分输入区间,最后一个窗口可以包含剩余像素。具体差异:当 H_in=7, output_size=2 时,AdaptiveAvgPool2d 把 7 个像素分成 (3, 4) 两段;普通 AvgPool2d(k=3, s=3, ceil_mode=False) 则会按 (3, 3) 划分并丢弃最后 1 个像素。

Q9. 为什么分类头几乎都是用自适应池化而非固定窗口的全局池化?

因为分类头要支持任意输入分辨率:AdaptiveAvgPool2d((1,1)) 把任意 H × W 压缩为 1 × 1,而固定窗口的 AvgPool2d(k=H, s=H) 要求开发者在每次输入尺寸变化时手动改 kernel。 实际工程中,模型通常会在训练时用 224×224、推理时可能用 256×256 或 384×384 微调分辨率。若分类头固定了窗口,迁移时就要重写层结构。PyTorch 官方 ResNet / EfficientNet 实现的最后都使用 nn.AdaptiveAvgPool2d((1, 1)),这是工业界的事实标准。

Q10. MaxPool2d 与 MaxUnpool2d 是配对使用的吗?

是的,前者在编码器下采样,后者在解码器上采样以恢复空间信息。需要在 MaxPool2d 时设 return_indices=True,把索引传给对应的 MaxUnpool2d。 这就是 SegNet 论文的核心思想:编码器用最大池化下采样并记录最大值索引;解码器用 MaxUnpool2d 把稀疏还原到与原图同样的位置,其余位置补 0。优点是相比转置卷积更省参数、边界更清晰;缺点是跳跃连接信息较少,通常还要与 encoder 的特征图做拼接。其余分割架构(U-Net、DeepLabV3+)更常用转置卷积或双线性插值上采样。

Q11. count_include_pad 设为 False 在什么场景下有用?

在拼接重建(pooling unpooling round-trip)、LPPool 风格归一化、或与 Im2Col 等需要「有效像素均值」语义的场景下有用。 默认 True 时 padding 位的 0 计入分母,等价于「先 0 填充再算均值」;设为 False 时只算有效像素,分母可能小于 kernel_size × kernel_size。典型例子:当你想把 3×3 步长 1 padding=1 池化的中心位置还原回原图的精确值时,需要 count_include_pad=False,否则会被 padding 的 0「稀释」。这是 LPPool2d 在 PyTorch 源码中的处理方式之一。

Q12. 平均池化会不会「稀释」强响应?

会,平均池化的「均值」语义会拉低强响应:若窗口内有 1 个 100 和 3 个 0,最大池化结果是 100,平均池化结果是 25。 这是平均池化的本质特征——它「反映整体水平」而非「突出极值」。在纹理特征丰富的图像分类任务中,平均池化会丢失部分强响应信息,因此表现通常略差于最大池化。但在语义分割、风格迁移、特征平滑等任务中,平均池化的平滑特性反而是有利的,因为它不放大单个噪声像素或异常激活。

Q13. 为什么现代分类网络几乎都用 GAP 而不是 flatten?

因为 GAP 把分类头参数量从「百万级」压缩到「万级」,同时天然支持任意输入分辨率。 VGG-16 等早期架构用 flatten 把 7×7×512 = 25088 维向量送入 Linear(25088, 4096),仅这一层就有上亿参数;ResNet 改用 GAP 后分类头只有 512 × 1000 ≈ 51 万 参数,缩小 50 倍以上。同时 GAP 输出与 H、W 无关,使得同一模型可处理不同分辨率输入(迁移学习、多尺度推理)。论文《Network In Network》(2014)和 ResNet(2015)反复验证了 GAP 的优势。

Q14. 卷积层能不能完全替代池化层?

部分可以:stride-2 卷积能替代中间下采样,但末端的全局平均池化(GAP)没有任何卷积能直接替代。 在 ResNet / MobileNet 等现代架构中确实流行「用 stride-2 卷积替换中间 MaxPool」,因为它把特征提取与降采样合并到一步。但 GAP 的本质是「每个通道一个标量」,对应 C × 1 × 1 输出,没有任何有意义的卷积核尺寸能做得这件事(除非用 kernel_size=H 的全局卷积,但那样需要 H 固定,丧失灵活性)。所以 GAP 仍然是分类头标配。

Q15. 池化层对反向传播有什么影响?

池化会缩减梯度的空间分布:最大池化让梯度只流向窗口最大值位置,平均池化让梯度均匀分布在窗口内。 这种「稀疏 vs. 平滑」梯度特性影响网络学到的特征——最大池化鼓励「只关心关键位置」(梯度集中在少数强响应位置),平均池化鼓励「关注所有位置」(梯度均匀分布)。从优化角度看,两者都是合理的;但要避免在深层堆叠过多最大池化导致「梯度稀疏化」——这时低层卷积可能学不到足够的细节特征。

Q16. torch.nn.functional.max_pool2d 和 torch.nn.MaxPool2d 应该选哪个?

两者数学完全等价,选哪个取决于使用场景:在 nn.Module 子类的 __init__ 中用 nn.MaxPool2d;在 forward 里临时用 F.max_pool2d。 用 nn.MaxPool2d 的好处是「可见性」:当打印模型结构(如 print(model))时池化层会作为子模块显示,便于调试;用 F.max_pool2d 的好处是「无状态」:不会被 model.parameters()、model.buffers() 等列举。PyTorch 官方实现中 nn.MaxPool2d.forward 直接调用 F.max_pool2d,所以两者只在「模型可序列化性」上有细微差别。

Q17. 池化的反向梯度会不会爆炸?

通常不会。池化是线性分段 / 线性求均操作,梯度不会自然指数放大或缩小。 最大池化的梯度最多为 1(每个输入位置至多收到一个输出位置的梯度);平均池化的梯度为 1 / (kH × kW),远小于 1。与 sigmoid / softmax 等有饱和区间的激活函数不同,池化函数处处有定义良好的梯度,不会出现「梯度饱和为零」或「梯度爆炸」。不过在多次堆叠池化后,梯度穿透到浅层时会被「拉平」——这是深层网络需要残差连接的原因之一。

Q18. 池化操作在 GPU 上效率如何?

效率高:池化是 PyTorch 深度优化的算子之一,使用专门的 cuDNN / 自定义 CUDA 内核,常比卷积快一个数量级。 因为池化不涉及权重加载、矩阵乘法或归约,所以瓶颈在内存访问而非计算。典型 ResNet-50 中所有 5 个 MaxPool 层合计的 GPU 时间只占总前向时间的百分之几。如果用 F.max_pool2d 自定义代码而非 nn.MaxPool2d,不会有性能差异——底层调用的是同一个 C++ 算子。

Q19. 池化层对显存占用有什么影响?

池化会显著降低显存:每次 2×2 步长 2 池化都让特征图 H × W 减半,显存占用降低 4 倍。 一个卷积层的中间特征图(用于反向传播)通常是显存的最大占用者;池化层缩小这些特征图,使后续层不需要保存大尺寸的中间结果。在训练 ResNet-50 时,没池化的等价网络在前 3 个 stage 就可能爆显存;加入池化后总显存减少数倍。设计上常用「在浅层用大步长池化快速降采样」 +「深层用 stride-2 卷积」组合来平衡显存与精度。

Q20. 现代 Transformer(如 ViT、Swin)还需要池化吗?

基本不需要显式池化层:ViT 用固定大小的 Patch Embedding,Swin 用 Patch Merging(类似池化的卷积式降采样),DETR / SegFormer 等用 stride-2 卷积。 ViT 在第一步就把图像切成 16×16 的 Patch,不需要中间池化;Swin Transformer 用 PatchMerging 模块(本质是 2×2 邻域拼接+线性层)实现类似池化的下采样。CNN 时代显式 MaxPool2d 层是「标准做法」,但在 Transformer 时代被 Patch-based 降采样替代。CNN 中仍广泛使用池化的场景:分类头 GAP、检测网络的多尺度特征金字塔、分割解码器的上采样(配 MaxUnpool)。

FAQ 总结

  • 池化本质:无参数的窗口聚合算子,下采样 + 平移鲁棒
  • 两类核心 API:MaxPool2d(取最大)vs. AvgPool2d(取均值)
  • 输出尺寸公式:H_out = floor((H_in + 2p − (k−1) − 1)/s) + 1
  • 自适应池化:AdaptiveAvgPool2d 按输出尺寸自动确定窗口,支持任意输入分辨率
  • 分类头标配:GAP(AdaptiveAvgPool2d((1,1)))+ Flatten + Linear
  • 现代趋势:CNN 中间层用 stride-2 卷积替代池化;Transformer 用 Patch-based 降采样;但分类头 GAP 仍是标配

十、Roadmap 预告

从「卷积步长 + 池化」到「自适应池化 + 全局平均池化」——池化是 CNN 空间下采样的核心机制

本文系统讲解了池化层的工作原理:最大池化(保留最显著响应)、平均池化(保留整体统计水平)、自适应池化(输出固定尺寸)、全局平均池化(替代 flatten 的大全连接层)。理解池化的窗口聚合数学、尺寸公式推导与 PyTorch API 的正确用法,是设计任何现代 CNN(分类 / 检测 / 分割)的基础。

池化是 CNN 下采样的核心机制之一。在掌握了卷积与池化后,下一个值得深入的关键主题:

  • 激活函数的发展演进:ReLU → LeakyReLU → PReLU → GELU → SiLU / Swish,激活函数如何配合卷积与池化决定非线性表达力
  • 归一化层(BatchNorm / LayerNorm / GroupNorm):配合卷积 + 池化的标准模式 BN-ReLU-Conv-Pool,理解为什么 BN 在卷积后、激活前最有效
  • 深度可分离卷积与 MobileNet:用深度卷积 + 逐点卷积替代普通卷积,再用自适应池化压缩特征——移动端 CNN 的标准设计
  • U-Net 分割架构与跳跃连接:池化(编码器下采样)+ MaxUnpool2d / 转置卷积(解码器上采样)+ 跳跃连接,理解分割网络的完整结构

掌握本文的池化基础,你已经具备设计任何 CNN 分类 / 检测 / 分割网络的「下采样模块选择能力」。继续探索激活函数与归一化层,会让你的网络设计更精炼、更高效!


posted @ 2026-07-19 17:57  左扬  阅读(51)  评论(0)    收藏  举报