【项目实践】EfficientDet原理讲解与目标检测项目实践(二)

简介: 【项目实践】EfficientDet原理讲解与目标检测项目实践(二)

2.2、BiFPN模块

   如下图所示,BiFPN在图e的基础上增加了shortcut,这些都是在现有的一些工作的基础上添砖加瓦。

image.png

图 BiFPN与其他的特征融合方法的比较

但是,以往的特征融合方法对所有输入特征一视同仁,在BiFPN中则引入了加权策略,下边介绍本文提出来的加权策略(类似attention机制)。


最直白的思想,加上一个可学习的权重即可,如下:

image.png

其中wi可以是一个标量(对每一个特征),可以是一个向量(对每一个通道),也可以是一个多维度的tenor(对每一个像素)。

   但是如果不对wi对限制容易导致训练不稳定,于是很自然的想到对每一个权重用softmax:

image.png

   但是计算softmax速度较慢,于是作者提出了快速的限制方法:

image.png

   为了保证weight大于0,weight前采用relu函数。以上图BiFPN结构中第6层为例:

image.png

PyTorch实现BiFPN模型:

import torch.nn as nn
import torch.nn.functional as F
from .module import ConvModule, xavier_init
import torch
class BIFPN(nn.Module):
    def __init__(self,
                 in_channels,
                 out_channels,
                 num_outs,
                 start_level=0,
                 end_level=-1,
                 stack=1,
                 add_extra_convs=False,
                 extra_convs_on_inputs=True,
                 relu_before_extra_convs=False,
                 no_norm_on_lateral=False,
                 conv_cfg=None,
                 norm_cfg=None,
                 activation=None):
        super(BIFPN, 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.activation = activation
        self.relu_before_extra_convs = relu_before_extra_convs
        self.no_norm_on_lateral = no_norm_on_lateral
        self.stack = stack
        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.add_extra_convs = add_extra_convs
        self.extra_convs_on_inputs = extra_convs_on_inputs
        self.lateral_convs = nn.ModuleList()
        self.fpn_convs = nn.ModuleList()
        self.stack_bifpn_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 if not self.no_norm_on_lateral else None,
                activation=self.activation,
                inplace=False)
            self.lateral_convs.append(l_conv)
        for ii in range(stack):
            self.stack_bifpn_convs.append(BiFPNModule(channels=out_channels,
                                                      levels=self.backbone_end_level-self.start_level,
                                                      conv_cfg=conv_cfg,
                                                      norm_cfg=norm_cfg,
                                                      activation=activation))
        # add extra conv layers (e.g., RetinaNet)
        extra_levels = num_outs - self.backbone_end_level + self.start_level
        if add_extra_convs and extra_levels >= 1:
            for i in range(extra_levels):
                if i == 0 and self.extra_convs_on_inputs:
                    in_channels = self.in_channels[self.backbone_end_level - 1]
                else:
                    in_channels = out_channels
                extra_fpn_conv = ConvModule(
                    in_channels,
                    out_channels,
                    3,
                    stride=2,
                    padding=1,
                    conv_cfg=conv_cfg,
                    norm_cfg=norm_cfg,
                    activation=self.activation,
                    inplace=False)
                self.fpn_convs.append(extra_fpn_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)
        ]
        # part 1: build top-down and down-top path with stack
        used_backbone_levels = len(laterals)
        for bifpn_module in self.stack_bifpn_convs:
            laterals = bifpn_module(laterals)
        outs = laterals
        # part 2: add extra levels
        if self.num_outs > len(outs):
            # use max pool to get more levels on top of outputs
            # (e.g., Faster R-CNN, Mask R-CNN)
            if not self.add_extra_convs:
                for i in range(self.num_outs - used_backbone_levels):
                    outs.append(F.max_pool2d(outs[-1], 1, stride=2))
            # add conv layers on top of original feature maps (RetinaNet)
            else:
                if self.extra_convs_on_inputs:
                    orig = inputs[self.backbone_end_level - 1]
                    outs.append(self.fpn_convs[0](orig))
                else:
                    outs.append(self.fpn_convs[0](outs[-1]))
                for i in range(1, self.num_outs - used_backbone_levels):
                    if self.relu_before_extra_convs:
                        outs.append(self.fpn_convs[i](F.relu(outs[-1])))
                    else:
                        outs.append(self.fpn_convs[i](outs[-1]))
        return tuple(outs)
class BiFPNModule(nn.Module):
    def __init__(self,
                 channels,
                 levels,
                 init=0.5,
                 conv_cfg=None,
                 norm_cfg=None,
                 activation=None,
                 eps=0.0001):
        super(BiFPNModule, self).__init__()
        self.activation = activation
        self.eps = eps
        self.levels = levels
        self.bifpn_convs = nn.ModuleList()
        # weighted
        self.w1 = nn.Parameter(torch.Tensor(2, levels).fill_(init))
        self.relu1 = nn.ReLU()
        self.w2 = nn.Parameter(torch.Tensor(3, levels - 2).fill_(init))
        self.relu2 = nn.ReLU()
        for jj in range(2):
            for i in range(self.levels-1):  # 1,2,3
                fpn_conv = nn.Sequential(
                    ConvModule(
                        channels,
                        channels,
                        3,
                        padding=1,
                        conv_cfg=conv_cfg,
                        norm_cfg=norm_cfg,
                        activation=self.activation,
                        inplace=False)
                )
                self.bifpn_convs.append(fpn_conv)
    # 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) == self.levels
        # build top-down and down-top path with stack
        levels = self.levels
        # w relu
        w1 = self.relu1(self.w1)
        w1 /= torch.sum(w1, dim=0) + self.eps  # normalize
        w2 = self.relu2(self.w2)
        w2 /= torch.sum(w2, dim=0) + self.eps  # normalize
        # build top-down
        idx_bifpn = 0
        pathtd = inputs
        inputs_clone = []
        for in_tensor in inputs:
            inputs_clone.append(in_tensor.clone())
        for i in range(levels - 1, 0, -1):
            pathtd[i - 1] = (w1[0, i-1]*pathtd[i - 1] + w1[1, i-1]*F.interpolate(
                pathtd[i], scale_factor=2, mode='nearest'))/(w1[0, i-1] + w1[1, i-1] + self.eps)
            pathtd[i - 1] = self.bifpn_convs[idx_bifpn](pathtd[i - 1])
            idx_bifpn = idx_bifpn + 1
        # build down-top
        for i in range(0, levels - 2, 1):
            pathtd[i + 1] = (w2[0, i] * pathtd[i + 1] + w2[1, i] * F.max_pool2d(pathtd[i], kernel_size=2) +
                             w2[2, i] * inputs_clone[i + 1])/(w2[0, i] + w2[1, i] + w2[2, i] + self.eps)
            pathtd[i + 1] = self.bifpn_convs[idx_bifpn](pathtd[i + 1])
            idx_bifpn = idx_bifpn + 1
        pathtd[levels - 1] = (w1[0, levels-1] * pathtd[levels - 1] + w1[1, levels-1] * F.max_pool2d(
            pathtd[levels - 2], kernel_size=2))/(w1[0, levels-1] + w1[1, levels-1] + self.eps)
        pathtd[levels - 1] = self.bifpn_convs[idx_bifpn](pathtd[levels - 1])
        return pathtd

2.3、EfficientDet结构

   组合了backbone(使用了EfficientNet)和BiFPN(特征网络)和Box prediction net,整个框架就是EfficientDet的基本模型,结构如下图:

image.png

   主干网络采用的是 EfficientNet 网络,BiFPN 是基于其 3~7 层的特征图进行的,融合后的特征喂给一个分类网络和 box 网络,分类与 box 网络在所有特征级上权重是共享的。

PyTorch实现EfficientDet结构:

class EfficientDet(nn.Module):
    def __init__(self,
                 num_classes,
                 network='efficientdet-d0',
                 D_bifpn=3,
                 W_bifpn=88,
                 D_class=3,
                 is_training=True,
                 threshold=0.01,
                 iou_threshold=0.5):
        super(EfficientDet, self).__init__()
        self.backbone = EfficientNet.from_pretrained(MODEL_MAP[network])
        self.is_training = is_training
        self.neck = BIFPN(in_channels=self.backbone.get_list_features()[-5:],
                          out_channels=W_bifpn,
                          stack=D_bifpn,
                          num_outs=5)
        self.bbox_head = RetinaHead(num_classes=num_classes, in_channels=W_bifpn)
        self.anchors = Anchors()
        self.regressBoxes = BBoxTransform()
        self.clipBoxes = ClipBoxes()
        self.threshold = threshold
        self.iou_threshold = iou_threshold
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
                m.weight.data.normal_(0, math.sqrt(2. / n))
            elif isinstance(m, nn.BatchNorm2d):
                m.weight.data.fill_(1)
                m.bias.data.zero_()
        self.freeze_bn()
        self.criterion = FocalLoss()
    def forward(self, inputs):
        if self.is_training:
            inputs, annotations = inputs
        else:
            inputs = inputs
        x = self.extract_feat(inputs)
        outs = self.bbox_head(x)
        classification = torch.cat([out for out in outs[0]], dim=1)
        regression = torch.cat([out for out in outs[1]], dim=1)
        anchors = self.anchors(inputs)
        if self.is_training:
            return self.criterion(classification, regression, anchors, annotations)
        else:
            transformed_anchors = self.regressBoxes(anchors, regression)
            transformed_anchors = self.clipBoxes(transformed_anchors, inputs)
            scores = torch.max(classification, dim=2, keepdim=True)[0]
            scores_over_thresh = (scores > self.threshold)[0, :, 0]
            if scores_over_thresh.sum() == 0:
                print('No boxes to NMS')
                # no boxes to NMS, just return
                return [torch.zeros(0), torch.zeros(0), torch.zeros(0, 4)]
            classification = classification[:, scores_over_thresh, :]
            transformed_anchors = transformed_anchors[:, scores_over_thresh, :]
            scores = scores[:, scores_over_thresh, :]
            anchors_nms_idx = nms(
                transformed_anchors[0, :, :], scores[0, :, 0], iou_threshold=self.iou_threshold)
            nms_scores, nms_class = classification[0, anchors_nms_idx, :].max(
                dim=1)
            return [nms_scores, nms_class, transformed_anchors[0, anchors_nms_idx, :]]

2.4、模型复合扩张

主干网络部分:这部分直接把 EfficientNet 缩放拿过来用即可,即 EfficientNet B0-B6,借助其现成的 checkpoints,就不折腾了;


BiFPN 网络部分:这部分借鉴 EfficientNet,在 Channel 上直线指数级增加,在深度上线性增加,具体的缩放系数公式为:


Box/class 预测网络部分:其宽度与 BiFPN 部分保持一致,深度方面采用


图片分辨率部分: 因为特征提取选择的是 3~7 层,第 7 层的大小为原始图片的1/2^7,所以输入图像的大小必须是 128 的倍数


   D7 明显是超出内存大小了,只是在 D6 基础上增加了分辨率大小。


2.5、EfficientDet结构总结

BiFPN模型复合扩张策略都非常有效,BiFPN和综合平衡分辨率、深度和宽度提升性能。但是一方面BiFPN除了Feature map的加权组合是新的提的,PANet和shortcut的思路其他论文也都提过,另一方面就是平衡三者的方法完全是个经验值,并没有理论上的分析或者指导,可能最后还是要依靠NAS来给出最优的策略。


2.6、训练过程与测试结果


参考:

https://zhuanlan.zhihu.com/p/111115509

https://blog.csdn.net/weixin_37179744/article/details/103217305

https://zhuanlan.zhihu.com/p/96773680

相关文章
|
20天前
|
机器学习/深度学习 人工智能 自然语言处理
深入浅出深度学习:从理论到实践的探索之旅
在人工智能的璀璨星空中,深度学习如同一颗耀眼的新星,以其强大的数据处理能力引领着技术革新的浪潮。本文将带您走进深度学习的核心概念,揭示其背后的数学原理,并通过实际案例展示如何应用深度学习模型解决现实世界的问题。无论您是初学者还是有一定基础的开发者,这篇文章都将为您提供宝贵的知识和启发。
47 5
|
1月前
|
机器学习/深度学习 自然语言处理 语音技术
深度学习的奇妙之旅:从理论到实践
【10月更文挑战第36天】在本文中,我们将一起探索深度学习的神秘世界。我们将首先了解深度学习的基本概念和原理,然后通过一个简单的Python代码示例,学习如何使用深度学习库Keras进行图像分类。无论你是深度学习的初学者,还是有一定基础的学习者,都可以从这篇文章中获得新的知识和启示。
|
14天前
|
机器学习/深度学习 存储 人工智能
探索深度学习的奥秘:从理论到实践的技术感悟
本文深入探讨了深度学习技术的核心原理、发展历程以及在实际应用中的体验与挑战。不同于常规摘要,本文旨在通过作者个人的技术实践经历,为读者揭示深度学习领域的复杂性与魅力,同时提供一些实用的技术见解和解决策略。
29 0
|
2月前
|
分布式计算 Java 应用服务中间件
NettyIO框架的深度技术解析与实战
【10月更文挑战第13天】Netty是一个异步事件驱动的网络应用程序框架,由JBOSS提供,现已成为Github上的独立项目。
55 0
|
3月前
|
传感器 人工智能 算法
AI计算机视觉笔记二十七:YOLOV8实现目标追踪
本文介绍了使用YOLOv8实现人员检测与追踪的方法。通过为每个人员分配唯一ID,实现持续追踪,并可统计人数,适用于小区或办公楼出入管理。首先解释了目标检测与追踪的区别,接着详细描述了使用匈牙利算法和卡尔曼滤波实现目标关联的过程。文章提供了基于IOU实现追踪的具体步骤,包括环境搭建、模型加载及追踪逻辑实现。通过示例代码展示了如何使用YOLOv8进行实时视频处理,并实现人员追踪功能。测试结果显示,该方法在实际场景中具有较好的应用潜力。
|
3月前
|
机器学习/深度学习 人工智能 自然语言处理
揭秘深度学习:从理论到实践的探索之旅
深度学习,这个听起来有些高冷的技术名词,其实已经悄然渗透进我们生活的方方面面。本文将深入浅出地介绍深度学习的基本概念、核心算法以及在多个领域的应用实例,帮助读者理解这一前沿技术的魅力所在,并探讨其未来的发展趋势。让我们一起踏上这段揭秘深度学习的奇妙旅程吧!
|
4月前
|
机器学习/深度学习 人工智能 算法
【专家系统】系统地掌握专家系统的基本概念、技术原理、实现方法以及应用实践。
专家系统是一种人工智能程序,它利用专家知识和推理能力来解决特定领域中的复杂问题,系统地掌握专家系统的基本概念、技术原理、实现方法以及应用实践。
437 1
|
4月前
|
机器学习/深度学习 传感器 人工智能
深度学习:从理论到实践的探索之旅
在人工智能领域,深度学习已成为推动技术进步的核心动力。本文将深入浅出地介绍深度学习的基本概念、关键技术以及实际应用案例,帮助读者理解这一复杂领域的基本原理和实践方法。通过本文的学习,你将能够掌握深度学习的基础框架,并了解如何将这些知识应用于解决实际问题。
|
7月前
|
缓存 负载均衡 测试技术
探究职业发展的关键:能力模型解读
能力模型是指导个人职业发展的蓝图,它定义了行业和职位所需的具体技能和能力。业务测试工程师的能力模型包括需求理解、架构理解、测试设计、测试工具应用/脚本开发和测试总结五个维度,而测试开发工程师的能力模型则涵盖架构理解、开发语言应用、测试工具/平台开发和专项测试四个维度。通过理解这些模型,个人可以明确提升方向,例如业务测试工程师可参考《测试开发体系介绍》、《测试用例设计》等课程进行学习,而测试开发工程师则可关注《编程语言》、《测试框架》等相关课程。知行合一,按照能力模型进行学习和实践,有助于在职业生涯中取得成功。
|
7月前
|
算法
【软件设计师—基础精讲笔记9】第九章 算法设计与分析
【软件设计师—基础精讲笔记9】第九章 算法设计与分析
60 1
下一篇
DataWorks