聊到 CNN 卷积网络很多人会把注意力放在卷积核大小、步长、激活函数这些参数上真正把 padding 的两种方式彻底吃透的人并不多。我第一次意识到 padding 是个大问题是在搭建一个图像分割网络时模型结构看起来完全正常可每次跑完一个 block特征图尺寸就莫名其妙少一圈叠加几次之后下采样路径竟然把边缘信息丢得干干净净最后分割结果糊成一片。排查了半天才发现问题根本不在结构设计而在每一层卷积的 padding 选择上。这篇内容主要围绕卷积网络里的 valid padding 和 same padding 展开说清楚它俩各自解决什么问题、怎么计算、框架里怎么实现、实际选型时该考虑哪些因素。适合正在学习卷积网络原理的初学者也适合自己动手搭模型但总被特征图尺寸问题困扰的开发者。我会结合自己踩过的坑、调试过的模型把这两个概念从数学推导到工程实践完整串一遍。1. Padding 到底在解决什么问题三个我实际遇到的现象1.1 特征图不停“缩水”整个网络结构被悄悄改变先从一个最常见的现象说起。假设输入是一张分辨率为 224×224 的图像卷积核大小是 3×3步长设为 1不加 padding 的话每个卷积层都会把特征图尺寸变成 222×222。你可能会说才缩了 2 个像素算什么大问题可一旦网络有十几层卷积累积效应就很明显20 层下来特征图直接缩到 184×184空间分辨率丢失了接近两成。在很多任务里空间分辨率就是信息量尤其是分割、检测、人脸关键点这类需要精确定位的任务。缩小的不仅是尺寸更意味着图像边缘处的结构被更快地“压扁”高层特征对边缘区域的表达能力被削弱。更隐蔽的副作用是当特征图尺寸在浅层不断变化时后续池化、归一化、跳层拼接都会受到影响。比如你用了全局平均池化做分类那还好但如果你在中间层做多尺度融合或者需要把浅层特征和深层特征拼接尺寸不一致就直接报错或者需要额外加插值层来对齐。这些被迫插入的额外层都会改掉你最初设计的感受野分布。我自己的经验是如果一个网络结构设计出来每一层输出尺寸都在变化模型的内在计算逻辑会变得很难追踪。真正稳定的结构往往在关键路径上保持空间尺寸不变只在明确需要降采样的位置才缩小分辨率。这种“显式降采样”的设计思路远比“每层都无意中缩一点”要清晰得多。而 padding 正是控制这种缩水行为的最直接手段。1.2 边缘像素的话语权被无视中心区域天然占便宜第二个问题更隐蔽也更容易被忽略卷积操作本质上是滑动窗口求和边缘区域的像素参与卷积计算的次数远少于中心区域。以 5×5 输入、3×3 卷积核、无 padding 为例左上角那个像素只出现在第一次卷积窗口中参与计算 1 次而正中心位置的像素会因为窗口滑过它周围的所有可能位置参与计算 9 次左右。你感受一下这个差距同样是图像里的一个像素中心点对最终特征图的贡献是角落点的几倍甚至十几倍。这种“位置不均衡”直接导致卷积网络天然更关注图像中心边缘信息的利用效率极低。如果输入图像的边缘恰好是重要目标比如医学影像中的病灶贴着图像边界或者遥感图像里目标就在切片边缘不加 padding 的训练结果就会非常不稳定同样的目标放在图像中心学习得很好放到边缘就检测不出来。零填充zero padding最直接的作用就是给边缘像素创造额外的“参与机会”。填充了几行零之后原本在角落的像素会重新出现在多个卷积窗口的覆盖范围之内相当于把它从“边缘居民”变成了“普通居民”参与度得到补偿。虽然这个补偿不是完全均匀的但相比完全不填充位置差异性已经大幅缩小。理解这一点比单纯记住“same padding 能保持尺寸”要重要得多因为这才是 padding 在特征层面上的真实价值。1.3 跳层连接和多尺度任务中的对齐问题第三个现象出现在我在做类似编码器-解码器结构的时候。跳跃连接skip connection需要把编码器某一层的特征图和解码器对应层的特征图拼接或相加。在原始设计里我原本期望两侧尺寸完全一致结果因为没有统一 padding 策略编码器某一层输出是 56×56解码器对应层却是 54×54直接在拼接环节报维度错误。除了这种明显的维度错误还有一种更隐蔽的问题即使尺寸一样如果两侧特征边界补零的方式不对称拼接之后也会在边界处留下明显的“接缝”。在做图像生成、超分这类像素级任务时这种接缝经常被网络理解为真实结构导致生成结果出现奇怪的边界纹理。多尺度任务更麻烦。特征金字塔等结构经常需要把不同层级的特征图相互融合一层的尺寸可以在降采样后任意变化但同一分支内部的尺寸一致性是必须保障的。如果每一步卷积都随手用不同的 padding 方式整个金字塔的尺寸关系就会乱成一团。所以我现在搭网络时有一种基本习惯先明确哪些层负责保持尺寸哪些层负责降采样然后统一制定 padding 策略而不是每一层临时决定。2. Valid 与 Same 的本质差异从 5×5 输入一步步推导2.1 输出尺寸公式与三个关键参数不管 padding 怎么设置卷积输出的尺寸都可以用一条统一公式表达输出尺寸 floor((输入尺寸 2×padding − 卷积核尺寸) / 步长) 1这里的 padding 指每侧补零的圈数。三个关键参数是卷积核尺寸 k、步长 s、padding 圈数 p。理解这个公式之后很多“为什么我算出来的尺寸和框架里报错不一样”的问题都能迎刃而解。需要注意一点这里用的 padding 是“单侧补零圈数”。比如 padding1意思是上下左右各补一圈零那么输入尺寸在公式里就加 2。不同的开源框架对 padding 的定义有时不太一样有些参数直接传总补零量有些传单侧量这个我在第 3 部分会展开讲。2.2 Valid Padding不做处理让特征自由缩减Valid padding 就是 p0不添加任何额外像素卷积窗口只在原始输入范围内滑动。也正因为窗口无法覆盖到边界之外的区域输出尺寸一定会小于或等于输入尺寸。还是拿 5×5 输入、3×3 卷积核、步长 1 来算输出尺寸 (5 0 − 3) / 1 1 3。输出是 3×3 特征图。如果换成 7×7 输入输出就是 (7 − 3) 1 5。规律很直观每经过一层 valid 卷积尺寸就减少 k−1。Valid padding 的立场是我只在真实存在的像素上做计算不引入任何人为构造的虚拟像素。它适合那些希望特征图尺寸逐层收缩或者边界信息本身可信度不高的场景。做某些关键点检测时如果图像边缘存在大量背景干扰直接用 valid 卷积把边缘区域逐渐丢弃反而能减少无效信息对网络的影响。代价是特征图尺寸会越来越小高层特征的感受野虽然变大空间分辨率却变低。2.3 Same Padding让输出尺寸与输入持平Same padding 的目标是在步长为 1 的前提下让输出特征图尺寸和输入保持一致。要达到这个效果通常取 p (k − 1) / 2也就是让卷积核覆盖范围在输入边界外侧各多出半个核的大小。依旧用 5×5 输入、3×3 卷积核、步长 1p (3 − 1) / 2 1。输出尺寸 (5 2×1 − 3) / 1 1 5。输入输出都是 5×5尺寸保持不变。换成 5×5 卷积核时p 2输入输出同样保持 5×5。这里有一个常见误区很多人以为只要 padding 层数够多任何卷积核都能“保住尺寸”。实际上关键在于补零量要和卷积核尺寸协同奇数卷积核配合 p(k−1)/2 是最自然的选择因为左右、上下可以对称分配。像 3×3 配 p1、5×5 配 p2、7×7 配 p3都是标准搭配。如果卷积核是偶数比如 4×4要让输出和输入一致就需要在一边补 2 个零、另一边补 1 个零具体哪边多补由框架内部决定。这种不对称补零虽然一般不影响数值结果却会让特征图在空间上产生一个微弱的偏移虽然神经网络可以学到这种偏移的补偿但在高精度定位任务里仍然建议尽量避开偶数卷积核。2.4 步长不为 1 时Same 的含义开始模糊严格来说same padding 的定义在步长为 1 时最清晰输出等于输入。可当步长变成 2输出尺寸就不再可能等于输入尺寸因为输出大致变成输入的一半。这种情况下“same”实际指的是“输出 输入 / 步长”也就是和“不补零但步长为 2”的输出结果相比保持相同的缩放关系。举例输入 5×5卷积核 3×3步长 2。valid 卷积的输出是 (5 − 3) / 2 1 2也就是 2×2。如果用 p1 做 padding输出是 (5 2 − 3) / 2 1 3也就是 3×3。两种结果差了整整一圈。如果你在框架里用 paddingsame底层会自动算出一组补零量确保输出尺寸等于 ceil(输入尺寸 / 步长)。理解这个行为比背结论更重要因为很多人在做步长为 2 的降采样时对 padding 的预期往往和实际不符。2.5 两种方式的快速对照表对比维度Valid PaddingSame Padding补零操作不补零p0补零p 根据卷积核大小确定输出尺寸步长 1输入 − k 1等于输入边缘信息利用直接丢弃边缘区域像素通过补零保留边缘参与度计算量更小略大但差距通常可忽略感受野增长速度每层增长更快每层增长稍慢典型使用场景边界干扰大、希望特征收缩分割检测、跳层结构、保持尺寸稳定这张表不是让你死记硬背而是帮助你在设计网络时形成明确判断需要保持空间尺寸、做密集预测时优先 same希望特征图逐步收缩、或者输入边界不需要太多关注时可以用 valid。没有绝对的好坏只有适不适合当前任务。3. 框架实现里的细节PyTorch 与 TensorFlow 的规则并不完全一致3.1 PyTorch 的 padding 参数和 padding_modePyTorch 的 Conv2d 接口里padding 参数是整数或元组默认是 0对应的就是 valid padding。设成 1 表示上下左右各补一圈。还有一个重要参数 padding_mode默认是 zeros也就是补零也可以设置成 reflect、replicate、circular。reflect 模式是镜像填充边缘像素值会被翻折到边界外侧replicate 是复制边缘像素值circular 是循环填充。三种非零模式在图像生成类任务里偶尔会用到尤其是 CCD 类图像修复任务中复制边缘比补零造成的伪影更少。但我必须提醒一句大多数主流卷积网络仍然使用默认的零填充不是因为其他模式完全没用而是因为零填充配合 BN 等归一化层时数值行为最稳定、最可控。边缘的零值在卷积计算过程中会被当作“无信息”处理不会引入额外的梯度信号。PyTorch 里没有直接类似 paddingsame 的字符串参数你需要自己计算 p。所以在 PyTorch 中写网络时我通常定义一个函数根据卷积核尺寸和步长自动算 pimport torch.nn as nn def conv3x3_basic(in_channels, out_channels, stride1): # 保证步长为1时3x3卷积输出尺寸不缩水 return nn.Conv2d(in_channels, out_channels, kernel_size3, stridestride, padding1, biasFalse)这种方式简单直接3×3 卷积配 padding1 已经成为我在 PyTorch 里最常用的默认设置。3.2 TensorFlow/Keras 的 Same 特殊行为TensorFlow/Keras 的 Conv2D 接受 paddingvalid 或 paddingsame。表面上只是字符串选择底层却藏着一个容易踩坑的细节当步长大于 1 时paddingsame 会自动决定在哪一侧多补像素。比如输入尺寸 5、步长 2、卷积核 3为了让输出是 ceil(5/2)3框架会把总补零量分成两部分通常额外的一列会补在右侧额外的一行补在下侧。这种“右下偏好”在大多数任务中没有影响但在需要严格对齐坐标的任务里可能会造成一个像素级别的偏移。TensorFlow 内部计算补零量的方法是需要的总 padding (步长 − 1) × 输入尺寸 卷积核尺寸 − 步长然后按“顶部 总 padding // 2底部 总 padding − 顶部”的方式分配。我做跨框架迁移时踩过这个坑同一份模型结构和权重从 Keras 换到 PyTorch因为 padding 分配策略不同输出结果有几处像素误差。所以如果要在不同框架之间对齐模型不能只盯着“same”这个名字最好打印每一层实际输出尺寸逐层比较。3.3 我用来做形状自检的最小代码片段无论是哪个框架我写好网络后的第一件事永远是跑一段“形状自检”代码。给一个假输入看每一层输出尺寸是否符合预期。这个习惯帮我发现了大量结构错误而且比肉眼查网络定义高效得多。import torch def shape_check(model, input_size(1, 3, 224, 224)): model.eval() with torch.no_grad(): x torch.randn(*input_size) for name, layer in model.named_children(): x layer(x) print(f{name:30s} - {tuple(x.shape)})这里只列模型顶层子层如果你需要逐层打印更细的信息可以用支持嵌套模块遍历的统计工具。重点不是工具本身而是形成“写网络必查形状”的条件反射。很多 padding 错误在形状打印面前会原形毕露。3.4 padding 对参数量和计算量的真实影响很多人担心加了 padding 会让模型参数量显著增加——其实不对。padding 本质是给输入特征图边缘补像素它不改变卷积核本身的参数量也不改变权重个数。参数量只由输入通道数、输出通道数和卷积核尺寸决定和 padding 没有直接关系。计算量确实会略有增加因为输入尺寸变大了卷积窗口滑动的次数变多。以 3×3 卷积为例same padding 比 valid padding 单层计算量大约多出 (输入尺寸2)² / (输入尺寸−2)² 倍输入尺寸越大这一比例越接近 1。当输入是 224×224 时增加约 4%当输入是 32×32 时增加约 25%。小分辨率特征图上的 padding 计算量影响更明显但相比之下这点开销通常远小于模型容量的灵活性收益。设计网络时我一般不会因为这一点点计算量差异而放弃 same padding真正需要考虑计算预算的场合优先从通道数和卷积核大小上做文章。4. 选择 Valid 还是 Same不止是“保不保尺寸”那么简单4.1 什么时候该用 Valid设计上就要舍弃边界不是所有任务都需要完整保留边缘信息。比如一些图像分类网络输入图像的边缘往往是最不稳定的区域一个目标物偶尔贴着边偶尔完全在取景框内部边缘像素在不同样本之间的相关性极低。这种情况下用 valid 卷积逐步丢弃边缘区域反而能提高模型对目标主体位置的鲁棒性。另外如果你使用的输入本身已经经过了严格的预处理比如已经将目标居中、裁剪到固定大小边缘区域包含的有效信息很低那么 valid padding 就是一种合理的设计。它相当于告诉模型你不需要花费额外的计算资源去理解边界那一圈像素之间的关系。我还见过一种用法在模型的第一层就用 valid 卷积做一个简单的边缘平滑把边界区域收紧后续层全部使用 same padding 保持尺寸稳定。这种做法适用于输入分辨率较高、而目标区域集中在中部的场景。要注意的是这种策略会让模型的输出尺寸缩小对于分类任务无所谓但对于分割任务最终特征图还要上采样回原分辨率多一套对齐逻辑并不划算。4.2 什么时候该用 Same保持空间结构、配合跳层连接Same padding 最大的价值在于维持空间结构。分割、检测、关键点定位这类任务输出就是和输入逐像素对齐的密集预测如果每一层都随意缩尺寸最后就得靠多次上采样恢复而每次上采样都伴随信息损失。更实际的理由是跳层连接。在编码器-解码器结构里编码器第 n 层和解码器第 n 层往往需要拼接。如果编码器各层用 same padding 保持尺寸就可以在降采样发生时明确记录尺寸变化点和解码器上采样结构精确对齐省去大量尺寸匹配的麻烦。我在做分割网络时整个骨干网络几乎完全采用 3×3 padding1 步长控制降采样的策略结构清晰、对齐简单输出尺寸的推导一目了然。4.3 池化与感受野padding 对高层特征的间接影响池化层本身不涉及 padding但池化之前的卷积层如果用了 valid padding特征图缩小后池化窗口覆盖的范围也在改变进而影响有效感受野。举一个例子两层 3×3 valid 卷积之后接一个 2×2 池化感受野是 6×6两层 3×3 same 卷积之后接一个 2×2 池化感受野同样是 6×6但第二种情况下特征图尺寸更大池化之前保留的中间特征更多。表面看感受野一样但在边界区域valid 卷积已经把边缘信息提前丢弃边界处的感受野并不完整same 卷积却通过补零让边缘处也拥有完整感受野。这个差异在深层特征上会持续放大最终影响模型对边缘小目标的敏感度。如果你做的是小目标检测尤其是目标经常出现在图像边缘的任务尽量用 same padding 保持特征完整。如果是做整体图像分类类别主要由中心大物体决定那 valid 造成的边缘信息损失通常无伤大雅。4.4 一个实用框架先用“不缩水”跑通再做边界取舍我个人的设计流程是这样的先在所有卷积层统一使用 same padding把网络结构跑通确认逻辑无误然后分析任务特性如果确实有理由减少边缘信息比如输入边界是纯背景再把对应层的 padding 改成 valid同时观察输出尺寸变化对下游结构的影响。先把网络做对再把它调好这个顺序能显著减少因 padding 导致的低级错误。反过来如果一开始就在各层混用 valid 和 same结构出错时你会很难判断问题是出在 padding 选择还是出在网络的其他逻辑部分。统一默认值再定点调优是实践中效率最高的做法。5. 边界假象和大坑反卷积、注意力、检测头里的 padding 问题5.1 转置卷积的“边缘重叠加法”和 output_padding很多人以为 padding 只在普通卷积里有讲究做转置卷积反卷积时同样会遇到边界问题只不过表现方式不同。转置卷积会把输入特征图的每个像素扩展成一个局部区域相邻像素的扩展区域会相互重叠重叠部分做累加。如果 padding 设置不当边缘区域的累加次数会明显多于中心区域产生类似“棋盘格伪影”的纹路尤其是在图像生成任务里非常明显。为了解决这个问题转置卷积提供了一个 output_padding 参数它的作用是调整输出一侧的额外补零量让尺寸刚好能对上普通卷积的逆过程。注意 output_padding 并不是在输出上补零而是增加输出的行数和列数通常取值是 0 或 1。在编码器-解码器结构里我怎么判断 output_padding 该取多少呢最简单的办法就是先用形状公式推一遍输出尺寸 (输入 − 1) × 步长 − 2×padding 卷积核尺寸 output_padding。把公式凑到和目标尺寸一致output_padding 就出来了。5.2 注意力机制会放大位置偏见近几年 transformer 类结构越来越常见很多人喜欢在卷积特征图上叠加注意力模块。注意力模块通常会对所有空间位置计算相关权重但卷积层输出的特征图如果因为 padding 而带有位置偏置注意力模块会放大这种偏置。我做过一个小实验对同一个图片分别用 valid 和 same 卷积提取特征然后接同一个全局注意力模块。结果 same 卷积的特征经过注意力后注意力的分布更均匀valid 卷积的特征则在中心区域出现明显的注意力集中。原因不难理解valid 卷积让中心像素在特征里更“强壮”注意力机制会进一步把这些强响应点的权重拉高。在目标检测、跟踪这些需要同时关注多个位置的场景里这种偏置会伤害模型表现。如果你发现自己的注意力模块总是关注固定位置除了检查数据也值得检查一下前面卷积层的 padding 策略。5.3 检测头里的中心偏置检测任务里还有一种常见现象模型对图像中心目标的检测效果明显好于边缘目标。排除数据集本身中心分布偏多的情况之后padding 的“中心偏置”是重要嫌疑。即便使用了 same padding零填充也会让边缘区域的信息“纯度”下降边缘自然像素旁边多了一圈零卷积计算时会把零值顺带融入特征相当于给边缘特征引入了一种特殊的污染。这一点在目标较小、上下文依赖较强的检测场景下尤其突出。缓解方法有几个一是在输入层面做随机裁剪、平移增强让目标出现在不同位置迫使网络学会位置无关的特征二是在骨干网络后期适当使用 valid 卷积丢弃一部分边缘特征牺牲边缘但换回中心的稳定性三是使用可学习的 padding 方式比如用反射填充替代零填充减少零值引入的伪信息。这些方法没有绝对优劣但意识到“边缘位置天然吃亏”这个事实是调试检测模型的前提。6. 我的调试习惯怎样快速判断 padding 没有选错6.1 形状日志每层都打印不合预期立刻停前面提过形状自检这里再补充一个更细的实践。我把所有会改变特征图尺寸的操作统一封装卷积、池化、步长、上采样都在一个函数里完成形状打印。开发阶段这个函数会输出每一层的输入输出尺寸和当前有效感受野估算值。一旦某层输出尺寸偏离预期立刻就能定位是哪一层、由哪个参数导致而不是等整个模型跑完报一个维度错误再回头翻半天代码。日志里我最关注三个量尺寸是否按预期降采样、边缘是否因为 valid 卷积被异常截断、同一条分支上同一分辨率的层是否保持一致。如果这三个量都正常padding 基本就不会出问题。6.2 边界响应检查用“角落激活”测试验证 padding 效果想更直观地验证 padding 对边缘的影响我会构造一张特殊输入只有一个像素点有值其他全部为零。把这个点分别放在左上角、中心、右下角跑一遍网络观察特征图输出中哪些位置有强响应。如果使用 valid padding放左上角时响应可能会在某些层消失如果使用 same padding不同位置产生的响应强度会更接近。这种测试虽然不能直接量化模型的精度但能帮你迅速理解当前 padding 策略对空间信息的保留程度比读公式直观得多。6.3 用同一模型不同 padding 做消融实验最后一种方法最朴素也最有说服力在验证集上把模型分别设置为 valid 和 same padding各训练一轮或几轮观察指标差异。很多初学者怕麻烦总想从理论上推导出最优 padding。但 padding 的最优选择和数据集分布、任务目标、网络深度都有关理论推导只能给出方向最终抉择还是得交给实验。我在做消融实验时会把控制变量做到极致只切换 padding 参数学习率、数据增强、训练轮数完全一致然后记录验证集上不同区域的指标。比如分割任务里我会按距离图像边界远近划分几个区域分别看 IoU。如果边缘区域指标在 same padding 下明显更好那这个选择就值得保留。如果相差不大优先用更简单的 valid减少计算量。最后补充一个和 padding 相关的操作细节在搭建网络时我习惯先在草稿本上画出整个网络的尺寸变化表每一层后面注明输入尺寸、输出尺寸、padding 圈数、步长。这个过程看起来很笨但实际帮我避开了大量维度错误尤其是做多分支结构时这张表就是网络设计的地图。等把尺寸变化表画顺了再写代码基本一遍过。如果你也被特征图尺寸问题折腾过我建议你先试试这个笨办法。
阅读完成 · 觉得有帮助?