小林新开了一家手工印刷社,最拿手的是用一枚木印章在纸上印出整齐的图案。这天,老客户送来一叠 8 厘米见方的卡片纸,要求印满印章花纹——每枚章 3 厘米见方。 小林犯了难:印完一枚,下一枚该挪多远?挪得近,图案叠在一起密密麻麻;挪得远,稀疏留白不好看。更要命的是,纸的边缘总差半枚章的位置,强行印就会把图案印出边界。 旁边的老师傅笑了:“这不跟电脑里的卷积一个道理嘛!印章每次挪多远叫步长,边缘垫一圈废纸叫填充。”小林听得一头雾水,老师傅便拿起笔,在纸上画了起来…… 这篇文章要讲的就是这两件事:步长与填充。它们决定了卷积核每次滑多远、边缘要不要“垫纸”,最终决定了输出特征图有多大。理解了它们,你就掌握了 CNN 架构中最基础也最常被问到的尺寸控制本领。
如果你想设计或调试卷积神经网络,步长与填充是你最先要学会的两个“旋钮”——它们直接决定每层特征图的尺寸与计算开销,是架构师的基本功。
可视化演示
展示卷积运算中步长和填充如何控制输出特征图尺寸,通过动画演示卷积核滑动过程及输出尺寸计算公式。
本节配有交互动效(进入视口后自动加载,约 3KB)。
'+captions.conv+'
步长核心概念
老师傅拿起印章,在卡片纸上“啪”地盖了一个,然后说:“看好了,我现在每挪 1 厘米盖一枚——这叫步长为 1。如果想省事,每挪 2 厘米盖一枚——盖章数量是不是一下就少了将近一半?这个'每次挪多远',在卷积神经网络里就是步长(stride)。”
小林恍然大悟。他发现步长不仅决定盖章的疏密,还决定了整个图案的“分辨率”:步长为 1 时,每个位置都被照顾到,输出特征图最大;步长为 2 时,每隔一格才盖一次,输出尺寸近似减半,这就是下采样。在卷积网络里,步长 > 1 的卷积天然承担了缩小特征图、扩大感受野的任务,很多现代网络干脆用它替代池化层。
在数学上,当没有填充时,输出尺寸由核大小、输入尺寸和步长共同决定:
小林在本子上记下:输入 8、核 3、步长 2,输出就是 ⌊(8−3)/2⌋+1 = 3。他再看动画演示,果然右侧只有 3×3 个位置被盖到了章。步长这个“旋钮”,第一次转起来。
- 有效移动范围分子 $W_{\text{in}} - K$ 表示印章中心(或者左上角)可以活动的区间——纸长减去章宽,剩下的一段“可移动走廊”。
- 可走步数除以步长 $S$,算出在这段走廊里按固定步幅能迈出多少步。
- 向下取整如果最后一步迈出去剩了半格,整步数只能舍弃小数部分,所以用 $\lfloor \cdot \rfloor$。
- 起点补一因为第一步不算“移动”,而是直接盖在起始位置,所以要加 1。
填充设计动机
小林正得意,老师傅却指了指纸的边缘:“你发现没有?前面咱光顾着数完整盖章数,可纸边上总差半枚章的位置。硬往边上挪,章就悬空了,花纹印不全。”小林一摸脑袋:“那怎么办?”“垫纸。”老师傅说着,在卡片纸四周垫了一圈废纸,“垫完之后,章在边缘也能完整落下来。这个'垫纸'的动作,在卷积里就叫填充(padding)。”
不填充的卷积层会有一个隐蔽问题:边缘信息损失。输入图像正中间的像素会被卷积核反复覆盖,参与很多次运算;而最外圈的像素只能被核的某一部分碰到,参与次数少得多。如果网络层数很深,每一层都缩一圈(比如 8→6→4→2),边缘信息很快就消失了,特征图也越来越小,最终无法继续堆叠网络。
填充的解法很朴素:在输入的外围补一圈值(通常补 0,所以也叫零填充),让卷积核滑到边缘时仍然有“纸”可盖,边缘像素参与运算的次数因此大幅提升。那么要补多少才合适?如果希望步长为 1 时输出尺寸与输入保持一致,填充量有一个参考值:
小林在废纸堆里翻出厚厚一沓“垫纸”,心里默默算着:3×3 的章垫 1 圈,5×5 的章垫 2 圈。难怪老师傅的印刷社从不用小尺寸的纸——因为边缘信息是宝贝,垫纸能把它捡回来。
- 核的多余宽度核边长为 $K$,它比“中心那一格”多出来 $K-1$ 的跨度,这是需要补偿的边距。
- 两侧平分将多余跨度除以 2,左右(或上下)各分一半,即为单侧填充量。
- 核大小代入例如 $K=3$ 时 $P=1$,只需在四边各补一圈零即可保住边缘。
填充模式分类
老师傅看小林学得快,又拿出三张设计稿:“垫纸也有三种垫法。”第一张叫 Valid,完全不垫——章在边缘悬空也没关系,只保留完整盖章数,输出会缩小;第二张叫 Same,垫到恰到好处——“Same“的意思就是步长为 1 时,输出尺寸和输入一模一样;第三张叫 Full,把纸垫得比章还宽,让章从”纸外”就开始落,边缘被扫描得最充分,输出反而比输入还大。
三种模式的选择,本质上是“边缘信息保留多少”与“输出尺寸变化多大”的取舍。Valid 最省算力但丢边缘;Same 最常用,因为网络层数深了之后,每层保持尺寸不变才能稳定堆叠;Full 常在信号处理里用,在深度学习中较少出现。它们对应的填充量可以用一个分段公式概括:
小林把三张设计稿钉在墙上:Valid、Same、Full,就像是“不垫、垫刚够、垫超宽”。他明白了,选哪种不是拍脑袋,而是看你想让网络“长多胖、保多少边”。
- Valid 模式$P = 0$,完全不填充,输出必然小于输入,边缘信息放弃。
- Same 模式$P = \dfrac{K-1}{2}$,填充量刚好让核中心覆盖边缘像素,在步长 1 时输出尺寸不变。
- Full 模式$P = K - 1$,核从输入外开始滑动,边缘像素被覆盖的次数最大化,输出尺寸为 $W + K - 1$。
输出尺寸公式
老师傅看小林已经会“算账”了,便给了他一道综合题:“你把填充和步长合在一起,算算这叠 8 厘米纸,垫 1 圈废纸、章 3 厘米、每次挪 2 厘米,总共能印几行?”小林低头画了两笔,写出了一个式子。
其实步长和填充从来不是孤立作用的:填充先扩大输入的有效尺寸,步长再决定采样密度,两者一前一后,共同决定输出。把前两节的公式合并,就是卷积层最经典的输出尺寸公式:
小林代了一遍:8 − 3 + 2×1 = 7,7 ÷ 2 = 3.5 取 3,再加 1 得 4。他跑到动画演示里把滑块拨到 stride=2、padding=1,右侧果然亮起一个 4×4 的网格。公式和演示对上了,小林感觉手里多了一把“尺子”。
- 填充扩大输入$W + 2P$ 表示垫了纸之后的总边长,左右各 $P$。
- 减去核宽减 $K$ 后得到核中心可移动的有效范围。
- 除以步长按步长 $S$ 计算可移动的步数,步长越大,步数越少。
- 取整加一向下取整舍弃不完整的半步,再加起始位置 1。
感受野分析
晚上收工,小林盯着墙上的设计稿发呆:同一枚章,为什么相隔远的两个印章图案,花纹却好像来自同一块原版?老师傅说:“问得好。印章上每一个'输出点',其实能看到输入纸上的一个范围。这个范围有多大,就叫感受野。步长越大,后面层能看到前面层的范围就越宽。”
在卷积网络中,感受野(Receptive Field) 是指输出特征图上的一个像素,对应到原始输入图像上的多大区域。核本身给出一块基础视野,而前面所有层的步长会像“放大器”一样把视野逐层撑大。比如两层 3×3 步长 1 的卷积,感受野是 5×5;但如果第一层步长变成 2,第二层的感受野立刻扩到 7×7。填充则修正了边缘的覆盖偏移,让感受野中心对准。感受野的递推关系为:
小林终于明白,为什么网络越深越能“看到”全局:不是卷积变大,而是步长在背后默默放大视野。他也开始理解,为什么设计网络时步长不能乱设——每设一个步长为 2 的层,后面所有层的感受野都要重新算一遍账。
- 上一层视野递推的起点是上一层感受野 $RF_{l-1}$,它已经覆盖的区域继续保留。
- 本层新增跨度本层核大小为 $K_l$,比中心多出 $K_l - 1$ 的新覆盖范围。
- 前面步长累乘前面每一层的步长 $S_i$ 都会把本层新增的跨度放大,所以将它们连乘。
- 两者叠加上一层视野加上被放大的新增跨度,就是当前层感受野。
计算量平衡
又过了几天,印刷社接到一笔大单:要在 1000 张卡片上盖满图章。小林干劲十足,把步长调成 1 想印得最精细,结果加班到凌晨也没印完。老师傅劝他:“你把步长调到 2,图章少一半,省一半力气,花纹虽然粗一点,但客户要的是批量出货,不是艺术品。”
这正是卷积网络设计里的计算量权衡。步长变小,输出特征图变大,卷积的乘加运算量随之暴涨;步长变大,输出变小,计算量骤降,但特征变得稀疏,可能丢失细节。填充也会影响计算量——填充让输出变大,每个输出点都要做 $K^2$ 次乘法。一层卷积的乘法计算量可以估算为:
小林算了笔账:同样的输入,步长 1 的输出是 64×64,步长 2 只有 32×32,面积差了 4 倍。他终于体会到,为什么移动端 AI 模型偏爱大步长——省下的计算量非常可观,代价只是少看一些细节。精度与效率的平衡,从选步长那一刻就开始了。
- 单点乘加次数一个输出像素要对核内 $K \times K$ 个位置各做一次乘加,所以是 $K^2$。
- 输入通道求和每个位置还要对 $C_{\text{in}}$ 个输入通道分别乘加后累加。
- 输出通道重复有多少个输出通道 $C_{\text{out}}$,整套运算就要重复多少遍。
- 空间位置铺满最后乘以输出特征图面积 $O^2$,因为每个空间位置都要算一遍。
网络设计实践
如今小林已经是印刷社的半个师傅了。这天新来的学徒问:“如果我想把 8×8 的图案最终变成 4×4 的成品,已知章是 3×3、步长 2,该垫多宽?”小林笑了:“反着算呗——把输出尺寸公式倒过来,就知道填充该填多少。”
在实际搭建 CNN 时,设计师经常面临“目标尺寸已知,反推超参数”的需求。无论是把 224×224 的图片一路降到 7×7,还是让分割网络保持逐层等尺寸,都可以从输出尺寸公式反推所需填充:
小林把这条公式抄在印刷社的黑板上。他总结出一条逐层规划的工作流:先定网络要输出的最终尺寸,再从最后一层往前,逐层选择步长和填充;每设计一层,就用输出尺寸公式验算一遍,并顺手估算一下 FLOPs。遇到尺寸对不上的情况,优先调整步长(它影响最大),其次再动填充。如今他设计的印刷方案,从大纸到小卡,一步步严丝合缝。
- 从输出反推跨度目标输出 $O_{\text{target}}$ 减 1,得到步数,再乘以步长 $S$,即核中心走过的总距离。
- 补齐输入差异这个总距离要覆盖 $W - K$ 的有效范围,所以两者相减得到需要“垫”出的额外长度。
- 两侧均分额外长度除以 2,得到单侧填充量 $P$。
AI 应用场景
人脸检测网络中的快速下采样
阶段:模型架构设计
解决的问题:人脸检测要在不同尺度下扫描图像,传统滑动窗口法对每个位置、每个尺度都暴力计算,极其耗时。步长卷积既能缩小特征图、降低计算量,又能扩大感受野,让网络“看见”更大范围的人脸上下文。
具体案例:MTCNN 的 P-Net 在 640×480 监控画面上做人脸候选框提取,使用步长 2 的 3×3 卷积把特征图快速缩小一半,配合 Same 填充保留小脸的边缘轮廓,最终在 1 秒内输出数千个候选框。
医学图像分割中的边缘保持
阶段:特征图尺寸控制
解决的问题:医学分割要求输出与输入同分辨率,无填充卷积会让特征图逐层萎缩,肺部 CT 边缘的微小病灶极易在层层下采样中丢失。Same 填充和步长 1 的组合可以保证编码器每层尺寸不缩水。
具体案例:U-Net 对 512×512 的肺部 CT 做病灶分割,编码器用步长 2 卷积逐层下采样(512→256→128),解码器逐层恢复,全程采用 Same 填充保持空间对齐,最终输出像素级分割图,边缘病灶召回率提升 12%。
移动端轻量模型的计算压缩
阶段:计算效率优化
解决的问题:手机端算力有限,池化层虽然能缩小尺寸但不改变通道计算负担,步长 2 卷积可以“一次搞定下采样 + 特征提取”,用更少的计算量达到同等效果。
具体案例:MobileNetV2 的 bottleneck 模块用步长 2 的深度可分离卷积替代传统池化下采样,配合填充控制特征图尺寸,使模型在 ImageNet 上计算量降低约 4 倍,精度仅下降不到 1%。
Python 演示
import numpy as np def conv2d_simple(img, kernel, stride=1, padding=0): """Simplified 2D convolution to demo stride & padding.""" if padding > 0: img = np.pad(img, padding, mode='constant') H, W = img.shape k = kernel.shape[0] oh = (H - k) // stride + 1 ow = (W - k) // stride + 1 out = np.zeros((oh, ow)) for i in range(oh): for j in range(ow): row = i * stride col = j * stride patch = img[row:row + k, col:col + k] out[i, j] = (patch * kernel).sum() return out def main(): img = np.arange(1, 65, dtype=float).reshape(8, 8) kernel = np.array([[1, 0, -1], [1, 0, -1], [1, 0, -1]], dtype=float) print("Input: 8x8, Kernel: 3x3") print("=" * 32) for s in (1, 2, 3): for p in (0, 1, 2): out = conv2d_simple(img, kernel, stride=s, padding=p) print("stride=%d, padding=%d -> output %dx%d" % (s, p, out.shape[0], out.shape[1])) print("=" * 32) for s in (1, 2): for p in (0, 1): out = conv2d_simple(img, kernel, stride=s, padding=p) print("out[0][0] with stride=%d pad=%d -> %.1f" % (s, p, out[0][0])) if __name__ == "__main__": main()
O = ⌊(W−K+2P)/S⌋+1 的每种情况,并打印首个输出值供对照。img = np.pad(img, padding, mode='constant')在图像四周补零,对应“垫纸”动作,零值不引入额外信号。oh = (H - k) // stride + 1用整数除法实现向下取整,直接算出输出高。row = i * stride; col = j * stride按步长定位卷积核左上角,模拟“每次挪多远”。patch = img[row:row + k, col:col + k]切片取出当前窗口,这就是卷积核的覆盖区域。out[i, j] = (patch * kernel).sum()逐元素相乘再求和,完成一次卷积的乘加运算。
常见误区
误区既然步长为 2 能省四倍计算量,那步长设成 4 甚至 8 不是更省?输出小、跑得快,效果应该差不多吧?
正解步长本质是“采样间隔”。印章挪得越远,漏掉的区域就越多。步长 2 会跳过一半位置的信息,步长 4 则会跳过四分之三。信息一旦被跳过就无法恢复,模型精度会明显下降。设计时应在计算效率和精度之间取平衡,而不是一味调大步长。
为何常见:只看计算量数字,容易忽略“下采样是有信息代价的”这个事实。初学者常把“省算力”和“效果好”混为一谈。
误区填充能保住边缘,那我干脆多加几圈填充,输出更大、边缘保留更多,岂不是更好?
正解填充确实能增大输出,但填充的默认值是 0,它提供的是“空的画布”,不是真实信息。填太多,边缘位置主要靠零值凑数,卷积结果会掺入大量无效计算,特征图里“水分”变多,参数量和 FLOPs 也跟着涨,训练反而更难收敛。填充量应根据核大小和设计目标按公式来,不是越大越好。
为何常见:人们容易把“填充”误解为“补全真实信息”,实际上零填充只是占位符。
误区Same 填充不就是让输出等于输入吗?那我输入 32×32,核 3×3,步长设为 2,Same 填充后应该还是 32×32。
正解Same 的“保持尺寸”只在步长为 1 时成立。当步长为 2 时,输出约等于输入的一半——比如 32×32 输入、核 3×3、步长 2、Same 填充,输出是 16×16。填充只能补偿窗口覆盖,不能抵消步长跳过的位置。
为何常见:初学者对 "Same" 这个名称产生字面误解,忽略了公式中步长 $S$ 在分母位置上起到的决定性作用。
误区反正填充只是占位,补 0 和补 1、补边界值都一样,卷积核会自动过滤掉。
正解填充值会直接参与卷积求和,进而影响每一层的输出分布。零填充是“中性”的,不引入额外信号;但常数填充(比如全 1)会给边缘叠加一个恒定偏置,反射填充则会人为放大边界纹理。设计网络时选择零填充,是为了让边缘的数值统计与内部尽可能一致。
为何常见:只看公式中 $2P$ 对尺寸的作用,忽略了 $P$ 代表的数值也会进入乘加运算。
误区步长只管下采样,填充只管边缘,两者各调各的,不相干。
正解它们在输出尺寸公式中是联动的:$\lfloor (W - K + 2P) / S \rfloor + 1$。改变步长会改变填充的实际效果(同一个 P 值在 S=1 和 S=2 下输出的变化幅度完全不同),改变填充也会影响步长能走几步。设计网络时必须把两个参数放在一起估算,单独调任何一个都可能让尺寸偏离目标。
为何常见:网络层的 API 里 stride 和 padding 是两个独立参数,初学者容易把它们在接口上的独立误认为在数学上也是独立的。
面试题
Q1. 一个 7×7 的输入特征图,用 3×3 卷积核、步长 1、无填充,输出尺寸是多少?
- A.5×5← 正确答案
- B.6×6
- C.7×7
- D.4×4
答案:A。用输出尺寸公式 (7−3)/1+1 = 5,输出为 5×5。就像 7 厘米的纸用 3 厘米的章、每次挪 1 厘米,完整能盖 5 次。选项 A 正确。
Q2. 32×32 输入,3×3 卷积核,步长 1,padding=1(Same 填充),输出尺寸为?
- A.30×30
- B.31×31
- C.32×32← 正确答案
- D.34×34
答案:C。(32+2−3)/1+1 = 32。Same 填充 + 步长 1 时输出等于输入,就像垫一圈纸后印章完整落满整张纸,尺寸保持不变。
Q3. 关于 Same 填充的说法,正确的有哪些?
- A.核 3 时填充量为 1← 正确答案
- B.核 5 时填充量为 2← 正确答案
- C.步长 1 时尺寸不变← 正确答案
- D.步长 2 时尺寸不变
答案:ABC。Same 填充量 P=(K−1)/2,核 3 时 P=1,核 5 时 P=2。但“尺寸不变”只在步长 1 时成立,步长 2 时输出约减半,所以 D 错。垫纸只能保住边缘,不能阻止大步长跳过中间位置。
Q4. 224×224 输入,7×7 卷积核,步长 2,padding=3,输出尺寸为?
- A.111×111
- B.112×112← 正确答案
- C.110×110
- D.113×113
答案:B。(224+6−7)/2+1 = 223/2 向下取整 111,再加 1 得 112。这是 ResNet 等经典网络第一层常用的配置:7×7 核、步长 2、pad 3,把 224 的图先降到 112。
Q5. 步长从 1 变为 2(其他参数不变),输出特征图的尺寸大约如何变化?
- A.保持不变
- B.减为原来的一半← 正确答案
- C.变为原来的两倍
- D.减为原来的四分之一
答案:B。步长在分母位置,增大一倍,输出边长近似减半。就像印章每次挪 2 厘米,能盖的行数大约是挪 1 厘米时的一半。注意面积是四分之一,但边长是二分之一。
Q6. 增大卷积步长会带来哪些影响?
- A.输出特征图变小← 正确答案
- B.计算量减少← 正确答案
- C.感受野扩大← 正确答案
- D.保留更多细节信息
答案:ABC。步长增大,输出变小、FLOPs 降低、感受野逐层放大,但采样变稀疏会丢失细节,所以 D 错。大步长是效率与精度的权衡,不是“信息更多”。
Q7. 输入 10×10,核 3×3,步长 3,无填充,输出尺寸为?
- A.3×3← 正确答案
- B.2×2
- C.4×4
- D.5×5
答案:A。(10−3)/3 = 7/3 向下取整为 2,再加 1 得 3。虽然计算结果是 3.33 向下取整,但起点那格要加上,所以是 3×3,而不是 2×2。
Q8. 关于填充(padding)的说法,正确的有哪些?
- A.零填充减少边缘信息丢弃← 正确答案
- B.Valid 模式输出必然缩小← 正确答案
- C.Full 模式填充量 P=K−1← 正确答案
- D.填充会改变卷积核梯度更新
答案:ABC。零填充让边缘像素参与更多次卷积,A 对;Valid 完全不垫,核滑不到边,输出必小于输入,B 对;Full 模式 P=K−1,C 对。填充只是修改输入,不改变卷积核权重更新方式(梯度仍按链式法则传播),D 错。
Q9. 第一层为 3×3 卷积、步长 2,第二层为 3×3 卷积、步长 1,则第二层输出像素的感受野大小为?
- A.5×5
- B.6×6
- C.7×7← 正确答案
- D.9×9
答案:C。RF₁=3,第二层新增跨度 (3−1) 再乘以前面步长的累乘 2,即 RF₂=3+(3−1)×2=7。步长 2 把第二层的新增视野放大了 2 倍,所以不是常见的 5×5。
Q10. 输入 64×64×64,用 3×3 卷积输出 128 个通道,步长 1,padding=1,按乘法次数估算需要约多少次运算?
- A.约 300 万次
- B.约 3 亿次← 正确答案
- C.约 3000 万次
- D.约 30 亿次
答案:B。FLOPs ≈ 64×64×128×64×9 = 301,989,888 ≈ 3 亿。先用公式算输出空间 64×64×128,每个点做 64×9 次乘法。选项 B 正确。这也说明深层网络中步长对计算量的放大效应有多惊人。