观文听傑

返回

上一篇看到,下采样让卷积网络用更低成本获得更大感受野,却可能在深层特征中抹掉小物体和精确位置。只保留浅层也不行:浅层虽然网格细,却还在响应边缘、纹理等局部模式,未必知道这些模式属于“行人”还是“路灯”。

本文只解决这个冲突:特征金字塔网络(Feature Pyramid Network, FPN)怎样用自顶向下路径(Top-down Pathway)把深层语义送回高分辨率,用横向连接(Lateral Connection)补回对应位置的浅层细节,并让每一层都得到通道数统一、可供下游预测的特征。

01 为什么直接选择某一层总会吃亏?#

设骨干网络(Backbone)每经过一次步幅 2 下采样,就输出一个阶段特征。以输入 [N,3,256,256] 为例:

阶段相对输入步幅张量形状示例优势缺口
C24[N,256,64,64]网格细、定位较精确语义弱、背景响应多
C38[N,512,32,32]开始组合局部部件小目标仍容易混入背景
C416[N,1024,16,16]语义与位置较折中极小目标只剩少数单元
C532[N,2048,8,8]感受野大、类别语义强空间粗,细小结构可能消失

若一个行人在原图中高 12 像素,那么在步幅 4 的 C2 上约覆盖 3 个单元,在步幅 32 的 C5 上只覆盖 12/32=0.37512/32=0.375 个单元。C5 可能知道“这里像人”,却难以给出边界;C2 看见几条边缘,却未必知道它们应组成什么。

传统图像金字塔(Image Pyramid)会把原图缩放成多种尺寸,再分别运行特征提取器:

图像 256² ── Backbone ──► 强语义特征(高计算)
图像 128² ── Backbone ──► 强语义特征(再算一次)
图像  64² ── Backbone ──► 强语义特征(再算一次)
text

FPN 不重复跑整套骨干网络,而是复用骨干网络已经自然形成的多尺度层次。

02 FPN 的两条信息流怎样相遇?#

FPN 有三类操作:1×1 横向投影、上采样与逐元素相加、3×3 输出卷积。

自底向上的骨干网络                 自顶向下路径                 输出金字塔

C2 [N, 256,64,64] ──1×1──► L2 ──(+)◄── up(M3) ──► M2 ──3×3──► P2 [N,D,64,64]
        │                                  ▲
        ▼                                  │
C3 [N, 512,32,32] ──1×1──► L3 ──(+)◄── up(M4) ──► M3 ──3×3──► P3 [N,D,32,32]
        │                                  ▲
        ▼                                  │
C4 [N,1024,16,16] ──1×1──► L4 ──(+)◄── up(M5) ──► M4 ──3×3──► P4 [N,D,16,16]
        │                                  ▲
        ▼                                  │
C5 [N,2048, 8, 8] ──1×1───────────────►  M5 ──3×3──► P5 [N,D, 8, 8]

骨干:分辨率逐层降低、语义增强        FPN:深层语义逐层回流         D 通常固定为 256
text

ClRN×Clch×Hl×WlC_l\in\mathbb{R}^{N\times C_l^{ch}\times H_l\times W_l} 为骨干第 ll 层,DD 为 FPN 的统一通道数。横向投影 ϕl\phi_l1×1 卷积:

Ll=ϕl(Cl),LlRN×D×Hl×WlL_l=\phi_l(C_l),\qquad L_l\in\mathbb{R}^{N\times D\times H_l\times W_l}

最深层先取 M5=L5M_5=L_5;其余层递推为:

Ml=Ll+Up(Ml+1;Hl,Wl),l{4,3,2}M_l=L_l+\operatorname{Up}(M_{l+1};H_l,W_l), \qquad l\in\{4,3,2\}

最后用 3×3 卷积 ψl\psi_l 得到输出:

Pl=ψl(Ml),PlRN×D×Hl×WlP_l=\psi_l(M_l),\qquad P_l\in\mathbb{R}^{N\times D\times H_l\times W_l}

横向路径把“这个位置原来有什么”交给融合点;自顶向下路径把“更大区域整体像什么”交给同一个融合点。二者必须空间尺寸和通道数都一致,才能逐元素相加。

03 1×1 卷积为什么不是可有可无?#

C2 到 C5 的通道数常为 256、512、1024、2048,不能直接相加。1×1 卷积在每个空间位置独立完成通道线性变换:

Ln,d,i,j=bd+c=1ClchWd,cCn,c,i,jL_{n,d,i,j}=b_d+\sum_{c=1}^{C_l^{ch}}W_{d,c}C_{n,c,i,j}

其中 WRD×Clch×1×1W\in\mathbb{R}^{D\times C_l^{ch}\times1\times1},输入位置 [n,:,i,j]ClchC_l^{ch} 维向量被投影为 DD 维,不改变 Hl,WlH_l,W_l。它有两个作用:

  1. 把不同阶段统一到同一通道维度 DD,使加法合法;
  2. 学习每个阶段哪些通道应该进入同一融合坐标系,而不是机械截断或补零。

04 用五个数手算一次自顶向下融合#

先把通道数简化为 D=1D=1。设最深层投影后只有一个空间值:

M5=[3]R1×1M_5=[3]\in\mathbb{R}^{1\times1}

上一层的横向投影为:

L4=[1021]R2×2L_4= \begin{bmatrix} 1&0\\ 2&-1 \end{bmatrix} \in\mathbb{R}^{2\times2}

最近邻上采样(Nearest-neighbor Upsampling)把 M5M_5 的值复制到目标尺寸:

Up(M5;2,2)=[3333]\operatorname{Up}(M_5;2,2)= \begin{bmatrix} 3&3\\ 3&3 \end{bmatrix}

逐元素相加得到:

M4=L4+Up(M5)=[4352]M_4=L_4+\operatorname{Up}(M_5)= \begin{bmatrix} 4&3\\ 5&2 \end{bmatrix}

这里的 3 是深层上下文,告诉所有四个细位置“这一片区域整体具有某种语义”;L4L_4 仍区分四个位置的局部证据。相加不会增加空间信息:若小物体在 C4 之前已经完全消失,FPN 不能从常量上采样中把它凭空重建出来。

随后 3×3 卷积在 M4M_4 上融合邻域并生成 P4P_4。它既让每个输出位置看到相邻融合结果,也减弱最近邻复制形成的块状边界;它不是第二次跨尺度融合。

05 为什么用相加,而不是拼接?#

设两个输入都是 [N,D,H,W]

融合方式融合后形状后续参数/显存信息路径
相加[N,D,H,W]较低两路落在同一通道,梯度直接分流
拼接[N,2D,H,W]后续卷积约更昂贵保留两路身份,再由卷积决定如何混
加权和[N,D,H,W]多少量融合权重可学习各尺度贡献,但需约束稳定性

原始 FPN 选择相加,计算简单且所有金字塔输出可共享同样的下游头。它隐含一个假设:横向 1×1 投影已把两路特征变换到可相加的表示空间。若任务需要保留来源身份,拼接或显式加权可能更合适,但那已经是另一种融合设计。

反向传播时,若 Ml=Ll+UlM_l=L_l+U_l,则:

LLl=LMl,LUl=LMl\frac{\partial\mathcal{L}}{\partial L_l} =\frac{\partial\mathcal{L}}{\partial M_l},\qquad \frac{\partial\mathcal{L}}{\partial U_l} =\frac{\partial\mathcal{L}}{\partial M_l}

同一上游梯度分别进入横向路径和更深层路径;更深的 Ml+1M_{l+1} 还会接收多个较浅输出经上采样链汇集的梯度。

06 完整前向伪代码#

输入:按分辨率从高到低排列的 C2...CL

for l = 2...L:
    lateral[l] = Conv1x1_l(C[l])       # [N,Cl,Hl,Wl] -> [N,D,Hl,Wl]

inner[L] = lateral[L]
output[L] = Conv3x3_L(inner[L])

for l = L-1...2:                       # 从深到浅
    top_down = Resize(inner[l+1], size=(Hl,Wl))
    assert top_down.shape == lateral[l].shape
    inner[l] = lateral[l] + top_down
    output[l] = Conv3x3_l(inner[l])

返回:P2...PL,顺序仍为高分辨率到低分辨率
text

注意上采样的是尚未经过输出 3×3 卷积的 inner[l+1],而不是随手取 P[l+1]。两种写法形状相同,计算图和参数语义却不同。

07 不调用 FPN 封装,先写出 PyTorch 本体#

下面实现接收一个有序字典,特征必须按“最高分辨率、最浅阶段”到“最低分辨率、最深阶段”排列:

示例故意使用奇数尺寸。若写成 scale_factor=2[8,9] 只能先变成 [16,18],无法与 [16,17] 相加;指定 size=lateral.shape[-2:] 才以横向分支为对齐真值。

mode="nearest" 不会创造插值混合值,且与经典 FPN 的简单设计一致。若改为双线性插值,要显式理解 align_corners 的几何语义,并对边界框、掩码或关键点做像素对齐测试。PyTorch 2.13 当前官方 interpolate 接受 4D NCHW 张量,并提醒部分 CUDA 上采样反向可能非确定。

08 训练时每层怎样收到监督?#

FPN 只生成多尺度表示,不定义任务损失。检测、分割或关键点头会消费 PlP_l。下面用最小二分类稠密头展示梯度数据流;真实任务需要按目标尺寸分配层级并构造合法标签。

完整训练流是:

image -> backbone -> {C2...C5} -> FPN -> {P2...P5} -> shared/task heads

target -> level assignment -> per-level targets -> losses ┘

loss.backward() -> head -> every P level -> lateral + top-down -> backbone
text

若把每层损失简单求平均,大尺寸 P2 因位置更多并不一定贡献更多,但正负样本比例、有效掩码和任务损失尺度仍可能严重失衡。必须逐层记录正样本数、损失和梯度范数,而不是只看总损失。

09 用 torchvision 0.26 当前官方 API 落地#

torchvision 当前官方 FeaturePyramidNetwork 接收按深度递增排列的有序特征字典;也就是浅层高分辨率在前、深层低分辨率在后。in_channels_list 必须与这个顺序逐一对应:

当前接口返回的结果按最高分辨率优先排列;extra_blocks 可在标准金字塔之外生成额外层,norm_layer 可指定卷积后的归一化模块。两者都应由下游架构需求驱动,不要为了“更完整”盲目开启。

10 一条最短的 FPN 调试路径#

  1. 打印每个 C 层。 对每层记录名字、[N,C,H,W]、相对输入步幅和有限值比例;先确认骨干本身没有错。
  2. 检查顺序与通道表。 names、实际 x.shape[1]in_channels_list 必须逐项一致,空间尺寸应随深度非增。
  3. 用目标尺寸上采样。 对奇数高宽测试所有融合点,逐次断言 top_down.shape == lateral.shape
  4. 分开观察三种张量。 保存 lateraltop_down 和相加后的 inner;若一条支路幅度大几个数量级,加法会近似只剩强支路。
  5. 逐层检查梯度。 对每个输出单独反向,记录各横向卷积和骨干阶段的梯度范数,确认深浅路径都能收到信号。
  6. 画跨尺度一致性。 对同一物体在 P2–P5 上可视化激活热点;热点应随步幅缩放落在对应位置,而不是系统性偏一格。
  7. 做最小过拟合。 用 2–4 张带单个目标的图训练到接近零损失;做不到时先查标签层级、坐标缩放与有效掩码。

一个实用的支路幅度诊断为:

Rl=RMS(Ll)RMS(Up(Ml+1))+ϵR_l=\frac{\operatorname{RMS}(L_l)} {\operatorname{RMS}(\operatorname{Up}(M_{l+1}))+\epsilon}

Rl1R_l\gg1 表示横向分支主导,Rl1R_l\ll1 表示自顶向下分支主导。理想区间依赖任务;这个比值用于发现异常,不是要求所有层都等于 1。

11 最常见的“形状对了,语义却错了”#

  • 层级顺序反了。 通道表若也被错误地一起反转,网络甚至可能正常运行,却把低分辨率当成浅层开始融合。
  • scale_factor=2 当成尺寸真值。 奇数输入、不同补边和向下取整会累积一像素错位。
  • 融合前坐标系不一致。 不同裁剪、补边或 align_corners 设置会让两个相同形状的张量代表不同原图位置。
  • 误以为上采样恢复细节。 最近邻或双线性只能重排已有深层值;真正的高分辨率信息来自横向连接。
  • 直接相加原始 C 层。 通道不匹配会报错;即使通道碰巧相同,也缺少可学习的语义对齐。
  • 输出层没有独立卷积。 只返回 inner 不是必然错误,但已经偏离经典 FPN;应记录变体,而不是仍声称实现完全相同。
  • 每层都复制一个独立预测头。 这增加参数且失去统一表示的好处;是否共享应由任务和消融实验决定。
  • 只看总体检测指标。 FPN 的目标与尺度相关,应至少分小、中、大目标或按面积分桶报告性能。

12 它会在哪里失败?#

  • 细节在最浅输入层前已经丢失。 若骨干的 stem 过早以大步幅下采样,C2 也没有小目标证据,横向连接无物可补。
  • 跨层语义鸿沟太大。 一个 1×1 线性投影未必足以让极浅纹理与极深语义直接相加;更强融合也会增加成本和优化难度。
  • 定位坐标被预处理破坏。 图像 resize、裁剪、padding 与标签变换稍有不一致,多尺度热点都会系统偏移。
  • 高分辨率输出耗尽显存。 P2 的元素数通常是 P5 的 64 倍;提高 DD 或输入尺寸时,激活和预测头成本迅速增长。
  • 固定等权相加不适合所有样本。 某些任务需要按尺度、位置或通道自适应选择来源;普通 FPN 没有显式学习融合权重。
  • 分布外尺度没有被训练覆盖。 有金字塔不等于天然尺度不变;极端大小目标仍可能落在所有训练层级之外。

13 与相近结构的边界#

结构信息路径主要目标与 FPN 的关键区别
图像金字塔多尺度原图分别提特征显式覆盖输入尺度重复骨干计算
残差连接同一阶段的恒等/投影捷径改善深层优化与信息路径不专门构造多尺度输出
U-Net编码器浅层拼接到解码器恢复像素级密集输出常用拼接和完整解码,仅末端输出也常见
FPN自顶向下 + 横向相加各尺度都获得强语义特征输出多层统一通道金字塔
PANet再增加自底向上的路径增强缩短低层定位到高层的路径在 FPN 后继续双向聚合
BiFPN重复双向路径并学习融合权重高效、可缩放的多尺度融合删除部分单输入节点并做加权融合

FPN 也不是目标检测器本身。它不产生类别、边界框或掩码,不定义目标该去哪个层,也不执行非极大值抑制(Non-maximum Suppression, NMS);它只是让下游头获得语义更一致的多尺度输入。

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

  1. 骨干浅层保留高分辨率定位,深层拥有更强语义;只选一层会在小目标细节与上下文之间取舍。
  2. FPN 用 1×1 横向投影统一通道,再把深层特征按目标尺寸上采样并逐元素相加,最后以 3×3 卷积生成各层输出。
  3. PlP_lClC_l 的空间尺寸相同,但统一为 DD 个通道;上采样传递语义,横向连接提供真实高分辨率证据。
  4. 工程中最危险的不是明显的通道报错,而是字典顺序、奇数尺寸和坐标约定造成的静默错位。
  5. FPN 不能恢复已经丢失的信息,也不自动解决层级分配、损失平衡、显存和分布外尺度问题。

15 思考题与小练习#

  1. 输入 [N=2,3,255,257] 的骨干输出分别是 C3=[2,64,32,33]C4=[2,128,16,17]C5=[2,256,8,9]。令 D=96D=96,写出每个横向权重、MP 的形状;解释为什么连续两次 scale_factor=2 不能安全对齐 C3。
  2. 把手算例中的 M5M_5 改为 [[-2]],重算 M4M_4。再假设损失对 M4M_4 每个元素的梯度都是 1,写出传给 L4L_4 与上采样结果的梯度;最近邻复制反向后,M5M_5 的梯度是多少?
  3. TinyFPN 中分别单独对 pyramid["c3"].mean()pyramid["c5"].mean() 反向,记录三组横向卷积的梯度范数。哪些参数在两次实验中收到梯度?这揭示了自顶向下路径怎样改变监督范围?

相关工作#

16 下一篇预告#

FPN 已让每个尺度都有较强语义,但下游仍要决定“一个 12 像素目标该由 P2 还是 P3 负责”。下一篇将研究目标尺寸如何映射到金字塔层级,解释跨层重复预测为何产生,以及训练标签与推理结果怎样保持同一尺度契约。

小物体为何在深层特征里消失?FPN 的自顶向下路径与横向连接
https://zwjcode.cn/blog/fpn-top-down-lateral-connection-semantic-localization
作者
发布于 2026年9月4日
版权协议 CC BY-NC-SA 4.0
评论加载似乎遇到了问题,请尝试刷新页面。