观文听傑

返回

上一篇用残差连接给深层网络铺了一条更直接的信息和梯度路径,其中的图像残差块却还把 Conv2d 当成了黑盒。本文只拆开这个核心算子:它怎样用局部连接接住二维邻域,用权重共享在整张图上寻找同一模式,以及多层堆叠后感受野如何扩大。

01 先拉平图像,究竟丢了什么?#

假设一张红绿蓝图像(Red-Green-Blue, RGB)形状为 [3,224,224]。把它拉平后接 64 个神经元,单层参数量就是:

64×(3×224×224+1)=963385664\times(3\times224\times224+1)=9\,633\,856

更根本的问题是,拉平后的向量没有显式告诉模型“哪些像素互为邻居”。图案向右移一格,原来的每条连接都要换到另一组权重。

3×3 卷积则让每个输出只看一个局部窗口,并让同一组权重滑遍所有位置。若输入 3 通道、输出 64 通道,带偏置的参数量只有:

64×(3×3×3+1)=179264\times(3\times3\times3+1)=1\,792
全连接:整张图 [3,224,224] ─ flatten ─► [150528] ─每个位置独立权重─► [64]
                                               └─ 二维位置被隐藏

卷积:  局部 3×3×3 窗口 ─相同卷积核在 H、W 上滑动─► [64,H_out,W_out]
                                    └─ 空间网格被保留
text

这两个假设是卷积神经网络(Convolutional Neural Network, CNN)的归纳偏置(Inductive Bias):近处像素更可能联合成有用模式,而同一模式可能出现在不同位置。它们适合图像,却不是所有数据的普遍真理。

02 一个输出像素究竟怎样算?#

先固定 PyTorch 的“批量—通道—高度—宽度”(Batch-Channel-Height-Width, NCHW)布局:

  • 输入 XRN×Cin×Hin×WinX\in\mathbb{R}^{N\times C_{in}\times H_{in}\times W_{in}}
  • 权重 KRCout×Cin×Kh×KwK\in\mathbb{R}^{C_{out}\times C_{in}\times K_h\times K_w}
  • 偏置 bRCoutb\in\mathbb{R}^{C_{out}}
  • 输出 YRN×Cout×Hout×WoutY\in\mathbb{R}^{N\times C_{out}\times H_{out}\times W_{out}}

NN 是批量大小,Cin,CoutC_{in},C_{out} 是输入/输出通道数,H,WH,W 是空间高度/宽度,Kh,KwK_h,K_w 是核的高度/宽度。

对输出位置 (n,o,i,j)(n,o,i,j),二维卷积层计算:

Yn,o,i,j=bo+c=0Cin1u=0Kh1v=0Kw1Ko,c,u,vXn,c,ish+udhph,jsw+vdwpwY_{n,o,i,j}=b_o+ \sum_{c=0}^{C_{in}-1}\sum_{u=0}^{K_h-1}\sum_{v=0}^{K_w-1} K_{o,c,u,v}\, X_{n,c,\,i s_h+u d_h-p_h,\,j s_w+v d_w-p_w}

sh,sws_h,s_w 是步幅(Stride),dh,dwd_h,d_w 是空洞率(Dilation),ph,pwp_h,p_w 是补零(Padding)宽度;落在边界外的 XX 按零处理。一个输出通道 oo 使用一只覆盖所有 CinC_{in} 输入通道的卷积核,而不是“每个 RGB 通道各算各的”。

03 用九个数手算一次滑窗#

令单样本、单通道输入和一只 2×2 核为:

X=[120013221],K=[1011]X=\begin{bmatrix}1&2&0\\0&1&3\\2&2&1\end{bmatrix},\qquad K=\begin{bmatrix}1&0\\-1&1\end{bmatrix}

设步幅为 1、不补零、偏置为 0。同一只 KK 在四个位置复用:

窗口 (0,0)        窗口 (0,1)        窗口 (1,0)        窗口 (1,1)
[1 2] ⊙ [ 1 0]      [2 0] ⊙ [ 1 0]      [0 1] ⊙ [ 1 0]      [1 3] ⊙ [ 1 0]
[0 1]   [-1 1]      [1 3]   [-1 1]      [2 2]   [-1 1]      [2 1]   [-1 1]
  = 2                  = 4                  = 0                  = 0
text

因此:

Y=[2400]Y=\begin{bmatrix}2&4\\0&0\end{bmatrix}

“局部连接”表示 Y0,0Y_{0,0} 只依赖 XX 左上 2×2 邻域;“权重共享”表示四个窗口都乘同一只 KK。如果为每个窗口分别学一只核,它仍是局部连接层(Locally Connected Layer),却已不是标准卷积。

04 权重共享为什么带来平移等变?#

TΔXT_\Delta X 为把输入平移 Δ\Delta 个格子。忽略边界与下采样时,卷积满足:

Conv(TΔX)=TΔConv(X)\operatorname{Conv}(T_\Delta X)=T_\Delta\operatorname{Conv}(X)

这叫平移等变性(Translation Equivariance):输入中的边缘向右移,特征图中的响应也向右移。它不等于平移不变性(Translation Invariance);后者要求图案移动后最终输出不变,通常还需要全局池化、数据增强或其他聚合。

输入左移/右移 Δ             同一只核 K                特征响应同向移动 Δ
□ □ ■ ■ □ □   ─────────────►   [K 在每个位置复用]   ─────────────►   · · ↑ ↑ · ·

边界补零改变了邻域;stride > 1 丢弃了中间位置。
所以真实 CNN 只是近似、有边界的平移等变,不是任意平移下的完美定理。
text

05 输出尺寸为什么总容易算错?#

有效卷积核高度为 dh(Kh1)+1d_h(K_h-1)+1。能放下多少个起点,决定输出高度:

Hout=Hin+2phdh(Kh1)1sh+1H_{out}=\left\lfloor \frac{H_{in}+2p_h-d_h(K_h-1)-1}{s_h}+1 \right\rfloor

WoutW_{out} 使用完全对称的公式。例如 Hin=32,Kh=3,sh=2,ph=1,dh=1H_{in}=32,K_h=3,s_h=2,p_h=1,d_h=1

Hout=32+2212+1=16H_{out}=\left\lfloor\frac{32+2-2-1}{2}+1\right\rfloor=16
参数改变了什么常见误解
kernel_size单层观察窗口与参数量核大就必然更好
stride滑窗间隔,通常同时下采样只减少计算,不丢空间信息
padding边界处可用的上下文'same' 对任意 stride 都保持尺寸
dilation核采样点的间隔,不增加权重数等价于更大的稠密核
out_channels学习多少种局部模式等于 RGB 等原始颜色通道
groups哪些输入通道可连到哪些输出通道只改变速度,不改变函数族

PyTorch 2.13 官方 Conv2d 文档说明:padding='same' 保持空间尺寸,但当前不支持 stride != 1in_channelsout_channels 都必须能被 groups 整除。不要用猜测替代形状断言。

06 感受野怎样从 3×3 长成 9×9#

某层一个特征位置能依赖原图的范围,叫感受野(Receptive Field)。记第 ll 层的感受野边长为 rlr_l,相邻特征位置在原图上的间隔为 jlj_l

rl=rl1+(kl1)dljl1,jl=jl1slr_l=r_{l-1}+(k_l-1)d_lj_{l-1},\qquad j_l=j_{l-1}s_l

r0=1,j0=1r_0=1,j_0=1 开始,追踪三层 3×3 卷积:

原图单点        Conv 3×3,s=1       Conv 3×3,s=2       Conv 3×3,s=1
r=1, j=1   ───►   r=3, j=1    ───►   r=5, j=2    ───►   r=9, j=2
  1×1                    3×3 视野              5×5 视野              9×9 视野
                                               └─ 下采样后,相邻特征跨原图 2 格
text

多个小核逐层把局部边缘组成更大图案,中间还能插入非线性。但公式给出的是理论感受野;训练后的有效感受野往往只在其中心区域有较大影响,不代表框内所有像素贡献相等。

07 不依赖框架,写出卷积本体#

下面实现 groups=1dilation=1 的 NCHW 二维互相关。输入是 [N,C_in,H,W],权重是 [C_out,C_in,K_h,K_w],输出是 [N,C_out,H_out,W_out]

这段循环为了暴露数据流,不适合生产训练。高性能库会使用向量化、专用中央处理器(Central Processing Unit, CPU)/图形处理器(Graphics Processing Unit, GPU)内核和自动算法选择;它们改变实现方式,不改变上面的张量语义。

08 从特征图到分类结果,数据怎样流?#

下面用线性整流激活(Rectified Linear Unit, ReLU)引入非线性,再把空间特征聚合为分类向量:

images [N,3,32,32]

   ├─ Conv2d(3→8,k=3,p=1)  → [N,8,32,32]
   ├─ ReLU                    → [N,8,32,32]
   ├─ Conv2d(8→16,k=3,s=2,p=1)
   │                           → [N,16,16,16]
   ├─ ReLU                    → [N,16,16,16]
   ├─ AdaptiveAvgPool2d(1)    → [N,16,1,1]
   ├─ flatten(start_dim=1)    → [N,16]
   └─ Linear(16→4)           → logits [N,4]
text

卷积不直接“输出类别”。它把每只核对局部模式的响应保存成一个特征图(Feature Map);非线性和后续卷积再把低层边缘组成高层结构。最后的聚合与线性头才生成分类分数(logit)。

09 用 PyTorch 2.13 写出可检查的 CNN#

Conv2d(3,8,3) 的权重形状是 [8,3,3,3],它为 8 种可学习的局部模式各保存一只覆盖 3 个输入通道的核。CrossEntropyLoss 接收未经 Softmax 的 [N,4] logits 和 [N] 整数类别索引;不要在模型中先转成 argmax

完整训练会对每个小批量(mini-batch)重复“前向→损失→清梯度→反向→更新”;验证和推理则使用 model.eval()torch.inference_mode()。卷积层自身没有训练/评估(train/eval)两套行为,但与它组合的批归一化(Batch Normalization, BatchNorm)和随机失活(Dropout)有。

10 一条最短的卷积调试路径#

  1. 先查输入契约。 打印 shape/dtype/device/min/max;图像 batch 应是 [N,C,H,W] 浮点张量,不是常见图像库的 [N,H,W,C] 整数数组。
  2. 用一个冲激点测位置。 令输入只有中央一点为 1,卷积核全为 1;响应区域可以暴露 padding、stride 和通道顺序错误。
  3. 逐层断言形状。 不要直到 Linear 报矩阵乘法错误才回头;对照输出公式查第一个偏离层。
  4. 与手算本体对齐。 把同一组小数据和权重复制到 NumPy 与 Conv2d,用 torch.testing.assert_close 比较输出。
  5. 尝试过拟合一个极小 batch。 若 8 个样本都无法几乎记住,先查标签、损失、学习率、梯度与数据处理,而不是继续加层。
  6. 观察激活和梯度。 记录每层非零比例、均值、标准差与梯度范数,找到第一个全零、NaN 或尺度突变的层。

11 最常见的“能跑,但语义错了”#

  • NHWC/NCHW 混淆。 若高度恰好等于期望通道数,错布局甚至可能不立即报错;在数据边界只转换一次并断言。
  • 过早 flatten。 一旦在特征提取前拉平,后续就无法使用二维权重共享。
  • 认为 padding 只改尺寸。 零填充还改变边界的输入分布;反射、复制与循环填充具有不同语义。
  • 忽略 groups 的连接约束。 groups=C_inC_out=K C_in 时是深度卷积(Depthwise Convolution);它不自动混合通道,通常还要配 1×1 点卷积。
  • 把理论感受野当成实际利用范围。 可用输出对输入的梯度热图检查有效感受野,而不只算层数。
  • 把平移等变当成不变。 对输入做一像素平移测试,同时观察中间特征与最终 logits;两者应用不同标准。
  • 忽略非确定算法。 官方文档提醒,统一计算设备架构(Compute Unified Device Architecture, CUDA)上的深度神经网络库(CUDA Deep Neural Network library, cuDNN)在某些形状下可能选择非确定内核;严格复现时要单独设置确定性策略,并接受性能代价。

12 卷积与相近操作的边界#

方法连接范围空间位置间共享参数主要用途/代价
全连接层所有输入不适用全局混合;忽略网格邻接,参数多
局部连接层局部窗口可学位置特定模式;参数随空间尺寸增长
标准卷积局部窗口、全输入通道空间等变特征;长距依赖需堆叠
深度可分离卷积局部窗口后再混通道减少计算;表达约束更强
池化局部统计通常无参数下采样/聚合;不学习线性模式
自注意力通常可全局交互投影权重共享,关系动态长距建模;计算和数据需求不同

卷积在网格不规则、空间位置语义完全不同、需要一层就建模全局交互,或关键模式会任意旋转、缩放时不再天然合适。数据增强、群等变卷积、图神经网络或注意力可以针对不同结构假设,但没有一种方法只因为更“现代”就在所有数据规模上更好。

13 工程中还要防哪些失败?#

  • 下采样混叠。 stride>1 直接丢弃位置;输入含高频纹理时可产生别名(Aliasing),必要时先低通滤波并做平移压力测试。
  • 边界成为捷径。 固定裁剪、零填充或水印可让模型学到位置偏见;可视化错误样本的激活/梯度热图。
  • 名义感受野大,中心仍过度主导。 小物体、长条结构和远距关系仍可被忽略;需要多尺度特征、空洞卷积或全局交互。
  • 小数据上过拟合。 参数少于全连接层不等于不过拟合;仍要独立验证集、合法增强、权重衰减与早停。
  • 只看准确率。 医学、遥感等任务还应分析群体、设备、时间和空间分布偏移,并检查模型是否利用了背景捷径。

14 今天真正需要记住什么?#

  1. 卷积的两个核心假设是局部连接与空间权重共享;它们保留网格结构、减少参数并带来有边界的平移等变性。
  2. Conv2d[N,C_in,H,W][C_out,C_in/groups,K_h,K_w] 权重组合为 [N,C_out,H_out,W_out];每个维度都有明确语义。
  3. 输出尺寸由核、步幅、填充和空洞率共同决定;每层应用公式和运行时断言双重核对。
  4. 多层小核通过堆叠扩大理论感受野,但有效感受野、边界效应与下采样信息损失仍需实测。
  5. 最可靠的卷积调试不是盲目换架构,而是从小数据、冲激输入、逐层形状、框架对齐和小 batch 过拟合逐步缩小问题。

15 思考题与小练习#

  1. 对一个 [N=2,C=3,H=7,W=7] 输入使用 Conv2d(3,5,kernel_size=3,stride=2,padding=2,dilation=2)。手算输出形状、权重形状与含偏置的参数量,再用 PyTorch 断言。
  2. 把手算例的输入右移一格并在左侧补零。分别在 padding=0padding=1 下比较输出;哪些位置符合平移等变,哪些因边界失效?
  3. TinyCNN 的每个卷积后保留激活梯度,对一个 logit 反向传播并画输入梯度热图。把模型改为三层后,比较理论感受野和非零/高强度梯度区域。

相关工作#

16 下一篇预告#

卷积已经能在空间网格上复用局部模式,但高分辨率特征图会让计算和显存快速增长。下一篇将比较池化、带步幅卷积与抗混叠下采样,追踪它们怎样生成多尺度特征层次,又分别丢掉什么。

一张图像为何不该先拉平?卷积的局部连接、权重共享与感受野
https://zwjcode.cn/blog/cnn-local-connectivity-weight-sharing-receptive-field
作者
发布于 2026年9月3日
版权协议 CC BY-NC-SA 4.0
评论加载似乎遇到了问题,请尝试刷新页面。