1. 项目概述为什么FPN在今天依然重要如果你做过目标检测或者实例分割尤其是在处理那些尺度变化剧烈的图片时比如一张图里既有远处的小汽车又有近处的行人你肯定遇到过模型“看大不看小”或者“看近不看远”的尴尬。传统的卷积神经网络CNN在提取特征时随着网络层数的加深感受野变大语义信息越来越强但空间细节比如物体的边缘、角点却越来越模糊。这就导致了一个矛盾深层特征图适合识别“是什么”语义强但说不清“在哪里”位置粗浅层特征图则相反位置精准但语义理解能力弱。这个矛盾在需要精确定位的任务里比如给图片里的每个像素分类语义分割或者框出每个物体目标检测就成了性能提升的瓶颈。特征金字塔网络Feature Pyramid Network, FPN就是为了优雅地解决这个矛盾而生的。它不是凭空冒出来的新网络而是一个“插件式”的架构思想可以轻松地嫁接到像ResNet、VGG这类主流的主干网络Backbone上。它的核心思路很简单既然不同层级的特征各有优劣那就把它们都利用起来并且让它们互相“取长补短”。具体来说FPN通过一个自顶向下Top-down的路径和横向连接Lateral Connection将深层的高语义特征与浅层的高分辨率特征进行融合最终构建出一个每一层都兼具强语义和高分辨率的特征金字塔。我最初接触FPN是在做遥感图像目标检测项目时图像中的建筑物、车辆尺寸差异极大直接用单层特征输出的模型效果惨不忍睹。引入FPN后模型对小目标的召回率Recall有了肉眼可见的提升。现在虽然Transformer架构在视觉领域风头正劲但FPN所代表的“多尺度特征融合”思想已经被广泛吸收成为了现代视觉模型设计中一个几乎不可或缺的组件。在PyTorch框架下实现并理解FPN不仅能帮你解决实际的尺度变化问题更能让你深入理解特征提取与融合的底层逻辑这是进阶计算机视觉的必修课。2. FPN核心结构深度拆解从图纸到代码的每一步FPN的结构图看起来清晰但真要自己用PyTorch实现里面有几个关键设计点必须琢磨透。我们不能只满足于调用torch.nn里的模块拼凑得搞清楚每个操作背后的意图。2.1 骨架网络与特征图选取FPN需要一个已经预训练好的主干网络来充当“骨架”Backbone。最常用的是ResNet。以ResNet-50为例它通常可以分为几个阶段stage每个阶段结束时特征图的尺寸会减半通道数会增加。FPN并不会利用所有层而是选取每个阶段最后一个残差块输出的特征图这些特征图代表了不同尺度的信息。假设输入图像是3xHxW那么C2: 来自ResNet的stage2输出尺寸较大例如256xH/4xW/4细节丰富但语义性弱。C3: 来自stage3输出例如512xH/8xW/8。C4: 来自stage4输出例如1024xH/16xW/16。C5: 来自stage5输出即通常所说的conv5_x的输出例如2048xH/32xW/32语义最强但尺寸最小最粗糙。FPN的目标就是基于C2到C5生成一套新的、融合后的特征金字塔 {P2, P3, P4, P5}有时还会从P5下采样得到P6用于更大目标的检测。这里的选择有讲究为什么从C2开始而不是C1因为C1初始卷积输出虽然分辨率最高但包含的语义信息太少噪声多融合进去收益不大且计算成本高。工程上的权衡就是放弃最早的一两层。2.2 横向连接与1x1卷积的桥梁作用这是FPN的第一个关键操作横向连接Lateral Connection。C2到C5这些来自主干网络的特征图它们的通道数各不相同256, 512, 1024, 2048。为了后续能进行逐元素相加Element-wise Addition必须先将它们的通道数统一。FPN的做法是为每个Cii2,3,4,5配备一个1x1的卷积层。这个1x1卷积的核心目的就是降维或调整维度将Ci的通道数全部调整到一个固定的较小值比如256。为什么是1x1卷积因为它是最轻量级的改变通道数的方式几乎不增加空间信息感受野为1可以看作是对每个空间位置的特征向量进行一个线性变换。这一步之后我们得到了一批通道数均为256的特征图我们暂时称它们为“侧边输出”。注意这个1x1卷积之后通常不立即接激活函数如ReLU。这是因为后续还要与上采样后的特征相加过早引入非线性可能会影响梯度流动和信息融合。通常是在相加操作之后再统一使用一个3x3卷积并加上激活函数来生成最终的Pi。2.3 自顶向下的上采样与逐元素相加这是FPN的第二个关键操作也是其名称中“金字塔”的体现。处理顺序是从最深层语义最强开始自顶向下Top-down。起点对于最深的C5调整通道后我们直接将其作为P5的“雏形”。有时会对它先做一个3x3卷积得到最终的P5用于最小尺度的预测。上采样与融合接下来我们要生成P4。首先将P5进行2倍上采样Upsample。上采样的方法在原始论文中是最近邻插值Nearest Upsample因为它简单且没有可学习的参数能保持特征的稳定性。现在也常使用双线性插值或转置卷积。我个人更倾向于使用F.interpolate(..., modenearest)或modebilinear因为转置卷积会引入额外的参数可能在小数据集上导致过拟合。相加将上采样后的P5与来自C4的、经过1x1卷积调整的侧边输出进行逐元素相加。这个操作是FPN的灵魂。它相当于把深层抽象的“这是什么”信息来自P5与浅层精确的“它在哪里”信息来自C4进行了直接融合。相加后的特征图既包含了强语义又保留了相对精细的位置信息。生成最终层将相加后的结果通过一个3x3卷积这是为了消除上采样带来的混叠效应并对融合后的特征进行微调生成最终的P4。迭代重复步骤2-4用P4上采样与C3侧边输出相加生成P3再用P3与C2相加生成P2。这样我们就得到了P2, P3, P4, P5这一系列特征图。它们的空间尺寸依次减半P2是H/4P5是H/32但通道数全部是256。这意味着不同尺度的特征被“标准化”了下游的任务头如检测器的RPN或分类回归头可以用完全相同结构的子网络来处理不同层级的特征大大简化了设计。2.4 代码结构蓝图在PyTorch中我们会构建一个FPN类。它的__init__方法需要接收主干网络backbone和输出的通道数通常为256。在初始化时我们需要遍历主干网络获取C2-C5的输出层引用。创建4个1x1卷积模块nn.Conv2d用于调整Ci的通道数。创建4个3x3卷积模块nn.Conv2d用于生成最终的Pi。可选创建用于生成P6的模块通常是对P5进行一个步长为2的3x3卷积。在forward方法中就是严格按上述逻辑执行提取Ci - 1x1卷积调整 - 自顶向下融合 - 3x3卷积输出Pi。最后返回一个有序字典或列表包含P2到P5或P6。3. FPN在目标检测与实例分割中的核心应用场景FPN最初就是在Faster R-CNN和Mask R-CNN这两大经典框架中一炮而红的。理解它在这两个任务中的角色就能明白其设计为何如此精妙。3.1 在Faster R-CNN与RPN中的角色在没有FPN的时代Faster R-CNN的RPN区域提议网络和Fast R-CNN头部通常只作用在主干网络的最后一个特征图如C5上。这就导致了一个问题所有尺度的锚点Anchor都从同一个低分辨率的特征图上生成小目标对应的特征响应非常弱很难被有效检测。FPN的引入改变了游戏规则。RPN被应用到FPN输出的每一层特征图P2-P6上。具体来说分层分配在P2到P6不同尺度的特征图上设置不同大小的基础锚点。例如在分辨率高的P2上特征图尺寸大设置小尺寸的锚点如32x32, 64x64专门负责检测小物体在分辨率低的P5、P6上设置大尺寸的锚点如512x512负责检测大物体。共享头尽管特征图尺度不同但它们的通道数都是256。因此RPN可以共享同一套卷积权重一个小的3x3卷积两个1x1卷积分别用于分类和回归在不同层上进行滑动窗口操作。这极大地提升了效率也使得模型能平等地对待不同尺度的物体。我的实测经验是在COCO这种包含大量小物体的数据集上为Faster R-CNN加入FPN能让小目标AP_s的指标提升5-10个点以上效果极其显著。这背后的原因正是“专业的人做专业的事”让高分辨率的特征层去处理小目标让高语义的特征层去处理大目标。3.2 在Mask R-CNN与ROI Align中的协同Mask R-CNN在Faster R-CNN基础上增加了实例分割分支。FPN在这里的作用更加关键。多尺度ROI分配RPN在FPN各层上生成候选框Proposals后这些大小不一的框需要被分配到FPN的不同层上进行后续的精细化分类、回归和掩码预测。分配策略通常基于ROI的尺寸。一个经典的启发式公式是k floor(4 log2(sqrt(w*h)/224))其中w和h是ROI的宽高224是ImageNet预训练的参考尺寸。计算出的k值对应分配到Pk层。例如一个很小的ROI会被分配到P2或P3层利用那里的高分辨率特征来预测其细节。ROI AlignMask R-CNN用ROI Align取代了ROI Pooling解决了量化误差问题。当ROI被分配到某一特征层如P3后ROI Align从该层的特征图上精确地提取固定大小的特征网格如7x7送给后续的头部网络。由于FPN提供的特征本身已是多尺度融合的ROI Align提取到的特征质量更高这对于需要像素级精度的掩码预测至关重要。在实例分割任务中FPNROI Align的组合确保了无论物体大小其用于预测掩码的特征都来自最合适分辨率的特征层从而显著提升了分割边界的准确性尤其是对小物体而言。3.3 在单阶段检测器如RetinaNet中的关键作用单阶段检测器没有RPN和ROI Pooling这类两步操作它需要在特征图上直接进行密集预测。FPN对于单阶段检测器同样是性能支柱。以RetinaNet为例它的架构可以清晰地分为三部分主干网络Backbone、特征金字塔网络Neck即FPN、以及任务特定的子网络Head。FPN在这里的作用同样是构建多尺度、高质量的特征金字塔{P3, P4, P5, P6, P7}P6和P7通常由P5通过卷积或池化得到。RetinaNet的“头”Head由两个并行的子网组成分类子网和边框回归子网。这两个子网以共享权重的方式分别附加在FPN的每一层输出上。也就是说P3到P7这五个特征层每个都连接着结构完全相同但参数共享的分类头和回归头。这样做的好处是参数效率高模型只需学习一套适用于多尺度特征的头部参数。尺度专门化虽然头部参数共享但由于输入的特征图来自FPN的不同层级P3分辨率高、语义稍弱P7分辨率低、语义强网络在训练过程中会自然地让这些共享的头部参数适应不同尺度的检测任务。可以说没有FPN提供的这套标准化、多尺度的特征像RetinaNet这样高效的单阶段检测器很难在保持速度的同时达到与两阶段检测器媲美的精度尤其是在多尺度物体检测上。4. 使用PyTorch搭建FPN从模块到集成理论说再多不如动手写一行代码。下面我们抛开高级API用纯PyTorch构建一个可用的FPN模块并集成到一个小型检测模型中。4.1 构建基础的FPN模块我们假设主干网络是ResNet并提供了返回中间层输出的功能。以下是FPN的核心实现import torch import torch.nn as nn import torch.nn.functional as F from collections import OrderedDict class FPN(nn.Module): def __init__(self, backbone_channels_list, out_channels256): Args: backbone_channels_list (list): 主干网络返回的各层通道数例如 [256, 512, 1024, 2048] 对应C2-C5。 out_channels (int): FPN输出特征图的通道数默认为256。 super(FPN, self).__init__() self.out_channels out_channels # 构建用于调整通道数的1x1卷积层 self.lateral_convs nn.ModuleList() # 构建用于生成最终输出的3x3卷积层 self.output_convs nn.ModuleList() for in_channels in backbone_channels_list: lateral_conv nn.Conv2d(in_channels, out_channels, kernel_size1) output_conv nn.Conv2d(out_channels, out_channels, kernel_size3, padding1) # 初始化权重对于1x1卷积常用kaiming初始化 nn.init.kaiming_uniform_(lateral_conv.weight, a1) nn.init.constant_(lateral_conv.bias, 0) nn.init.kaiming_uniform_(output_conv.weight, a1) nn.init.constant_(output_conv.bias, 0) self.lateral_convs.append(lateral_conv) self.output_convs.append(output_conv) # 可选用于生成P6的额外层通常是一个步长为2的卷积或池化 self.extra_p6 nn.Conv2d(out_channels, out_channels, kernel_size3, stride2, padding1) self.extra_p7 nn.Conv2d(out_channels, out_channels, kernel_size3, stride2, padding1) nn.init.kaiming_uniform_(self.extra_p6.weight, a1) nn.init.constant_(self.extra_p6.bias, 0) nn.init.kaiming_uniform_(self.extra_p7.weight, a1) nn.init.constant_(self.extra_p7.bias, 0) def forward(self, inputs): Args: inputs (list[Tensor]): 主干网络返回的特征图列表顺序应为[C2, C3, C4, C5] 尺寸从大到小通道数对应backbone_channels_list。 Returns: dict[str, Tensor]: 输出特征金字塔字典键为p2, p3, p4, p5, p6, p7。 # 步骤1: 通过1x1卷积调整所有输入特征的通道数 laterals [] for i, x in enumerate(inputs): laterals.append(self.lateral_convs[i](x)) # 步骤2: 自顶向下构建金字塔 # 从最深层开始 pyramid_features [] # 最深层C5调整后直接作为P5的初始特征 prev_feature laterals[-1] pyramid_features.append(prev_feature) # 自顶向下迭代从倒数第二层到第一层 for i in range(len(laterals) - 2, -1, -1): # 上采样前一层特征 upsample_feature F.interpolate(prev_feature, scale_factor2, modenearest) # 与当前层的侧边输出相加 fused_feature laterals[i] upsample_feature # 用3x3卷积生成当前层最终输出 output_feature self.output_convs[i](fused_feature) pyramid_features.append(output_feature) prev_feature output_feature # pyramid_features现在是逆序的[P5, P4, P3, P2]需要反转 pyramid_features pyramid_features[::-1] # 步骤3: 生成额外的P6和P7常用于目标检测 p5 pyramid_features[-1] # 取P5 p6 self.extra_p6(p5) p7 self.extra_p7(F.relu(p6)) # 原始论文中P7是在P6的ReLU激活后得到的 # 组装成有序字典 outputs OrderedDict() outputs[p2] pyramid_features[0] outputs[p3] pyramid_features[1] outputs[p4] pyramid_features[2] outputs[p5] pyramid_features[3] outputs[p6] p6 outputs[p7] p7 return outputs这个FPN类是一个通用模块。在初始化时你需要传入主干网络各层的通道数列表。在forward中它接收一个特征图列表并返回一个包含P2到P7的特征字典。4.2 与主干网络如ResNet集成要让FPN工作我们需要一个能返回中间层特征的主干网络。通常我们会修改或包装一个标准的ResNet。import torchvision.models as models class BackboneWithFPN(nn.Module): def __init__(self, backbone_nameresnet50, pretrainedTrue): super(BackboneWithFPN, self).__init__() # 加载预训练的ResNet if backbone_name resnet50: backbone models.resnet50(pretrainedpretrained) else: raise ValueError(fUnsupported backbone: {backbone_name}) # 提取ResNet的中间层输出 self.conv1 backbone.conv1 self.bn1 backbone.bn1 self.relu backbone.relu self.maxpool backbone.maxpool self.layer1 backbone.layer1 # 输出C2 self.layer2 backbone.layer2 # 输出C3 self.layer3 backbone.layer3 # 输出C4 self.layer4 backbone.layer4 # 输出C5 # 获取各层通道数 c2_channels self.layer1[-1].conv3.out_channels # ResNet bottleneck的最终输出通道数 c3_channels self.layer2[-1].conv3.out_channels c4_channels self.layer3[-1].conv3.out_channels c5_channels self.layer4[-1].conv3.out_channels # 初始化FPN self.fpn FPN(backbone_channels_list[c2_channels, c3_channels, c4_channels, c5_channels]) def forward(self, x): # 标准ResNet前向传播捕获中间特征 x self.conv1(x) x self.bn1(x) x self.relu(x) x self.maxpool(x) c2 self.layer1(x) c3 self.layer2(c2) c4 self.layer3(c3) c5 self.layer4(c4) # 将中间特征送入FPN features self.fpn([c2, c3, c4, c5]) return features这样我们就得到了一个完整的特征提取器。输入一张图片输出就是一个包含多尺度特征的金字塔字典。这些特征可以直接喂给RPN、RetinaNet Head等检测头。4.3 一个简单的应用示例特征可视化为了直观感受FPN的效果我们可以写一个简单的脚本可视化FPN各层输出的特征图。import cv2 import numpy as np from torchvision import transforms from PIL import Image def visualize_fpn_features(model, image_path, output_dir./vis_fpn): 可视化FPN各层输出的特征图。 注意这里为了可视化对每个特征图通道取平均并上采样到原图尺寸。 import os os.makedirs(output_dir, exist_okTrue) # 1. 预处理图像 transform transforms.Compose([ transforms.Resize((800, 800)), # 固定尺寸输入 transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) img Image.open(image_path).convert(RGB) input_tensor transform(img).unsqueeze(0) # [1, 3, H, W] # 2. 模型推理 model.eval() with torch.no_grad(): features model(input_tensor) # 3. 可视化每一层 for name, feat in features.items(): # feat形状: [1, C, H, W] feat feat.squeeze(0) # [C, H, W] # 对通道维度取平均得到单通道特征图 [H, W] feat_mean feat.mean(dim0).cpu().numpy() # 归一化到[0, 1] feat_mean (feat_mean - feat_mean.min()) / (feat_mean.max() - feat_mean.min() 1e-8) # 缩放到0-255并转换为uint8 feat_vis (feat_mean * 255).astype(np.uint8) # 上采样到原输入尺寸800x800以便观察 feat_vis cv2.resize(feat_vis, (800, 800), interpolationcv2.INTER_NEAREST) # 应用颜色映射JET使其更明显 feat_colored cv2.applyColorMap(feat_vis, cv2.COLORMAP_JET) save_path os.path.join(output_dir, f{name}_feature.jpg) cv2.imwrite(save_path, feat_colored) print(fSaved {save_path}) # 4. 保存原图对比 img_np np.array(img.resize((800, 800))) cv2.imwrite(os.path.join(output_dir, original.jpg), cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR)) # 使用示例 if __name__ __main__: model BackboneWithFPN(pretrainedTrue) visualize_fpn_features(model, your_image.jpg)运行这个脚本你会看到p2的特征图非常精细几乎能看清物体的纹理和边缘但可能包含很多背景噪声而p5或p6的特征图非常粗糙可能只在高语义区域如整个物体、人脸有强烈响应。这正是FPN价值的直观体现它为你提供了从细节到语义的完整特征谱系。5. 训练技巧、常见问题与调优实战把FPN搭起来跑通只是第一步要让它在你的任务上发挥最大威力还需要一些训练技巧和对常见问题的深刻理解。5.1 初始化与学习率策略FPN中的新增层1x1和3x3卷积需要正确初始化。如上文代码所示通常使用Kaiming初始化kaiming_uniform_或kaiming_normal_这是针对ReLU激活函数的推荐方法。偏置初始化为0。在训练时一个常见的策略是差异化学习率。主干网络如ResNet通常使用预训练权重在训练初期应该用较小的学习率进行微调以免破坏已经学到的通用特征。而FPN新增的层是随机初始化的可以从头开始学习因此可以使用比主干网络更高的学习率。例如在配置优化器时from torch.optim import SGD # 假设我们有一个包含backbone和fpn的模型 model BackboneWithFPN() # 将参数分组 backbone_params [] fpn_params [] for name, param in model.named_parameters(): if fpn in name: fpn_params.append(param) else: backbone_params.append(param) # 为不同组设置不同的学习率 optimizer SGD([ {params: backbone_params, lr: base_lr * 0.1}, # 主干网络学习率小 {params: fpn_params, lr: base_lr} # FPN层学习率大 ], momentum0.9, weight_decay1e-4)5.2 特征图尺寸对齐的坑在FPN的forward过程中最易出错的就是特征图尺寸对齐。上采样后的特征图必须与侧边输出的特征图尺寸完全一致才能相加。如果输入图像的尺寸不是2的幂次或者主干网络的下采样总步长不是32对于ResNet可能会导致中间层特征图尺寸出现小数进而导致尺寸不匹配。解决方案固定输入尺寸在训练和推理时将输入图像resize到固定的尺寸如800x1333或800x800。这是最常见和最简单的方法被许多检测框架MMDetection, Detectron2采用。使用动态尺寸如果必须支持可变尺寸输入则需要确保上采样操作能精确地将特征图尺寸放大到目标尺寸。可以使用F.interpolate(..., size(target_h, target_w), modenearest)来指定精确的输出尺寸而不是简单的scale_factor2。在构建网络时需要小心计算每一层的理论输出尺寸。5.3 梯度流动与训练不稳定FPN结构包含跳跃连接相加操作这通常有利于梯度流动。但在某些极端情况下如果来自深层和浅层的特征数值范围差异巨大例如由于初始化或学习率不当相加操作可能导致训练初期不稳定。排查与解决梯度检查在训练初期可以使用torch.autograd.grad或hook来检查FPN各层的梯度范数。如果发现某层梯度爆炸或消失需要检查该层的初始化。添加归一化一个实践技巧是在横向连接的1x1卷积后或相加操作后加入一个批归一化BatchNorm层。这有助于稳定特征的分布加速收敛。许多后续的改进版FPN如PANet都加入了BN层。谨慎使用激活函数如前所述在1x1卷积后和相加操作前通常不立即使用ReLU。激活函数应放在3x3卷积之后。5.4 内存消耗与计算优化FPN增加了额外的卷积层和更大的特征图尤其是P2这会增加显存占用和计算量。在资源受限的情况下可以考虑以下优化减少FPN输出层数如果您的任务中极小物体不多可以考虑只使用P3-P5甚至P4-P5舍弃P2。这能显著减少计算量。降低输出通道数将FPN的输出通道数从256减少到128或64。这会降低后续检测头的参数量但可能会牺牲一些性能。使用更轻量的上采样用双线性插值代替转置卷积可以减少参数。混合精度训练使用PyTorch的AMPAutomatic Mixed Precision进行混合精度训练可以大幅减少显存占用并可能加快训练速度。5.5 在自定义数据集上的调优经验在我的一个工业缺陷检测项目中缺陷尺寸从几个像素到几百个像素不等。直接使用标准的COCO预训练模型带FPN效果不佳。以下是我们的调优过程锚点尺寸重设计COCO的锚点尺寸是为自然场景设计的32, 64, 128...。我们的缺陷很小因此我们重新设计了RPN在P2-P5上的锚点基础尺寸。例如在P2上设置锚点尺寸为4, 8, 16在P3上设置8, 16, 32以此类推。这能让RPN在早期层就生成匹配小缺陷的候选框。ROI分配参数调整在Mask R-CNN中ROI分配到FPN哪一层的公式k floor(4 log2(sqrt(wh)/224))中的224和4是可以调整的。我们通过分析训练集中标注框的面积分布微调了这个公式的偏移量使得更多的小框被分配到分辨率更高的层。数据增强侧重小目标使用了更多针对小目标的增强如随机裁剪确保裁剪后小目标还在、 mosaic增强将多张小图拼成大图模拟小目标密集场景等。损失函数权重调整由于小目标难检测我们适当增加了RPN阶段对于小锚点样本的分类损失权重让模型更关注小目标的识别。经过这些调整模型对小缺陷的召回率从不足60%提升到了85%以上。这个经验说明FPN提供了一个强大的多尺度特征框架但要根据具体任务的数据特性进行细致的“调参”才能让它发挥出最大潜力。6. FPN的变体与演进思路FPN的成功催生了一系列改进工作了解它们能帮助我们根据任务需求选择或设计更合适的结构。6.1 PANetPath Aggregation NetworkPANet在FPN的基础上增加了一个自底向上Bottom-up的增强路径。FPN只有自顶向下的融合路径PANet认为浅层特征在传递到深层时其信息会逐渐稀释。因此它在FPN的金字塔构建完成后又增加了一个从浅层到深层的二次融合路径让低层的高分辨率信息也能直接增强高层特征。这在实例分割任务上带来了进一步的提升尤其是对物体边界的分割更加精细。6.2 BiFPNBidirectional Feature Pyramid NetworkBiFPN出自EfficientDet它主要做了两点改进移除只有一条输入边的节点认为它们对特征融合贡献小以及增加同一层级的跨尺度连接。更关键的是它引入了可学习的权重来对不同分辨率的输入特征进行加权融合而不是简单的相加。这种加权融合让网络能自适应的学习哪些尺度的特征更重要。BiFPN的设计更加高效在计算量和精度之间取得了更好的平衡被广泛应用于轻量级检测模型中。6.3 NAS-FPNNeural Architecture Search for Feature Pyramid NetworksNAS-FPN是使用神经网络架构搜索NAS技术来自动设计特征金字塔结构。它摆脱了人工设计的FPN、PANet等固定模式让搜索算法在给定的计算预算下寻找最优的特征融合连接方式。NAS-FPN找到的结构往往比人工设计更复杂、更不对称但在特定硬件和数据集上能达到更高的性能。这给我们一个启示对于有充足计算资源的重要项目可以尝试用NAS来搜索一个定制化的特征融合模块。6.4 将FPN思想融入Transformer随着Vision TransformerViT及其变体的兴起多尺度特征融合的思想也被引入。例如Swin Transformer通过划窗Shifted Windows和分层下采样自然构建了多尺度特征表示。许多基于Transformer的检测器如DETR的变体也会在Transformer编码器后接入一个类似FPN的结构或者设计跨尺度的注意力机制来融合不同层级的特征。这说明无论底层架构是CNN还是Transformer“多尺度特征融合”这一核心思想是通用的、持续有效的。选择哪种变体我的建议是从标准的FPN开始。它简单、稳定、易于实现并且在绝大多数任务上都能带来显著的基线提升。当标准FPN成为性能瓶颈或者你有极致的精度或效率需求时再考虑引入PANet追求精度、BiFPN追求效率平衡或探索基于NAS/Transformer的先进结构。