卷积神经网络 从局部连接到共享权重的梯度
全连接层可以把一张图像展平后分类,但它不直接利用相邻像素的结构。如果希望同一种局部模式在不同位置使用同一套参数,就会得到卷积层的两个核心条件:只看局部窗口,在不同空间位置共享权重。
本文接上反向传播与优化器,从一次可手算的卷积进入共享参数的梯度累积。实验只实现单样本卷积层,不是完整图像分类器,也不把示意核当成训练后一定会出现的特征。
局部连接和共享权重各省了什么
Stanford CS231n 的卷积网络讲义把局部感受野与参数共享分别讨论:局部连接限制一个输出看见的输入范围,共享则让同一输出通道在不同位置使用相同核与偏置。
设输入 3×3、输出 2×2、一个通道,局部窗口为 2×2。
| 结构 | 每个输出看什么 | 四个输出是否共享参数 | 参数数 |
|---|---|---|---|
| 全连接 | 全部 9 个输入 | 不共享 | 4×(9+1)=40 |
| 局部连接但不共享 | 各自 4 个输入 | 不共享 | 4×(4+1)=20 |
| 卷积层 | 各自 4 个输入 | 同一套核与偏置 | 4+1=5 |
共享减少的是可学习参数,不是取消不同位置的计算。输入越大,同一核会被使用更多次;输出与中间激活仍然可能占用大量内存。
多通道卷积不是每个颜色独立出一个结果
本文采用输入 C_in×H×W、权重 C_out×C_in×K_h×K_w、输出 C_out×H_out×W_out,没有批次维,groups=1、dilation=1。
每个输出通道有一组跨越所有输入通道的核。在一个空间位置,先把不同输入通道的乘积相加,再加一次该输出通道的偏置。RGB 的三个通道通常参与同一个输出特征,不是默认各自产生互不相干的输出。
对输出位置 i,j,输入坐标是 iS+u−P、jS+v−P。越界位置按零处理。尺寸为:
H_out=⌊(H+2P−K_h)/S⌋+1,W_out=⌊(W+2P−K_w)/S⌋+1。
除不尽时舍弃不完整的尾部窗口。现代 PyTorch Conv2d 文档使用向下取整的尺寸公式;数学意义的卷积通常翻转核,而深度学习卷积层通常执行不翻转核的互相关,本文跟随这一约定。
flowchart LR A[输入的多通道局部窗口] --> B[各通道与对应核相乘] B --> C[跨输入通道求和并加一次偏置] C --> D[得到一个输出通道的一个位置] D --> E[移动窗口并复用同一组权重]
四个窗口的手工演算
输入 X=[[1,2,3],[4,5,6],[7,8,9]],核 K=[[1,0],[0,−1]],偏置 0,步幅 1、填充 0。这个核只是教学设定。
| 输出位置 | 局部窗口 | 计算 | 结果 |
|---|---|---|---|
| 0,0 | [[1,2],[4,5]] | 1−5 | −4 |
| 0,1 | [[2,3],[5,6]] | 2−6 | −4 |
| 1,0 | [[4,5],[7,8]] | 4−8 | −4 |
| 1,1 | [[5,6],[8,9]] | 5−9 | −4 |
共享权重为什么要累加所有位置的梯度
记上游梯度 G[o,i,j]=∂L/∂Y[o,i,j]。同一个权重参与许多输出,所以链式法则必须汇总所有这些路径:
| 对象 | 每个合法窗口贡献 |
|---|---|
| 核 K[o,c,u,v] | 加上 G[o,i,j]×X[c,iS+u−P,jS+v−P] |
| 输入 X[c,h,w] | 加上 G[o,i,j]×K[o,c,u,v] |
| 偏置 b[o] | 加上 G[o,i,j] |
选择 L 为四个输出之和,就有每个 G=1。左上角核权重的梯度是 1+2+4+5=12,而不是只取一个窗口的 1;完整核梯度为 [[12,16],[24,28]],偏置梯度为 4。输入中心同时收到正负贡献,最终梯度为 0;不能用赋值覆盖前一个窗口的贡献。
flowchart TD A[损失传回每个输出的梯度] --> B[遍历这个输出使用的局部窗口] B --> C[输入梯度累积 核权重乘上游梯度] B --> D[共享核梯度累积 输入乘上游梯度] A --> E[每个输出通道偏置累积所有位置] C --> F[全部贡献汇总后再更新参数] D --> F E --> F
纯 Python 前向与反向实现
代码支持多个输入、输出通道,以及正整数步幅和非负零填充。grad_out 是调用方已经算好的上游梯度;不传时只做前向,梯度字段返回 None。它不实现批次、分组卷积、膨胀卷积或 GPU 优化。
1 | import json |
卷积计算主体的前向与反向时间均为 O(C_out H_out W_out C_in K_h K_w)。代码还检查全部输入、核、偏置与上游梯度,需额外 O(C_in HW+C_out C_in K_h K_w+C_out H_out W_out) 时间。输出占 O(C_out H_out W_out),反向额外保存输入、核、偏置的梯度;这里只是便于逐项核验的直接实现,不代表框架底层的实际执行速度。
从卷积层到卷积网络还差什么
卷积后常接非线性,再通过更多卷积、下采样和输出头完成任务。没有非线性时,连续线性层的表达会受到限制;卷积层本身不是分类概率。共享局部核也不自动保证整个模型对任意平移完全不变,边界、步幅和后续结构都会影响结果。
自检:同一个核被使用四次,更新时能只取最后一个窗口的梯度吗?
不能。共享参数有四条通向损失的路径,应该按链式法则累加。只有损失定义本身含有平均因子时才应用对应缩放,不能因为“共享”就自行除以四。
| 边界 | 检查 |
|---|---|
| 多输入通道 | 所有通道求和后只加一次输出偏置 |
| 核翻转 | 本文采用互相关约定,不能与数学卷积混用 |
| 步幅除不尽 | 输出尺寸向下取整,只计算完整窗口 |
| 重叠窗口 | 输入与权重梯度都需要 += |
| 填充 | 零不是可学习输入,不给虚拟像素保存梯度 |
| 上游梯度 | 形状必须与输出一致,不能猜成输入形状 |
| 数值差分 | 验证抽样点,不等于证明整个网络训练正确 |
项目 tests/verify-convolution-article.cjs 用独立展开矩阵核验前向,用标量损失的中心差分核验输入、核和偏置梯度。继续读 DQN 的神经 Q 网络,可以比较“从图像提取特征”与“输出动作价值”是怎样连接但又不相同的任务。