MMDetection Register 和 Python decorator 裝飾器理解與實作

李謦伊
謦伊的閱讀筆記
11 min readSep 24, 2022

MMDetection 是一個基於 Pytorch 的 object detection open source tool,將object detection 演算法解耦成不同模塊並進行封裝,擁有高度的靈活性,讓使用者能夠很便利地建構整個訓練流程。

配置 config 的方式是採用字典的形式來實行,可直接使用官方提供的或是自定義 dataset、model、schedule 等模塊,配置設定如下圖

source

建構模型的方式是透過 key 去查找所對應的類別,再根據配置的參數進行實例化,比如上圖的 backbone key 設置為 ResNet,就會去查找 ResNet 類別

source

之所以能夠用字典的方式來建構模型是因為使用了 Registry 註冊器,透過 ResNet 類別上面的裝飾器 @BACKBONES.register_module() 將該類別進行註冊。Registry 的實現方式如下圖:將註冊的類別加入至字典中。

source

BACKBONES 為 Registry 的實例

source

@ 為 python 提供的語法糖 — 裝飾器 (Decorator) ,其目的是讓程式碼變得更簡潔優雅且有較高的易讀性。裝飾器本質上就是一個函式或類別,將其他函式作為參數傳入後,會進行裝飾器的功能以及調用被裝飾的函式。換言之,在函式上頭加上裝飾器,就可以直接使用到裝飾器的功能,並且不需要對函式做任何修改。如此一來,能夠將裝飾器與函式的功能抽離開來。

來看個簡單的範例,做法為三個部分:

  • 定義裝飾器功能
  • 定義函式
  • 將裝飾器綁定在函式上方

由輸出可知,當函式被調用時,該函式會作為參數傳入至裝飾器中,先進行裝飾器所設定的功能 (輸出 “start~~~”),再調用被裝飾函式,最後再繼續進行裝飾器功能 (輸出 “end~~~”)。

此外,裝飾器還有很多種用法,可參考最下方的 Reference。

def mydecorator(func):
def wrapper(*args, **kw):
print("start~~~")
func()
print("end~~~")
return wrapper
@mydecorator
def function1():
print("use function1 !!!")

@mydecorator
def function2():
print("use function2 !!!")
print("---function1---")
print(function1())
print("---function2---")
print(function2())
# === output ===
---function1---
start~~~
use function1 !!!
end~~~
---function2---
start~~~
use function2 !!!
end~~~

接著來實作使用 MMDetection framework 建構模型並進行訓練,安裝方式可參考:https://github.com/open-mmlab/mmdetection/blob/master/docs/en/get_started.md/

!pip install -U openmim
!mim install mmcv-full
!git clone https://github.com/open-mmlab/mmdetection.git
%cd mmdetection
!pip install -v -e .
  • 資料準備

訓練資料使用 Oxford Pets Dataset,下載的格式為 coco。將訓練/驗證/測試集的 annotations 移至新資料夾 annotations 裡。

!curl -L “https://public.roboflow.com/ds/xcrhGBJ1aB?key=bdAQK4gH2e" > data_pets.zip!unzip 'data_pets.zip' -d data_pets!mkdir data_pets/annotations!mv data_pets/train/_annotations.coco.json data_pets/annotations/instances_train.json!mv data_pets/valid/_annotations.coco.json data_pets/annotations/instances_val.json!mv data_pets/test/_annotations.coco.json data_pets/annotations/instances_test.json
  • 自定義模型並註冊到 Registry

這邊示範自定義 backbone,首先利用 mmcv.cnn 中的函式 build_conv_layer, build_norm_layer, build_activation_layer 來建立 CNN layer、BN、Activation Function,也可以使用其他的 layer,可參考 mmcv/cnn/__init__.py

將自定義模型繼承 mmcv.runner.BaseModule,並註冊到 Registry 中。

@BACKBONES.register_module()
class MyModel(BaseModule):
def __init__(self, in_channels, stride=2, padding=1,
conv_cfg=None, norm_cfg=dict(type='BN'),
act_cfg=dict(type='ReLU')):
super(MyModel, self).__init__()
self.layers = nn.ModuleList() for i, out_channels in enumerate([128, 256, 512]):
block = nn.Sequential(
build_conv_layer(conv_cfg, in_channels,
out_channels, kernel_size=3,
stride=stride,
padding=padding, bias=False),

build_norm_layer(norm_cfg, out_channels)[1],
build_activation_layer(act_cfg))
in_channels = out_channels
self.layers.append(block)
def forward(self, x):
outputs = []
for module in self.layers:
x = module(x)
outputs.append(x)
return tuple(outputs)

加入自定義模型至 mmdet/models/backbones/__init__.py

from .my_model import MyModel__all__ = [
‘RegNet’, ‘ResNet’, ‘ResNetV1d’, ‘ResNeXt’, ‘SSDVGG’, ‘HRNet’,
‘MobileNetV2’, ‘Res2Net’, ‘HourglassNet’, ‘DetectoRS_ResNet’,
‘DetectoRS_ResNeXt’, ‘Darknet’, ‘ResNeSt’, ‘TridentResNet’, ‘CSPDarknet’,
‘SwinTransformer’, ‘PyramidVisionTransformer’,
‘PyramidVisionTransformerV2’, ‘EfficientNet’, ‘MyModel’
]
  • 建立 config

使用字典的形式來設定 dataset、model、schedule 等模塊,backbone type 設定為自定義的模型、neck, head 則沿用原本的 FasterRCNN 設定;數據資料設定中的 data_root 和 data 中的 train, val, test 要更改為自己的訓練/驗證/測試集資料夾路徑以及正確的資料名稱。

# 使用 base 的 schedule 和 default 設定
_base_ = [
'../_base_/schedules/schedule_1x.py',
'../_base_/default_runtime.py'
]
# 模型架構設定
model = dict(
type='FasterRCNN',
backbone=dict(
type='MyModel',
in_channels=3),
neck=dict(
type='FPN',
in_channels=[128, 256, 512],
out_channels=256,
num_outs=5),
...# 數據資料設定
dataset_type = 'CocoDataset'
data_root = 'data_pets/'
img_norm_cfg = dict(
mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
...
data = dict(
samples_per_gpu=2,
workers_per_gpu=2,
train=dict(
type=dataset_type,
ann_file=data_root + 'annotations/instances_train.json',
img_prefix=data_root + 'train/',
pipeline=train_pipeline),
val=dict(
type=dataset_type,
ann_file=data_root + 'annotations/instances_val.json',
img_prefix=data_root + 'valid/',
pipeline=test_pipeline),
test=dict(
type=dataset_type,
ann_file=data_root + 'annotations/instances_test.json',
img_prefix=data_root + 'test/',
pipeline=test_pipeline))
evaluation = dict(interval=1, metric='bbox')
  • 進行訓練

最後就可以開始進行訓練啦~~

! python tools/train.py configs/my_model/my_model_fpn_1x_coco.py

若我的文章對你有幫助的話,可以按下方的 “Give a tip” 按鈕給予我支持與鼓勵,謝謝 :)

--

--