我们用生活化比喻 + 直观图解 + 简单代码 + 参数详解,向初学者彻底讲清楚 PyTorch 中最常用的两个图像处理层:
✅
nn.Conv2d—— 卷积层(特征提取器)
✅nn.MaxPool2d—— 最大池化层(降维压缩器)
🎯 一句话总览:
Conv2d像“扫描仪+特征探测器”,从图像中提取边缘、纹理等特征;
MaxPool2d像“压缩器”,保留最强特征,缩小图像尺寸,减少计算量。
它们是构建 CNN(卷积神经网络)的基石!
一、预备知识:图像在计算机中长什么样?
一张彩色图像 = 一个 3D 张量 → 形状 [C, H, W]
C= 通道数(Channel)→ RGB 彩色图 = 3,灰度图 = 1H= 高度(Height)W= 宽度(Width)
📌 示例:一张 224x224 的 RGB 图片 → torch.Size([3, 224, 224])
🧱 1. nn.Conv2d —— 卷积层(Convolution Layer)
🧩 生活化比喻:
想象你拿着一个“小放大镜”(卷积核),在图像上从左到右、从上到下滑动扫描。每扫到一个位置,就把放大镜下的局部区域和放大镜上的“探测模板”做匹配,输出一个“匹配强度值”。
👉 多个放大镜 → 提取不同特征(边缘、角点、纹理…)
👉 输出 → 一堆“特征图”(Feature Maps)
📐 核心参数详解:
torch.nn.Conv2d(
in_channels, # 输入通道数(如 RGB=3)
out_channels, # 输出通道数(你想要多少个“放大镜”=多少个特征图)
kernel_size, # 卷积核大小(如 3x3, 5x5)
stride=1, # 步长:放大镜每次移动几步
padding=0, # 填充:在图像边缘补0,控制输出大小
bias=True # 是否加偏置
)
🖼️ 图解卷积过程(以 5x5 图像,3x3 卷积核,padding=0, stride=1 为例):
输入图像 (1通道):
[[1 2 3 4 5]
[6 7 8 9 0]
[1 2 3 4 5]
[6 7 8 9 0]
[1 2 3 4 5]]
卷积核 (3x3):
[[1 0 -1]
[1 0 -1]
[1 0 -1]]
👉 滑动计算 → 输出 3x3 特征图
🧑💻 代码示例:
import torch
import torch.nn as nn
# 创建一个卷积层:输入3通道(RGB),输出16个特征图,卷积核3x3
conv = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1, padding=1)
# 模拟输入:batch=2, 3通道, 32x32 图像
x = torch.randn(2, 3, 32, 32)
# 前向计算
out = conv(x)
print(out.shape) # torch.Size([2, 16, 32, 32])
📌 输出尺寸计算公式(H 和 W 相同):
输出尺寸 = (输入尺寸 + 2 * padding - kernel_size) / stride + 1
✅ 上例:(32 + 2*1 - 3)/1 + 1 = 32 → 尺寸不变!
💡 为什么用 padding=1?
- 保持输入输出尺寸一致(常见于 ResNet 等结构)
- 避免边缘信息丢失
🎨 卷积层的作用:
- 提取局部特征(边缘、纹理、形状)
- 参数共享 → 减少参数量
- 平移不变性 → 物体在图中移动,仍能识别
🧱 2. nn.MaxPool2d —— 最大池化层(Max Pooling Layer)
🧩 生活化比喻:
想象你把图像划分成 2x2 的小格子,在每个格子里只保留最大的那个像素值,然后把整个格子“压缩”成一个点。
👉 效果:图像尺寸缩小,保留最显著特征,去除冗余,抗微小平移。
📐 核心参数:
torch.nn.MaxPool2d(
kernel_size, # 池化窗口大小(常用 2 或 3)
stride=None, # 步长(默认 = kernel_size)
padding=0 # 填充
)
✅ 最常用:
MaxPool2d(2)→ 2x2 窗口,步长=2 → 图像长宽减半
🖼️ 图解最大池化(2x2 窗口,stride=2):
输入特征图:
[[1 3 2 1]
[4 2 1 0]
[1 5 3 2]
[2 1 0 1]]
划分 2x2 区块:
区块1: [[1 3] → max=4
[4 2]]
区块2: [[2 1] → max=2
[1 0]]
区块3: [[1 5] → max=5
[2 1]]
区块4: [[3 2] → max=3
[0 1]]
输出:
[[4 2]
[5 3]] → 尺寸减半!
🧑💻 代码示例:
pool = nn.MaxPool2d(kernel_size=2, stride=2) # 常用配置
# 输入:batch=2, 16通道, 32x32
x = torch.randn(2, 16, 32, 32)
out = pool(x)
print(out.shape) # torch.Size([2, 16, 16, 16]) ← 长宽减半!
💡 池化层的作用:
- 降维 → 减少后续计算量
- 防止过拟合 → 丢弃部分细节
- 增强平移不变性 → 物体稍微移动,池化后结果变化不大
🏗️ 组合使用:构建一个简单 CNN
import torch
import torch.nn as nn
class SimpleCNN(nn.Module):
def __init__(self):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=3, padding=1), # 32x32 → 32x32
nn.ReLU(),
nn.MaxPool2d(2), # 32x32 → 16x16
nn.Conv2d(32, 64, kernel_size=3, padding=1), # 16x16 → 16x16
nn.ReLU(),
nn.MaxPool2d(2), # 16x16 → 8x8
nn.Conv2d(64, 128, kernel_size=3, padding=1),# 8x8 → 8x8
nn.ReLU(),
nn.AdaptiveAvgPool2d(1) # 8x8 → 1x1
)
self.classifier = nn.Linear(128, 10) # 分10类
def forward(self, x):
x = self.features(x)
x = x.view(x.size(0), -1) # 展平: [batch, 128, 1, 1] → [batch, 128]
x = self.classifier(x)
return x
model = SimpleCNN()
x = torch.randn(4, 3, 32, 32)
out = model(x)
print(out.shape) # torch.Size([4, 10])
🆚 对比总结:
| 层 | 作用 | 是否有可学习参数 | 输出尺寸变化 | 常用配置 |
|---|---|---|---|---|
Conv2d | 提取局部特征 | ✅ 有权重和偏置 | 可控(靠padding) | kernel=3, padding=1 |
MaxPool2d | 降维、保留最强特征 | ❌ 无参数 | 通常减半 | kernel=2, stride=2 |
🧠 给初学者的贴心提示:
- ✅ 卷积层是“智能特征提取器”,池化层是“压缩降噪器”
- ✅
padding=1, kernel_size=3是黄金搭档 → 保持尺寸不变 - ✅
MaxPool2d(2)是最常用池化 → 尺寸减半,计算量降为1/4 - ✅ 卷积后通常接
ReLU()激活函数 → 增加非线性 - ✅ 调试时打印
.shape看每层输出尺寸是否符合预期!
🎁 小挑战(巩固理解):
假设输入图像尺寸是 [3, 64, 64],经过以下层:
nn.Conv2d(3, 16, 5, stride=2, padding=2)
nn.MaxPool2d(3, stride=2, padding=1)
👉 输出尺寸是多少?
解答:
-
卷积后:H = (64 + 2*2 - 5)/2 + 1 = (64+4-5)/2+1 = 63/2+1 = 31.5+1 → ❌ 不行!
→ 实际 PyTorch 会向下取整 →(64+4-5)//2 + 1 = 31 + 1 = 32 -
池化后:H = (32 + 2*1 - 3)/2 + 1 = (32+2-3)/2 +1 = 31/2 +1 = 15.5+1 → 向下取整 = 15+1 = 16
✅ 最终输出:[16, 16, 16]
💡 公式记住:
output = floor( (input + 2*pad - kernel) / stride ) + 1
🎉 恭喜你!现在你已经掌握了 CNN 最核心的两个构建块!