PulseFocusPlatform/docs/advanced_tutorials/MODEL_TECHNICAL.md

15 KiB
Raw Permalink Blame History

新增模型算法

为了让用户更好的使用PaddleDetection本文档中我们将介绍PaddleDetection的主要模型技术细节及应用

目录

1.简介

PaddleDetecion中的每一种模型对应一个文件夹以yolov3为例yolov3系列的模型对应于configs/yolov3文件夹其中yolov3_darknet的总配置文件configs/yolov3/yolov3_darknet53_270e_coco.yml的内容如下:

_BASE_: [
  '../datasets/coco_detection.yml', # 数据集配置文件,所有模型共用
  '../runtime.yml', # 运行时相关配置
  '_base_/optimizer_270e.yml', # 优化器相关配置
  '_base_/yolov3_darknet53.yml', # yolov3网络结构配置文件
  '_base_/yolov3_reader.yml', # yolov3 Reader模块配置
]

# 定义在此处的相关配置可以覆盖上述文件中的同名配置
snapshot_epoch: 5
weights: output/yolov3_darknet53_270e_coco/model_final

可以看到配置文件中的模块进行了清晰的划分除了公共的数据集配置以及运行时配置其他配置被划分为优化器网络结构以及Reader模块。PaddleDetection中支持丰富的优化器学习率调整策略预处理算子等因此大多数情况下不需要编写优化器以及Reader相关的代码而只需要在配置文件中配置即可。因此新增一个模型的主要在于搭建网络结构。

PaddleDetection网络结构的代码在ppdet/modeling/中,所有网络结构以组件的形式进行定义与组合,网络结构的主要构成如下所示:

  ppdet/modeling/
  ├── architectures
  │   ├── faster_rcnn.py # Faster Rcnn模型
  │   ├── ssd.py         # SSD模型
  │   ├── yolo.py      # YOLOv3模型
  │   │   ...
  ├── heads       # 检测头模块
  │   ├── xxx_head.py    # 定义各类检测头
  │   ├── roi_extractor.py #检测感兴趣区域提取
  ├── backbones          # 基干网络模块
  │   ├── resnet.py      # ResNet网络
  │   ├── mobilenet.py   # MobileNet网络
  │   │   ...
  ├── losses             # 损失函数模块
  │   ├── xxx_loss.py    # 定义注册各类loss函数
  ├── necks     # 特征融合模块
  │   ├── xxx_fpn.py  # 定义各种FPN模块
  ├── proposal_generator # anchor & proposal生成与匹配模块
  │   ├── anchor_generator.py   # anchor生成模块
  │   ├── proposal_generator.py # proposal生成模块
  │   ├── target.py   # anchor & proposal的匹配函数
  │   ├── target_layer.py   # anchor & proposal的匹配模块
  ├── tests  # 单元测试模块
  │   ├── test_xxx.py  # 对网络中的算子以及模块结构进行单元测试
  ├── ops.py  # 封装各类PaddlePaddle物体检测相关公共检测组件/算子
  ├── layers.py  # 封装及注册各类PaddlePaddle物体检测相关公共检测组件/算子
  ├── bbox_utils.py # 封装检测框相关的函数
  ├── post_process.py # 封装及注册后处理相关模块
  ├── shape_spec.py # 定义模块输出shape的类

2.新增模型

接下来以单阶段检测器YOLOv3为例对建立模型过程进行详细描述按照此思路您可以快速搭建新的模型。

2.1新增网络结构

2.1.1新增Backbone

PaddleDetection中现有所有Backbone网络代码都放置在ppdet/modeling/backbones目录下,所以我们在其中新建darknet.py如下:

import paddle.nn as nn
from ppdet.core.workspace import register, serializable

@register
@serializable
class DarkNet(nn.Layer):

    __shared__ = ['norm_type']

    def __init__(self,
                 depth=53,
                 return_idx=[2, 3, 4],
                 norm_type='bn',
                 norm_decay=0.):
        super(DarkNet, self).__init__()
        # 省略内容

    def forward(self, inputs):
        # 省略处理逻辑
        pass

    @property
    def out_shape(self):
        # 省略内容
        pass

然后在backbones/__init__.py中加入引用:

from . import darknet
from .darknet import *

几点说明:

  • 为了在yaml配置文件中灵活配置网络所有Backbone需要利用ppdet.core.workspace里的register进行注册,形式请参考如上示例。此外,可以使用serializable以使backbone支持序列化
  • 所有的Backbone需继承paddle.nn.Layer并实现forward函数。此外还需实现out_shape属性定义输出的feature map的channel信息具体可参见源码
  • __shared__为了实现一些参数的配置全局共享这些参数可以被backbone, neckheadloss等所有注册模块共享。
2.1.2新增Neck

特征融合模块放置在ppdet/modeling/necks目录下,我们在其中新建yolo_fpn.py如下:

import paddle.nn as nn
from ppdet.core.workspace import register, serializable

@register
@serializable
class YOLOv3FPN(nn.Layer):
    __shared__ = ['norm_type']

    def __init__(self,
                in_channels=[256, 512, 1024],
                norm_type='bn'):
        super(YOLOv3FPN, self).__init__()
        # 省略内容

    def forward(self, blocks):
        # 省略内容
        pass

    @classmethod
    def from_config(cls, cfg, input_shape):
        # 省略内容
        pass

    @property
    def out_shape(self):
        # 省略内容
        pass

然后在necks/__init__.py中加入引用:

from . import yolo_fpn
from .yolo_fpn import *

几点说明:

  • neck模块需要使用register进行注册,可以使用serializable进行序列化;
  • neck模块需要继承paddle.nn.Layer并实现forward函数。除此之外还需要实现out_shape属性用于定义输出的feature map的channel信息还需要实现类函数from_config用于在配置文件中推理出输入channel并用于YOLOv3FPN的初始化;
  • neck模块可以使用__shared__实现一些参数的配置全局共享。
2.1.3新增Head

Head模块全部存放在ppdet/modeling/heads目录下,我们在其中新建yolo_head.py如下

import paddle.nn as nn
from ppdet.core.workspace import register

@register
class YOLOv3Head(nn.Layer):
    __shared__ = ['num_classes']
    __inject__ = ['loss']

    def __init__(self,
                 anchors=[[10, 13], [16, 30], [33, 23],
                   [30, 61], [62, 45],[59, 119],
                   [116, 90], [156, 198], [373, 326]],
                 anchor_masks=[[6, 7, 8], [3, 4, 5], [0, 1, 2]],
                 num_classes=80,
                 loss='YOLOv3Loss',
                 iou_aware=False,
                 iou_aware_factor=0.4):
        super(YOLOv3Head, self).__init__()
        # 省略内容

    def forward(self, feats, targets=None):
        # 省略内容
        pass

然后在heads/__init__.py中加入引用:

from . import yolo_head
from .yolo_head import *

几点说明:

  • Head模块需要使用register进行注册;
  • Head模块需要继承paddle.nn.Layer并实现forward函数。
  • __inject__表示引入全局字典中已经封装好的模块。如loss等。
2.1.4新增Loss

Loss模块全部存放在ppdet/modeling/losses目录下,我们在其中新建yolo_loss.py

import paddle.nn as nn
from ppdet.core.workspace import register

@register
class YOLOv3Loss(nn.Layer):

    __inject__ = ['iou_loss', 'iou_aware_loss']
    __shared__ = ['num_classes']

    def __init__(self,
                 num_classes=80,
                 ignore_thresh=0.7,
                 label_smooth=False,
                 downsample=[32, 16, 8],
                 scale_x_y=1.,
                 iou_loss=None,
                 iou_aware_loss=None):
        super(YOLOv3Loss, self).__init__()
        # 省略内容

    def forward(self, inputs, targets, anchors):
        # 省略内容
        pass

然后在losses/__init__.py中加入引用:

from . import yolo_loss
from .yolo_loss import *

几点说明:

  • loss模块需要使用register进行注册;
  • loss模块需要继承paddle.nn.Layer并实现forward函数。
  • 可以使用__inject__表示引入全局字典中已经封装好的模块,使用__shared__可以实现一些参数的配置全局共享。
2.1.5新增后处理模块

后处理模块定义在ppdet/modeling/post_process.py中,其中定义了BBoxPostProcess类来进行后处理操作,如下所示:

from ppdet.core.workspace import register

@register
class BBoxPostProcess(object):
    __shared__ = ['num_classes']
    __inject__ = ['decode', 'nms']

    def __init__(self, num_classes=80, decode=None, nms=None):
        # 省略内容
        pass

    def __call__(self, head_out, rois, im_shape, scale_factor):
        # 省略内容
        pass

几点说明:

  • 后处理模块需要使用register进行注册
  • __inject__注入了全局字典中封装好的模块如decode和nms等。decode和nms定义在ppdet/modeling/layers.py中。
2.1.6新增Architecture

所有architecture网络代码都放置在ppdet/modeling/architectures目录下,meta_arch.py中定义了BaseArch类,代码如下:

import paddle.nn as nn
from ppdet.core.workspace import register

@register
class BaseArch(nn.Layer):
     def __init__(self):
        super(BaseArch, self).__init__()

    def forward(self, inputs):
        self.inputs = inputs
        self.model_arch()

        if self.training:
            out = self.get_loss()
        else:
            out = self.get_pred()
        return out

    def model_arch(self, ):
        pass

    def get_loss(self, ):
        raise NotImplementedError("Should implement get_loss method!")

    def get_pred(self, ):
        raise NotImplementedError("Should implement get_pred method!")

所有的architecture需要继承BaseArch类,如yolo.py中的YOLOv3定义如下:

@register
class YOLOv3(BaseArch):
    __category__ = 'architecture'
    __inject__ = ['post_process']

    def __init__(self,
                 backbone='DarkNet',
                 neck='YOLOv3FPN',
                 yolo_head='YOLOv3Head',
                 post_process='BBoxPostProcess'):
        super(YOLOv3, self).__init__()
        self.backbone = backbone
        self.neck = neck
        self.yolo_head = yolo_head
        self.post_process = post_process

    @classmethod
    def from_config(cls, cfg, *args, **kwargs):
        # 省略内容
        pass

    def get_loss(self):
        # 省略内容
        pass

    def get_pred(self):
        # 省略内容
        pass

几点说明:

  • 所有的architecture需要使用register进行注册
  • 在组建一个完整的网络时必须要设定__category__ = 'architecture'来表示一个完整的物体检测模型;
  • backbone, neck, yolo_head以及post_process等检测组件传入到architecture中组成最终的网络。像这样将检测模块化提升了检测模型的复用性可以通过组合不同的检测组件得到多个模型。
  • from_config类函数实现了模块间组合时channel的自动配置。

2.2新增配置文件

2.2.1网络结构配置文件

上面详细地介绍了如何新增一个architecture接下来演示如何配置一个模型yolov3关于网络结构的配置在configs/yolov3/_base_/文件夹中定义,如yolov3_darknet53.yml定义了yolov3_darknet的网络结构其定义如下

architecture: YOLOv3
pretrain_weights: https://paddledet.bj.bcebos.com/models/pretrained/DarkNet53_pretrained.pdparams
norm_type: sync_bn

YOLOv3:
  backbone: DarkNet
  neck: YOLOv3FPN
  yolo_head: YOLOv3Head
  post_process: BBoxPostProcess

DarkNet:
  depth: 53
  return_idx: [2, 3, 4]

# use default config
# YOLOv3FPN:

YOLOv3Head:
  anchors: [[10, 13], [16, 30], [33, 23],
            [30, 61], [62, 45], [59, 119],
            [116, 90], [156, 198], [373, 326]]
  anchor_masks: [[6, 7, 8], [3, 4, 5], [0, 1, 2]]
  loss: YOLOv3Loss

YOLOv3Loss:
  ignore_thresh: 0.7
  downsample: [32, 16, 8]
  label_smooth: false

BBoxPostProcess:
  decode:
    name: YOLOBox
    conf_thresh: 0.005
    downsample_ratio: 32
    clip_bbox: true
  nms:
    name: MultiClassNMS
    keep_top_k: 100
    score_threshold: 0.01
    nms_threshold: 0.45
    nms_top_k: 1000

可以看到在配置文件中首先需要指定网络的architecturepretrain_weights指定训练模型的url或者路径norm_type等可以作为全局参数共享。模型的定义自上而下依次在文件中定义与上节中的模型组件一一对应。对于一些模型组件如果采用默认 的参数,可以不用配置,如上文中的yolo_fpn。通过改变相关配置,我们可以轻易地组合出另一个模型,比如configs/yolov3/_base_/yolov3_mobilenet_v1.yml将backbone从Darknet切换成MobileNet。

2.2.2优化器配置文件

优化器配置文件定义模型使用的优化器以及学习率的调度策略目前PaddleDetection中已经集成了多种多样的优化器和学习率策略具体可参见代码ppdet/optimizer.py。比如yolov3的优化器配置文件定义在configs/yolov3/_base_/optimizer_270e.yml,其定义如下:

epoch: 270

LearningRate:
  base_lr: 0.001
  schedulers:
  - !PiecewiseDecay
    gamma: 0.1
    milestones:
    # epoch数目
    - 216
    - 243
  - !LinearWarmup
    start_factor: 0.
    steps: 4000

OptimizerBuilder:
  optimizer:
    momentum: 0.9
    type: Momentum
  regularizer:
    factor: 0.0005
    type: L2

几点说明:

  • 可以通过OptimizerBuilder.optimizer指定优化器的类型及参数目前支持的优化器可以参考PaddlePaddle官方文档
  • 可以设置LearningRate.schedulers设置不同学习率调整策略的组合PaddlePaddle目前支持多种学习率调整策略具体也可参考PaddlePaddle官方文档。需要注意的是你需要对于PaddlePaddle中的学习率调整策略进行简单的封装具体可参考源码ppdet/optimizer.py
2.2.3Reader配置文件

关于Reader的配置可以参考Reader配置文档

看过此文档您应该对PaddleDetection中模型搭建与配置有了一定经验结合源码会理解的更加透彻。关于模型技术如您有其他问题或建议请给我们提issue我们非常欢迎您的反馈。