suzai 发表于 2025-2-9 17:06:31

特征金字塔网络及变体【详解及代码实现】

在深度学习和计算机视觉领域,架构创新在推动技术进步中发挥了重要作用。在这些创新中,特征金字塔网络(Feature Pyramid Networks, FPN)脱颖而出,成为革命性的基础构建模块,彻底改变了我们在神经网络中处理多尺度特征表示的方式。本文将深入探讨FPN。

一、现代神经网络的三个主要组成部分(骨干网络、颈部网络和头部网络)  


在深入FPN之前,了解现代计算机视觉神经网络的三个主要组成部分非常重要:
1. 骨干网络(Backbone)

骨干网络通常是一个卷积神经网络(如ResNet或VGG),作为主要特征提取器。它处理原始输入图像,并生成不同尺度的层次化特征表示。可以将其视为捕捉从边缘、纹理到高级语义信息的基础。
2. 颈部网络(Neck)

颈部网络是骨干网络和头部网络之间的特征融合和增强模块。其主要目的是处理和组合来自骨干网络不同尺度或阶段的特征,以生成更具判别性的特征表示。可以将其视为一个加工厂,将来自不同来源的原材料(特征)提炼成更有用的产品。  
颈部网络可以执行多种操作,例如:  

[*]跨不同尺度的特征融合  
[*]通过额外卷积增强特征  
[*]管理不同网络层级之间的信息流  
特征金字塔网络是颈部网络架构的一种流行实现,但还有其他实现,如路径聚合网络(PANet)和高分辨率网络(HRNet)。
3. 头部网络(Head)

头部网络是任务特定的组件,使用经过优化的特征进行最终预测。不同任务(检测、分割、分类)需要不同的头部架构,但它们都受益于颈部网络提供的经过良好处理的特征。
二、为什么需要特征金字塔网络?  

计算机视觉中的多尺度挑战源于传统CNN架构的多个基本限制:  
1. 特征层次问题

随着CNN的深入,空间分辨率降低,而语义层次提高。例如,在典型的ResNet中:  

[*]早期层(如Conv1)具有1/2分辨率,捕捉基本特征(边缘、纹理)  
[*]中间层(如Conv3)具有1/8分辨率,捕捉中级特征(部件、模式)  
[*]深层(如Conv5)具有1/32分辨率,捕捉高级特征(物体、场景)  
2. 尺度变化

自然图像中的物体以不同的尺度出现。例如,在自动驾驶中:  

[*]附近的行人可能占据300x600像素  
[*]远处的车辆可能仅占据30x60像素  
[*]交通标志可能以任何大小出现  
3. 信息丢失 

传统的特征金字塔(如图像金字塔)保持了空间分辨率,但在较低层次缺乏语义强度,使其在现代深度学习中效率低下。这些问题的结合构成了计算机视觉中的重大挑战。  
现实世界的例子:想象一辆自动驾驶汽车试图检测街道上的物体。  摄像头看到的物体距离不同,有些近,有些远。  要检测远处的行人,系统需要处理高分辨率(详细)图像以捕捉小细节。  但问题在于:处理这些详细图像的早期网络层并不擅长理解内容。它们可能看到人的基本形状,但无法区分是行人还是路灯杆,因为它们缺乏深层次的理解。  
为什么不使用传统方法(如图像金字塔)?  这种方法提取的特征信息不够丰富,无法真正用于现代深度学习。  
我们陷入了两难选择: 要么获得良好的细节但理解力差,要么获得良好的理解力但细节差。这就像在放大镜(能看清细节但无法识别物体)和模糊眼镜(能识别物体但看不清细节)之间做出选择。  这种“看清”与“理解”之间的权衡正是研究人员开发特征金字塔网络的原因——最终解决这一困境。
三、特征金字塔网络(FPN)

图片、、
FPN通过三个关键组件结合了低层次和高层次特征:  
(1) 自下而上路径(骨干网络)

[*]这是常规的卷积神经网络前向传播。  
[*]特征逐渐变得更语义化,但空间分辨率降低。  
[*]每个阶段输出不同尺度的特征图(C₂, C₃, C₄, C₅)。  
(2) 自上而下路径

[*]从最深层开始,逐步上采样空间较粗糙但语义较强的特征。  
[*]创建更高分辨率的特征(P₅, P₄, P₃, P₂)。  
[*]使用最近邻上采样来增加分辨率。  
(3) 横向连接 

[*]1x1卷积减少骨干网络特征的通道维度。  
[*]逐元素加法合并自下而上和自上而下的特征。  
[*]3x3卷积平滑合并后的特征。  
1. 技术过程


[*]提取自下而上的特征 {C₂, C₃, C₄, C₅}  
[*]通过1x1卷积处理顶层特征C₅以创建P₅  
[*]上采样P₅并与处理后的C₄合并以创建P₄  
[*]此过程持续到P₂  
[*]最终金字塔的每个层级 {P₂, P₃, P₄, P₅} 包含丰富的语义信息,同时保持适当的空间分辨率  
2. 示例代码  

以下是一个使用ResNet-18骨干网络实现FPN进行图像分类的示例代码。  
import torchimport torch.nn as nnimport torchvision.models as modelsclass FPNNeck(nn.Module):    def __init__(self, in_channels_list, out_channels):      super(FPNNeck, self).__init__()                # Lateral connections (1x1 convolutions)      self.lateral_convs = nn.ModuleList([            nn.Conv2d(in_channels, out_channels, 1)            for in_channels in in_channels_list      ])                # Top-down pathway (upsampling + smoothing)      self.fpn_convs = nn.ModuleList([            nn.Conv2d(out_channels, out_channels, 3, padding=1)            for _ in range(len(in_channels_list))      ])            def forward(self, features):      # features should be ordered from highest resolution to lowest      laterals =                 # Top-down pathway      for i in range(len(laterals)-1, 0, -1):            laterals += nn.functional.interpolate(                laterals, size=laterals.shape[-2:], mode='nearest'            )                # Smoothing      outputs =       return outputsclass ResNetFPN(nn.Module):    def __init__(self, num_classes):      super(ResNetFPN, self).__init__()                # Load pretrained ResNet-18 as backbone      resnet = models.resnet18(pretrained=True)      self.backbone_layers = nn.ModuleList([            nn.Sequential(resnet.conv1, resnet.bn1, resnet.relu, resnet.maxpool, resnet.layer1),            resnet.layer2,            resnet.layer3,            resnet.layer4      ])                # FPN neck      in_channels_list = # ResNet-18 output channels      self.fpn = FPNNeck(in_channels_list, out_channels=256)                # Classification head      self.avgpool = nn.AdaptiveAvgPool2d((1, 1))      self.fc = nn.Linear(256 * 4, num_classes)# 4 feature maps from FPN            def forward(self, x):      # Extract features from backbone      features = []      for layer in self.backbone_layers:            x = layer(x)            features.append(x)                  # FPN forward pass      fpn_features = self.fpn(features)                # Global average pooling on each FPN level      pooled_features = []      for feature in fpn_features:            pooled = self.avgpool(feature)            pooled_features.append(pooled.flatten(1))                  # Concatenate all pooled features      x = torch.cat(pooled_features, dim=1)      x = self.fc(x)                return x

[*]1.
[*]2.
[*]3.
[*]4.
[*]5.
[*]6.
[*]7.
[*]8.
[*]9.
[*]10.
[*]11.
[*]12.
[*]13.
[*]14.
[*]15.
[*]16.
[*]17.
[*]18.
[*]19.
[*]20.
[*]21.
[*]22.
[*]23.
[*]24.
[*]25.
[*]26.
[*]27.
[*]28.
[*]29.
[*]30.
[*]31.
[*]32.
[*]33.
[*]34.
[*]35.
[*]36.
[*]37.
[*]38.
[*]39.
[*]40.
[*]41.
[*]42.
[*]43.
[*]44.
[*]45.
[*]46.
[*]47.
[*]48.
[*]49.
[*]50.
[*]51.
[*]52.
[*]53.
[*]54.
[*]55.
[*]56.
[*]57.
[*]58.
[*]59.
[*]60.
[*]61.
[*]62.
[*]63.
[*]64.
[*]65.
[*]66.
[*]67.
[*]68.
[*]69.
[*]70.
[*]71.
[*]72.
[*]73.
[*]74.
[*]75.
[*]76.





import torchimport torch.nn as nnimport torchvision.models as modelsclass FPNNeck(nn.Module):    def __init__(self, in_channels_list, out_channels):      super(FPNNeck, self).__init__()                # Lateral connections (1x1 convolutions)      self.lateral_convs = nn.ModuleList([            nn.Conv2d(in_channels, out_channels, 1)            for in_channels in in_channels_list      ])                # Top-down pathway (upsampling + smoothing)      self.fpn_convs = nn.ModuleList([            nn.Conv2d(out_channels, out_channels, 3, padding=1)            for _ in range(len(in_channels_list))      ])

[*]1.
[*]2.
[*]3.
[*]4.
[*]5.
[*]6.
[*]7.
[*]8.
[*]9.
[*]10.
[*]11.
[*]12.
[*]13.
[*]14.
[*]15.
[*]16.
[*]17.
[*]18.
[*]19.





FPNNeck类实现了FPN的核心架构:  

[*]`lateral_convs` 创建1x1卷积,减少来自不同层级骨干网络特征的通道维度。  
[*]`fpn_convs` 是3x3卷积,用于平滑合并后的特征。  
def forward(self, features):      # features should be ordered from highest resolution to lowest      laterals =                 # Top-down pathway      for i in range(len(laterals)-1, 0, -1):            laterals += nn.functional.interpolate(                laterals, size=laterals.shape[-2:], mode='nearest'            )                # Smoothing      outputs =       return outputs

[*]1.
[*]2.
[*]3.
[*]4.
[*]5.
[*]6.
[*]7.
[*]8.
[*]9.
[*]10.
[*]11.
[*]12.
[*]13.





前向传播展示了FPN如何处理特征:

[*]它对所有特征层级应用横向卷积。  
[*]它实现自上而下的路径:从最深层开始,上采样特征并将其添加到上一层级。  
[*]它对所有层级应用平滑卷积。
class ResNetFPN(nn.Module):    def __init__(self, num_classes):      super(ResNetFPN, self).__init__()                # Load pretrained ResNet-18 as backbone      resnet = models.resnet18(pretrained=True)      self.backbone_layers = nn.ModuleList([            nn.Sequential(resnet.conv1, resnet.bn1, resnet.relu, resnet.maxpool, resnet.layer1),            resnet.layer2,            resnet.layer3,            resnet.layer4      ])

[*]1.
[*]2.
[*]3.
[*]4.
[*]5.
[*]6.
[*]7.
[*]8.
[*]9.
[*]10.
[*]11.
[*]12.





ResNetFPN类组合所有内容:  

[*]使用预训练的ResNet-18作为骨干网络。  
[*]添加FPN颈部网络以处理来自ResNet四个阶段的特征。  
[*]添加一个简单的分类头,用于池化每个FPN层级的特征并最终分类。  
# FPN neck      in_channels_list = # ResNet-18 output channels      self.fpn = FPNNeck(in_channels_list, out_channels=256)                # Classification head      self.avgpool = nn.AdaptiveAvgPool2d((1, 1))      self.fc = nn.Linear(256 * 4, num_classes)# 4 feature maps from FPN

[*]1.
[*]2.
[*]3.
[*]4.
[*]5.
[*]6.
[*]7.





对于分类任务:我们创建FPN颈部网络,用于处理来自ResNet四个阶段的特征。我们添加一个简单的分类头,其功能包括:

[*]对每个FPN层级的特征进行池化
[*]将它们连接在一起
[*]进行最终的分类
def forward(self, x):      # Extract features from backbone      features = []      for layer in self.backbone_layers:            x = layer(x)            features.append(x)                  # FPN forward pass      fpn_features = self.fpn(features)                # Global average pooling on each FPN level      pooled_features = []      for feature in fpn_features:            pooled = self.avgpool(feature)            pooled_features.append(pooled.flatten(1))                  # Concatenate all pooled features      x = torch.cat(pooled_features, dim=1)      x = self.fc(x)                return x

[*]1.
[*]2.
[*]3.
[*]4.
[*]5.
[*]6.
[*]7.
[*]8.
[*]9.
[*]10.
[*]11.
[*]12.
[*]13.
[*]14.
[*]15.
[*]16.
[*]17.
[*]18.
[*]19.
[*]20.
[*]21.





前向传播将所有内容结合在一起:

[*]输入图像通过ResNet骨干网络,收集每个阶段的特征。
[*]这些特征通过FPN颈部网络,生成特征金字塔。
[*]我们对金字塔的每个层级的特征进行池化。
[*]最后,我们结合所有这些特征以进行最终的分类预测。
四、变体(特征金字塔网络的演进)  

自FPN诞生以来,研究人员对其进行了多种改进。以下是一些常见变体:  
1. PANet(路径聚合网络)


[*]通过添加额外的自下而上路径增强信息流。  
[*]用于实例分割的Mask Scoring R-CNN和实时目标检测的Thunder-Net。  
2. BiFPN(双向FPN)


[*]引入加权双向跨尺度连接。  
[*]用于EfficientDet系列目标检测器。  
3. 最新模型(2023-2024)


[*]RT-DETR:使用基于变形Transformer的FPN变体。  
[*]DINO-V2:实现混合FPN-Transformer颈部网络。  
[*]YOLOv8:采用受FPN启发的改进CSP-PAN颈部网络。  
五、结论  

特征金字塔网络代表了计算机视觉架构设计的一项重大成就。其有效处理多尺度特征表示并保持计算效率的能力,使其成为现代计算机视觉系统中不可或缺的组成部分。随着领域的不断发展,FPN的影响可以在新架构中看到,其设计原则继续激发神经网络设计的创新。  
页: [1]
查看完整版本: 特征金字塔网络及变体【详解及代码实现】