forked from PulseFocusPlatform/PulseFocusPlatform
408 lines
15 KiB
Markdown
408 lines
15 KiB
Markdown
# 新增模型算法
|
||
为了让用户更好的使用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,我们非常欢迎您的反馈。
|