深度学习笔记(十一):各种特征金字塔合集

简介: 这篇文章详细介绍了特征金字塔网络(FPN)及其变体PAN和BiFPN在深度学习目标检测中的应用,包括它们的结构、特点和代码实现。

摘要

目标检测中的金字塔集合通常指的是特征金字塔网络(Feature Pyramid Networks,FPN),这是一种在深度学习目标检测模型中常用的结构,用于构建多尺度的特征表示,以检测不同尺寸的目标。特征金字塔网络通过结合深层(高语义但低分辨率)和浅层(低语义但高分辨率)的特征图,生成一个多尺度的特征金字塔,每个尺度的特征图都适合检测特定尺寸的目标。

特征金字塔网络的主要组成部分包括:

  1. 自底向上路径(Bottom-Up Pathway):这是标准的卷积神经网络路径,通过连续的卷积和池化操作,逐渐降低特征图的空间分辨率,同时增加特征图的深度,从而提取图像的高层语义信息。

  2. 自顶向下路径(Top-Down Pathway):通过将深层的高语义特征图进行上采样(如使用反卷积或双线性插值),增加其空间分辨率,使其更适合检测小尺寸目标。

  3. 横向连接(Lateral Connections):在自顶向下路径中,将上采样的深层特征图与相应尺度的浅层特征图通过1x1卷积融合,以增强特征图的语义信息。

  4. 特征融合:通过上述结构,特征金字塔网络能够在不同尺度的特征图上同时保留丰富的语义信息和细节信息,从而有效地检测不同尺寸的目标。

特征金字塔网络在多种目标检测模型中得到应用,如Faster R-CNN、Mask R-CNN、RetinaNet等,显著提高了模型对多尺度目标的检测性能。

此外,还有一些变种和改进的结构,例如:

  • BiFPN:构建了带有自学习权重的特征融合模块,然后重复使用该模块。
  • PANet:在FPN的基础上添加了自底向上的路径聚合模块,进一步融合信息。
  • NAS-FPN:采用网络结构搜索的方法,得出新的特征融合模块。

这些结构的共同目标是更有效地融合不同尺度的特征,以提高目标检测的准确性和效率。

FPN

特征金字塔网络(Feature Pyramid Networks,FPN)是一种在计算机视觉领域中用于目标检测任务的网络结构,它由Tsung-Yi Lin等人在2017年的CVPR会议上提出。FPN的主要贡献是有效地结合了深层和浅层特征,以处理不同尺寸的目标检测问题。以下是FPN的关键特点和组件:

  1. 自底向上路径(Bottom-Up Pathway)

    • 这是标准的卷积神经网络层,通过连续的卷积和池化操作逐渐减少特征图的空间维度,同时增加通道数,从而提取图像的高层语义信息。
  2. 自顶向下路径(Top-Down Pathway)

    • FPN通过自顶向下的路径来增强深层特征图的分辨率。这通常通过将高层的特征图进行上采样(如使用最近邻或双线性插值)来实现,以便与低层的特征图具有相似的空间分辨率。
  3. 横向连接(Lateral Connections)

    • 为了将低层特征图的高分辨率细节信息和高层特征图的强语义信息结合起来,FPN引入了横向连接。这些连接通常是1x1的卷积操作,用于匹配特征图的通道数,并将自顶向下路径的特征图与自底向上路径的特征图融合。
  4. 特征融合

    • 通过横向连接融合的特征图,FPN生成了一个新的特征金字塔,每个级别的特征图都包含了丰富的语义信息和空间信息,适合检测不同尺寸的目标。
  5. 多尺度特征预测

    • FPN的输出是一系列多尺度的特征图,这些特征图可以直接用于目标检测任务,如边框回归和分类。
  6. 应用广泛

    • FPN不仅提高了目标检测的准确性,还提高了检测的效率。它被广泛应用于各种目标检测模型中,如Faster R-CNN、Mask R-CNN、RetinaNet等。
  7. 改进和变体

    • 由于FPN的成功,研究者们提出了许多基于FPN的改进和变体,如BiFPN、PANet、NAS-FPN等,这些变体进一步优化了特征融合和多尺度特征的表示。

FPN的提出,解决了深度卷积神经网络在目标检测中对小目标检测能力不足的问题,并且提高了对大目标的检测精度。通过构建特征金字塔,FPN使得网络能够同时捕捉到图像的全局语义信息和局部细节信息,从而在多尺度目标检测任务中取得了显著的性能提升。

# Modification 2020 RangiLyu
# Copyright 2018-2019 Open-MMLab.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import torch.nn as nn
import torch.nn.functional as F
from ..module.conv import ConvModule
from ..module.init_weights import xavier_init

class FPN(nn.Module):

    def __init__(self,
                 in_channels,
                 out_channels,
                 num_outs,
                 start_level=0,
                 end_level=-1,
                 conv_cfg=None,
                 norm_cfg=None,
                 activation=None
                 ):
        super(FPN, self).__init__()
        assert isinstance(in_channels, list)
        self.in_channels = in_channels
        self.out_channels = out_channels
        self.num_ins = len(in_channels)
        self.num_outs = num_outs
        self.fp16_enabled = False

        if end_level == -1:
            self.backbone_end_level = self.num_ins
            assert num_outs >= self.num_ins - start_level
        else:
            # if end_level < inputs, no extra level is allowed
            self.backbone_end_level = end_level
            assert end_level <= len(in_channels)
            assert num_outs == end_level - start_level
        self.start_level = start_level
        self.end_level = end_level
        self.lateral_convs = nn.ModuleList()

        for i in range(self.start_level, self.backbone_end_level):
            l_conv = ConvModule(
                in_channels[i],
                out_channels,
                1,
                conv_cfg=conv_cfg,
                norm_cfg=norm_cfg,
                activation=activation,
                inplace=False)

            self.lateral_convs.append(l_conv)
        self.init_weights()

    # default init_weights for conv(msra) and norm in ConvModule
    def init_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                xavier_init(m, distribution='uniform')

    def forward(self, inputs):
        assert len(inputs) == len(self.in_channels)

        # build laterals
        laterals = [
            lateral_conv(inputs[i + self.start_level])
            for i, lateral_conv in enumerate(self.lateral_convs)
        ]

        # build top-down path
        used_backbone_levels = len(laterals)
        for i in range(used_backbone_levels - 1, 0, -1):
            prev_shape = laterals[i - 1].shape[2:]
            laterals[i - 1] += F.interpolate(
                laterals[i], scale_factor=2, mode='bilinear')

        # build outputs
        outs = [
            # self.fpn_convs[i](laterals[i]) for i in range(used_backbone_levels)
            laterals[i] for i in range(used_backbone_levels)
        ]
        return tuple(outs)

# if __name__ == '__main__':

PAN

PANet(Path Aggregation Network)是一种用于实例分割的深度学习网络,由Shu Liu等人在2018年的CVPR会议上提出。它在COCO 2017挑战赛中取得了实例分割任务第一名和目标检测任务第二名的优异成绩。PANet的核心贡献在于它通过增强信息流和整合不同层级的特征来提高预测掩码的质量。

PANet的主要特点包括:

  1. 自底向上的路径增强(Bottom-up Path Augmentation):这一特性通过在低层使用精确的定位信号来增强整个特征层次结构,从而缩短了从底层到顶层的信息路径。

  2. 自适应特征池化(Adaptive Feature Pooling):它允许每个提议(proposal)获取所有特征层级的信息,避免了仅依赖于被人为指定的某个特征层。

  3. 全连接融合(Fully-connected Fusion):在掩码预测分支中增加全连接层,与原有的FCN路径互补,以获取不同视角的预测结果进行融合,从而提高分割质量。

PANet在设计上有几个关键组件:

  • 特征金字塔网络(FPN):作为基础网络,为路径聚合模块提供了多尺度的特征图。
  • 路径聚合模块:包括自上而下和自下而上的路径聚合,通过横向连接和逐点加法操作整合不同尺度的信息。

PANet的优势在于:

  • 提高了对小目标和遮挡目标的处理能力。
  • 提升了模型的泛化性能。
  • 降低了计算复杂度,使得模型在实际应用中更加高效。

PANet在目标检测与实例分割任务中取得了显著的成果,证明了其在处理小目标和遮挡目标时的强大能力。此外,PANet采用了多尺度特征融合的方式,使得模型能够适应不同尺度的目标,从而提高了模型的泛化性能。

import torch.nn as nn
import torch.nn.functional as F
from ..module.conv import ConvModule
from .fpn import FPN

class PAN(FPN):
    """Path Aggregation Network for Instance Segmentation.

    This is an implementation of the `PAN in Path Aggregation Network
    <https://arxiv.org/abs/1803.01534>`_.

    Args:
        in_channels (List[int]): Number of input channels per scale.
        out_channels (int): Number of output channels (used at each scale)
        num_outs (int): Number of output scales.
        start_level (int): Index of the start input backbone level used to
            build the feature pyramid. Default: 0.
        end_level (int): Index of the end input backbone level (exclusive) to
            build the feature pyramid. Default: -1, which means the last level.
        add_extra_convs (bool): Whether to add conv layers on top of the
            original feature maps. Default: False.
        extra_convs_on_inputs (bool): Whether to apply extra conv on
            the original feature from the backbone. Default: False.
        relu_before_extra_convs (bool): Whether to apply relu before the extra
            conv. Default: False.
        no_norm_on_lateral (bool): Whether to apply norm on lateral.
            Default: False.
        conv_cfg (dict): Config dict for convolution layer. Default: None.
        norm_cfg (dict): Config dict for normalization layer. Default: None.
        act_cfg (str): Config dict for activation layer in ConvModule.
            Default: None.
    """

    def __init__(self,
                 in_channels,
                 out_channels,
                 num_outs, # 输出规模的数量
                 start_level=0, # 用于起始输入骨干级别的索引构建特征金字塔
                 end_level=-1,
                 conv_cfg=None,
                 norm_cfg=None,
                 activation=None):
        super(PAN,
              self).__init__(in_channels, out_channels, num_outs, start_level,
                             end_level, conv_cfg, norm_cfg, activation)
        self.init_weights()

    def forward(self, inputs):
        """Forward function."""
        assert len(inputs) == len(self.in_channels)

        # build laterals
        laterals = [
            lateral_conv(inputs[i + self.start_level])
            for i, lateral_conv in enumerate(self.lateral_convs)
        ]

        # build top-down path
        used_backbone_levels = len(laterals)  # 3
        for i in range(used_backbone_levels - 1, 0, -1):
            laterals[i - 1] += F.interpolate(
                laterals[i], scale_factor=2, mode='bilinear')

        # build outputs
        # part 1: from original levels
        inter_outs = [
            laterals[i] for i in range(used_backbone_levels)
        ]

        # part 2: add bottom-up path
        for i in range(0, used_backbone_levels - 1):
            inter_outs[i + 1] += F.interpolate(inter_outs[i], scale_factor=0.5, mode='bilinear')

        outs = []
        outs.append(inter_outs[0])
        outs.extend([
            inter_outs[i] for i in range(1, used_backbone_levels)
        ])
        # print(outs)
        return tuple(outs)

BiFPN

BiFPN(Bidirectional Feature Pyramid Network)是一种高效的特征融合网络,用于目标检测和图像分割任务。它在传统的特征金字塔网络(FPN)的基础上进行了优化,引入了双向特征融合机制和加权特征融合方法,以提高特征融合的效率和效果。

BiFPN的核心特点包括:

  1. 双向特征融合:BiFPN不仅保持了自顶向下的特征融合路径(如传统FPN),还引入了自底向上的路径,使得不同尺度的特征可以在两个方向上进行有效的信息交流和融合。

  2. 加权特征融合:在特征融合过程中,BiFPN引入了可学习的权重,以区分不同输入特征的重要性,从而优化特征融合的效果。

  3. 结构优化:BiFPN通过移除只有一个输入边的节点、添加同一层级的输入输出节点之间的额外边,并将每个双向路径视为一个特征网络层,重复多次以实现更高级别的特征融合。

  4. 多尺度特征融合:BiFPN能够处理不同尺度的特征图,并在融合过程中考虑到这些尺度变化,通过上下采样等操作,有效地处理不同分辨率的特征图。

BiFPN在设计上非常灵活,可以适应不同的网络架构和任务需求。它通过双向特征融合和加权特征融合,克服了传统FPN的局限性,提高了特征融合的效率和效果。这种灵活高效的设计使其在计算机视觉任务中得到广泛应用,为提升模型性能提供了有力支持。

在实际应用中,BiFPN被集成到EfficientDet模型中,作为其特征融合网络部分,显著提升了模型的检测性能。EfficientDet模型结合了EfficientNet骨干网络和BiFPN特征融合网络,通过高效的特征融合机制,在目标检测任务中实现了优异的性能。

import torch
import torch.nn as nn
import torch.nn.functional as F

from torch.autograd import Variable

class DepthwiseConvBlock(nn.Module):
    """
    Depthwise seperable convolution.


    """

    def __init__(self, in_channels, out_channels, kernel_size=1, stride=1, padding=0, dilation=1, freeze_bn=False):
        super(DepthwiseConvBlock, self).__init__()
        self.depthwise = nn.Conv2d(in_channels, in_channels, kernel_size, stride,
                                   padding, dilation, groups=in_channels, bias=False)
        self.pointwise = nn.Conv2d(in_channels, out_channels, kernel_size=1,
                                   stride=1, padding=0, dilation=1, groups=1, bias=False)

        self.bn = nn.BatchNorm2d(out_channels, momentum=0.9997, eps=4e-5)
        self.act = nn.ReLU()

    def forward(self, inputs):
        x = self.depthwise(inputs)
        x = self.pointwise(x)
        x = self.bn(x)
        return self.act(x)

class ConvBlock(nn.Module):
    """
    Convolution block with Batch Normalization and ReLU activation.

    """

    def __init__(self, in_channels, out_channels, kernel_size=1, stride=1, padding=0, dilation=1, freeze_bn=False):
        super(ConvBlock, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, stride=stride, padding=padding)
        self.bn = nn.BatchNorm2d(out_channels, momentum=0.9997, eps=4e-5)
        self.act = nn.ReLU()

    def forward(self, inputs):
        x = self.conv(inputs)
        x = self.bn(x)
        return self.act(x)

class BiFPNBlock(nn.Module):
    """
    Bi-directional Feature Pyramid Network
    """

    def __init__(self, feature_size=64, epsilon=0.0001):
        super(BiFPNBlock, self).__init__()
        self.epsilon = epsilon

        self.p3_td = DepthwiseConvBlock(feature_size, feature_size)
        self.p4_td = DepthwiseConvBlock(feature_size, feature_size)
        self.p5_td = DepthwiseConvBlock(feature_size, feature_size)
        self.p6_td = DepthwiseConvBlock(feature_size, feature_size)

        self.p4_out = DepthwiseConvBlock(feature_size, feature_size)
        self.p5_out = DepthwiseConvBlock(feature_size, feature_size)
        self.p6_out = DepthwiseConvBlock(feature_size, feature_size)
        self.p7_out = DepthwiseConvBlock(feature_size, feature_size)

        # TODO: Init weights
        self.w1 = nn.Parameter(torch.Tensor(2, 4))
        self.w1_relu = nn.ReLU()
        self.w2 = nn.Parameter(torch.Tensor(3, 4))
        self.w2_relu = nn.ReLU()

    def forward(self, inputs):
        p3_x, p4_x, p5_x, p6_x, p7_x = inputs

        # Calculate Top-Down Pathway
        w1 = self.w1_relu(self.w1)
        w1 /= torch.sum(w1, dim=0) + self.epsilon
        w2 = self.w2_relu(self.w2)
        w2 /= torch.sum(w2, dim=0) + self.epsilon

        p7_td = p7_x
        p6_td = self.p6_td(w1[0, 0] * p6_x + w1[1, 0] * F.interpolate(p7_x, scale_factor=2))
        p5_td = self.p5_td(w1[0, 1] * p5_x + w1[1, 1] * F.interpolate(p6_x, scale_factor=2))
        p4_td = self.p4_td(w1[0, 2] * p4_x + w1[1, 2] * F.interpolate(p5_x, scale_factor=2))
        p3_td = self.p3_td(w1[0, 3] * p3_x + w1[1, 3] * F.interpolate(p4_x, scale_factor=2))

        # Calculate Bottom-Up Pathway
        p3_out = p3_td
        p4_out = self.p4_out(w2[0, 0] * p4_x + w2[1, 0] * p4_td + w2[2, 0] * nn.Upsample(scale_factor=0.5)(p3_out))
        p5_out = self.p5_out(w2[0, 1] * p5_x + w2[1, 1] * p5_td + w2[2, 1] * nn.Upsample(scale_factor=0.5)(p4_out))
        p6_out = self.p6_out(w2[0, 2] * p6_x + w2[1, 2] * p6_td + w2[2, 2] * nn.Upsample(scale_factor=0.5)(p5_out))
        p7_out = self.p7_out(w2[0, 3] * p7_x + w2[1, 3] * p7_td + w2[2, 3] * nn.Upsample(scale_factor=0.5)(p6_out))

        return [p3_out, p4_out, p5_out, p6_out, p7_out]

class BiFPN(nn.Module):
    def __init__(self, in_channels, out_channels=64, num_layers=2, epsilon=0.0001):
        super(BiFPN, self).__init__()
        self.p3 = nn.Conv2d(in_channels[0], out_channels, kernel_size=1, stride=1, padding=0)
        self.p4 = nn.Conv2d(in_channels[1], out_channels, kernel_size=1, stride=1, padding=0)
        self.p5 = nn.Conv2d(in_channels[2], out_channels, kernel_size=1, stride=1, padding=0)

        # p6 is obtained via a 3x3 stride-2 conv on C5
        self.p6 = nn.Conv2d(in_channels[2], out_channels, kernel_size=3, stride=2, padding=1)

        # p7 is computed by applying ReLU followed by a 3x3 stride-2 conv on p6
        self.p7 = ConvBlock(out_channels, out_channels, kernel_size=3, stride=2, padding=1)

        bifpns = []
        for _ in range(num_layers):
            bifpns.append(BiFPNBlock(out_channels))
        self.bifpn = nn.Sequential(*bifpns)

    def forward(self, inputs):
        c3, c4, c5 = inputs

        # Calculate the input column of BiFPN
        p3_x = self.p3(c3)
        p4_x = self.p4(c4)
        p5_x = self.p5(c5)
        p6_x = self.p6(c5)
        p7_x = self.p7(p6_x)

        features = [p3_x, p4_x, p5_x, p6_x, p7_x]
        return self.bifpn(features)
目录
相关文章
|
2天前
|
机器学习/深度学习 TensorFlow 算法框架/工具
深度学习之格式转换笔记(三):keras(.hdf5)模型转TensorFlow(.pb) 转TensorRT(.uff)格式
将Keras训练好的.hdf5模型转换为TensorFlow的.pb模型,然后再转换为TensorRT支持的.uff格式,并提供了转换代码和测试步骤。
17 3
深度学习之格式转换笔记(三):keras(.hdf5)模型转TensorFlow(.pb) 转TensorRT(.uff)格式
|
2天前
|
机器学习/深度学习 vr&ar
深度学习笔记(十):深度学习评估指标
关于深度学习评估指标的全面介绍,涵盖了专业术语解释、一级和二级指标,以及各种深度学习模型的性能评估方法。
7 0
深度学习笔记(十):深度学习评估指标
|
2天前
|
机器学习/深度学习 Python
深度学习笔记(九):神经网络剪枝(Neural Network Pruning)详细介绍
神经网络剪枝是一种通过移除不重要的权重来减小模型大小并提高效率的技术,同时尽量保持模型性能。
8 0
深度学习笔记(九):神经网络剪枝(Neural Network Pruning)详细介绍
|
2天前
|
机器学习/深度学习 算法 TensorFlow
深度学习笔记(五):学习率过大过小对于网络训练有何影响以及如何解决
学习率是深度学习中的关键超参数,它影响模型的训练进度和收敛性,过大或过小的学习率都会对网络训练产生负面影响,需要通过适当的设置和调整策略来优化。
28 0
深度学习笔记(五):学习率过大过小对于网络训练有何影响以及如何解决
|
2天前
|
机器学习/深度学习 算法
深度学习笔记(四):神经网络之链式法则详解
这篇文章详细解释了链式法则在神经网络优化中的作用,说明了如何通过引入中间变量简化复杂函数的微分计算,并通过实例展示了链式法则在反向传播算法中的应用。
10 0
深度学习笔记(四):神经网络之链式法则详解
|
2天前
|
机器学习/深度学习 编解码
深度学习笔记(三):神经网络之九种激活函数Sigmoid、tanh、ReLU、ReLU6、Leaky Relu、ELU、Swish、Mish、Softmax详解
本文介绍了九种常用的神经网络激活函数:Sigmoid、tanh、ReLU、ReLU6、Leaky ReLU、ELU、Swish、Mish和Softmax,包括它们的定义、图像、优缺点以及在深度学习中的应用和代码实现。
24 0
深度学习笔记(三):神经网络之九种激活函数Sigmoid、tanh、ReLU、ReLU6、Leaky Relu、ELU、Swish、Mish、Softmax详解
|
2天前
|
机器学习/深度学习 数据可视化 Windows
深度学习笔记(七):如何用Mxnet来将神经网络可视化
这篇文章介绍了如何使用Mxnet框架来实现神经网络的可视化,包括环境依赖的安装、具体的代码实现以及运行结果的展示。
9 0
|
2天前
|
机器学习/深度学习 Python
深度学习笔记(六):如何运用梯度下降法来解决线性回归问题
这篇文章介绍了如何使用梯度下降法解决线性回归问题,包括梯度下降法的原理、线性回归的基本概念和具体的Python代码实现。
10 0
|
2天前
|
机器学习/深度学习 移动开发 TensorFlow
深度学习之格式转换笔记(四):Keras(.h5)模型转化为TensorFlow(.pb)模型
本文介绍了如何使用Python脚本将Keras模型转换为TensorFlow的.pb格式模型,包括加载模型、重命名输出节点和量化等步骤,以便在TensorFlow中进行部署和推理。
18 0
|
3天前
|
机器学习/深度学习 边缘计算 人工智能
探讨深度学习在图像识别中的应用及优化策略
【10月更文挑战第5天】探讨深度学习在图像识别中的应用及优化策略
14 1