别再死记硬背公式了!用PyTorch/TensorFlow代码直观理解CNN的stride与padding
2026/4/23 19:45:33 网站建设 项目流程

用代码实验理解CNN的stride与padding:告别公式恐惧的实战指南

刚接触卷积神经网络时,那些关于特征图尺寸计算的公式总让人头疼。与其死记硬背(W-F+2P)/S + 1这样的数学表达式,不如直接打开Python编辑器,用几行代码观察不同参数下张量的实际变化。这种"所见即所得"的学习方式,往往比抽象推导更能建立直观理解。

1. 环境准备与基础概念

在开始实验前,我们先快速回顾几个核心概念:

  • Stride(步幅):卷积核在输入图像上滑动的步长。步幅为1时逐个像素移动,步幅为2则每次跳过1个像素。
  • Padding(填充):在输入图像边缘添加的像素层(通常用0填充),用于控制输出尺寸。
  • Channel(通道):输入数据的深度维度。RGB图像有3个通道,而卷积层的每个滤波器会产生一个新的通道。

准备好你的Python环境,我们需要以下工具包:

import torch import torch.nn as nn import tensorflow as tf

提示:本文同时提供PyTorch和TensorFlow两种实现,读者可任选熟悉的框架进行实验。

2. PyTorch实战:动态观察参数影响

让我们从PyTorch开始,创建一个简单的卷积层并观察其行为:

# 创建一个输入张量 (batch_size=1, channels=1, height=5, width=5) input_tensor = torch.ones(1, 1, 5, 5) # 定义不同参数的卷积层 conv_stride1 = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=0) conv_stride2 = nn.Conv2d(1, 1, 3, stride=2, padding=0) conv_padding1 = nn.Conv2d(1, 1, 3, stride=1, padding=1) # 打印输出尺寸 print(f"Stride=1, Padding=0: {conv_stride1(input_tensor).shape}") print(f"Stride=2, Padding=0: {conv_stride2(input_tensor).shape}") print(f"Stride=1, Padding=1: {conv_padding1(input_tensor).shape}")

运行这段代码,你会看到三种不同参数组合的输出尺寸:

参数组合输出尺寸 (H×W)
stride=1, padding=03×3
stride=2, padding=02×2
stride=1, padding=15×5

这个简单的实验已经揭示了几个关键规律:

  1. 无padding时,卷积核(kernel)会"吃掉"边缘。3×3的kernel会使每边减少1像素,所以5×5输入变成3×3输出
  2. 增大stride相当于"跳着采样",会显著缩小输出尺寸
  3. 适当padding可以保持输入输出尺寸相同(当stride=1时)

3. TensorFlow验证:更复杂的参数组合

为了加深理解,我们在TensorFlow中尝试更复杂的参数组合:

# 创建输入张量 (batch, height, width, channels) inputs = tf.ones((1, 6, 6, 1)) # 不同参数组合的卷积层 conv1 = tf.keras.layers.Conv2D(filters=1, kernel_size=3, strides=1, padding='valid') conv2 = tf.keras.layers.Conv2D(1, 3, strides=2, padding='same') conv3 = tf.keras.layers.Conv2D(1, 3, strides=1, padding='same') # 查看输出形状 print("Valid padding, stride=1:", conv1(inputs).shape) print("Same padding, stride=2:", conv2(inputs).shape) print("Same padding, stride=1:", conv3(inputs).shape)

观察到的输出:

  • Valid padding (即padding=0), stride=1 → 4×4
  • Same padding, stride=2 → 3×3
  • Same padding, stride=1 → 6×6

这里有几个值得注意的细节:

  1. TensorFlow的padding='same'会自动计算需要的填充量,保持输出尺寸与输入尺寸的比例关系
  2. 当stride=2时,samepadding会确保输出尺寸是输入尺寸的一半(向上取整)
  3. 实际项目中推荐使用samevalid这种语义化参数,而非硬编码数字

4. 通道(Channel)的维度变化

理解了空间维度后,我们来看看通道维度的变化规律。创建一个多通道的卷积实验:

# 输入:3通道的5×5图像 multi_input = torch.ones(1, 3, 5, 5) # 卷积层:3输入通道,2个滤波器 multi_conv = nn.Conv2d(in_channels=3, out_channels=2, kernel_size=3, stride=1, padding=1) output = multi_conv(multi_input) print("输出形状:", output.shape) # 输出:torch.Size([1, 2, 5, 5])

关键发现:

  • 输入通道数必须与卷积核的通道数匹配(这里都是3)
  • 输出通道数由滤波器的数量决定(这里设置了2个滤波器)
  • 空间尺寸受padding影响(这里padding=1保持了5×5)

5. 边界情况与常见陷阱

通过代码实验,我们可以发现一些容易混淆的情况:

# 特殊情况:kernel_size=1 conv_k1 = nn.Conv2d(1, 1, kernel_size=1, stride=2, padding=0) print("1×1 kernel, stride=2:", conv_k1(input_tensor).shape) # 输出:3×3 # 不对称stride conv_asym = nn.Conv2d(1, 1, kernel_size=3, stride=(1,2), padding=0) print("不对称stride:", conv_asym(input_tensor).shape) # 输出:3×2

常见误区:

  1. 1×1卷积的特殊性:它不进行空间混合,只做通道变换
  2. 非对称参数:stride和padding可以分别是(height, width)的元组
  3. 小数结果的处理:当计算出的尺寸不是整数时,不同框架可能有不同的舍入方式

注意:实际项目中建议保持对称参数,除非有特殊需求。非对称参数可能导致难以调试的维度问题。

6. 可视化理解:代码辅助的尺寸计算

为了彻底掌握这些概念,我们编写一个辅助函数来预测输出尺寸:

def calc_output_size(input_size, kernel_size, stride, padding): return (input_size - kernel_size + 2 * padding) // stride + 1 # 测试我们之前的例子 print(calc_output_size(5, 3, 1, 0)) # 输出:3 print(calc_output_size(5, 3, 2, 0)) # 输出:2 print(calc_output_size(5, 3, 1, 1)) # 输出:5

现在你可以:

  1. 先用这个函数计算预期输出尺寸
  2. 然后创建实际的卷积层验证结果
  3. 当两者不一致时,深入思考原因

这种"预测-验证"的学习循环能有效强化理解。

7. 实际应用技巧

基于这些实验,总结几个实用技巧:

  • 保持尺寸的技巧

    • 当stride=1时,设置padding=(kernel_size-1)/2可保持尺寸
    • 因此常见的kernel_size都是奇数(3,5,7等)
  • 下采样的选择

    • 用stride=2的卷积同时实现特征提取和下采样
    • 比先卷积再池化更高效
  • 通道数的设置

    • 通常逐层增加通道数,减少空间尺寸
    • 常见模式:(通道数:64→128→256, 尺寸:224→112→56→28)
# 一个典型的CNN块示例 typical_block = nn.Sequential( nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1), nn.BatchNorm2d(128), nn.ReLU() )

8. 扩展到其他操作

同样的实验方法可以应用于其他CNN操作:

# 转置卷积的输出尺寸 deconv = nn.ConvTranspose2d(1, 1, kernel_size=3, stride=2, padding=1) print(deconv(input_tensor).shape) # 输出:9×9 # 空洞卷积 dilated_conv = nn.Conv2d(1, 1, kernel_size=3, stride=1, padding=2, dilation=2) print(dilated_conv(input_tensor).shape) # 输出:5×5

发现规律:

  • 转置卷积:可以增大空间尺寸(常用于生成或上采样)
  • 空洞卷积:增大感受野而不增加参数(通过间隔采样)

9. 调试维度问题的工具箱

当在实际项目中遇到维度不匹配时,这套方法能快速定位问题:

  1. 打印每一层的输入输出形状
  2. calc_output_size函数验证预期
  3. 检查参数是否对称
  4. 特别注意stride>1时的尺寸变化
# 调试示例 problematic_net = nn.Sequential( nn.Conv2d(3, 16, 5, stride=2, padding=1), nn.Conv2d(16, 32, 3, stride=1, padding=1), nn.Conv2d(32, 64, 3, stride=2, padding=0) ) x = torch.rand(1, 3, 32, 32) for layer in problematic_net: x = layer(x) print(x.shape)

输出会显示每一层后的尺寸变化,帮助定位哪一层的参数设置有问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询