为了让用户更好的使用PaddleDetection,本文档中,我们将介绍PaddleDetection的主要模型技术细节及应用
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的类
接下来,以单阶段检测器YOLOv3为例,对建立模型过程进行详细描述,按照此思路您可以快速搭建新的模型。
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 *
几点说明:
ppdet.core.workspace
里的register
进行注册,形式请参考如上示例。此外,可以使用serializable
以使backbone支持序列化;paddle.nn.Layer
类,并实现forward函数。此外,还需实现out_shape属性定义输出的feature map的channel信息,具体可参见源码;__shared__
为了实现一些参数的配置全局共享,这些参数可以被backbone, neck,head,loss等所有注册模块共享。特征融合模块放置在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 *
几点说明:
register
进行注册,可以使用serializable
进行序列化;paddle.nn.Layer
类,并实现forward函数。除此之外,还需要实现out_shape
属性,用于定义输出的feature map的channel信息,还需要实现类函数from_config
用于在配置文件中推理出输入channel,并用于YOLOv3FPN
的初始化;__shared__
实现一些参数的配置全局共享。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 *
几点说明:
register
进行注册;paddle.nn.Layer
类,并实现forward函数。__inject__
表示引入全局字典中已经封装好的模块。如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 *
几点说明:
register
进行注册;paddle.nn.Layer
类,并实现forward函数。__inject__
表示引入全局字典中已经封装好的模块,使用__shared__
可以实现一些参数的配置全局共享。后处理模块定义在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
中。所有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
几点说明:
register
进行注册__category__ = 'architecture'
来表示一个完整的物体检测模型;上面详细地介绍了如何新增一个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。
优化器配置文件定义模型使用的优化器以及学习率的调度策略,目前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
几点说明:
ppdet/optimizer.py
。关于Reader的配置可以参考Reader配置文档。
看过此文档,您应该对PaddleDetection中模型搭建与配置有了一定经验,结合源码会理解的更加透彻。关于模型技术,如您有其他问题或建议,请给我们提issue,我们非常欢迎您的反馈。