# 新增模型算法 为了让用户更好的使用PaddleDetection,本文档中,我们将介绍PaddleDetection的主要模型技术细节及应用 ## 目录 - [1.简介](#1.简介) - [2.新增模型](#2.新增模型) - [2.1新增网络结构](#2.1新增网络结构) - [2.1.1新增Backbone](#2.1.1新增Backbone) - [2.1.2新增Neck](#2.1.2新增Neck) - [2.1.3新增Head](#2.1.3新增Head) - [2.1.4新增Loss](#2.1.4新增Loss) - [2.1.5新增后处理模块](#2.1.5新增后处理模块) - [2.1.6新增Architecture](#2.1.6新增Architecture) - [2.2新增配置文件](#2.2新增配置文件) - [2.2.1网络结构配置文件](#2.2.1网络结构配置文件) - [2.2.2优化器配置文件](#2.2.2优化器配置文件) - [2.2.3Reader配置文件](#2.2.3Reader配置文件) ### 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的类 ``` ![](../images/model_figure.png) ### 2.新增模型 接下来,以单阶段检测器YOLOv3为例,对建立模型过程进行详细描述,按照此思路您可以快速搭建新的模型。 #### 2.1新增网络结构 ##### 2.1.1新增Backbone PaddleDetection中现有所有Backbone网络代码都放置在`ppdet/modeling/backbones`目录下,所以我们在其中新建`darknet.py`如下: ```python 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`中加入引用: ```python 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, neck,head,loss等所有注册模块共享。 ##### 2.1.2新增Neck 特征融合模块放置在`ppdet/modeling/necks`目录下,我们在其中新建`yolo_fpn.py`如下: ``` python 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`中加入引用: ```python 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`如下 ``` python 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`中加入引用: ```python 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`下 ```python 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`中加入引用: ```python 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`类来进行后处理操作,如下所示: ``` python 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`类,代码如下: ``` python 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`定义如下: ``` python @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 ``` 可以看到在配置文件中,首先需要指定网络的architecture,pretrain_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官方文档](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/optimizer/Overview_cn.html) - 可以设置LearningRate.schedulers设置不同学习率调整策略的组合,PaddlePaddle目前支持多种学习率调整策略,具体也可参考[PaddlePaddle官方文档](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/optimizer/Overview_cn.html)。需要注意的是,你需要对于PaddlePaddle中的学习率调整策略进行简单的封装,具体可参考源码`ppdet/optimizer.py`。 ##### 2.2.3Reader配置文件 关于Reader的配置可以参考[Reader配置文档](./READER.md#5.配置及运行)。 > 看过此文档,您应该对PaddleDetection中模型搭建与配置有了一定经验,结合源码会理解的更加透彻。关于模型技术,如您有其他问题或建议,请给我们提issue,我们非常欢迎您的反馈。