PulseFocusPlatform/docs/advanced_tutorials/MODEL_TECHNICAL.md

408 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 新增模型算法
为了让用户更好的使用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, neckheadloss等所有注册模块共享。
##### 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
```
可以看到在配置文件中首先需要指定网络的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官方文档](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我们非常欢迎您的反馈。