为了让用户更好的使用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,我们非常欢迎您的反馈。
Dear OpenI User
Thank you for your continuous support to the Openl Qizhi Community AI Collaboration Platform. In order to protect your usage rights and ensure network security, we updated the Openl Qizhi Community AI Collaboration Platform Usage Agreement in January 2024. The updated agreement specifies that users are prohibited from using intranet penetration tools. After you click "Agree and continue", you can continue to use our services. Thank you for your cooperation and understanding.
For more agreement content, please refer to the《Openl Qizhi Community AI Collaboration Platform Usage Agreement》