作者: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)中,以便後面讀取資料,載入資料。