mmdetection原始碼筆記(二):模型之registry.py和builder.py解讀(上)
作者:Activewaste(CSDN)
原文連結:CSDN-專業IT技術社群-登入
引言:
在上篇文章中,講了train。py訓練檔案,主要是讀取命令列函式和主函式main。main主要先做了一些config,work_dir以及log等操作(這些操作都是從命令列獲得的,或者從命令列帶有的檔案裡得到的引數等。)。最主要的三個步驟就是呼叫build_detector()來建立模型,然後同樣呼叫build_dataset()對資料集建立模型,然後在訓練檢測器train_detector()。注:build_dataset()和build_detector()不在同一個builder。py中實現,所以以下的builder。py實現的是build_detector(),是在mmdet/models/下的py檔案。
具體詳情看:mmdetection原始碼筆記(一):
本篇文章主要就是講一下,搭建模型的思路,以及registry。py和builder。py中各個函式塊的作用。
注:builder。py是在mmdet/models資料夾下,是用來建立BACKBONES、NECKS、ROI_EXTRACTORS、SHARED_HEADS、HEADS、LOSSES、DETECTORS的模型的。而關於build_dataset()(在mmdet/datasets/builder。py中),在後面講到資料集的時候再來講它。
在mmdet/utils資料夾下的registry。py為主要的實現過程,後面詳細講解。
先來看在mmdet/models資料夾下的registry。py,較簡單,程式碼如下:
# -*- coding: utf-8 -*-
from
mmdet。utils
import
Registry
BACKBONES
=
Registry
(
‘backbone’
)
NECKS
=
Registry
(
‘neck’
)
ROI_EXTRACTORS
=
Registry
(
‘roi_extractor’
)
SHARED_HEADS
=
Registry
(
‘shared_head’
)
HEADS
=
Registry
(
‘head’
)
LOSSES
=
Registry
(
‘loss’
)
DETECTORS
=
Registry
(
‘detector’
)
#類的例項化,Registry是一個類,傳入的是一個字串。該字串為Registry類的name屬性值
舉個例子:DETECTORS為登錄檔Registry的例項化物件,DETECTORS。name = ‘detector’,Registry類的定義在mmdet/utils/檔案中。
所以,根據上面程式碼,我們就應該知道了,不止一個名為DETECTORS的登錄檔Registry,後面還會有名為NECKS、ROI_EXTRACTORS 、SHARED_HEADS 、HEADS 、LOSSES 的登錄檔,這些登錄檔下的_module_dict屬性,則是用來存對應的相同類物件的,舉個例子:比如DETECTORS的_module_dict下就有可能有:Faster R-CNN、Cascade R-CNN、FPN、HTC等常見的檢測器,到這或許你就明白了登錄檔的作用咯。
而在mmdet/utils/Registry。py中,有一個類Registry的定義和一個方法:build_from_cfg()的實現。
build_from_cfg()方法的作用是從 congfig/py配置檔案中獲取字典資料,建立module(其實也就是一個class類),然後將這個module新增到之前建立的登錄檔Registry的屬性_module_dict中(這是一個字典,key為類名,value為具體的類),返回值是一個例項化後的類物件。
所以,可以這樣理解,從config/py配置檔案中,將字典提取出來,然後為其對映成一個類,放進Registry物件的_module_dict屬性中。(具體看下面的程式碼)
Registry。py檔案
以下程式碼分三部分
Part one:
inspect
模組是針對模組,類,方法,功能等物件提供些有用的方法。例如可以幫助我們檢查類的內容,檢查方法的程式碼,提取和格式化方法的引數等。
# -*- coding: utf-8 -*-
import
inspect
import
mmcv
Part two:
透過前面第一段的程式碼段,我們知道DETECTORS = Registry(‘detector’)
detector是幹什麼的 ???
其實,DETECTORS = Registry(‘detector’) 只是註冊了一個物件名為DETECTORS ,屬性name為detector的物件。然後用屬性_module_dict 來儲存config配置檔案中的對應的字典資料所對應的class類(看第三部分程式碼)。請看如下類Registry的定義程式碼:
class
Registry
(
object
):
def
__init__
(
self
,
name
):
#此處的self,是個物件(Object),是當前類的例項,name即為傳進來的‘detector’值
self
。
_name
=
name
self
。
_module_dict
=
dict
()
#定義的屬性,是一個字典
def
__repr__
(
self
):
#返回一個可以用來表示物件的可列印字串,可以理解為java中的toString()。
format_str
=
self
。
__class__
。
__name__
+
‘(name=
{}
, items=
{}
)’
。
format
(
self
。
_name
,
list
(
self
。
_module_dict
。
keys
()))
return
format_str
@property
#把方法變成屬性,透過self。name 就能獲得name的值。
def
name
(
self
):
return
self
。
_name
#因為沒有定義它的setter方法,所以是個只讀屬性,不能透過 self。name = newname進行修改。
@property
def
module_dict
(
self
):
#同上,透過self。module_dict可以獲取屬性_module_dict,也是隻讀的
return
self
。
_module_dict
def
get
(
self
,
key
):
#普通方法,獲取字典中指定key的value,_module_dict是一個字典,然後就可以透過self。get(key),獲取value值
return
self
。
_module_dict
。
get
(
key
,
None
)
def
_register_module
(
self
,
module_class
):
#關鍵的一個方法,作用就是Register a module。
#在model資料夾下的py檔案中,裡面的class定義上面都會出現 @DETECTORS。register_module,意思就是將類當做形參,
#將類送入了方法register_module()中執行。@的具體用法看後面解釋。
“”“Register a module。
Args:
module (:obj:`nn。Module`): Module to be registered。
”“”
if
not
inspect
。
isclass
(
module_class
):
#判斷是否為類,是類的話,就為True,跳過判斷
raise
TypeError
(
‘module must be a class, but got
{}
’
。
format
(
type
(
module_class
)))
module_name
=
module_class
。
__name__
#獲取類名
if
module_name
in
self
。
_module_dict
:
#看該類是否已經登記在屬性_module_dict中
raise
KeyError
(
‘
{}
is already registered in
{}
’
。
format
(
module_name
,
self
。
name
))
self
。
_module_dict
[
module_name
]
=
module_class
#在module中dict新增key和value。key為類名,value為類物件
def
register_module
(
self
,
cls
):
#對上面的方法,修改了名字,添加了返回值,即返回類本身
self
。
_register_module
(
cls
)
return
cls
note:
@的含義:
Python當直譯器讀到@的這樣的修飾符之後,會先解析@後的內容,直接就把@下一行的函式或者類作為@後邊的函式的引數,然後將返回值賦值給下一行修飾的函式物件。
在網上看到一個這樣的例子:
def
a
(
x
):
if
x
==
2
:
return
4
return
6
def
b
(
x
):
if
x
==
1
:
return
2
return
3
@a
@b
def
c
():
return
1
python會按照自下而上的順序把各自的函式結果作為下一個函式(上面的函式)的形參輸入,也就是a(b(c()))。
Part three:
以下我們透過配置檔案
cascade_rcnn_r50_fpn_1x。py
進行講解 build 模型的過程。
在train中,最先執行Registry的是DETECTORS,傳入的引數是配置檔案中的model字典。
#在 train。py中
model
=
build_detector
(
cfg
。
model
,
train_cfg
=
cfg
。
train_cfg
,
test_cfg
=
cfg
。
test_cfg
)
#在builder。py中
def
build_detector
(
cfg
,
train_cfg
=
None
,
test_cfg
=
None
):
return
build
(
cfg
,
DETECTORS
,
dict
(
train_cfg
=
train_cfg
,
test_cfg
=
test_cfg
))
所以,後面出現的引數cfg,指的就是配置檔案中的model字典。下面是model字典的部分程式碼:
# model settings
model
=
dict
(
type
=
‘CascadeRCNN’
,
num_stages
=
3
,
pretrained
=
‘torchvision://resnet50’
,
backbone
=
dict
(
type
=
‘ResNet’
,
depth
=
50
,
num_stages
=
4
,
out_indices
=
(
0
,
1
,
2
,
3
),
frozen_stages
=
1
,
style
=
‘pytorch’
),
neck
=
dict
(
type
=
‘FPN’
,
in_channels
=
[
256
,
512
,
1024
,
2048
],
out_channels
=
256
,
num_outs
=
5
),
rpn_head
=
dict
(
type
=
‘RPNHead’
,
in_channels
=
256
,
feat_channels
=
256
,
anchor_scales
=
[
8
],
anchor_ratios
=
[
0。5
,
1。0
,
2。0
],
anchor_strides
=
[
4
,
8
,
16
,
32
,
64
],
target_means
=
[
。
0
,
。
0
,
。
0
,
。
0
],
target_stds
=
[
1。0
,
1。0
,
1。0
,
1。0
],
loss_cls
=
dict
(
type
=
‘CrossEntropyLoss’
,
use_sigmoid
=
True
,
loss_weight
=
1。0
),
loss_bbox
=
dict
(
type
=
‘SmoothL1Loss’
,
beta
=
1。0
/
9。0
,
loss_weight
=
1。0
)),
我們繼續往下看
先看build_from_cfg()方法的引數:
Args:
cfg (dict): Config dict。 It should at least contain the key “type”。這個cfg就是py配置檔案中的字典。在py配置檔案中,基本上dict都會有一個key為“type”,當然也有不是的,不是的,這一步就不會執行,也就不會為他建立module。也就是這邊建立成module的dict,都必須有key為“type”才可以建立(這裡,我們主要講的是登錄檔DETECTORS,所以此時cfg對應的是配置檔案中的model字典,看上面截圖)。舉個例子:比如type=‘CascadeRCNN’,後面我們會知道,這個value為“CascadeRCNN”的,其實就是models資料夾中某py檔案中的類名,他們透過@DETECTORS。register_module,將類名當做形參,傳入register_module。並儲存下來。
registry (:obj:Registry): The registry to search the type from。
default_args (dict, optional): Default initialization arguments。
def
build_from_cfg
(
cfg
,
registry
,
default_args
=
None
):
“”“Build a module from config dict。
Args:
cfg (dict): Config dict。 It should at least contain the key ”type“。
registry (:obj:`Registry`): The registry to search the type from。
default_args (dict, optional): Default initialization arguments。
Returns:
obj: The constructed object。
”“”
assert
isinstance
(
cfg
,
dict
)
and
‘type’
in
cfg
assert
isinstance
(
default_args
,
dict
)
or
default_args
is
None
#兩個是斷言,相當於判斷,否的話丟擲異常。
args
=
cfg
。
copy
()
#args相當於temp中間變數,是個字典。
obj_type
=
args
。
pop
(
‘type’
)
#字典的pop作用:移除序列中key為‘type’的元素,並且返回該元素的值
if
mmcv
。
is_str
(
obj_type
):
obj_type
=
registry
。
get
(
obj_type
)
#獲取obj_type的value。
#如果obj_type已經註冊到登錄檔registry中,即在屬性_module_dict中,則obj_type 不為None
if
obj_type
is
None
:
raise
KeyError
(
‘
{}
is not in the
{}
registry’
。
format
(
obj_type
,
registry
。
name
))
elif
not
inspect
。
isclass
(
obj_type
):
raise
TypeError
(
‘type must be a str or valid type, but got
{}
’
。
format
(
type
(
obj_type
)))
if
default_args
is
not
None
:
for
name
,
value
in
default_args
。
items
():
#items()返回字典的鍵值對用於遍歷
args
。
setdefault
(
name
,
value
)
#將default_args的鍵值對加入到args中,將模型和訓練配置進行整合,然後送入類中返回
return
obj_type
(
**
args
)
obj_type(**args),* *args是將字典unpack得到各個元素,分別與形參匹配送入函式中;看上面model的截圖,所以這邊,其實就是將除了’type’的所有欄位,當做形參,送入了名為CascadeRCNN()的類中(type =‘ CascadeRCNN’)。所以字典裡的key就是類中的屬性?繼續看下面。
根據Cascade R-CNN的例子,我們在models/detectors找cascade_rcnn的py檔案。
參考裡面的引數時,直接開啟對應的cascade_rcnn配置檔案,在init中,裡面的引數則
對應了配置檔案中的字典名。下面兩個截圖分別是配置檔案cascade_rcnn。py和model/detectors/cascade_rcnn。py中的類定義。
configs/cascade_rcnn.py:
# model settings
model
=
dict
(
type
=
‘CascadeRCNN’
,
num_stages
=
3
,
pretrained
=
‘torchvision://resnet50’
,
backbone
=
dict
(
type
=
‘ResNet’
,
depth
=
50
,
num_stages
=
4
,
out_indices
=
(
0
,
1
,
2
,
3
),
frozen_stages
=
1
,
style
=
‘pytorch’
),
neck
=
dict
(
type
=
‘FPN’
,
in_channels
=
[
256
,
512
,
1024
,
2048
],
out_channels
=
256
,
num_outs
=
5
),
rpn_head
=
dict
(
type
=
‘RPNHead’
,
model/detectors/cascade_rcnn.py:
@DETECTORS。register_module
class CascadeRCNN(BaseDetector, RPNTestMixin):
def __init__(self,
num_stages,
backbone,
neck=None,
shared_head=None,
rpn_head=None,
bbox_roi_extractor=None,
bbox_head=None,
mask_roi_extractor=None,
mask_head=None,
train_cfg=None,
test_cfg=None,
pretrained=None):
assert bbox_roi_extractor is not None
assert bbox_head is not None
super(CascadeRCNN, self)。__init__()
注意的是,在py配置檔案中,好多py檔案中都有type = ‘CascadeRCNN’,所以有些引數和屬性對不上很正常(畢竟已經設定為None了),因為這個引數可能是其他的cascade R-CNN裡面的字典。
所以,我們在訓練時,測試時,就要給出配置檔案,配置檔案可以不同,但相同type
detector等檔案是相同的,畢竟已經將資料和實現完全的分離了。
注意:無論訓練/檢測,都會build DETECTORS;
builder。py檔案
builder檔案較為簡單,因為train。py中,只出現了build_detector(),所以我們先記住裡面的兩個方法:build_detector和build()。
build_detector:是建立一個detector,方法裡呼叫了build()方法(所有的build_xx都是直接呼叫build方法,所以看懂這一個也就看懂所有了)。
build():則是呼叫的Registry。py檔案中的build_from_cfg()方法,這個方法我們已經在上面講過了。
import:
# -*- coding: utf-8 -*-
from
torch
import
nn
from
mmdet。utils
import
build_from_cfg
#此處不會在執行registry而是直接進行sys。modules查詢得到
from
。registry
import
(
BACKBONES
,
NECKS
,
ROI_EXTRACTORS
,
SHARED_HEADS
,
HEADS
,
LOSSES
,
DETECTORS
)
#上面的registry是在models資料夾下,registry類的具體實現是在mmdet/utils資料夾下
只需要看一下build()的兩個引數:cfg, registry
build_detector()在train。py中的呼叫,我們就可以知道,cfg是py配置檔案中的字典, 以registry是DETECTORS為例,cfg就是model字典 (後面登錄檔為BACKBONES、NECKS等時,就是配置檔案中的其他的字典了,不是model) 。
build()方法中,主幹是一個判斷結構,其實就是判斷傳進來的cfg是字典列表還是單獨的字典,來分情況處理。(以登錄檔DETECTORS為例,是一個單獨的字典)
字典列表的話:挨個呼叫build_from_cfg(),將其加到登錄檔的_module_dict中,然後再返回return nn。Sequential(*modules),這個地方的作用,有待博主繼續研究一下下???
字典的話:直接呼叫build_from_cfg(),將其新增到登錄檔DETECTORS中(以DETECTORS為例)。
def
build
(
cfg
,
registry
,
default_args
=
None
):
if
isinstance
(
cfg
,
list
):
modules
=
[
build_from_cfg
(
cfg_
,
registry
,
default_args
)
for
cfg_
in
cfg
#build_from_cfg()返回值是一個帶形參的類,返回時也就完成了例項化的過程。
]
#所以modules就是一個class類的列表
return
nn
。
Sequential
(
*
modules
)
#nn。Sequential 一個有序的容器,神經網路模組將按照在傳入構造器的順序依次被新增到計算圖中執行,同時以神經網路模組為元素的有序字典也可以作為傳入引數
else
:
return
build_from_cfg
(
cfg
,
registry
,
default_args
)
#Config dict
def
build_detector
(
cfg
,
train_cfg
=
None
,
test_cfg
=
None
):
return
build
(
cfg
,
DETECTORS
,
dict
(
train_cfg
=
train_cfg
,
test_cfg
=
test_cfg
))
#DETECTORS = Registry(‘detector’),建立一個名為DETECTORS的登錄檔Registry。
def
build_backbone
(
cfg
):
return
build
(
cfg
,
BACKBONES
)
def
build_neck
(
cfg
):
return
build
(
cfg
,
NECKS
)
def
build_roi_extractor
(
cfg
):
return
build
(
cfg
,
ROI_EXTRACTORS
)
def
build_shared_head
(
cfg
):
return
build
(
cfg
,
SHARED_HEADS
)
def
build_head
(
cfg
):
return
build
(
cfg
,
HEADS
)
def
build_loss
(
cfg
):
return
build
(
cfg
,
LOSSES
)
後面的幾個build_XXXXX()的方法也就跟build_detector()相同咯。
還是以登錄檔DETECTORS為例,配置檔案為cascade_rcnn_r50_fpn_1x。py來講解:在model資料夾下的cascade_rcnn。py檔案中,有類Cascade_RCNN()的定義,在配置檔案中,對應的key被傳入類中當做屬性,這些屬性被初始化的時候,呼叫對應的build_XXXXX(),由此建立它們對應的登錄檔。
再以NECK為例,呼叫build_neck(cfg);然後執行build(cfg, NECKS),這一步,形參用到NECKS,所以在Registry中,又多了一個名為NECKS的登錄檔了。然後將配置檔案中,字典名為neck的,然後生成一個類(類名是neck字典中的type的值,該類在models/necks資料夾下),同時將該類新增到了登錄檔NECKS的_module_dict中。
#在model/detectors/cascade_rcnn。py中
if
neck
is
not
None
:
self
。
neck
=
builder
。
build_neck
(
neck
)
#再builder。py中
def
build_neck
(
cfg
):
return
build
(
cfg
,
NECKS
)
#在configs/cascade_rcnn_r50_fpn_1x。py中
neck
=
dict
(
type
=
‘FPN’
,
in_channels
=
[
256
,
512
,
1024
,
2048
],
out_channels
=
256
,
num_outs
=
5
),
到這,NECK的註冊和資料讀入,相信大家已經很清楚了,其他的登錄檔也是類似的。
總結:
搭建模型思路:
首先,建立一個名為DETECTORS的登錄檔Registry。這個登錄檔有屬性name=‘detector’,和屬性_module_dict。_module_dict 是一個字典,專門用來存各個物件名和對應的物件。
其次,讀取py配置檔案,py配置檔案是個字典,(字典裡還有字典,這裡面的字典,也是後面來建立模型的,道理是一樣的)。根據key為‘type’的字典,建立module,對於的value為其module名,然後再models資料夾下中,已經存在了這些module的類。將字典中的其他資料,作為形參,例項化這些類。並儲存這些module到屬性_module_dict中。
到這,配置檔案的資料,裡面的字典(含有type的字典)對應著一個類,type為類名,其他欄位則為其屬性(其他欄位也可能是個字典,後面也有可能要再為它們搭建模型哦)。由此完成模型的搭建。
這是搭建模型的一個思路,雖然講得篇幅很大,有點亂亂的感覺,但是看懂後,就會發現很簡單。
mmdetection搭建模型用途:
mmdetection將配置檔案中,字典名為:backbone、neck、roi_extractor、shared_head、head、loss、detector的字典,全部例項化成登錄檔(Registry),然後這些字典裡的type,都被例項化成對應的類(module),並新增到登錄檔的屬性_module_dict中,其他的欄位,則為這個類的屬性,由此完成模型的建立,實際上,就是將配置檔案的字典資料儲存到類(module)中,以便後面讀取資料,載入資料。